├── .gitignore ├── .jshintrc ├── README.md ├── examples └── basic.js ├── gulpfile.js ├── lib └── index.js ├── package.json └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": false, 3 | "eqeqeq": false, 4 | "indent": false, 5 | "newcap": false, 6 | "plusplus": false, 7 | "quotmark": false, 8 | "unused": false, 9 | "strict": false, 10 | "trailing": false, 11 | "maxstatements": false, 12 | "maxlen": false, 13 | 14 | "eqnull": true, 15 | "smarttabs": true, 16 | 17 | "browser": true, 18 | "node": true 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Odoo 2 | 3 | React Native library for Odoo 4 | 5 | ## Installation 6 | 7 | ```bash 8 | $ npm install react-native-odoo 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | import Odoo from 'odoo' 15 | 16 | const odoo = new Odoo({ 17 | host: 'localhost', 18 | port: 8069, 19 | database: 'demo', 20 | username: 'admin', 21 | password: 'admin' 22 | }); 23 | 24 | // Connect to Odoo 25 | odoo.connect(function (err) { 26 | if (err) { return console.log(err); } 27 | }); 28 | 29 | // Get a partner 30 | // https://www.odoo.com/documentation/8.0/api_integration.html#read-records 31 | // https://www.odoo.com/documentation/8.0/reference/orm.html#openerp.models.Model.read 32 | var params = { 33 | ids: [1,2,3,4,5], 34 | fields: [ 'name' ], 35 | }; //params 36 | odoo.get('res.partner', params, function (err, partners) { 37 | if (err) { return console.log(err); } 38 | 39 | console.log(partners); 40 | // [ 41 | // { id: 1, name: 'Demo Company' }, 42 | // { id: 3, name: 'Administrator' }, 43 | // { id: 4, name: 'Public user' }, 44 | // { id: 5, name: 'Demo User' } 45 | // ] 46 | }); //get 47 | 48 | 49 | // Search & Get products in one RPC call 50 | // https://www.odoo.com/documentation/8.0/api_integration.html#search-and-read 51 | // https://www.odoo.com/documentation/8.0/reference/orm.html#openerp.models.Model.search 52 | // https://www.odoo.com/documentation/8.0/reference/orm.html#openerp.models.Model.read 53 | var params = { 54 | ids: [1,2,3,4,5], 55 | domain: [ [ 'list_price', '>', '50' ], [ 'list_price', '<', '65' ] ], 56 | fields: [ 'name', 'list_price', 'items' ], 57 | order: 'list_price', 58 | limit: 5, 59 | offset: 0, 60 | }; //params 61 | odoo.search_read('product.product', params, function (err, products) { 62 | if (err) { return console.log(err); } 63 | 64 | console.log(products); 65 | // [ 66 | // { list_price: 60, id: 52, name: 'Router R430' }, 67 | // { list_price: 62, id: 39, name: 'Headset standard' } 68 | // ] 69 | 70 | }); //search_read 71 | 72 | 73 | // Browse products by ID 74 | // Not a direct implementation of Odoo RPC 'browse' but rather a workaround based on 'search_read' 75 | // https://www.odoo.com/documentation/8.0/reference/orm.html#openerp.models.Model.browse 76 | var params = { 77 | fields: [ 'name', 'list_price'], 78 | limit: 5, 79 | offset: 0, 80 | }; //params 81 | odoo.browse_by_id('product.product', params, function (err, products) { 82 | if (err) { return console.log(err); } 83 | 84 | console.log(products); 85 | // [ 86 | // { list_price: 4.49, id: 1180, name: 'Fruit Cup' }, 87 | // { list_price: 0, id: 1139, name: 'Orange Crush' }, 88 | // { list_price: 1.59, id: 1062, name: 'Blueberry muffin' }, 89 | // { list_price: 1.35, id: 1381, name: 'Otis Harvest Bran' } 90 | // ] 91 | }); //browse_by_id 92 | 93 | 94 | // Generic RPC call 95 | // Note that, unlike the other methods, the context is not automatically included 96 | var endpoint = '/web/dataset/call_kw'; 97 | var model = 'sale.order'; 98 | var method = 'action_view_delivery'; 99 | 100 | var args = [ 101 | [sale_order_id], 102 | { 103 | tz: odoo.context.tz, 104 | uid: odoo.context.uid, 105 | }, 106 | ];//args 107 | 108 | var params = { 109 | model: model, 110 | method: method, 111 | args: args, 112 | kwargs: {}, 113 | };//params 114 | 115 | // View Delivery Order 116 | odoo.rpc_call(endpoint, params, function(err, result) { 117 | if(err) { 118 | console.log(err); 119 | return; 120 | }//if 121 | 122 | var delivery_order = result; 123 | 124 | console.log(delivery_order); 125 | });//odoo.rpc_call 126 | 127 | 128 | ``` 129 | 130 | ## Methods 131 | 132 | * odoo.connect(callback) 133 | * odoo.get(model, id, callback) 134 | * odoo.search(model, params, callback) 135 | * odoo.search_read(model, params, callback) 136 | * odoo.browse_by_id(model, params, callback) 137 | * odoo.create(model, params, callback) 138 | * odoo.update(model, id, params, callback) 139 | * odoo.delete(model, id, callback) 140 | * odoo.rpc_call(endpoint, params, callback); 141 | 142 | ##Node version 143 | Works better with NodeJS v11.16 and further 144 | 145 | ## Reference 146 | 147 | * [Odoo Technical Documentation](https://www.odoo.com/documentation/8.0) 148 | * [Odoo Web Service API](https://www.odoo.com/documentation/8.0/api_integration.html) 149 | 150 | ## License 151 | 152 | The MIT License (MIT) 153 | 154 | Copyright (c) 2015 Marco Godínez, 4yopping and all the related trademarks 155 | 156 | Permission is hereby granted, free of charge, to any person obtaining a copy 157 | of this software and associated documentation files (the "Software"), to deal 158 | in the Software without restriction, including without limitation the rights 159 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 160 | copies of the Software, and to permit persons to whom the Software is 161 | furnished to do so, subject to the following conditions: 162 | 163 | The above copyright notice and this permission notice shall be included in 164 | all copies or substantial portions of the Software. 165 | 166 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 167 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 168 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 169 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 170 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 171 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 172 | THE SOFTWARE. 173 | -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | var Odoo = require('../lib/index'); 2 | 3 | var odoo = new Odoo({ 4 | host: 'localhost', 5 | port: 4569, 6 | database: '4yopping', 7 | username: 'admin', 8 | password: '4yopping' 9 | }); 10 | 11 | // Connect to Odoo 12 | odoo.connect(function (err) { 13 | if (err) { return console.log(err); } 14 | 15 | // Get a partner 16 | odoo.get('res.partner', 4, function (err, partner) { 17 | if (err) { return console.log(err); } 18 | 19 | console.log('Partner', partner); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | mocha = require('gulp-mocha'); 3 | 4 | gulp.task('test', function () { 5 | return gulp.src(['./test/**/*.js'], { read: false }) 6 | .pipe(mocha({ reporter: 'spec' })); 7 | }); 8 | 9 | gulp.task('default', function () { 10 | gulp.watch(['./lib/**/*', './test/**/*'], ['test']); 11 | }); 12 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Odoo = function (config) { 4 | config = config || {}; 5 | 6 | this.host = config.host; 7 | this.port = config.port || 80; 8 | this.database = config.database; 9 | this.username = config.username; 10 | this.password = config.password; 11 | }; 12 | 13 | // Connect 14 | 15 | Odoo.prototype.connect = function(cb){ 16 | var params = { 17 | db: this.database, 18 | login: this.username, 19 | password: this.password 20 | }; 21 | 22 | var json = JSON.stringify({ params: params }); 23 | var url = 'http://' + this.host + ':' + this.port + '/web/session/authenticate'; 24 | 25 | var options = { 26 | method: 'POST', 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | 'Accept': 'application/json', 30 | 'Content-Length': json.length 31 | }, 32 | body: json 33 | }; 34 | fetch(url, options) 35 | .then(res=>{ 36 | this.sid = res.headers.map['set-cookie'][0].split(';')[0]; 37 | console.log('sid:', this.sid); 38 | return res.json() 39 | }) 40 | .then(data=>{ 41 | if (data.error){ 42 | cb(data.error, null); 43 | }else{ 44 | this.uid = data.result.uid; 45 | this.session_id = data.result.session_id; 46 | this.context = data.result.user_context; 47 | this.username = data.result.username; 48 | cb(null, data.result); 49 | } 50 | }, err=>{ 51 | cb(err, null); 52 | }); 53 | 54 | }; 55 | 56 | // Search records 57 | Odoo.prototype.search = function (model, params, callback) { 58 | // assert(params.ids, "Must provide a list of IDs."); 59 | //assert(params.domain, "Must provide a search domain."); 60 | 61 | this._request('/web/dataset/call_kw', { 62 | kwargs: { 63 | context: this.context 64 | }, 65 | model: model, 66 | method: 'search', 67 | args: [ 68 | params.domain, 69 | ], 70 | }, callback); 71 | }; 72 | 73 | // Search & Read records 74 | // https://www.odoo.com/documentation/8.0/api_integration.html#search-and-read 75 | // https://www.odoo.com/documentation/8.0/reference/orm.html#openerp.models.Model.search 76 | // https://www.odoo.com/documentation/8.0/reference/orm.html#openerp.models.Model.read 77 | Odoo.prototype.search_read = function (model, params, callback) { 78 | //assert(params.domain, "'domain' parameter required. Must provide a search domain."); 79 | //assert(params.limit, "'limit' parameter required. Must specify max. number of results to return."); 80 | 81 | this._request('/web/dataset/call_kw', { 82 | model: model, 83 | method: 'search_read', 84 | args: [], 85 | kwargs: { 86 | context: this.context, 87 | domain: params.domain, 88 | offset: params.offset, 89 | limit: params.limit, 90 | order: params.order, 91 | fields: params.fields, 92 | }, 93 | }, callback); 94 | }; 95 | 96 | // Get record 97 | // https://www.odoo.com/documentation/8.0/api_integration.html#read-records 98 | // https://www.odoo.com/documentation/8.0/reference/orm.html#openerp.models.Model.read 99 | Odoo.prototype.get = function (model, params, callback) { 100 | //assert(params.ids, "Must provide a list of IDs."); 101 | 102 | this._request('/web/dataset/call_kw', { 103 | model: model, 104 | method: 'read', 105 | args: [ 106 | params.ids, 107 | ], 108 | kwargs: { 109 | fields: params.fields, 110 | }, 111 | }, callback); 112 | }; //get 113 | 114 | 115 | // Browse records by ID 116 | // Not a direct implementation of Odoo RPC 'browse' but rather a workaround based on 'search_read' 117 | // https://www.odoo.com/documentation/8.0/reference/orm.html#openerp.models.Model.browse 118 | Odoo.prototype.browse_by_id = function(model, params, callback) { 119 | params.domain = [['id', '>', '0' ]]; // assumes all records IDs are > 0 120 | this.search_read(model, params, callback); 121 | }; //browse 122 | 123 | 124 | // Create record 125 | Odoo.prototype.create = function (model, params, callback) { 126 | this._request('/web/dataset/call_kw', { 127 | kwargs: { 128 | context: this.context 129 | }, 130 | model: model, 131 | method: 'create', 132 | args: [params] 133 | }, callback); 134 | }; 135 | 136 | // Update record 137 | Odoo.prototype.update = function (model, id, params, callback) { 138 | if (id) { 139 | this._request('/web/dataset/call_kw', { 140 | kwargs: { 141 | context: this.context 142 | }, 143 | model: model, 144 | method: 'write', 145 | args: [[id], params] 146 | }, callback); 147 | } 148 | }; 149 | 150 | // Delete record 151 | Odoo.prototype.delete = function (model, id, callback) { 152 | this._request('/web/dataset/call_kw', { 153 | kwargs: { 154 | context: this.context 155 | }, 156 | model: model, 157 | method: 'unlink', 158 | args: [[id]] 159 | }, callback); 160 | }; 161 | 162 | 163 | // Generic RPC wrapper 164 | // DOES NOT AUTO-INCLUDE context 165 | Odoo.prototype.rpc_call = function (endpoint, params, callback) { 166 | //assert(params.model); 167 | // assert(params.method); 168 | // assert(params.args); 169 | // assert(params.kwargs); 170 | // assert(params.kwargs.context); 171 | 172 | this._request(endpoint, params, callback); 173 | }; //generic 174 | 175 | 176 | // Private functions 177 | Odoo.prototype._request = function (path, params, cb) { 178 | params = params || {}; 179 | 180 | var url = 'http://' + this.host + ':' + this.port + (path || '/') + ''; 181 | var options = { 182 | method: 'POST', 183 | headers: { 184 | 'Content-Type': 'application/json', 185 | 'Accept': 'application/json', 186 | 'Cookie': this.sid + ';' 187 | }, 188 | body: JSON.stringify({jsonrpc: '2.0', id: new Date().getUTCMilliseconds(), method: 'call', params: params}) 189 | }; 190 | 191 | fetch(url, options) 192 | .then(res=>res.json()) 193 | .then(data=>{ 194 | if (data.error){ 195 | cb(data.error, null); 196 | }else{ 197 | cb(null, data.result); 198 | } 199 | }, err=>{ 200 | cb(err, null); 201 | }); 202 | }; 203 | 204 | module.exports = Odoo; 205 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-odoo", 3 | "version": "0.1.0", 4 | "description": "React Native library for Odoo using JSON-RPC", 5 | "main": "./lib/index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "mocha test" 11 | }, 12 | "engines": { 13 | "node": ">=0.11.16" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/kevin3274/react-native-odoo.git" 18 | }, 19 | "keywords": [ 20 | "odoo", 21 | "openerp", 22 | "jsonrpc" 23 | "react-native" 24 | ], 25 | "author": { 26 | "name": "Kevin Wang", 27 | "email": "kevin_327@163.com" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/kevin327/react-native-odoo/issues" 31 | }, 32 | "homepage": "https://github.com/kevin3274/react-native-odoo", 33 | "dependencies": { 34 | }, 35 | "devDependencies": { 36 | "gulp": "^3.8.11", 37 | "gulp-mocha": "^2.0.0", 38 | "sinon": "^1.12.2" 39 | }, 40 | "maintainers": [ 41 | { 42 | "name": "kevin3274", 43 | "email": "kevin_327@163.com" 44 | } 45 | ], 46 | "readme": "ERROR: No README data found!" 47 | } 48 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'), 2 | sinon = require('sinon'), 3 | Odoo = require('../lib/index'); 4 | 5 | var config = { 6 | host: 'odoo4yopping.vagrantshare.com', 7 | port: 80, 8 | database: '4yopping', 9 | username: 'admin', 10 | password: '4yopping' 11 | }; 12 | 13 | var odoo = new Odoo(config); 14 | 15 | describe('Odoo', function () { 16 | this.timeout(3000); 17 | 18 | it('Odoo should be a function', function () { 19 | assert.equal(typeof Odoo, 'function'); 20 | }); 21 | 22 | it('odoo should be an instance of Odoo', function () { 23 | assert(odoo instanceof Odoo); 24 | }); 25 | 26 | it('odoo should have this properties', function () { 27 | assert.notEqual(odoo.host, undefined); 28 | assert.equal(odoo.host, config.host); 29 | assert.notEqual(odoo.port, undefined); 30 | assert.equal(odoo.port, config.port); 31 | assert.notEqual(odoo.database, undefined); 32 | assert.equal(odoo.database, config.database); 33 | assert.notEqual(odoo.username, undefined); 34 | assert.equal(odoo.username, config.username); 35 | assert.notEqual(odoo.password, undefined); 36 | assert.equal(odoo.password, config.password); 37 | }); 38 | 39 | it('odoo should have this public functions', function () { 40 | assert.equal(typeof odoo.connect, 'function'); 41 | assert.equal(typeof odoo.create, 'function'); 42 | assert.equal(typeof odoo.get, 'function'); 43 | assert.equal(typeof odoo.update, 'function'); 44 | assert.equal(typeof odoo.delete, 'function'); 45 | assert.equal(typeof odoo.search, 'function'); 46 | }); 47 | 48 | it('odoo should have this private functions', function () { 49 | assert.equal(typeof odoo._request, 'function'); 50 | }); 51 | 52 | describe('Creating client', function () { 53 | 54 | it('client should not be able to connect to odoo server', function (done) { 55 | var client = new Odoo({ 56 | host: config.host, 57 | database: 'DatabaseNotFound', 58 | username: config.username, 59 | password: config.password 60 | }), 61 | callback = sinon.spy(); 62 | 63 | client.connect(callback); 64 | 65 | setTimeout(function () { 66 | assert(callback.called); 67 | assert.equal(typeof callback.args[0][0], 'object'); 68 | assert.equal(callback.args[0][1], null); 69 | 70 | done(); 71 | }, 2000); 72 | }); 73 | 74 | it('client should be able to connect to odoo server', function (done) { 75 | var callback = sinon.spy(); 76 | 77 | odoo.connect(callback); 78 | 79 | setTimeout(function () { 80 | assert(callback.calledWith(null)); 81 | assert.equal(typeof callback.args[0][1], 'object'); 82 | assert(odoo.uid); 83 | assert(odoo.sid); 84 | assert(odoo.session_id); 85 | assert(odoo.context); 86 | 87 | done(); 88 | }, 2000); 89 | }); 90 | 91 | }); 92 | 93 | describe('Records', function () { 94 | 95 | var created; 96 | 97 | it('client should create a record', function (done) { 98 | var callback = sinon.spy(); 99 | odoo.create('hr.employee', { 100 | name: 'John Doe', 101 | work_email: 'john@doe.com' 102 | }, callback); 103 | 104 | setTimeout(function () { 105 | assert(callback.calledWith(null)); 106 | assert.equal(typeof callback.args[0][1], 'number'); 107 | 108 | created = callback.args[0][1]; 109 | 110 | done(); 111 | }, 2000); 112 | 113 | }); 114 | 115 | it('client should get a record', function (done) { 116 | var callback = sinon.spy(); 117 | odoo.get('hr.employee', created, callback); 118 | 119 | setTimeout(function () { 120 | assert(callback.calledWith(null)); 121 | assert.equal(typeof callback.args[0][1], 'object'); 122 | assert.equal(callback.args[0][1].display_name, 'John Doe'); 123 | assert.equal(callback.args[0][1].work_email, 'john@doe.com'); 124 | 125 | done(); 126 | }, 2000); 127 | 128 | }); 129 | 130 | it('client should update a record', function (done) { 131 | var callback = sinon.spy(); 132 | odoo.update('hr.employee', created, { 133 | name: 'Jane Doe', 134 | work_email: 'jane@doe.com' 135 | }, callback); 136 | 137 | setTimeout(function () { 138 | assert(callback.calledWith(null)); 139 | assert(callback.args[0][1]); 140 | 141 | done(); 142 | }, 2000); 143 | }); 144 | 145 | it('client should delete a record', function (done) { 146 | var callback = sinon.spy(); 147 | odoo.delete('hr.employee', created, callback); 148 | 149 | setTimeout(function () { 150 | assert(callback.calledWith(null)); 151 | assert(callback.args[0][1]); 152 | 153 | done(); 154 | }, 2000); 155 | }); 156 | 157 | it('client should search records', function (done) { 158 | var callback = sinon.spy(); 159 | odoo.search('hr.employee', [['login', '=', 'admin']], callback); 160 | 161 | setTimeout(function () { 162 | assert(callback.calledWith(null)); 163 | assert.equal(typeof callback.args[0][1], 'array'); 164 | 165 | done(); 166 | }, 2000); 167 | }); 168 | 169 | }); 170 | 171 | }); 172 | --------------------------------------------------------------------------------