diff --git a/packages/cli/BREAKING-CHANGES.md b/packages/cli/BREAKING-CHANGES.md index fb393e0f3c..150554886c 100644 --- a/packages/cli/BREAKING-CHANGES.md +++ b/packages/cli/BREAKING-CHANGES.md @@ -2,6 +2,16 @@ This list shows all the versions which include breaking changes and how to upgrade. +## 0.131.0 + +### What changed? + +For the Pipedrive regular node, the `deal:create` operation now requires an organization ID or person ID, in line with upcoming changes to the Pipedrive API. + +### When is action necessary? + +If you are using the `deal:create` operation in the Pipedrive regular node, set an organization ID or a person ID. + ## 0.130.0 ### What changed? diff --git a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts index b9e4394499..2fbb5de908 100644 --- a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts +++ b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts @@ -21,6 +21,10 @@ import { pipedriveResolveCustomProperties, } from './GenericFunctions'; +import { + currencies, +} from './utils'; + interface CustomProperty { name: string; value: string; @@ -118,6 +122,10 @@ export class Pipedrive implements INodeType { name: 'File', value: 'file', }, + { + name: 'Lead', + value: 'lead', + }, { name: 'Note', value: 'note', @@ -285,6 +293,46 @@ export class Pipedrive implements INodeType { description: 'The operation to perform.', }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'lead', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a lead', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a lead', + }, + { + name: 'Get', + value: 'get', + description: 'Get data of a lead', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get data of all leads', + }, + { + name: 'Update', + value: 'update', + description: 'Update a lead', + }, + ], + default: 'create', + }, { displayName: 'Operation', name: 'operation', @@ -846,6 +894,75 @@ export class Pipedrive implements INodeType { }, description: 'The title of the deal to create', }, + { + displayName: 'Associate With', + name: 'associateWith', + type: 'options', + options: [ + { + name: 'Organization', + value: 'organization', + }, + { + name: 'Person', + value: 'person', + }, + ], + default: 'organization', + description: 'Type of entity to link to this deal', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Organization ID', + name: 'org_id', + type: 'number', + default: 0, + description: 'ID of the organization this deal will be associated with', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'deal', + ], + associateWith: [ + 'organization', + ], + }, + }, + }, + { + displayName: 'Person ID', + name: 'person_id', + type: 'number', + default: 0, + description: 'ID of the person this deal will be associated with.', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'deal', + ], + associateWith: [ + 'person', + ], + }, + }, + }, { displayName: 'Additional Fields', name: 'additionalFields', @@ -925,18 +1042,30 @@ export class Pipedrive implements INodeType { { displayName: 'Organization ID', name: 'org_id', - type: 'options', - typeOptions: { - loadOptionsMethod: 'getOrganizationIds', + type: 'number', + default: 0, + required: true, + displayOptions: { + show: { + '/associateWith': [ + 'person', + ], + }, }, - default: '', - description: 'ID of the organization this deal will be associated with.', + description: 'ID of the organization this deal will be associated with', }, { displayName: 'Person ID', name: 'person_id', type: 'number', default: 0, + displayOptions: { + show: { + '/associateWith': [ + 'organization', + ], + }, + }, description: 'ID of the person this deal will be associated with.', }, { @@ -1617,7 +1746,358 @@ export class Pipedrive implements INodeType { }, default: 0, required: true, - description: 'ID of the file to get.', + description: 'ID of the file to get', + }, + + // ---------------------------------------- + // lead: create + // ---------------------------------------- + { + displayName: 'Title', + name: 'title', + description: 'Name of the lead to create', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Associate With', + name: 'associateWith', + type: 'options', + options: [ + { + name: 'Organization', + value: 'organization', + }, + { + name: 'Person', + value: 'person', + }, + ], + default: 'organization', + description: 'Type of entity to link to this lead', + required: true, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Organization ID', + name: 'organization_id', + type: 'number', + default: 0, + description: 'ID of the organization to link to this lead', + required: true, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'create', + ], + associateWith: [ + 'organization', + ], + }, + }, + }, + { + displayName: 'Person ID', + name: 'person_id', + type: 'number', + default: 0, + description: 'ID of the person to link to this lead', + required: true, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'create', + ], + associateWith: [ + 'person', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Expected Close Date', + name: 'expected_close_date', + type: 'dateTime', + default: '', + description: 'Date when the lead’s deal is expected to be closed, in ISO-8601 format', + }, + { + displayName: 'Label IDs', + name: 'label_ids', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLeadLabels', + }, + default: [], + description: 'ID of the labels to attach to the lead to create', + }, + { + displayName: 'Organization ID', + name: 'organization_id', + type: 'number', + default: 0, + description: 'ID of the organization to link to this lead', + displayOptions: { + show: { + '/associateWith': [ + 'person', + ], + }, + }, + }, + { + displayName: 'Owner ID', + name: 'owner_id', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getUserIds', + }, + default: '', + description: 'ID of the user who will own the lead to create', + }, + { + displayName: 'Person ID', + name: 'person_id', + type: 'number', + default: 0, + description: 'ID of the person to link to this lead', + displayOptions: { + show: { + '/associateWith': [ + 'organization', + ], + }, + }, + }, + { + displayName: 'Value', + name: 'value', + type: 'fixedCollection', + description: 'Potential monetary value associated with the lead', + default: {}, + options: [ + { + displayName: 'Value Properties', + name: 'valueProperties', + values: [ + { + displayName: 'Amount', + name: 'amount', + type: 'number', + default: '', + }, + { + displayName: 'Currency', + name: 'currency', + type: 'options', + default: 'USD', + options: currencies.sort((a, b) => a.name.localeCompare(b.name)), + }, + ], + }, + ], + }, + ], + }, + + // ---------------------------------------- + // lead: delete + // ---------------------------------------- + { + displayName: 'Lead ID', + name: 'leadId', + description: 'ID of the lead to delete', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // lead: get + // ---------------------------------------- + { + displayName: 'Lead ID', + name: 'leadId', + description: 'ID of the lead to retrieve', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------------- + // lead: update + // ---------------------------------------- + { + displayName: 'Lead ID', + name: 'leadId', + description: 'ID of the lead to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Name of the lead to update', + }, + { + displayName: 'Owner ID', + name: 'owner_id', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getUserIds', + }, + default: '', + description: 'ID of the user who will own the lead to update', + }, + { + displayName: 'Label IDs', + name: 'label_ids', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLeadLabels', + }, + default: [], + description: 'ID of the labels to attach to the lead to update', + }, + { + displayName: 'Person ID', + name: 'person_id', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPersons', + }, + default: '', + description: 'ID of the person to link to this lead', + }, + { + displayName: 'Value', + name: 'value', + type: 'fixedCollection', + description: 'Potential monetary value associated with the lead', + default: {}, + options: [ + { + displayName: 'Value Properties', + name: 'valueProperties', + values: [ + { + displayName: 'Amount', + name: 'amount', + type: 'number', + default: '', + }, + { + displayName: 'Currency', + name: 'currency', + type: 'options', + default: 'USD', + options: currencies.sort((a, b) => a.name.localeCompare(b.name)), + }, + ], + }, + ], + }, + { + displayName: 'Expected Close Date', + name: 'expected_close_date', + type: 'dateTime', + default: '', + description: 'Date when the lead’s deal is expected to be closed, in ISO-8601 format', + }, + ], }, @@ -2487,6 +2967,49 @@ export class Pipedrive implements INodeType { description: 'How many results to return.', }, + // ---------------------------------------- + // lead: getAll + // ---------------------------------------- + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Archived Status', + name: 'archived_status', + type: 'options', + default: 'all', + options: [ + { + name: 'Archived', + value: 'archived', + }, + { + name: 'All', + value: 'all', + }, + { + name: 'Not Archived', + value: 'not_archived', + }, + ], + }, + ], + }, + // ---------------------------------- // person:getAll // ---------------------------------- @@ -2981,6 +3504,27 @@ export class Pipedrive implements INodeType { } return returnData; }, + + // Get all the persons to display them to user so that he can + // select them easily + async getPersons(this: ILoadOptionsFunctions): Promise { + const { data } = await pipedriveApiRequest.call(this, 'GET', '/persons', {}) as { + data: Array<{ id: string; name: string; }> + }; + + return data.map(({ id, name }) => ({ value: id, name })); + }, + + // Get all the lead labels to display them to user so that he can + // select them easily + async getLeadLabels(this: ILoadOptionsFunctions): Promise { + const { data } = await pipedriveApiRequest.call(this, 'GET', '/leadLabels', {}) as { + data: Array<{ id: string; name: string; }> + }; + + return data.map(({ id, name }) => ({ value: id, name })); + }, + // Get all the labels to display them to user so that he can // select them easily async getDealLabels(this: ILoadOptionsFunctions): Promise { @@ -3147,6 +3691,15 @@ export class Pipedrive implements INodeType { endpoint = '/deals'; body.title = this.getNodeParameter('title', i) as string; + + const associateWith = this.getNodeParameter('associateWith', i) as 'organization' | 'person'; + + if (associateWith === 'organization') { + body.org_id = this.getNodeParameter('org_id', i) as string; + } else { + body.person_id = this.getNodeParameter('person_id', i) as string; + } + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; addAdditionalFields(body, additionalFields); @@ -3376,6 +3929,140 @@ export class Pipedrive implements INodeType { addAdditionalFields(body, updateFields); } + + } else if (resource === 'lead') { + + if (operation === 'create') { + + // ---------------------------------------- + // lead: create + // ---------------------------------------- + + // https://developers.pipedrive.com/docs/api/v1/Leads#addLead + + body = { + title: this.getNodeParameter('title', i), + } as IDataObject; + + const associateWith = this.getNodeParameter('associateWith', i) as 'organization' | 'person'; + + if (associateWith === 'organization') { + body.organization_id = this.getNodeParameter('organization_id', i) as number; + } else { + body.person_id = this.getNodeParameter('person_id', i) as number; + } + + const { value, expected_close_date, ...rest } = this.getNodeParameter('additionalFields', i) as { + value: { + valueProperties: { + amount: number; + currency: string; + } + }; + expected_close_date: string; + person_id: number, + organization_id: number, + }; + + if (Object.keys(rest).length) { + Object.assign(body, rest); + } + + if (value) { + Object.assign(body, { value: value.valueProperties }); + } + + if (expected_close_date) { + body.expected_close_date = expected_close_date.split('T')[0]; + } + + requestMethod = 'POST'; + endpoint = '/leads'; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // lead: delete + // ---------------------------------------- + + // https://developers.pipedrive.com/docs/api/v1/Leads#deleteLead + + const leadId = this.getNodeParameter('leadId', i); + + requestMethod = 'DELETE'; + endpoint = `/leads/${leadId}`; + + } else if (operation === 'get') { + + // ---------------------------------------- + // lead: get + // ---------------------------------------- + + // https://developers.pipedrive.com/docs/api/v1/Leads#getLead + + const leadId = this.getNodeParameter('leadId', i); + + requestMethod = 'GET'; + endpoint = `/leads/${leadId}`; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // lead: getAll + // ---------------------------------------- + + // https://developers.pipedrive.com/docs/api/v1/Leads#getLeads + + const filters = this.getNodeParameter('filters', i) as IDataObject; + + if (Object.keys(filters).length) { + Object.assign(qs, filters); + } + + requestMethod = 'GET'; + endpoint = '/leads'; + + } else if (operation === 'update') { + + // ---------------------------------------- + // lead: update + // ---------------------------------------- + + // https://developers.pipedrive.com/docs/api/v1/Leads#updateLead + + const { value, expected_close_date, ...rest } = this.getNodeParameter('updateFields', i) as { + value: { + valueProperties: { + amount: number; + currency: string; + } + }; + expected_close_date: string; + }; + + if (Object.keys(rest).length) { + Object.assign(body, rest); + } + + if (value) { + Object.assign(body, { value: value.valueProperties }); + } + + if (expected_close_date) { + body.expected_close_date = expected_close_date.split('T')[0]; + } + + if (Object.keys(rest).length) { + Object.assign(body, rest); + } + + const leadId = this.getNodeParameter('leadId', i); + + requestMethod = 'PATCH'; + endpoint = `/leads/${leadId}`; + + } + } else if (resource === 'organization') { if (operation === 'create') { // ---------------------------------- @@ -3624,6 +4311,8 @@ export class Pipedrive implements INodeType { if (Array.isArray(responseData.data)) { returnData.push.apply(returnData, responseData.data as IDataObject[]); + } else if (responseData.data === true) { + returnData.push({ success: true }); } else { returnData.push(responseData.data as IDataObject); } diff --git a/packages/nodes-base/nodes/Pipedrive/utils.ts b/packages/nodes-base/nodes/Pipedrive/utils.ts new file mode 100644 index 0000000000..e8d88b0363 --- /dev/null +++ b/packages/nodes-base/nodes/Pipedrive/utils.ts @@ -0,0 +1,89 @@ +export const currencies = [ + { name: 'US Dollar', value: 'USD' }, + { name: 'Euro', value: 'EUR' }, + { name: 'UAE Dirham', value: 'AED' }, + { name: 'Afghani', value: 'AFN' }, + { name: 'Lek', value: 'ALL' }, + { name: 'Argentine Peso', value: 'ARS' }, + { name: 'Australian Dollar', value: 'AUD' }, + { name: 'Azerbaijan Manat', value: 'AZN' }, + { name: 'Barbados Dollar', value: 'BBD' }, + { name: 'Taka', value: 'BDT' }, + { name: 'Bulgarian Lev', value: 'BGN' }, + { name: 'Bermudian Dollar', value: 'BMD' }, + { name: 'Brunei Dollar', value: 'BND' }, + { name: 'Boliviano', value: 'BOB' }, + { name: 'Brazilian Real', value: 'BRL' }, + { name: 'Bahamian Dollar', value: 'BSD' }, + { name: 'Pula', value: 'BWP' }, + { name: 'Belize Dollar', value: 'BZD' }, + { name: 'Canadian Dollar', value: 'CAD' }, + { name: 'Swiss Franc', value: 'CHF' }, + { name: 'Chilean Peso', value: 'CLP' }, + { name: 'Yuan Renminbi', value: 'CNY' }, + { name: 'Colombian Peso', value: 'COP' }, + { name: 'Costa Rican Colon', value: 'CRC' }, + { name: 'Czech Koruna', value: 'CZK' }, + { name: 'Danish Krone', value: 'DKK' }, + { name: 'Dominican Peso', value: 'DOP' }, + { name: 'Algerian Dinar', value: 'DZD' }, + { name: 'Egyptian Pound', value: 'EGP' }, + { name: 'Fiji Dollar', value: 'FJD' }, + { name: 'Pound Sterling', value: 'GBP' }, + { name: 'Quetzal', value: 'GTQ' }, + { name: 'Hong Kong Dollar', value: 'HKD' }, + { name: 'Lempira', value: 'HNL' }, + { name: 'Kuna', value: 'HRK' }, + { name: 'Forint', value: 'HUF' }, + { name: 'Rupiah', value: 'IDR' }, + { name: 'New Israeli Sheqel', value: 'ILS' }, + { name: 'Indian Rupee', value: 'INR' }, + { name: 'Jamaican Dollar', value: 'JMD' }, + { name: 'Yen', value: 'JPY' }, + { name: 'Kenyan Shilling', value: 'KES' }, + { name: 'Won', value: 'KRW' }, + { name: 'Tenge', value: 'KZT' }, + { name: 'Lao Kip', value: 'LAK' }, + { name: 'Lebanese Pound', value: 'LBP' }, + { name: 'Sri Lanka Rupee', value: 'LKR' }, + { name: 'Liberian Dollar', value: 'LRD' }, + { name: 'Moroccan Dirham', value: 'MAD' }, + { name: 'Kyat', value: 'MMK' }, + { name: 'Pataca', value: 'MOP' }, + { name: 'Ouguiya', value: 'MRO' }, + { name: 'Mauritius Rupee', value: 'MUR' }, + { name: 'Rufiyaa', value: 'MVR' }, + { name: 'Mexican Peso', value: 'MXN' }, + { name: 'Malaysian Ringgit', value: 'MYR' }, + { name: 'Cordoba Oro', value: 'NIO' }, + { name: 'Norwegian Krone', value: 'NOK' }, + { name: 'Nepalese Rupee', value: 'NPR' }, + { name: 'New Zealand Dollar', value: 'NZD' }, + { name: 'Sol', value: 'PEN' }, + { name: 'Kina', value: 'PGK' }, + { name: 'Philippine Peso', value: 'PHP' }, + { name: 'Pakistan Rupee', value: 'PKR' }, + { name: 'Zloty', value: 'PLN' }, + { name: 'Qatari Rial', value: 'QAR' }, + { name: 'Romanian Leu', value: 'RON' }, + { name: 'Russian Ruble', value: 'RUB' }, + { name: 'Saudi Riyal', value: 'SAR' }, + { name: 'Solomon Islands Dollar ', value: 'SBD' }, + { name: 'Seychelles Rupee', value: 'SCR' }, + { name: 'Swedish Krona', value: 'SEK' }, + { name: 'Singapore Dollar', value: 'SGD' }, + { name: 'Syrian Pound', value: 'SYP' }, + { name: 'Baht', value: 'THB' }, + { name: 'Pa’anga', value: 'TOP' }, + { name: 'Turkish Lira', value: 'TRY' }, + { name: 'Trinidad and Tobago Dollar', value: 'TTD' }, + { name: 'New Taiwan Dollar', value: 'TWD' }, + { name: 'Hryvnia', value: 'UAH' }, + { name: 'Dong', value: 'VND' }, + { name: 'Vatu', value: 'VUV' }, + { name: 'Tala', value: 'WST' }, + { name: 'East Caribbean Dollar', value: 'XCD' }, + { name: 'West African CFA Franc', value: 'XOF' }, + { name: 'Yemeni Rial', value: 'YER' }, + { name: 'Rand', value: 'ZAR' }, +];