diff --git a/packages/nodes-base/credentials/OdooApi.credentials.ts b/packages/nodes-base/credentials/OdooApi.credentials.ts new file mode 100644 index 0000000000..91b5e77f76 --- /dev/null +++ b/packages/nodes-base/credentials/OdooApi.credentials.ts @@ -0,0 +1,41 @@ +import { ICredentialType, INodeProperties, NodePropertyTypes } from 'n8n-workflow'; + +export class OdooApi implements ICredentialType { + name = 'odooApi'; + displayName = 'Odoo API'; + documentationUrl = 'odoo'; + properties: INodeProperties[] = [ + { + displayName: 'Site URL', + name: 'url', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'https://my-organization.odoo.com', + required: true, + }, + { + displayName: 'Username', + name: 'username', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'user@email.com', + required: true, + }, + { + displayName: 'Password or API Key', + name: 'password', + type: 'string' as NodePropertyTypes, + default: '', + typeOptions: { + password: true, + }, + required: true, + }, + { + displayName: 'Database Name', + name: 'db', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Odoo/GenericFunctions.ts b/packages/nodes-base/nodes/Odoo/GenericFunctions.ts new file mode 100644 index 0000000000..8e69468105 --- /dev/null +++ b/packages/nodes-base/nodes/Odoo/GenericFunctions.ts @@ -0,0 +1,436 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, + JsonObject, + NodeApiError, +} from 'n8n-workflow'; + +const serviceJSONRPC = 'object'; +const methodJSONRPC = 'execute'; + +export const mapOperationToJSONRPC = { + create: 'create', + get: 'read', + getAll: 'search_read', + update: 'write', + delete: 'unlink', +}; + +export const mapOdooResources: { [key: string]: string } = { + contact: 'res.partner', + opportunity: 'crm.lead', + note: 'note.note', +}; + +export const mapFilterOperationToJSONRPC = { + equal: '=', + notEqual: '!=', + greaterThen: '>', + lesserThen: '<', + greaterOrEqual: '>=', + lesserOrEqual: '<=', + like: 'like', + in: 'in', + notIn: 'not in', + childOf: 'child_of', +}; + +type FilterOperation = + | 'equal' + | 'notEqual' + | 'greaterThen' + | 'lesserThen' + | 'greaterOrEqual' + | 'lesserOrEqual' + | 'like' + | 'in' + | 'notIn' + | 'childOf'; + +export interface IOdooFilterOperations { + filter: Array<{ + fieldName: string; + operator: string; + value: string; + }>; +} + +export interface IOdooNameValueFields { + fields: Array<{ + fieldName: string; + fieldValue: string; + }>; +} + +export interface IOdooResponceFields { + fields: Array<{ + field: string; + fromList?: boolean; + }>; +} + +type OdooCRUD = 'create' | 'update' | 'delete' | 'get' | 'getAll'; + +export function odooGetDBName (databaseName: string | undefined, url: string) { + if (databaseName) return databaseName; + const odooURL = new URL(url); + const hostname = odooURL.hostname; + if (!hostname) return ''; + return odooURL.hostname.split('.')[0]; +} + +function processFilters(value: IOdooFilterOperations) { + return value.filter?.map((item) => { + const operator = item.operator as FilterOperation; + item.operator = mapFilterOperationToJSONRPC[operator]; + return Object.values(item); + }); +} + +export function processNameValueFields(value: IDataObject) { + const data = value as unknown as IOdooNameValueFields; + return data?.fields?.reduce((acc, record) => { + return Object.assign(acc, { [record.fieldName]: record.fieldValue }); + }, {}); +} + +// function processResponceFields(value: IDataObject) { +// const data = value as unknown as IOdooResponceFields; +// return data?.fields?.map((entry) => entry.field); +// } + +export async function odooJSONRPCRequest( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + body: IDataObject, + url: string, +): Promise { + try { + const options: OptionsWithUri = { + headers: { + 'User-Agent': 'n8n', + Connection: 'keep-alive', + Accept: '*/*', + 'Content-Type': 'application/json', + }, + method: 'POST', + body, + uri: `${url}/jsonrpc`, + json: true, + }; + + const responce = await this.helpers.request!(options); + if (responce.error) { + throw new NodeApiError(this.getNode(), responce.error.data, { + message: responce.error.data.message, + }); + } + return responce.result; + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +export async function odooGetModelFields( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + db: string, + userID: number, + password: string, + resource: string, + url: string, +) { + try { + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: serviceJSONRPC, + method: methodJSONRPC, + args: [ + db, + userID, + password, + mapOdooResources[resource] || resource, + 'fields_get', + [], + ['string', 'type', 'help', 'required', 'name'], + ], + }, + id: Math.floor(Math.random() * 100), + }; + + const result = await odooJSONRPCRequest.call(this, body, url); + return result; + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +export async function odooCreate( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + db: string, + userID: number, + password: string, + resource: string, + operation: OdooCRUD, + url: string, + newItem: IDataObject, +) { + try { + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: serviceJSONRPC, + method: methodJSONRPC, + args: [ + db, + userID, + password, + mapOdooResources[resource] || resource, + mapOperationToJSONRPC[operation], + newItem || {}, + ], + }, + id: Math.floor(Math.random() * 100), + }; + + const result = await odooJSONRPCRequest.call(this, body, url); + return { id: result }; + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +export async function odooGet( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + db: string, + userID: number, + password: string, + resource: string, + operation: OdooCRUD, + url: string, + itemsID: string, + fieldsToReturn?: IDataObject[], +) { + try { + if (!/^\d+$/.test(itemsID) || !parseInt(itemsID, 10)) { + throw new NodeApiError(this.getNode(), { + status: 'Error', + message: `Please specify a valid ID: ${itemsID}`, + }); + } + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: serviceJSONRPC, + method: methodJSONRPC, + args: [ + db, + userID, + password, + mapOdooResources[resource] || resource, + mapOperationToJSONRPC[operation], + [+itemsID] || [], + fieldsToReturn || [], + ], + }, + id: Math.floor(Math.random() * 100), + }; + + const result = await odooJSONRPCRequest.call(this, body, url); + return result; + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +export async function odooGetAll( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + db: string, + userID: number, + password: string, + resource: string, + operation: OdooCRUD, + url: string, + filters?: IOdooFilterOperations, + fieldsToReturn?: IDataObject[], + limit = 0, +) { + try { + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: serviceJSONRPC, + method: methodJSONRPC, + args: [ + db, + userID, + password, + mapOdooResources[resource] || resource, + mapOperationToJSONRPC[operation], + (filters && processFilters(filters)) || [], + fieldsToReturn || [], + 0, // offset + limit, + ], + }, + id: Math.floor(Math.random() * 100), + }; + + const result = await odooJSONRPCRequest.call(this, body, url); + return result; + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +export async function odooUpdate( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + db: string, + userID: number, + password: string, + resource: string, + operation: OdooCRUD, + url: string, + itemsID: string, + fieldsToUpdate: IDataObject, +) { + try { + if (!Object.keys(fieldsToUpdate).length) { + throw new NodeApiError(this.getNode(), { + status: 'Error', + message: `Please specify at least one field to update`, + }); + } + if (!/^\d+$/.test(itemsID) || !parseInt(itemsID, 10)) { + throw new NodeApiError(this.getNode(), { + status: 'Error', + message: `Please specify a valid ID: ${itemsID}`, + }); + } + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: serviceJSONRPC, + method: methodJSONRPC, + args: [ + db, + userID, + password, + mapOdooResources[resource] || resource, + mapOperationToJSONRPC[operation], + [+itemsID] || [], + fieldsToUpdate, + ], + }, + id: Math.floor(Math.random() * 100), + }; + + await odooJSONRPCRequest.call(this, body, url); + return { id: itemsID }; + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +export async function odooDelete( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + db: string, + userID: number, + password: string, + resource: string, + operation: OdooCRUD, + url: string, + itemsID: string, +) { + if (!/^\d+$/.test(itemsID) || !parseInt(itemsID, 10)) { + throw new NodeApiError(this.getNode(), { + status: 'Error', + message: `Please specify a valid ID: ${itemsID}`, + }); + } + try { + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: serviceJSONRPC, + method: methodJSONRPC, + args: [ + db, + userID, + password, + mapOdooResources[resource] || resource, + mapOperationToJSONRPC[operation], + [+itemsID] || [], + ], + }, + id: Math.floor(Math.random() * 100), + }; + + await odooJSONRPCRequest.call(this, body, url); + return { success: true }; + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +export async function odooGetUserID( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + db: string, + username: string, + password: string, + url: string, +): Promise { + try { + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: 'common', + method: 'login', + args: [db, username, password], + }, + id: Math.floor(Math.random() * 100), + }; + const loginResult = await odooJSONRPCRequest.call(this, body, url); + return loginResult as unknown as number; + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +export async function odooGetServerVersion( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + url: string, +) { + try { + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: 'common', + method: 'version', + args: [], + }, + id: Math.floor(Math.random() * 100), + }; + const result = await odooJSONRPCRequest.call(this, body, url); + return result; + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} diff --git a/packages/nodes-base/nodes/Odoo/Odoo.node.json b/packages/nodes-base/nodes/Odoo/Odoo.node.json new file mode 100644 index 0000000000..048bae3efa --- /dev/null +++ b/packages/nodes-base/nodes/Odoo/Odoo.node.json @@ -0,0 +1,20 @@ +{ + "node": "n8n-nodes-base.odoo", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Data & Storage" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/odoo" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.odoo/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/Odoo/Odoo.node.ts b/packages/nodes-base/nodes/Odoo/Odoo.node.ts new file mode 100644 index 0000000000..bc72e3d8c6 --- /dev/null +++ b/packages/nodes-base/nodes/Odoo/Odoo.node.ts @@ -0,0 +1,757 @@ +import { IExecuteFunctions } from 'n8n-core'; +import { OptionsWithUri } from 'request'; + +import { + ICredentialsDecrypted, + ICredentialTestFunctions, + IDataObject, + ILoadOptionsFunctions, + INodeCredentialTestResult, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, + JsonObject, +} from 'n8n-workflow'; + +import { + contactDescription, + contactOperations, + customResourceDescription, + customResourceOperations, + noteDescription, + noteOperations, + opportunityDescription, + opportunityOperations, +} from './descriptions'; + +import { + IOdooFilterOperations, + odooCreate, + odooDelete, + odooGet, + odooGetAll, + odooGetDBName, + odooGetModelFields, + odooGetUserID, + odooJSONRPCRequest, + odooUpdate, + processNameValueFields, +} from './GenericFunctions'; + +import { capitalCase } from 'change-case'; +export class Odoo implements INodeType { + description: INodeTypeDescription = { + displayName: 'Odoo', + name: 'odoo', + icon: 'file:odoo.svg', + group: ['transform'], + version: 1, + description: 'Consume Odoo API', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + defaults: { + name: 'Odoo', + color: '#714B67', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'odooApi', + required: true, + testedBy: 'odooApiTest', + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + default: 'contact', + noDataExpression: true, + options: [ + { + name: 'Contact', + value: 'contact', + }, + { + name: 'Custom Resource', + value: 'custom', + }, + { + name: 'Note', + value: 'note', + }, + { + name: 'Opportunity', + value: 'opportunity', + }, + ], + description: 'The resource to operate on', + }, + + ...customResourceOperations, + ...customResourceDescription, + ...opportunityOperations, + ...opportunityDescription, + ...contactOperations, + ...contactDescription, + ...noteOperations, + ...noteDescription, + ], + }; + + methods = { + loadOptions: { + async getModelFields(this: ILoadOptionsFunctions): Promise { + let resource; + resource = this.getCurrentNodeParameter('resource') as string; + if (resource === 'custom') { + resource = this.getCurrentNodeParameter('customResource') as string; + if (!resource) return []; + } + + const credentials = await this.getCredentials('odooApi'); + const url = credentials?.url as string; + const username = credentials?.username as string; + const password = credentials?.password as string; + const db = odooGetDBName(credentials?.db as string, url); + const userID = await odooGetUserID.call(this, db, username, password, url); + + const responce = await odooGetModelFields.call(this, db, userID, password, resource, url); + + const options = Object.values(responce).map((field) => { + const optionField = field as { [key: string]: string }; + return { + name: capitalCase(optionField.name), + value: optionField.name, + // nodelinter-ignore-next-line + description: `name: ${optionField?.name}, type: ${optionField?.type} required: ${optionField?.required}`, + }; + }); + + return options.sort((a, b) => a.name?.localeCompare(b.name) || 0); + }, + async getModels(this: ILoadOptionsFunctions): Promise { + const credentials = await this.getCredentials('odooApi'); + const url = credentials?.url as string; + const username = credentials?.username as string; + const password = credentials?.password as string; + const db = odooGetDBName(credentials?.db as string, url); + const userID = await odooGetUserID.call(this, db, username, password, url); + + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: 'object', + method: 'execute', + args: [ + db, + userID, + password, + 'ir.model', + 'search_read', + [], + ['name', 'model', 'modules'], + ], + }, + id: Math.floor(Math.random() * 100), + }; + + const responce = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[]; + + const options = responce.map((model) => { + return { + name: model.name, + value: model.model, + description: `model: ${model.model}
modules: ${model.modules}`, + }; + }); + return options as INodePropertyOptions[]; + }, + async getStates(this: ILoadOptionsFunctions): Promise { + const credentials = await this.getCredentials('odooApi'); + const url = credentials?.url as string; + const username = credentials?.username as string; + const password = credentials?.password as string; + const db = odooGetDBName(credentials?.db as string, url); + const userID = await odooGetUserID.call(this, db, username, password, url); + + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: 'object', + method: 'execute', + args: [db, userID, password, 'res.country.state', 'search_read', [], ['id', 'name']], + }, + id: Math.floor(Math.random() * 100), + }; + + const responce = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[]; + + const options = responce.map((state) => { + return { + name: state.name as string, + value: state.id, + }; + }); + return options.sort((a, b) => a.name?.localeCompare(b.name) || 0) as INodePropertyOptions[]; + }, + async getCountries(this: ILoadOptionsFunctions): Promise { + const credentials = await this.getCredentials('odooApi'); + const url = credentials?.url as string; + const username = credentials?.username as string; + const password = credentials?.password as string; + const db = odooGetDBName(credentials?.db as string, url); + const userID = await odooGetUserID.call(this, db, username, password, url); + + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: 'object', + method: 'execute', + args: [db, userID, password, 'res.country', 'search_read', [], ['id', 'name']], + }, + id: Math.floor(Math.random() * 100), + }; + + const responce = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[]; + + const options = responce.map((country) => { + return { + name: country.name as string, + value: country.id, + }; + }); + + return options.sort((a, b) => a.name?.localeCompare(b.name) || 0) as INodePropertyOptions[]; + }, + }, + credentialTest: { + async odooApiTest( + this: ICredentialTestFunctions, + credential: ICredentialsDecrypted, + ): Promise { + const credentials = credential.data; + + try { + const body = { + jsonrpc: '2.0', + method: 'call', + params: { + service: 'common', + method: 'login', + args: [odooGetDBName(credentials?.db as string, credentials?.url as string), credentials?.username, credentials?.password], + }, + id: Math.floor(Math.random() * 100), + }; + + const options: OptionsWithUri = { + headers: { + 'User-Agent': 'n8n', + Connection: 'keep-alive', + Accept: '*/*', + 'Content-Type': 'application/json', + }, + method: 'POST', + body, + uri: `${(credentials?.url as string).replace(/\/$/, '')}/jsonrpc`, + json: true, + }; + const result = await this.helpers.request!(options); + if (result.error || !result.result) { + return { + status: 'Error', + message: `Credentials are not valid`, + }; + } else if (result.error) { + return { + status: 'Error', + message: `Credentials are not valid: ${result.error.data.message}`, + }; + } + } catch (error) { + return { + status: 'Error', + message: `Settings are not valid: ${error}`, + }; + } + return { + status: 'OK', + message: 'Authentication successful!', + }; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + let items = this.getInputData(); + items = JSON.parse(JSON.stringify(items)); + const returnData: IDataObject[] = []; + let responseData; + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + const credentials = await this.getCredentials('odooApi'); + const url = (credentials?.url as string).replace(/\/$/, ''); + const username = credentials?.username as string; + const password = credentials?.password as string; + const db = odooGetDBName(credentials?.db as string, url); + const userID = await odooGetUserID.call(this, db, username, password, url); + + //---------------------------------------------------------------------- + // Main loop + //---------------------------------------------------------------------- + + for (let i = 0; i < items.length; i++) { + try { + if (resource === 'contact') { + if (operation === 'create') { + let additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (additionalFields.address) { + const addressFields = (additionalFields.address as IDataObject).value as IDataObject; + if (addressFields) { + additionalFields = { + ...additionalFields, + ...addressFields, + }; + } + delete additionalFields.address; + } + + const name = this.getNodeParameter('contactName', i) as string; + const fields: IDataObject = { + name, + ...additionalFields, + }; + responseData = await odooCreate.call( + this, + db, + userID, + password, + resource, + operation, + url, + fields, + ); + } + + if (operation === 'delete') { + const contactId = this.getNodeParameter('contactId', i) as string; + responseData = await odooDelete.call( + this, + db, + userID, + password, + resource, + operation, + url, + contactId, + ); + } + + if (operation === 'get') { + const contactId = this.getNodeParameter('contactId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + const fields = options.fieldsList as IDataObject[] || []; + responseData = await odooGet.call( + this, + db, + userID, + password, + resource, + operation, + url, + contactId, + fields, + ); + } + + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + const fields = options.fieldsList as IDataObject[] || []; + if (returnAll) { + responseData = await odooGetAll.call( + this, + db, + userID, + password, + resource, + operation, + url, + undefined, + fields, + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + responseData = await odooGetAll.call( + this, + db, + userID, + password, + resource, + operation, + url, + undefined, // filters, only for custom resource + fields, + limit, + ); + } + } + + if (operation === 'update') { + const contactId = this.getNodeParameter('contactId', i) as string; + let updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (updateFields.address) { + const addressFields = (updateFields.address as IDataObject).value as IDataObject; + if (addressFields) { + updateFields = { + ...updateFields, + ...addressFields, + }; + } + delete updateFields.address; + } + + responseData = await odooUpdate.call( + this, + db, + userID, + password, + resource, + operation, + url, + contactId, + updateFields, + ); + } + } + + if (resource === 'custom') { + const customResource = this.getNodeParameter('customResource', i) as string; + if (operation === 'create') { + const fields = this.getNodeParameter('fieldsToCreateOrUpdate', i) as IDataObject; + responseData = await odooCreate.call( + this, + db, + userID, + password, + customResource, + operation, + url, + processNameValueFields(fields), + ); + } + + if (operation === 'delete') { + const customResourceId = this.getNodeParameter('customResourceId', i) as string; + responseData = await odooDelete.call( + this, + db, + userID, + password, + customResource, + operation, + url, + customResourceId, + ); + } + + if (operation === 'get') { + const customResourceId = this.getNodeParameter('customResourceId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + const fields = options.fieldsList as IDataObject[] || []; + responseData = await odooGet.call( + this, + db, + userID, + password, + customResource, + operation, + url, + customResourceId, + fields, + ); + } + + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + const fields = options.fieldsList as IDataObject[] || []; + const filter = this.getNodeParameter('filterRequest', i) as IOdooFilterOperations; + if (returnAll) { + responseData = await odooGetAll.call( + this, + db, + userID, + password, + customResource, + operation, + url, + filter, + fields, + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + responseData = await odooGetAll.call( + this, + db, + userID, + password, + customResource, + operation, + url, + filter, + fields, + limit, + ); + } + } + + if (operation === 'update') { + const customResourceId = this.getNodeParameter('customResourceId', i) as string; + const fields = this.getNodeParameter('fieldsToCreateOrUpdate', i) as IDataObject; + responseData = await odooUpdate.call( + this, + db, + userID, + password, + customResource, + operation, + url, + customResourceId, + processNameValueFields(fields), + ); + } + } + + if (resource === 'note') { + if (operation === 'create') { + // const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const memo = this.getNodeParameter('memo', i) as string; + const fields: IDataObject = { + memo, + // ...additionalFields, + }; + responseData = await odooCreate.call( + this, + db, + userID, + password, + resource, + operation, + url, + fields, + ); + } + + if (operation === 'delete') { + const noteId = this.getNodeParameter('noteId', i) as string; + responseData = await odooDelete.call( + this, + db, + userID, + password, + resource, + operation, + url, + noteId, + ); + } + + if (operation === 'get') { + const noteId = this.getNodeParameter('noteId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + const fields = options.fieldsList as IDataObject[] || []; + responseData = await odooGet.call( + this, + db, + userID, + password, + resource, + operation, + url, + noteId, + fields, + ); + } + + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + const fields = options.fieldsList as IDataObject[] || []; + if (returnAll) { + responseData = await odooGetAll.call( + this, + db, + userID, + password, + resource, + operation, + url, + undefined, + fields, + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + responseData = await odooGetAll.call( + this, + db, + userID, + password, + resource, + operation, + url, + undefined, // filters, only for custom resource + fields, + limit, + ); + } + } + + if (operation === 'update') { + const noteId = this.getNodeParameter('noteId', i) as string; + const memo = this.getNodeParameter('memo', i) as string; + const fields: IDataObject = { + memo, + }; + responseData = await odooUpdate.call( + this, + db, + userID, + password, + resource, + operation, + url, + noteId, + fields, + ); + } + } + + if (resource === 'opportunity') { + if (operation === 'create') { + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const name = this.getNodeParameter('opportunityName', i) as string; + const fields: IDataObject = { + name, + ...additionalFields, + }; + + responseData = await odooCreate.call( + this, + db, + userID, + password, + resource, + operation, + url, + fields, + ); + } + + if (operation === 'delete') { + const opportunityId = this.getNodeParameter('opportunityId', i) as string; + responseData = await odooDelete.call( + this, + db, + userID, + password, + resource, + operation, + url, + opportunityId, + ); + } + + if (operation === 'get') { + const opportunityId = this.getNodeParameter('opportunityId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + const fields = options.fieldsList as IDataObject[] || []; + responseData = await odooGet.call( + this, + db, + userID, + password, + resource, + operation, + url, + opportunityId, + fields, + ); + } + + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + const fields = options.fieldsList as IDataObject[] || []; + if (returnAll) { + responseData = await odooGetAll.call( + this, + db, + userID, + password, + resource, + operation, + url, + undefined, + fields, + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + responseData = await odooGetAll.call( + this, + db, + userID, + password, + resource, + operation, + url, + undefined, // filters, only for custom resource + fields, + limit, + ); + } + } + + if (operation === 'update') { + const opportunityId = this.getNodeParameter('opportunityId', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + responseData = await odooUpdate.call( + this, + db, + userID, + password, + resource, + operation, + url, + opportunityId, + updateFields, + ); + } + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData); + } else if (responseData !== undefined) { + returnData.push(responseData); + } + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ error: (error as JsonObject).message }); + continue; + } + throw error; + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Odoo/descriptions/ContactDescription.ts b/packages/nodes-base/nodes/Odoo/descriptions/ContactDescription.ts new file mode 100644 index 0000000000..dccb1e8df2 --- /dev/null +++ b/packages/nodes-base/nodes/Odoo/descriptions/ContactDescription.ts @@ -0,0 +1,437 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const contactOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'create', + noDataExpression: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new contact', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a contact', + }, + { + name: 'Get', + value: 'get', + description: 'Get a contact', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all contacts', + }, + { + name: 'Update', + value: 'update', + description: 'Update a contact', + }, + ], + }, +]; + +export const contactDescription: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* contact:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'contactName', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'contact', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'contact', + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'address', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Address', + typeOptions: { + multipleValues: false, + }, + options: [ + { + name: 'value', + displayName: 'Address', + values: [ + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getCountries', + }, + }, + { + displayName: 'State', + name: 'state_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getStates', + }, + }, + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'Street 2', + name: 'street2', + type: 'string', + default: '', + }, + { + displayName: 'Zip Code', + name: 'zip', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'Internal Notes', + name: 'comment', + type: 'string', + default: '', + }, + { + displayName: 'Job Position', + name: 'function', + type: 'string', + default: '', + }, + { + displayName: 'Mobile', + name: 'mobile', + type: 'string', + default: '', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Tax ID', + name: 'vat', + type: 'string', + default: '', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* contact:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + 'delete', + ], + resource: [ + 'contact', + ], + }, + }, + }, + + /* -------------------------------------------------------------------------- */ + /* contact:getAll */ + /* -------------------------------------------------------------------------- */ + + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + description: 'Max number of results to return', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getAll', + 'get', + ], + resource: [ + 'contact', + ], + }, + }, + options: [ + { + displayName: 'Fields To Include', + name: 'fieldsList', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getModelFields', + }, + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* contact:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'contact', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'contact', + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'address', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Address', + typeOptions: { + multipleValues: false, + }, + options: [ + { + name: 'value', + displayName: 'Address', + values: [ + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getCountries', + }, + }, + { + displayName: 'State', + name: 'state_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getStates', + }, + }, + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'Street 2', + name: 'street2', + type: 'string', + default: '', + }, + { + displayName: 'Zip Code', + name: 'zip', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'Internal Notes', + name: 'comment', + type: 'string', + default: '', + }, + { + displayName: 'Job Position', + name: 'function', + type: 'string', + default: '', + }, + { + displayName: 'Mobile', + name: 'mobile', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Tax ID', + name: 'vat', + type: 'string', + default: '', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Odoo/descriptions/CustomResourceDescription.ts b/packages/nodes-base/nodes/Odoo/descriptions/CustomResourceDescription.ts new file mode 100644 index 0000000000..473e952aaa --- /dev/null +++ b/packages/nodes-base/nodes/Odoo/descriptions/CustomResourceDescription.ts @@ -0,0 +1,375 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const customResourceOperations: INodeProperties[] = [ + { + displayName: 'Custom Resource', + name: 'customResource', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getModels', + }, + displayOptions: { + show: { + resource: [ + 'custom', + ], + }, + }, + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'create', + noDataExpression: true, + displayOptions: { + show: { + resource: [ + 'custom', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new item', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete an item', + }, + { + name: 'Get', + value: 'get', + description: 'Get an item', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all items', + }, + { + name: 'Update', + value: 'update', + description: 'Update an item', + }, + ], + }, +]; + +export const customResourceDescription: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* custom:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Fields', + name: 'fieldsToCreateOrUpdate', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Field', + }, + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'custom', + ], + }, + }, + options: [ + { + displayName: 'Field Record:', + name: 'fields', + values: [ + { + displayName: 'Field Name', + name: 'fieldName', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getModelFields', + }, + }, + { + displayName: 'New Value', + name: 'fieldValue', + type: 'string', + default: '', + }, + ], + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* custom:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Custom Resource ID', + name: 'customResourceId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + 'delete', + ], + resource: [ + 'custom', + ], + }, + }, + }, + /* -------------------------------------------------------------------------- */ + /* custom:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'custom', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + displayOptions: { + show: { + resource: [ + 'custom', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + description: 'Max number of results to return', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getAll', + 'get', + ], + resource: [ + 'custom', + ], + }, + }, + options: [ + { + displayName: 'Fields To Include', + name: 'fieldsList', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getModelFields', + loadOptionsDependsOn: [ + 'customResource', + ], + }, + }, + ], + }, + { + displayName: 'Filters', + name: 'filterRequest', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Filter', + }, + default: {}, + description: 'Filter request by applying filters', + placeholder: 'Add condition', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'custom', + ], + }, + }, + options: [ + { + name: 'filter', + displayName: 'Filter', + values: [ + { + displayName: 'Field', + name: 'fieldName', + type: 'options', + default: '', + typeOptions: { + loadOptionsDependsOn: [ + 'customResource', + ], + loadOptionsMethod: 'getModelFields', + }, + }, + { + displayName: 'Operator', + name: 'operator', + type: 'options', + default: 'equal', + description: 'Specify an operator', + options: [ + { + name: '!=', + value: 'notEqual', + }, + { + name: '<', + value: 'lesserThen', + }, + { + name: '=', + value: 'equal', + }, + { + name: '<=', + value: 'lesserOrEqual', + }, + { + name: '>', + value: 'greaterThen', + }, + { + name: '>=', + value: 'greaterOrEqual', + }, + { + name: ' Child of', + value: 'childOf', + }, + { + name: 'In', + value: 'in', + }, + { + name: 'Like', + value: 'like', + }, + { + name: 'Not In', + value: 'notIn', + }, + ], + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Specify value for comparison', + }, + ], + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* custom:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Custom Resource ID', + name: 'customResourceId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'custom', + ], + }, + }, + }, + + { + displayName: 'Update Fields', + name: 'fieldsToCreateOrUpdate', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Field', + }, + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'custom', + ], + }, + }, + options: [ + { + displayName: 'Field Record:', + name: 'fields', + values: [ + { + displayName: 'Field Name', + name: 'fieldName', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getModelFields', + }, + }, + { + displayName: 'New Value', + name: 'fieldValue', + type: 'string', + default: '', + }, + ], + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Odoo/descriptions/NoteDescription.ts b/packages/nodes-base/nodes/Odoo/descriptions/NoteDescription.ts new file mode 100644 index 0000000000..e62b32d5d6 --- /dev/null +++ b/packages/nodes-base/nodes/Odoo/descriptions/NoteDescription.ts @@ -0,0 +1,260 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const noteOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'create', + noDataExpression: true, + displayOptions: { + show: { + resource: [ + 'note', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new note', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a note', + }, + { + name: 'Get', + value: 'get', + description: 'Get a note', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all notes', + }, + { + name: 'Update', + value: 'update', + description: 'Update a note', + }, + ], + }, +]; + +export const noteDescription: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* note:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Memo', + name: 'memo', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'note', + ], + }, + }, + }, + // { + // displayName: 'Additional Fields', + // name: 'additionalFields', + // type: 'collection', + // default: {}, + // placeholder: 'Add Field', + // displayOptions: { + // show: { + // operation: [ + // 'create', + // ], + // resource: [ + // 'note', + // ], + // }, + // }, + // options: [ + // { + // displayName: 'Name', + // name: 'name', + // type: 'string', + // default: '', + // }, + // ], + // }, + + /* -------------------------------------------------------------------------- */ + /* note:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Note ID', + name: 'noteId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + 'delete', + ], + resource: [ + 'note', + ], + }, + }, + }, + /* -------------------------------------------------------------------------- */ + /* note:getAll */ + /* -------------------------------------------------------------------------- */ + + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'note', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + displayOptions: { + show: { + resource: [ + 'note', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + description: 'Max number of results to return', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getAll', + 'get', + ], + resource: [ + 'note', + ], + }, + }, + options: [ + { + displayName: 'Fields To Include', + name: 'fieldsList', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getModelFields', + }, + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* note:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Note ID', + name: 'noteId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'note', + ], + }, + }, + }, + { + displayName: 'Memo', + name: 'memo', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'note', + ], + }, + }, + }, + // { + // displayName: 'Update Fields', + // name: 'updateFields', + // type: 'collection', + // default: {}, + // placeholder: 'Add Field', + // displayOptions: { + // show: { + // operation: [ + // 'update', + // ], + // resource: [ + // 'note', + // ], + // }, + // }, + // options: [ + // { + // displayName: 'Name', + // name: 'name', + // type: 'string', + // default: '', + // }, + // { + // displayName: 'Memo', + // name: 'memo', + // type: 'string', + // default: '', + // }, + // ], + // }, +]; diff --git a/packages/nodes-base/nodes/Odoo/descriptions/OpportunityDescription.ts b/packages/nodes-base/nodes/Odoo/descriptions/OpportunityDescription.ts new file mode 100644 index 0000000000..ea4fe08a44 --- /dev/null +++ b/packages/nodes-base/nodes/Odoo/descriptions/OpportunityDescription.ts @@ -0,0 +1,352 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const opportunityOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'create', + noDataExpression: true, + displayOptions: { + show: { + resource: [ + 'opportunity', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new opportunity', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete an opportunity', + }, + { + name: 'Get', + value: 'get', + description: 'Get an opportunity', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all opportunities', + }, + { + name: 'Update', + value: 'update', + description: 'Update an opportunity', + }, + ], + }, +]; + +export const opportunityDescription: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* opportunity:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'opportunityName', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'opportunity', + ], + }, + }, + }, + + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'opportunity', + ], + }, + }, + options: [ + { + displayName: 'Email', + name: 'email_from', + type: 'string', + default: '', + }, + // { + // displayName: 'Expected Closing Date', + // name: 'date_deadline', + // type: 'dateTime', + // default: '', + // }, + { + displayName: 'Expected Revenue', + name: 'expected_revenue', + type: 'number', + default: 0, + }, + { + displayName: 'Internal Notes', + name: 'description', + type: 'string', + default: '', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Priority', + name: 'priority', + type: 'options', + default: '1', + options: [ + { + name: '1', + value: '1', + }, + { + name: '2', + value: '2', + }, + { + name: '3', + value: '3', + }, + ], + }, + { + displayName: 'Probability', + name: 'probability', + type: 'number', + default: 0, + typeOptions: { + maxValue: 100, + minValue: 0, + }, + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* opportunity:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Opportunity ID', + name: 'opportunityId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + 'delete', + ], + resource: [ + 'opportunity', + ], + }, + }, + }, + /* -------------------------------------------------------------------------- */ + /* opportunity:getAll */ + /* -------------------------------------------------------------------------- */ + + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'opportunity', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + displayOptions: { + show: { + resource: [ + 'opportunity', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + description: 'Max number of results to return', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getAll', + 'get', + ], + resource: [ + 'opportunity', + ], + }, + }, + options: [ + { + displayName: 'Fields To Include', + name: 'fieldsList', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getModelFields', + }, + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* opportunity:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Opportunity ID', + name: 'opportunityId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'opportunity', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'opportunity', + ], + }, + }, + options: [ + { + displayName: 'Email', + name: 'email_from', + type: 'string', + default: '', + }, + // { + // displayName: 'Expected Closing Date', + // name: 'date_deadline', + // type: 'dateTime', + // default: '', + // }, + { + displayName: 'Expected Revenue', + name: 'expected_revenue', + type: 'number', + default: 0, + }, + { + displayName: 'Internal Notes', + name: 'description', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Priority', + name: 'priority', + type: 'options', + default: '1', + options: [ + { + name: '1', + value: '1', + }, + { + name: '2', + value: '2', + }, + { + name: '3', + value: '3', + }, + ], + }, + { + displayName: 'Probability', + name: 'probability', + type: 'number', + default: 0, + typeOptions: { + maxValue: 100, + minValue: 0, + }, + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Odoo/descriptions/index.ts b/packages/nodes-base/nodes/Odoo/descriptions/index.ts new file mode 100644 index 0000000000..12094d57ee --- /dev/null +++ b/packages/nodes-base/nodes/Odoo/descriptions/index.ts @@ -0,0 +1,15 @@ +import { customResourceDescription, customResourceOperations } from './CustomResourceDescription'; +import { noteDescription, noteOperations } from './NoteDescription'; +import { contactDescription, contactOperations } from './ContactDescription'; +import { opportunityDescription, opportunityOperations } from './OpportunityDescription'; + +export { + customResourceDescription, + customResourceOperations, + noteDescription, + noteOperations, + contactDescription, + contactOperations, + opportunityDescription, + opportunityOperations, +}; diff --git a/packages/nodes-base/nodes/Odoo/odoo.svg b/packages/nodes-base/nodes/Odoo/odoo.svg new file mode 100644 index 0000000000..fe8335d69a --- /dev/null +++ b/packages/nodes-base/nodes/Odoo/odoo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index ad3e79ebdc..6c04aa9e29 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -209,6 +209,7 @@ "dist/credentials/NotionOAuth2Api.credentials.js", "dist/credentials/OAuth1Api.credentials.js", "dist/credentials/OAuth2Api.credentials.js", + "dist/credentials/OdooApi.credentials.js", "dist/credentials/OneSimpleApi.credentials.js", "dist/credentials/OnfleetApi.credentials.js", "dist/credentials/OpenWeatherMapApi.credentials.js", @@ -548,6 +549,7 @@ "dist/nodes/Onfleet/OnfleetTrigger.node.js", "dist/nodes/Notion/Notion.node.js", "dist/nodes/Notion/NotionTrigger.node.js", + "dist/nodes/Odoo/Odoo.node.js", "dist/nodes/OneSimpleApi/OneSimpleApi.node.js", "dist/nodes/OpenThesaurus/OpenThesaurus.node.js", "dist/nodes/OpenWeatherMap/OpenWeatherMap.node.js",