├── index.js
├── .gitignore
├── README.md
├── gulpfile.js
├── nodes
└── Odoo
│ ├── Odoo.node.json
│ ├── odoo.svg
│ ├── ResourceDescription.ts
│ ├── Odoo.node.ts
│ └── GenericFunctions.ts
├── CONTRIBUTOR_LICENSE_AGREEMENT.md
├── tsconfig.json
├── LICENSE.md
├── .prettierrc.js
├── package.json
├── .eslintrc.js
└── tslint.json
/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .tmp
4 | tmp
5 | dist
6 | npm-debug.log*
7 | package-lock.json
8 | yarn.lock
9 | .idea
10 | .vscode
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # n8n-nodes-odoo
2 |
3 | 
4 |
5 | [Click here to view the README](https://digital-boss.de/n8n/?packageName=n8n-nodes-odoo)
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { task, src, dest } = require('gulp');
3 |
4 | task('build:icons', copyIcons);
5 |
6 | function copyIcons() {
7 | const nodeSource = path.resolve('nodes', '**', '*.{png,svg}');
8 | const nodeDestination = path.resolve('dist', 'nodes');
9 |
10 | return src(nodeSource).pipe(dest(nodeDestination));
11 | }
12 |
--------------------------------------------------------------------------------
/nodes/Odoo/Odoo.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "node": "n8n-nodes-base.odoo",
3 | "nodeVersion": "1.0",
4 | "codexVersion": "1.0",
5 | "categories": ["Data & Storage"],
6 | "resources": {
7 | "credentialDocumentation": [
8 | {
9 | "url": "https://docs.n8n.io/credentials/odoo"
10 | }
11 | ],
12 | "primaryDocumentation": [
13 | {
14 | "url": "https://docs.n8n.io/nodes/n8n-nodes-base.odoo/"
15 | }
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/CONTRIBUTOR_LICENSE_AGREEMENT.md:
--------------------------------------------------------------------------------
1 | # n8n Contributor License Agreement
2 |
3 | I give n8n permission to license my contributions on any terms they like. I am giving them this license in order to make it possible for them to accept my contributions into their project.
4 |
5 | **_As far as the law allows, my contributions come as is, without any warranty or condition, and I will not be liable to anyone for any damages related to this software or this license, under any kind of legal claim._**
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "target": "es2019",
7 | "lib": ["es2019", "es2020", "es2022.error", "dom"],
8 | "removeComments": true,
9 | "useUnknownInCatchVariables": false,
10 | "forceConsistentCasingInFileNames": true,
11 | "noImplicitAny": true,
12 | "noImplicitReturns": true,
13 | "noUnusedLocals": true,
14 | "strictNullChecks": true,
15 | "preserveConstEnums": true,
16 | "esModuleInterop": true,
17 | "resolveJsonModule": true,
18 | "incremental": true,
19 | "declaration": true,
20 | "sourceMap": true,
21 | "skipLibCheck": true,
22 | "outDir": "./dist/"
23 | },
24 | "include": ["nodes/**/*", "nodes/**/*.json", "package.json"]
25 | }
26 |
--------------------------------------------------------------------------------
/nodes/Odoo/odoo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2022 n8n GmbH
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /**
3 | * https://prettier.io/docs/en/options.html#semicolons
4 | */
5 | semi: true,
6 |
7 | /**
8 | * https://prettier.io/docs/en/options.html#trailing-commas
9 | */
10 | trailingComma: 'all',
11 |
12 | /**
13 | * https://prettier.io/docs/en/options.html#bracket-spacing
14 | */
15 | bracketSpacing: true,
16 |
17 | /**
18 | * https://prettier.io/docs/en/options.html#tabs
19 | */
20 | useTabs: true,
21 |
22 | /**
23 | * https://prettier.io/docs/en/options.html#tab-width
24 | */
25 | tabWidth: 2,
26 |
27 | /**
28 | * https://prettier.io/docs/en/options.html#arrow-function-parentheses
29 | */
30 | arrowParens: 'always',
31 |
32 | /**
33 | * https://prettier.io/docs/en/options.html#quotes
34 | */
35 | singleQuote: true,
36 |
37 | /**
38 | * https://prettier.io/docs/en/options.html#quote-props
39 | */
40 | quoteProps: 'as-needed',
41 |
42 | /**
43 | * https://prettier.io/docs/en/options.html#end-of-line
44 | */
45 | endOfLine: 'lf',
46 |
47 | /**
48 | * https://prettier.io/docs/en/options.html#print-width
49 | */
50 | printWidth: 100,
51 | };
52 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@digital-boss/n8n-nodes-odoo",
3 | "version": "0.5.0",
4 | "description": "A node consuming the Odoo API.",
5 | "keywords": [
6 | "n8n",
7 | "workflow",
8 | "n8n-community-node-package",
9 | "digital boss",
10 | "odoo"
11 | ],
12 | "license": "MIT",
13 | "homepage": "https://n8n.io",
14 | "author": {
15 | "name": "Steffen Hannesschlaeger",
16 | "email": "steffen_hannesschlaeger@hotmail.de"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/digital-boss/n8n-nodes-odoo"
21 | },
22 | "main": "index.js",
23 | "scripts": {
24 | "build": "tsc && gulp build:icons",
25 | "dev": "tsc --watch",
26 | "format": "prettier nodes --write",
27 | "lint": "eslint nodes package.json",
28 | "lintfix": "eslint nodes package.json --fix",
29 | "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes package.json"
30 | },
31 | "files": [
32 | "dist"
33 | ],
34 | "n8n": {
35 | "n8nNodesApiVersion": 1,
36 | "nodes": [
37 | "dist/nodes/Odoo/Odoo.node.js"
38 | ]
39 | },
40 | "devDependencies": {
41 | "@types/express": "^4.17.6",
42 | "@types/request-promise-native": "~1.0.15",
43 | "@typescript-eslint/parser": "^5.54.0",
44 | "eslint-plugin-n8n-nodes-base": "^1.12.1",
45 | "gulp": "^4.0.2",
46 | "n8n-core": "*",
47 | "n8n-workflow": "*",
48 | "nodelinter": "^0.1.19",
49 | "prettier": "^2.8.4",
50 | "typescript": "~4.8.4"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('@types/eslint').ESLint.ConfigData}
3 | */
4 | module.exports = {
5 | root: true,
6 |
7 | env: {
8 | browser: true,
9 | es6: true,
10 | node: true,
11 | },
12 |
13 | parser: '@typescript-eslint/parser',
14 |
15 | parserOptions: {
16 | project: ['./tsconfig.json'],
17 | sourceType: 'module',
18 | extraFileExtensions: ['.json'],
19 | },
20 |
21 | ignorePatterns: ['.eslintrc.js', '**/*.js', '**/node_modules/**', '**/dist/**'],
22 |
23 | overrides: [
24 | {
25 | files: ['package.json'],
26 | plugins: ['eslint-plugin-n8n-nodes-base'],
27 | extends: ['plugin:n8n-nodes-base/community'],
28 | rules: {
29 | 'n8n-nodes-base/community-package-json-name-still-default': 'off',
30 | },
31 | },
32 | {
33 | files: ['./credentials/**/*.ts'],
34 | plugins: ['eslint-plugin-n8n-nodes-base'],
35 | extends: ['plugin:n8n-nodes-base/credentials'],
36 | rules: {
37 | 'n8n-nodes-base/cred-class-field-documentation-url-missing': 'off',
38 | 'n8n-nodes-base/cred-class-field-documentation-url-miscased': 'off',
39 | },
40 | },
41 | {
42 | files: ['./nodes/**/*.ts'],
43 | plugins: ['eslint-plugin-n8n-nodes-base'],
44 | extends: ['plugin:n8n-nodes-base/nodes'],
45 | rules: {
46 | 'n8n-nodes-base/node-execute-block-missing-continue-on-fail': 'off',
47 | 'n8n-nodes-base/node-resource-description-filename-against-convention': 'off',
48 | 'n8n-nodes-base/node-param-fixed-collection-type-unsorted-items': 'off',
49 | 'n8n-nodes-base/node-param-description-missing-from-dynamic-options': 'off',
50 | 'n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options': 'off',
51 | 'n8n-nodes-base/node-param-type-options-max-value-present': 'off',
52 | 'n8n-nodes-base/node-param-description-wrong-for-dynamic-options': 'off',
53 | 'n8n-nodes-base/node-param-description-missing-from-dynamic-multi-options': 'off',
54 | 'n8n-nodes-base/node-param-resource-without-no-data-expression': 'off',
55 | 'n8n-nodes-base/node-param-operation-without-no-data-expression': 'off',
56 | },
57 | },
58 | ],
59 | };
60 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "linterOptions": {
3 | "exclude": ["node_modules/**/*"]
4 | },
5 | "defaultSeverity": "error",
6 | "jsRules": {},
7 | "rules": {
8 | "array-type": [true, "array-simple"],
9 | "arrow-return-shorthand": true,
10 | "ban": [
11 | true,
12 | {
13 | "name": "Array",
14 | "message": "tsstyle#array-constructor"
15 | }
16 | ],
17 | "ban-types": [
18 | true,
19 | ["Object", "Use {} instead."],
20 | ["String", "Use 'string' instead."],
21 | ["Number", "Use 'number' instead."],
22 | ["Boolean", "Use 'boolean' instead."]
23 | ],
24 | "class-name": true,
25 | "curly": [true, "ignore-same-line"],
26 | "forin": true,
27 | "jsdoc-format": true,
28 | "label-position": true,
29 | "indent": [true, "tabs", 2],
30 | "member-access": [true, "no-public"],
31 | "new-parens": true,
32 | "no-angle-bracket-type-assertion": true,
33 | "no-any": true,
34 | "no-arg": true,
35 | "no-conditional-assignment": true,
36 | "no-construct": true,
37 | "no-debugger": true,
38 | "no-default-export": true,
39 | "no-duplicate-variable": true,
40 | "no-inferrable-types": true,
41 | "ordered-imports": [
42 | true,
43 | {
44 | "import-sources-order": "any",
45 | "named-imports-order": "case-insensitive"
46 | }
47 | ],
48 | "no-namespace": [true, "allow-declarations"],
49 | "no-reference": true,
50 | "no-string-throw": true,
51 | "no-unused-expression": true,
52 | "no-var-keyword": true,
53 | "object-literal-shorthand": true,
54 | "only-arrow-functions": [true, "allow-declarations", "allow-named-functions"],
55 | "prefer-const": true,
56 | "radix": true,
57 | "semicolon": [true, "always", "ignore-bound-class-methods"],
58 | "switch-default": true,
59 | "trailing-comma": [
60 | true,
61 | {
62 | "multiline": {
63 | "objects": "always",
64 | "arrays": "always",
65 | "functions": "always",
66 | "typeLiterals": "ignore"
67 | },
68 | "esSpecCompliant": true
69 | }
70 | ],
71 | "triple-equals": [true, "allow-null-check"],
72 | "use-isnan": true,
73 | "quotes": ["error", "single"],
74 | "variable-name": [
75 | true,
76 | "check-format",
77 | "ban-keywords",
78 | "allow-leading-underscore",
79 | "allow-trailing-underscore"
80 | ]
81 | },
82 | "rulesDirectory": []
83 | }
84 |
--------------------------------------------------------------------------------
/nodes/Odoo/ResourceDescription.ts:
--------------------------------------------------------------------------------
1 | import { INodeProperties } from 'n8n-workflow';
2 |
3 | export const resourceOperations: INodeProperties[] = [
4 | {
5 | displayName: 'Resource',
6 | name: 'resource',
7 | type: 'options',
8 | default: '',
9 | description: 'Choose from the list',
10 | typeOptions: {
11 | loadOptionsMethod: 'getModels',
12 | },
13 | },
14 | {
15 | displayName: 'Operation',
16 | name: 'operation',
17 | type: 'options',
18 | default: '',
19 | description: 'Choose from the list',
20 | typeOptions: {
21 | loadOptionsMethod: 'getOperations',
22 | },
23 | },
24 | {
25 | displayName: 'Custom Operation',
26 | name: 'customOperation',
27 | type: 'options',
28 | default: '',
29 | description:
30 | 'Trigger any actionable method associated with the resource, such as action_confirm',
31 | required: true,
32 | typeOptions: {
33 | loadOptionsDependsOn: ['resource'],
34 | loadOptionsMethod: 'getActions',
35 | },
36 | displayOptions: {
37 | show: {
38 | operation: ['workflow'],
39 | },
40 | },
41 | },
42 | ];
43 |
44 | export const resourceDescription: INodeProperties[] = [
45 | /* -------------------------------------------------------------------------- */
46 | /* custom:create */
47 | /* -------------------------------------------------------------------------- */
48 | {
49 | displayName: 'Fields',
50 | name: 'fieldsToCreateOrUpdate',
51 | type: 'fixedCollection',
52 | typeOptions: {
53 | multipleValues: true,
54 | multipleValueButtonText: 'Add Field',
55 | },
56 | default: {},
57 | placeholder: 'Add Field',
58 | displayOptions: {
59 | show: {
60 | operation: ['create'],
61 | },
62 | },
63 | options: [
64 | {
65 | displayName: 'Field Record:',
66 | name: 'fields',
67 | values: [
68 | {
69 | displayName: 'Field Name',
70 | name: 'fieldName',
71 | type: 'options',
72 | default: '',
73 | typeOptions: {
74 | loadOptionsMethod: 'getModelFields',
75 | },
76 | },
77 | {
78 | displayName: 'New Value',
79 | name: 'fieldValue',
80 | type: 'string',
81 | default: '',
82 | },
83 | ],
84 | },
85 | ],
86 | },
87 |
88 | /* -------------------------------------------------------------------------- */
89 | /* custom:get */
90 | /* -------------------------------------------------------------------------- */
91 | {
92 | displayName: 'ID',
93 | name: 'id',
94 | type: 'string',
95 | description: 'The resource ID',
96 | default: '',
97 | required: true,
98 | displayOptions: {
99 | show: {
100 | operation: ['get', 'delete'],
101 | },
102 | },
103 | },
104 | /* -------------------------------------------------------------------------- */
105 | /* custom:getAll */
106 | /* -------------------------------------------------------------------------- */
107 | {
108 | displayName: 'Return All',
109 | name: 'returnAll',
110 | type: 'boolean',
111 | displayOptions: {
112 | show: {
113 | operation: ['getAll'],
114 | },
115 | },
116 | default: false,
117 | description: 'Whether to return all results or only up to a given limit',
118 | },
119 |
120 | {
121 | displayName: 'Offset',
122 | name: 'offset',
123 | type: 'number',
124 | default: 0,
125 | displayOptions: {
126 | show: {
127 | operation: ['getAll'],
128 | returnAll: [false],
129 | },
130 | },
131 | typeOptions: {
132 | minValue: 0,
133 | },
134 | description: 'Number of results to skip',
135 | },
136 | {
137 | displayName: 'Limit',
138 | name: 'limit',
139 | type: 'number',
140 | default: 50,
141 | displayOptions: {
142 | show: {
143 | operation: ['getAll'],
144 | returnAll: [false],
145 | },
146 | },
147 | typeOptions: {
148 | minValue: 1,
149 | maxValue: 1000,
150 | },
151 | description: 'Max number of results to return',
152 | },
153 | {
154 | displayName: 'Options',
155 | name: 'options',
156 | type: 'collection',
157 | default: {},
158 | placeholder: 'Add Field',
159 | displayOptions: {
160 | show: {
161 | operation: ['getAll', 'get'],
162 | },
163 | },
164 | options: [
165 | {
166 | displayName: 'Fields To Include',
167 | name: 'fieldsList',
168 | type: 'multiOptions',
169 | default: [],
170 | typeOptions: {
171 | loadOptionsMethod: 'getModelFields',
172 | loadOptionsDependsOn: ['resource'],
173 | },
174 | },
175 | ],
176 | },
177 | {
178 | displayName: 'Filters',
179 | name: 'filterRequest',
180 | type: 'fixedCollection',
181 | typeOptions: {
182 | multipleValues: true,
183 | multipleValueButtonText: 'Add Filter',
184 | },
185 | default: {},
186 | description: 'Filter request by applying filters',
187 | placeholder: 'Add condition',
188 | displayOptions: {
189 | show: {
190 | operation: ['getAll'],
191 | },
192 | },
193 | options: [
194 | {
195 | name: 'filter',
196 | displayName: 'Filter',
197 | values: [
198 | {
199 | displayName: 'Field',
200 | name: 'fieldName',
201 | type: 'options',
202 | default: '',
203 | typeOptions: {
204 | loadOptionsDependsOn: ['resource'],
205 | loadOptionsMethod: 'getModelFields',
206 | },
207 | },
208 | {
209 | displayName: 'Operator',
210 | name: 'operator',
211 | type: 'options',
212 | default: 'equal',
213 | description: 'Specify an operator',
214 | options: [
215 | {
216 | name: '!=',
217 | value: 'notEqual',
218 | },
219 | {
220 | name: '<',
221 | value: 'lesserThen',
222 | },
223 | {
224 | name: '<=',
225 | value: 'lesserOrEqual',
226 | },
227 | {
228 | name: '=',
229 | value: 'equal',
230 | },
231 | {
232 | name: '>',
233 | value: 'greaterThen',
234 | },
235 | {
236 | name: '>=',
237 | value: 'greaterOrEqual',
238 | },
239 | {
240 | name: 'Child Of',
241 | value: 'childOf',
242 | },
243 | {
244 | name: 'In',
245 | value: 'in',
246 | },
247 | {
248 | name: 'Like',
249 | value: 'like',
250 | },
251 | {
252 | name: 'Not In',
253 | value: 'notIn',
254 | },
255 | ],
256 | },
257 | {
258 | displayName: 'Value',
259 | name: 'value',
260 | type: 'string',
261 | default: '',
262 | description: 'Specify value for comparison',
263 | },
264 | ],
265 | },
266 | ],
267 | },
268 | /* -------------------------------------------------------------------------- */
269 | /* custom:update */
270 | /* -------------------------------------------------------------------------- */
271 | {
272 | displayName: 'ID',
273 | name: 'id',
274 | type: 'string',
275 | description: 'The resource ID',
276 | default: '',
277 | required: true,
278 |
279 | displayOptions: {
280 | show: {
281 | operation: ['update'],
282 | },
283 | },
284 | },
285 |
286 | {
287 | displayName: 'Update Fields',
288 | name: 'fieldsToCreateOrUpdate',
289 | type: 'fixedCollection',
290 | typeOptions: {
291 | multipleValues: true,
292 | multipleValueButtonText: 'Add Field',
293 | },
294 | default: {},
295 | placeholder: 'Add Field',
296 | displayOptions: {
297 | show: {
298 | operation: ['update'],
299 | },
300 | },
301 | options: [
302 | {
303 | displayName: 'Field Record:',
304 | name: 'fields',
305 | values: [
306 | {
307 | displayName: 'Field Name',
308 | name: 'fieldName',
309 | type: 'options',
310 | default: '',
311 | typeOptions: {
312 | loadOptionsMethod: 'getModelFields',
313 | },
314 | },
315 | {
316 | displayName: 'New Value',
317 | name: 'fieldValue',
318 | type: 'string',
319 | default: '',
320 | },
321 | ],
322 | },
323 | ],
324 | },
325 |
326 | /* -------------------------------------------------------------------------- */
327 | /* custom:workflow */
328 | /* -------------------------------------------------------------------------- */
329 | {
330 | displayName: 'ID',
331 | name: 'id',
332 | type: 'string',
333 | description: 'The resource ID',
334 | default: '',
335 | required: true,
336 | displayOptions: {
337 | show: {
338 | operation: ['workflow'],
339 | },
340 | },
341 | },
342 | ];
343 |
--------------------------------------------------------------------------------
/nodes/Odoo/Odoo.node.ts:
--------------------------------------------------------------------------------
1 | import { IExecuteFunctions } from 'n8n-core';
2 |
3 | import {
4 | IDataObject,
5 | ILoadOptionsFunctions,
6 | INodeExecutionData,
7 | INodePropertyOptions,
8 | INodeType,
9 | INodeTypeDescription,
10 | JsonObject,
11 | } from 'n8n-workflow';
12 |
13 | import {
14 | IOdooFilterOperations,
15 | odooCreate,
16 | odooDelete,
17 | odooGet,
18 | odooGetActionMethods,
19 | odooGetAll,
20 | odooGetDBName,
21 | odooGetModelFields,
22 | odooGetUserID,
23 | odooIsAddonInstalled,
24 | odooJSONRPCRequest,
25 | odooUpdate,
26 | odooWorkflow,
27 | processNameValueFields,
28 | } from './GenericFunctions';
29 | import { resourceDescription, resourceOperations } from './ResourceDescription';
30 |
31 | export class Odoo implements INodeType {
32 | description: INodeTypeDescription = {
33 | displayName: 'Odoo',
34 | name: 'odoo',
35 | icon: 'file:odoo.svg',
36 | group: ['transform'],
37 | version: 1,
38 | description: 'Consume Odoo API',
39 | subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
40 | defaults: {
41 | name: 'Odoo',
42 | },
43 | inputs: ['main'],
44 | outputs: ['main'],
45 | credentials: [
46 | {
47 | name: 'odooApi',
48 | required: true,
49 | testedBy: 'odooApiTest',
50 | },
51 | ],
52 | properties: [...resourceOperations, ...resourceDescription],
53 | };
54 |
55 | methods = {
56 | loadOptions: {
57 | async getModelFields(this: ILoadOptionsFunctions): Promise {
58 | let resource;
59 | resource = this.getCurrentNodeParameter('resource') as string;
60 | if (!resource) {
61 | return [];
62 | }
63 |
64 | const credentials = await this.getCredentials('odooApi');
65 | const url = credentials?.url as string;
66 | const username = credentials?.username as string;
67 | const password = credentials?.password as string;
68 | const db = odooGetDBName(credentials?.db as string, url);
69 | const userID = await odooGetUserID.call(this, db, username, password, url);
70 |
71 | const response = await odooGetModelFields.call(this, db, userID, password, resource, url);
72 |
73 | const options = Object.entries(response).map(([k, v]) => {
74 | const optionField = v as { [key: string]: string };
75 | return {
76 | name: optionField.string,
77 | value: k,
78 | // nodelinter-ignore-next-line
79 | description: `name: ${optionField?.string}, type: ${optionField?.type} required: ${optionField?.required}`,
80 | };
81 | });
82 |
83 | return options.sort((a, b) => a.name?.localeCompare(b.name) || 0);
84 | },
85 | async getModels(this: ILoadOptionsFunctions): Promise {
86 | const credentials = await this.getCredentials('odooApi');
87 | const url = credentials?.url as string;
88 | const username = credentials?.username as string;
89 | const password = credentials?.password as string;
90 | const db = odooGetDBName(credentials?.db as string, url);
91 | const userID = await odooGetUserID.call(this, db, username, password, url);
92 |
93 | const body = {
94 | jsonrpc: '2.0',
95 | method: 'call',
96 | params: {
97 | service: 'object',
98 | method: 'execute',
99 | args: [
100 | db,
101 | userID,
102 | password,
103 | 'ir.model',
104 | 'search_read',
105 | [],
106 | ['name', 'model', 'modules'],
107 | ],
108 | },
109 | id: Math.floor(Math.random() * 100),
110 | };
111 |
112 | const response = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
113 |
114 | const options = response.map((model) => ({
115 | name: model.name,
116 | value: model.model,
117 | // eslint-disable-next-line n8n-nodes-base/node-param-description-line-break-html-tag
118 | description: `Model: ${model.model}
Modules: ${model.modules}`,
119 | }));
120 | return options as INodePropertyOptions[];
121 | },
122 | async getStates(this: ILoadOptionsFunctions): Promise {
123 | const credentials = await this.getCredentials('odooApi');
124 | const url = credentials?.url as string;
125 | const username = credentials?.username as string;
126 | const password = credentials?.password as string;
127 | const db = odooGetDBName(credentials?.db as string, url);
128 | const userID = await odooGetUserID.call(this, db, username, password, url);
129 |
130 | const body = {
131 | jsonrpc: '2.0',
132 | method: 'call',
133 | params: {
134 | service: 'object',
135 | method: 'execute',
136 | args: [db, userID, password, 'res.country.state', 'search_read', [], ['id', 'name']],
137 | },
138 | id: Math.floor(Math.random() * 100),
139 | };
140 |
141 | const response = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
142 |
143 | const options = response.map((state) => ({
144 | name: state.name as string,
145 | value: state.id,
146 | }));
147 | return options.sort((a, b) => a.name?.localeCompare(b.name) || 0) as INodePropertyOptions[];
148 | },
149 | async getCountries(this: ILoadOptionsFunctions): Promise {
150 | const credentials = await this.getCredentials('odooApi');
151 | const url = credentials?.url as string;
152 | const username = credentials?.username as string;
153 | const password = credentials?.password as string;
154 | const db = odooGetDBName(credentials?.db as string, url);
155 | const userID = await odooGetUserID.call(this, db, username, password, url);
156 |
157 | const body = {
158 | jsonrpc: '2.0',
159 | method: 'call',
160 | params: {
161 | service: 'object',
162 | method: 'execute',
163 | args: [db, userID, password, 'res.country', 'search_read', [], ['id', 'name']],
164 | },
165 | id: Math.floor(Math.random() * 100),
166 | };
167 |
168 | const response = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
169 |
170 | const options = response.map((country) => ({
171 | name: country.name as string,
172 | value: country.id,
173 | }));
174 |
175 | return options.sort((a, b) => a.name?.localeCompare(b.name) || 0) as INodePropertyOptions[];
176 | },
177 | async getActions(this: ILoadOptionsFunctions): Promise {
178 | let resource;
179 | resource = this.getCurrentNodeParameter('resource') as string;
180 | if (!resource) {
181 | return [];
182 | }
183 |
184 | const credentials = await this.getCredentials('odooApi');
185 | const url = credentials?.url as string;
186 | const username = credentials?.username as string;
187 | const password = credentials?.password as string;
188 | const db = odooGetDBName(credentials?.db as string, url);
189 | const userID = await odooGetUserID.call(this, db, username, password, url);
190 |
191 | const response = await odooGetActionMethods.call(this, db, userID, password, resource, url);
192 |
193 | if (response) {
194 | const options = response.map((x) => ({
195 | name: x,
196 | value: x,
197 | }));
198 |
199 | return options;
200 | } else {
201 | return [];
202 | }
203 | },
204 | async getOperations(this: ILoadOptionsFunctions): Promise {
205 | const operations = [
206 | {
207 | name: 'Create',
208 | value: 'create',
209 | description: 'Create a new item',
210 | },
211 | {
212 | name: 'Delete',
213 | value: 'delete',
214 | description: 'Delete an item',
215 | },
216 | {
217 | name: 'Get',
218 | value: 'get',
219 | description: 'Get an item',
220 | },
221 | {
222 | name: 'Get Many',
223 | value: 'getAll',
224 | description: 'Get all items',
225 | },
226 | {
227 | name: 'Update',
228 | value: 'update',
229 | description: 'Update an item',
230 | },
231 | ];
232 |
233 | const installed = await odooIsAddonInstalled.call(this);
234 |
235 | if (installed) {
236 | operations.push({
237 | name: 'Workflow',
238 | value: 'workflow',
239 | description: 'Trigger a workflow action',
240 | });
241 | }
242 |
243 | return operations;
244 | },
245 | },
246 | };
247 |
248 | async execute(this: IExecuteFunctions): Promise {
249 | let items = this.getInputData();
250 | items = JSON.parse(JSON.stringify(items));
251 | const returnData: IDataObject[] = [];
252 | let responseData;
253 |
254 | const resource = this.getNodeParameter('resource', 0) as string;
255 | const operation = this.getNodeParameter('operation', 0) as string;
256 |
257 | const credentials = await this.getCredentials('odooApi');
258 | const url = (credentials?.url as string).replace(/\/$/, '');
259 | const username = credentials?.username as string;
260 | const password = credentials?.password as string;
261 | const db = odooGetDBName(credentials?.db as string, url);
262 | const userID = await odooGetUserID.call(this, db, username, password, url);
263 |
264 | //----------------------------------------------------------------------
265 | // Main loop
266 | //----------------------------------------------------------------------
267 |
268 | for (let i = 0; i < items.length; i++) {
269 | try {
270 | if (operation === 'create') {
271 | const fields = this.getNodeParameter('fieldsToCreateOrUpdate', i) as IDataObject;
272 | responseData = await odooCreate.call(
273 | this,
274 | db,
275 | userID,
276 | password,
277 | resource,
278 | operation,
279 | url,
280 | processNameValueFields(fields),
281 | );
282 | }
283 |
284 | if (operation === 'delete') {
285 | const id = this.getNodeParameter('id', i) as string;
286 | responseData = await odooDelete.call(
287 | this,
288 | db,
289 | userID,
290 | password,
291 | resource,
292 | operation,
293 | url,
294 | id,
295 | );
296 | }
297 |
298 | if (operation === 'get') {
299 | const id = this.getNodeParameter('id', i) as string;
300 | const options = this.getNodeParameter('options', i) as IDataObject;
301 | const fields = (options.fieldsList as IDataObject[]) || [];
302 | responseData = await odooGet.call(
303 | this,
304 | db,
305 | userID,
306 | password,
307 | resource,
308 | operation,
309 | url,
310 | id,
311 | fields,
312 | );
313 | }
314 |
315 | if (operation === 'getAll') {
316 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
317 | const options = this.getNodeParameter('options', i) as IDataObject;
318 | const fields = (options.fieldsList as IDataObject[]) || [];
319 | const filter = this.getNodeParameter('filterRequest', i) as IOdooFilterOperations;
320 | if (returnAll) {
321 | responseData = await odooGetAll.call(
322 | this,
323 | db,
324 | userID,
325 | password,
326 | resource,
327 | operation,
328 | url,
329 | filter,
330 | fields,
331 | );
332 | } else {
333 | const offset = this.getNodeParameter('offset', i) as number;
334 | const limit = this.getNodeParameter('limit', i) as number;
335 | responseData = await odooGetAll.call(
336 | this,
337 | db,
338 | userID,
339 | password,
340 | resource,
341 | operation,
342 | url,
343 | filter,
344 | fields,
345 | offset,
346 | limit,
347 | );
348 | }
349 | }
350 |
351 | if (operation === 'update') {
352 | const id = this.getNodeParameter('id', i) as string;
353 | const fields = this.getNodeParameter('fieldsToCreateOrUpdate', i) as IDataObject;
354 | responseData = await odooUpdate.call(
355 | this,
356 | db,
357 | userID,
358 | password,
359 | resource,
360 | operation,
361 | url,
362 | id,
363 | processNameValueFields(fields),
364 | );
365 | }
366 |
367 | if (operation === 'workflow') {
368 | const id = this.getNodeParameter('id', i) as string;
369 | const customOperation = this.getNodeParameter('customOperation', i) as string;
370 | responseData = await odooWorkflow.call(
371 | this,
372 | db,
373 | userID,
374 | password,
375 | resource,
376 | customOperation,
377 | url,
378 | id,
379 | );
380 | }
381 |
382 | if (Array.isArray(responseData)) {
383 | returnData.push.apply(returnData, responseData);
384 | } else if (responseData !== undefined) {
385 | returnData.push(responseData);
386 | }
387 | } catch (error) {
388 | if (this.continueOnFail()) {
389 | returnData.push({ error: (error as JsonObject).message });
390 | continue;
391 | }
392 | throw error;
393 | }
394 | }
395 |
396 | return [this.helpers.returnJsonArray(returnData)];
397 | }
398 | }
399 |
--------------------------------------------------------------------------------
/nodes/Odoo/GenericFunctions.ts:
--------------------------------------------------------------------------------
1 | import { OptionsWithUri } from 'request';
2 |
3 | import {
4 | IExecuteFunctions,
5 | IExecuteSingleFunctions,
6 | IHookFunctions,
7 | ILoadOptionsFunctions,
8 | } from 'n8n-core';
9 |
10 | import { IDataObject, JsonObject, NodeApiError } from 'n8n-workflow';
11 |
12 | const serviceJSONRPC = 'object';
13 | const methodJSONRPC = 'execute';
14 |
15 | export const mapOperationToJSONRPC = {
16 | create: 'create',
17 | get: 'read',
18 | getAll: 'search_read',
19 | update: 'write',
20 | delete: 'unlink',
21 | };
22 |
23 | export const mapOdooResources: { [key: string]: string } = {
24 | contact: 'res.partner',
25 | opportunity: 'crm.lead',
26 | note: 'note.note',
27 | };
28 |
29 | export const mapFilterOperationToJSONRPC = {
30 | equal: '=',
31 | notEqual: '!=',
32 | greaterThen: '>',
33 | lesserThen: '<',
34 | greaterOrEqual: '>=',
35 | lesserOrEqual: '<=',
36 | like: 'like',
37 | in: 'in',
38 | notIn: 'not in',
39 | childOf: 'child_of',
40 | };
41 |
42 | type FilterOperation =
43 | | 'equal'
44 | | 'notEqual'
45 | | 'greaterThen'
46 | | 'lesserThen'
47 | | 'greaterOrEqual'
48 | | 'lesserOrEqual'
49 | | 'like'
50 | | 'in'
51 | | 'notIn'
52 | | 'childOf';
53 |
54 | export interface IOdooFilterOperations {
55 | filter: Array<{
56 | fieldName: string;
57 | operator: string;
58 | value: string | number;
59 | }>;
60 | }
61 |
62 | export interface IOdooNameValueFields {
63 | fields: Array<{
64 | fieldName: string;
65 | fieldValue: string;
66 | }>;
67 | }
68 |
69 | export interface IOdooResponseFields {
70 | fields: Array<{
71 | field: string;
72 | fromList?: boolean;
73 | }>;
74 | }
75 |
76 | type OdooCRUD = 'create' | 'update' | 'delete' | 'get' | 'getAll';
77 |
78 | export function odooGetDBName(databaseName: string | undefined, url: string) {
79 | if (databaseName) return databaseName;
80 | const odooURL = new URL(url);
81 | const hostname = odooURL.hostname;
82 | if (!hostname) return '';
83 | return odooURL.hostname.split('.')[0];
84 | }
85 |
86 | function processFilters(value: IOdooFilterOperations) {
87 | return value.filter?.map((item) => {
88 | const operator = item.operator as FilterOperation;
89 | item.operator = mapFilterOperationToJSONRPC[operator];
90 | return Object.values(item);
91 | });
92 | }
93 |
94 | export function processNameValueFields(value: IDataObject) {
95 | const data = value as unknown as IOdooNameValueFields;
96 | return data?.fields?.reduce((acc, record) => {
97 | return Object.assign(acc, { [record.fieldName]: record.fieldValue });
98 | }, {});
99 | }
100 |
101 | // function processResponseFields(value: IDataObject) {
102 | // const data = value as unknown as IOdooResponseFields;
103 | // return data?.fields?.map((entry) => entry.field);
104 | // }
105 |
106 | export async function odooJSONRPCRequest(
107 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
108 | body: IDataObject,
109 | url: string,
110 | ): Promise {
111 | try {
112 | const options: OptionsWithUri = {
113 | headers: {
114 | 'User-Agent': 'n8n',
115 | Connection: 'keep-alive',
116 | Accept: '*/*',
117 | 'Content-Type': 'application/json',
118 | },
119 | method: 'POST',
120 | body,
121 | uri: `${url}/jsonrpc`,
122 | json: true,
123 | };
124 |
125 | const response = await this.helpers.request!(options);
126 | if (response.error) {
127 | throw new NodeApiError(this.getNode(), response.error.data, {
128 | message: response.error.data.message,
129 | });
130 | }
131 | return response.result;
132 | } catch (error) {
133 | throw new NodeApiError(this.getNode(), error as JsonObject);
134 | }
135 | }
136 |
137 | export async function odooGetModelFields(
138 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
139 | db: string,
140 | userID: number,
141 | password: string,
142 | resource: string,
143 | url: string,
144 | ) {
145 | try {
146 | const body = {
147 | jsonrpc: '2.0',
148 | method: 'call',
149 | params: {
150 | service: serviceJSONRPC,
151 | method: methodJSONRPC,
152 | args: [
153 | db,
154 | userID,
155 | password,
156 | mapOdooResources[resource] || resource,
157 | 'fields_get',
158 | [],
159 | ['string', 'type', 'help', 'required', 'name'],
160 | ],
161 | },
162 | id: Math.floor(Math.random() * 100),
163 | };
164 |
165 | const result = await odooJSONRPCRequest.call(this, body, url);
166 | return result;
167 | } catch (error) {
168 | throw new NodeApiError(this.getNode(), error as JsonObject);
169 | }
170 | }
171 |
172 | export async function odooIsAddonInstalled(
173 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
174 | ): Promise {
175 | try {
176 | const credentials = await this.getCredentials('odooApi');
177 | const url = credentials?.url as string;
178 | const username = credentials?.username as string;
179 | const password = credentials?.password as string;
180 | const db = odooGetDBName(credentials?.db as string, url);
181 | const userID = await odooGetUserID.call(this, db, username, password, url);
182 |
183 | const body = {
184 | jsonrpc: '2.0',
185 | method: 'call',
186 | params: {
187 | service: serviceJSONRPC,
188 | method: methodJSONRPC,
189 | args: [db, userID, password, 'ir.model', 'search_read', [['model', '=', 'base']], []],
190 | },
191 | id: Math.floor(Math.random() * 100),
192 | };
193 |
194 | const result = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
195 | if (result?.length === 1 && result[0].hasOwnProperty('methods')) {
196 | return true;
197 | } else {
198 | return false;
199 | }
200 | } catch (error) {
201 | return false;
202 | }
203 | }
204 |
205 | export async function odooGetActionMethods(
206 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
207 | db: string,
208 | userID: number,
209 | password: string,
210 | resource: string,
211 | url: string,
212 | ): Promise {
213 | try {
214 | const body = {
215 | jsonrpc: '2.0',
216 | method: 'call',
217 | params: {
218 | service: serviceJSONRPC,
219 | method: methodJSONRPC,
220 | args: [
221 | db,
222 | userID,
223 | password,
224 | 'ir.model',
225 | 'search_read',
226 | [['model', '=', resource]],
227 | ['methods'],
228 | ],
229 | },
230 | id: Math.floor(Math.random() * 100),
231 | };
232 |
233 | const result = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
234 | if (result?.length === 1 && result[0].hasOwnProperty('methods')) {
235 | const methods = JSON.parse(result[0].methods as string) as string[];
236 | return methods;
237 | } else {
238 | return undefined;
239 | }
240 | } catch (error) {
241 | throw new NodeApiError(this.getNode(), error as JsonObject);
242 | }
243 | }
244 |
245 | export async function odooCreate(
246 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
247 | db: string,
248 | userID: number,
249 | password: string,
250 | resource: string,
251 | operation: OdooCRUD,
252 | url: string,
253 | newItem: IDataObject,
254 | ) {
255 | try {
256 | const body = {
257 | jsonrpc: '2.0',
258 | method: 'call',
259 | params: {
260 | service: serviceJSONRPC,
261 | method: methodJSONRPC,
262 | args: [
263 | db,
264 | userID,
265 | password,
266 | mapOdooResources[resource] || resource,
267 | mapOperationToJSONRPC[operation],
268 | newItem || {},
269 | ],
270 | },
271 | id: Math.floor(Math.random() * 100),
272 | };
273 |
274 | const result = await odooJSONRPCRequest.call(this, body, url);
275 | return { id: result };
276 | } catch (error) {
277 | throw new NodeApiError(this.getNode(), error as JsonObject);
278 | }
279 | }
280 |
281 | export async function odooGet(
282 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
283 | db: string,
284 | userID: number,
285 | password: string,
286 | resource: string,
287 | operation: OdooCRUD,
288 | url: string,
289 | itemsID: string,
290 | fieldsToReturn?: IDataObject[],
291 | ) {
292 | try {
293 | if (!/^\d+$/.test(itemsID) || !parseInt(itemsID, 10)) {
294 | throw new NodeApiError(this.getNode(), {
295 | status: 'Error',
296 | message: `Please specify a valid ID: ${itemsID}`,
297 | });
298 | }
299 | const body = {
300 | jsonrpc: '2.0',
301 | method: 'call',
302 | params: {
303 | service: serviceJSONRPC,
304 | method: methodJSONRPC,
305 | args: [
306 | db,
307 | userID,
308 | password,
309 | mapOdooResources[resource] || resource,
310 | mapOperationToJSONRPC[operation],
311 | [+itemsID] || [],
312 | fieldsToReturn || [],
313 | ],
314 | },
315 | id: Math.floor(Math.random() * 100),
316 | };
317 |
318 | const result = await odooJSONRPCRequest.call(this, body, url);
319 | return result;
320 | } catch (error) {
321 | throw new NodeApiError(this.getNode(), error as JsonObject);
322 | }
323 | }
324 |
325 | export async function odooGetAll(
326 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
327 | db: string,
328 | userID: number,
329 | password: string,
330 | resource: string,
331 | operation: OdooCRUD,
332 | url: string,
333 | filters?: IOdooFilterOperations,
334 | fieldsToReturn?: IDataObject[],
335 | offset = 0,
336 | limit = 0,
337 | ) {
338 | try {
339 | const body = {
340 | jsonrpc: '2.0',
341 | method: 'call',
342 | params: {
343 | service: serviceJSONRPC,
344 | method: methodJSONRPC,
345 | args: [
346 | db,
347 | userID,
348 | password,
349 | mapOdooResources[resource] || resource,
350 | mapOperationToJSONRPC[operation],
351 | (filters && processFilters(filters)) || [],
352 | fieldsToReturn || [],
353 | offset,
354 | limit,
355 | ],
356 | },
357 | id: Math.floor(Math.random() * 100),
358 | };
359 |
360 | const result = await odooJSONRPCRequest.call(this, body, url);
361 | return result;
362 | } catch (error) {
363 | throw new NodeApiError(this.getNode(), error as JsonObject);
364 | }
365 | }
366 |
367 | export async function odooUpdate(
368 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
369 | db: string,
370 | userID: number,
371 | password: string,
372 | resource: string,
373 | operation: OdooCRUD,
374 | url: string,
375 | itemsID: string,
376 | fieldsToUpdate: IDataObject,
377 | ) {
378 | try {
379 | if (!Object.keys(fieldsToUpdate).length) {
380 | throw new NodeApiError(this.getNode(), {
381 | status: 'Error',
382 | message: `Please specify at least one field to update`,
383 | });
384 | }
385 | if (!/^\d+$/.test(itemsID) || !parseInt(itemsID, 10)) {
386 | throw new NodeApiError(this.getNode(), {
387 | status: 'Error',
388 | message: `Please specify a valid ID: ${itemsID}`,
389 | });
390 | }
391 | const body = {
392 | jsonrpc: '2.0',
393 | method: 'call',
394 | params: {
395 | service: serviceJSONRPC,
396 | method: methodJSONRPC,
397 | args: [
398 | db,
399 | userID,
400 | password,
401 | mapOdooResources[resource] || resource,
402 | mapOperationToJSONRPC[operation],
403 | [+itemsID] || [],
404 | fieldsToUpdate,
405 | ],
406 | },
407 | id: Math.floor(Math.random() * 100),
408 | };
409 |
410 | await odooJSONRPCRequest.call(this, body, url);
411 | return { id: itemsID };
412 | } catch (error) {
413 | throw new NodeApiError(this.getNode(), error as JsonObject);
414 | }
415 | }
416 |
417 | export async function odooWorkflow(
418 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
419 | db: string,
420 | userID: number,
421 | password: string,
422 | resource: string,
423 | customOperation: string,
424 | url: string,
425 | itemsID: string,
426 | ) {
427 | try {
428 | if (!/^\d+$/.test(itemsID) || !parseInt(itemsID, 10)) {
429 | throw new NodeApiError(this.getNode(), {
430 | status: 'Error',
431 | message: `Please specify a valid ID: ${itemsID}`,
432 | });
433 | }
434 | const body = {
435 | jsonrpc: '2.0',
436 | method: 'call',
437 | params: {
438 | service: 'object',
439 | method: 'execute',
440 | args: [db, userID, password, resource, customOperation, [+itemsID] || []],
441 | },
442 | id: Math.floor(Math.random() * 100),
443 | };
444 |
445 | const result = await odooJSONRPCRequest.call(this, body, url);
446 | return result;
447 | } catch (error) {
448 | throw new NodeApiError(this.getNode(), error as JsonObject);
449 | }
450 | }
451 |
452 | export async function odooDelete(
453 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
454 | db: string,
455 | userID: number,
456 | password: string,
457 | resource: string,
458 | operation: OdooCRUD,
459 | url: string,
460 | itemsID: string,
461 | ) {
462 | if (!/^\d+$/.test(itemsID) || !parseInt(itemsID, 10)) {
463 | throw new NodeApiError(this.getNode(), {
464 | status: 'Error',
465 | message: `Please specify a valid ID: ${itemsID}`,
466 | });
467 | }
468 | try {
469 | const body = {
470 | jsonrpc: '2.0',
471 | method: 'call',
472 | params: {
473 | service: serviceJSONRPC,
474 | method: methodJSONRPC,
475 | args: [
476 | db,
477 | userID,
478 | password,
479 | mapOdooResources[resource] || resource,
480 | mapOperationToJSONRPC[operation],
481 | [+itemsID] || [],
482 | ],
483 | },
484 | id: Math.floor(Math.random() * 100),
485 | };
486 |
487 | await odooJSONRPCRequest.call(this, body, url);
488 | return { success: true };
489 | } catch (error) {
490 | throw new NodeApiError(this.getNode(), error as JsonObject);
491 | }
492 | }
493 |
494 | export async function odooGetUserID(
495 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
496 | db: string,
497 | username: string,
498 | password: string,
499 | url: string,
500 | ): Promise {
501 | try {
502 | const body = {
503 | jsonrpc: '2.0',
504 | method: 'call',
505 | params: {
506 | service: 'common',
507 | method: 'login',
508 | args: [db, username, password],
509 | },
510 | id: Math.floor(Math.random() * 100),
511 | };
512 | const loginResult = await odooJSONRPCRequest.call(this, body, url);
513 | return loginResult as unknown as number;
514 | } catch (error) {
515 | throw new NodeApiError(this.getNode(), error as JsonObject);
516 | }
517 | }
518 |
519 | export async function odooGetServerVersion(
520 | this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
521 | url: string,
522 | ) {
523 | try {
524 | const body = {
525 | jsonrpc: '2.0',
526 | method: 'call',
527 | params: {
528 | service: 'common',
529 | method: 'version',
530 | args: [],
531 | },
532 | id: Math.floor(Math.random() * 100),
533 | };
534 | const result = await odooJSONRPCRequest.call(this, body, url);
535 | return result;
536 | } catch (error) {
537 | throw new NodeApiError(this.getNode(), error as JsonObject);
538 | }
539 | }
540 |
--------------------------------------------------------------------------------