diff --git a/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts b/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts index 01ee92de03..4fecdcd94d 100644 --- a/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts +++ b/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts @@ -10,8 +10,8 @@ import { contactOperations, contactFields } from './ContactDescription'; -import { agileCrmApiRequest, validateJSON} from './GenericFunctions'; -import { IContact, IProperty } from './ContactInterface'; +import { agileCrmApiRequest, validateJSON, agileCrmApiRequestUpdate} from './GenericFunctions'; +import { IContact, IProperty, IContactUpdate } from './ContactInterface'; export class AgileCrm implements INodeType { @@ -223,6 +223,133 @@ export class AgileCrm implements INodeType { responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, body); } + if(operation === "update") { + const contactId = this.getNodeParameter('contactId', i) as string; + let contactUpdatePayload : IContactUpdate = {id: contactId}; + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; + const body: IContact = {}; + let properties : IDataObject[] = []; + + if (jsonParameters) { + const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; + + if (additionalFieldsJson !== '' ) { + + if (validateJSON(additionalFieldsJson) !== undefined) { + + Object.assign(body, JSON.parse(additionalFieldsJson)); + + } else { + throw new Error('Additional fields must be a valid JSON'); + } + } + } else { + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (additionalFields.starValue) { + body.star_value = additionalFields.starValue as string; + } + if (additionalFields.leadScore) { + body.lead_score = additionalFields.leadScore as string; + } + if (additionalFields.tags) { + body.tags = additionalFields.tags as string[]; + } + if(additionalFields.firstName){ + properties.push({ + type: 'SYSTEM', + name: 'first_name', + value: additionalFields.firstName as string + } as IDataObject); + } + if(additionalFields.lastName){ + properties.push({ + type: 'SYSTEM', + name: 'last_name', + value: additionalFields.lastName as string + } as IDataObject); + } + if(additionalFields.company){ + properties.push({ + type: 'SYSTEM', + name: 'company', + value: additionalFields.company as string + } as IDataObject); + } + if(additionalFields.title){ + properties.push({ + type: 'SYSTEM', + name: 'title', + value: additionalFields.title as string + } as IDataObject); + } + if(additionalFields.emailOptions){ + //@ts-ignore + additionalFields.emailOptions.emailProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'email', + value: property.email as string + } as IDataObject); + }) + } + if(additionalFields.addressOptions){ + //@ts-ignore + additionalFields.addressOptions.addressProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'address', + value: property.address as string + } as IDataObject); + }) + } + if(additionalFields.websiteOptions){ + //@ts-ignore + additionalFields.websiteOptions.websiteProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'webiste', + value: property.url as string + } as IDataObject); + }) + } + if(additionalFields.phoneOptions){ + //@ts-ignore + additionalFields.phoneOptions.phoneProperties.map(property => { + properties.push({ + type: 'SYSTEM', + subtype: property.subtype as string, + name: 'phone', + value: property.number as string + } as IDataObject); + }) + } + if(additionalFields.customProperties){ + //@ts-ignore + additionalFields.customProperties.customProperty.map(property => { + properties.push({ + type: 'CUSTOM', + subtype: property.subtype as string, + name: property.name, + value: property.value as string + } as IDataObject); + }) + } + body.properties = properties; + + + + } + + Object.assign(contactUpdatePayload, body); + + responseData = await agileCrmApiRequestUpdate.call(this, 'PUT', '', contactUpdatePayload); + + } + if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else { diff --git a/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts b/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts index c9414f5fa8..e37dc65e38 100644 --- a/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts +++ b/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts @@ -588,5 +588,478 @@ export const contactFields = [ default: '', description: 'Unique identifier for a particular contact', }, +/* -------------------------------------------------------------------------- */ +/* contact:update */ +/* -------------------------------------------------------------------------- */ +{ + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + description: 'Unique identifier for a particular contact', +}, +{ + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, +}, +{ + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + jsonParameters: [ + true, + ], + }, + }, + + description: `Object of values to set as described here.`, +}, +{ + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + displayName: 'Star Value', + name: 'starValue', + type: 'options', + default: '', + required: false, + description: 'Rating of contact (Max value 5). This is not applicable for companies.', + options: [ + { + name: '0', + value: 0 + }, + { + name: '1', + value: 1 + }, + { + name: '2', + value: 2 + }, + { + name: '3', + value: 3 + }, + { + name: '4', + value: 4 + }, + { + name: '5', + value: 5 + }, + ] + }, + { + displayName: 'Lead Score', + name: 'leadScore', + type: 'number', + default: '', + description: 'Score of contact. This is not applicable for companies.', + required: false, + typeOptions: { + minValue: 0 + } + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Tag', + }, + default: [], + placeholder: 'Tag', + description: 'Unique identifiers added to contact, for easy management of contacts. This is not applicable for companies.', + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + required: false, + default: "", + placeholder: 'First Name', + description: 'Contact first name.', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + required: false, + default: "", + placeholder: 'Last Name', + description: 'Contact last name.', + }, + { + displayName: 'Company', + name: 'company', + type: 'string', + required: false, + default: "", + placeholder: 'Company', + description: 'Company Name.', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + required: false, + default: "", + placeholder: 'Title', + description: 'Professional title.', + }, + { + displayName: 'Email', + name: 'emailOptions', + type: 'fixedCollection', + required: false, + description: 'Contact email.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Email Properties', + name: 'emailProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: "", + placeholder: '', + description: 'Type of Email', + options: [ + { + name: 'Work', + value: 'work' + }, + { + name: 'Personal', + value: 'personal' + } + ] + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: "", + placeholder: '', + description: 'Email', + } + ] + }, + + ] + }, + { + displayName: 'Address', + name: 'addressOptions', + type: 'fixedCollection', + required: false, + description: 'Contacts address.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Address Properties', + name: 'addressProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: "", + placeholder: '', + description: 'Type of address.', + options: [ + { + name: 'Home', + value: 'home' + }, + { + name: 'Postal', + value: 'postal' + } + , + { + name: 'Office', + value: 'office' + } + ] + }, + { + displayName: 'Address', + name: 'address', + type: 'string', + required: true, + default: "", + placeholder: '', + description: 'Full address.', + } + ] + }, + + ] + }, + { + displayName: 'Website', + name: 'websiteOptions', + type: 'fixedCollection', + required: false, + description: 'Contacts websites.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Website properties.', + name: 'websiteProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: "", + placeholder: '', + description: 'Type of website.', + options: [ + { + name: 'URL', + value: 'url', + }, + { + name: 'SKYPE', + value: 'skype', + }, + { + name: 'TWITTER', + value: 'twitter', + }, + { + name: 'LINKEDIN', + value: 'linkedin', + }, + { + name: 'FACEBOOK', + value: 'facebook', + }, + { + name: 'XING', + value: 'xing', + }, + { + name: 'FEED', + value: 'feed', + }, + { + name: 'GOOGLE_PLUS', + value: 'googlePlus', + }, + { + name: 'FLICKR', + value: 'flickr', + }, + { + name: 'GITHUB', + value: 'github', + }, + { + name: 'YOUTUBE', + value: 'youtube', + }, + ] + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + required: true, + default: "", + placeholder: '', + description: 'Website URL', + } + ] + }, + + ] + }, + { + displayName: 'Phone', + name: 'phoneOptions', + type: 'fixedCollection', + required: false, + description: 'Contacts phone.', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Phone properties', + name: 'phoneProperties', + values: [ + { + displayName: 'Type', + name: 'subtype', + type: 'options', + required: true, + default: "", + placeholder: '', + description: 'Type of phone number.', + options: [ + { + name: 'Home', + value: 'home' + }, + { + name: 'Work', + value: 'work' + } + , + { + name: 'Mobile', + value: 'mobile' + }, + { + name: 'Main', + value: 'main' + }, + { + name: 'Home Fax', + value: 'homeFax' + }, + { + name: 'Work Fax', + value: 'workFax' + }, + { + name: 'Other', + value: 'other' + }, + ] + }, + { + displayName: 'Number', + name: 'number', + type: 'string', + required: true, + default: "", + placeholder: '', + description: 'Phone number.', + } + ] + }, + + ] + }, + { + displayName: 'Custom Properties', + name: 'customProperties', + type: 'fixedCollection', + required: false, + description: 'Custom Properties', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Property', + name: 'customProperty', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: "", + placeholder: '', + description: 'Property name.' + }, + { + displayName: 'Sub Type', + name: 'subtype', + type: 'string', + required: false, + default: "", + placeholder: '', + description: 'Property sub type.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + required: false, + default: "", + placeholder: '', + description: 'Property value.', + } + ] + }, + + ] + }, + ], +}, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/AgileCrm/ContactInterface.ts b/packages/nodes-base/nodes/AgileCrm/ContactInterface.ts index 661a4c4742..ae384df943 100644 --- a/packages/nodes-base/nodes/AgileCrm/ContactInterface.ts +++ b/packages/nodes-base/nodes/AgileCrm/ContactInterface.ts @@ -16,3 +16,11 @@ import { properties?: IDataObject[]; } + export interface IContactUpdate { + id: string, + properties?: IDataObject[], + star_value?: string; + lead_score?: string; + tags?: string[]; + } + diff --git a/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts b/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts index b87fedd024..046b7c187a 100644 --- a/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts +++ b/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts @@ -12,6 +12,7 @@ import { import { IDataObject, } from 'n8n-workflow'; +import { IContactUpdate } from './ContactInterface'; export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { @@ -46,6 +47,86 @@ export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunction throw error; } + +} + +export async function agileCrmApiRequestUpdate(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string = 'PUT', endpoint?: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { + const baseUri = 'https://n8nio.agilecrm.com/dev/'; + const credentials = this.getCredentials('agileCrmApi'); + const options: OptionsWithUri = { + method, + headers: { + 'Accept': 'application/json', + }, + body: {id: body.id}, + auth: { + username: credentials!.email as string, + password: credentials!.apiKey as string + }, + uri: uri || baseUri, + json: true + }; + + let successfulUpdates = []; + let lastSuccesfulUpdateReturn : any; + let payload : IContactUpdate = body; + + try { + // Due to API, we must update each property separately + if(payload.properties){ + options.body.properties = payload.properties; + options.uri = baseUri + 'api/contacts/edit-properties'; + lastSuccesfulUpdateReturn = await this.helpers.request!(options); + + // Iterate trough properties and show them as individial updates instead of only vague "properties" + payload.properties?.map((property : any) => { + successfulUpdates.push(`${property.name} `); + }) + + delete options.body.properties; + } + if(payload.lead_score){ + options.body.lead_score = payload.lead_score; + options.uri = baseUri + 'api/contacts/edit/lead-score'; + lastSuccesfulUpdateReturn = await this.helpers.request!(options); + + successfulUpdates.push('lead_score'); + + delete options.body.lead_score; + } + if(body.tags){ + options.body.tags = payload.tags; + options.uri = baseUri + 'api/contacts/edit/tagaas'; + lastSuccesfulUpdateReturn = await this.helpers.request!(options); + + payload.tags?.map((tag : string) => { + successfulUpdates.push(`(Tag) ${tag} `); + }) + + delete options.body.tags; + } + if(body.star_value){ + options.body.star_value = payload.star_value; + options.uri = baseUri + 'api/contacts/edit/add-star'; + lastSuccesfulUpdateReturn = await this.helpers.request!(options); + + successfulUpdates.push('star_value'); + + delete options.body.star_value; + } + + return lastSuccesfulUpdateReturn; + + } catch (error) { + + if (error.response && error.response.body && error.response.body.errors) { + const errorMessages = error.response.body.errors.map((e: IDataObject) => e.message); + throw new Error(`AgileCRM error response [${error.statusCode}]: ${errorMessages.join(' | ')}`); + } + + throw new Error(`Not all items updated. Updated items: ${successfulUpdates.join(' , ')} \n \n` + error); + } + } export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any