n8n-nodes-close-crm
Version:
N8N community node for Close CRM integration with comprehensive lead, opportunity, task, note, and call management
401 lines (400 loc) • 20.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Close = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const GenericFunctions_1 = require("./GenericFunctions");
const LeadDescription_1 = require("./descriptions/LeadDescription");
const OpportunityDescription_1 = require("./descriptions/OpportunityDescription");
const TaskDescription_1 = require("./descriptions/TaskDescription");
const NoteDescription_1 = require("./descriptions/NoteDescription");
const CallDescription_1 = require("./descriptions/CallDescription");
const CustomActivityDescription_1 = require("./descriptions/CustomActivityDescription");
class Close {
constructor() {
this.description = {
displayName: 'Close CRM',
name: 'close',
icon: 'file:close.svg',
group: ['trigger'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Close CRM API',
defaults: {
name: 'Close CRM',
},
inputs: [{ type: 'main' }],
outputs: [{ type: 'main' }],
credentials: [
{
name: 'closeApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Lead',
value: 'lead',
},
{
name: 'Opportunity',
value: 'opportunity',
},
{
name: 'Task',
value: 'task',
},
{
name: 'Note',
value: 'note',
},
{
name: 'Call',
value: 'call',
},
{
name: 'Custom Activity',
value: 'customActivity',
},
],
default: 'lead',
},
...LeadDescription_1.leadOperations,
...LeadDescription_1.leadFields,
...OpportunityDescription_1.opportunityOperations,
...OpportunityDescription_1.opportunityFields,
...TaskDescription_1.taskOperations,
...TaskDescription_1.taskFields,
...NoteDescription_1.noteOperations,
...NoteDescription_1.noteFields,
...CallDescription_1.callOperations,
...CallDescription_1.callFields,
...CustomActivityDescription_1.customActivityOperations,
...CustomActivityDescription_1.customActivityFields,
],
};
this.methods = {
loadOptions: {
async getLeadStatuses() {
const returnData = [];
const statuses = await GenericFunctions_1.closeApiRequest.call(this, 'GET', '/status/lead/');
for (const status of statuses.data) {
returnData.push({
name: status.label,
value: status.id,
});
}
return returnData;
},
async getOpportunityStatuses() {
const returnData = [];
const statuses = await GenericFunctions_1.closeApiRequest.call(this, 'GET', '/status/opportunity/');
for (const status of statuses.data) {
returnData.push({
name: status.label,
value: status.id,
});
}
return returnData;
},
async getCustomFields() {
const returnData = [];
const fields = await GenericFunctions_1.closeApiRequest.call(this, 'GET', '/custom_field/lead/');
for (const field of fields.data) {
// Include field type in the description for user clarity
const fieldTypeLabel = field.type === 'choices' ? ' (Dropdown)' : ` (${field.type || 'Text'})`;
returnData.push({
name: `${field.name}${fieldTypeLabel}`,
value: `${field.id}|${field.type || 'text'}`, // Encode type in value
});
}
return returnData;
},
async getCustomFieldChoices() {
const fieldIdWithType = this.getCurrentNodeParameter('fieldId');
if (!fieldIdWithType) {
return [];
}
// Parse the encoded field ID and type
const [fieldId, fieldType] = fieldIdWithType.split('|');
if (fieldType !== 'choices') {
return [];
}
try {
const field = await GenericFunctions_1.closeApiRequest.call(this, 'GET', `/custom_field/lead/${fieldId}/`);
if (field.type === 'choices' && field.choices && Array.isArray(field.choices)) {
return field.choices.map((choice) => ({
name: choice,
value: choice,
}));
}
}
catch (error) {
// If field details can't be fetched, return empty array
return [];
}
return [];
},
async getSmartViews() {
const returnData = [];
const views = await GenericFunctions_1.closeApiRequest.call(this, 'GET', '/saved_search/');
for (const view of views.data) {
returnData.push({
name: view.name,
value: view.id,
});
}
return returnData;
},
},
};
}
async execute() {
var _a;
const items = this.getInputData();
const returnData = [];
const length = items.length;
const qs = {};
let responseData;
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < length; i++) {
try {
// Input validation
if (!resource) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Resource is required');
}
if (!operation) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Operation is required');
}
if (resource === 'lead') {
if (operation === 'find') {
const query = this.getNodeParameter('query', i);
const returnAll = this.getNodeParameter('returnAll', i);
const smartViewId = this.getNodeParameter('smartViewId', i, '');
const statusId = this.getNodeParameter('statusId', i, '');
if (query) {
qs.query = query;
}
if (smartViewId) {
qs.saved_search_id = smartViewId;
}
if (statusId) {
qs.status_id = statusId;
}
if (returnAll) {
responseData = await GenericFunctions_1.closeApiRequestAllItems.call(this, 'data', 'GET', '/lead/', {}, qs);
}
else {
const limit = this.getNodeParameter('limit', i);
qs._limit = limit;
responseData = await GenericFunctions_1.closeApiRequest.call(this, 'GET', '/lead/', {}, qs);
responseData = responseData.data;
}
}
if (operation === 'update') {
const leadId = this.getNodeParameter('leadId', i);
if (!leadId) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Lead ID is required for update operation');
}
// Validate lead ID format (Close CRM uses specific ID patterns)
if (!leadId.match(/^lead_[a-zA-Z0-9]+$/)) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid lead ID format: "${leadId}". Lead IDs should start with "lead_" followed by alphanumeric characters.`);
}
const updateFields = this.getNodeParameter('updateFields', i);
const customFields = this.getNodeParameter('customFieldsUi', i);
// Ensure we have at least one field to update
const hasUpdateFields = Object.keys(updateFields).length > 0;
const hasCustomFields = customFields.customFieldsValues && customFields.customFieldsValues.length > 0;
if (!hasUpdateFields && !hasCustomFields) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'At least one field must be specified for update operation');
}
const body = {};
// Add standard fields
if (updateFields.name) {
body.name = updateFields.name;
}
if (updateFields.description) {
body.description = updateFields.description;
}
if (updateFields.statusId) {
body.status_id = updateFields.statusId;
}
// Add custom fields with validation
if ((_a = customFields.customFieldsValues) === null || _a === void 0 ? void 0 : _a.length) {
for (const field of customFields.customFieldsValues) {
if (!field.fieldId) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Custom field ID is required when adding custom fields');
}
// Parse the encoded field ID and type
const fieldParts = field.fieldId.split('|');
if (fieldParts.length !== 2) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid custom field ID format: "${field.fieldId}". Expected format: "fieldId|fieldType"`);
}
const [actualFieldId, fieldType] = fieldParts;
// Use the appropriate value based on field type
const value = fieldType === 'choices' ? field.fieldValueChoice : field.fieldValue;
if (value !== undefined && value !== null && value !== '') {
body[`custom.${actualFieldId}`] = value;
}
else if (fieldType === 'choices' && !field.fieldValueChoice) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Choice value is required for dropdown field: "${field.fieldId}"`);
}
else if (fieldType !== 'choices' && !field.fieldValue) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Field value is required for field: "${field.fieldId}"`);
}
}
}
// Log the request for debugging (only in development)
if (process.env.NODE_ENV === 'development') {
console.log(`Updating lead ${leadId} with data:`, JSON.stringify(body, null, 2));
}
responseData = await GenericFunctions_1.closeApiRequest.call(this, 'PUT', `/lead/${leadId}/`, body);
}
}
if (resource === 'opportunity') {
if (operation === 'find') {
const leadId = this.getNodeParameter('leadId', i, '');
const statusId = this.getNodeParameter('statusId', i, '');
const returnAll = this.getNodeParameter('returnAll', i);
if (leadId) {
qs.lead_id = leadId;
}
if (statusId) {
qs.status_id = statusId;
}
if (returnAll) {
responseData = await GenericFunctions_1.closeApiRequestAllItems.call(this, 'data', 'GET', '/opportunity/', {}, qs);
}
else {
const limit = this.getNodeParameter('limit', i);
qs._limit = limit;
responseData = await GenericFunctions_1.closeApiRequest.call(this, 'GET', '/opportunity/', {}, qs);
responseData = responseData.data;
}
}
if (operation === 'update') {
const opportunityId = this.getNodeParameter('opportunityId', i);
if (!opportunityId) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Opportunity ID is required for update operation');
}
const updateFields = this.getNodeParameter('updateFields', i);
const body = {};
if (updateFields.statusId) {
body.status_id = updateFields.statusId;
}
if (updateFields.note) {
body.note = updateFields.note;
}
if (updateFields.value) {
body.value = updateFields.value;
}
if (updateFields.valueFormatted) {
body.value_formatted = updateFields.valueFormatted;
}
responseData = await GenericFunctions_1.closeApiRequest.call(this, 'PUT', `/opportunity/${opportunityId}/`, body);
}
}
if (resource === 'task') {
if (operation === 'create') {
const leadId = this.getNodeParameter('leadId', i);
const text = this.getNodeParameter('text', i);
const date = this.getNodeParameter('date', i);
const assignedTo = this.getNodeParameter('assignedTo', i, '');
if (!leadId) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Lead ID is required for task creation');
}
if (!text) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Task text is required');
}
if (!date) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Task date is required');
}
const body = {
lead_id: leadId,
text,
date,
};
if (assignedTo) {
body.assigned_to = assignedTo;
}
responseData = await GenericFunctions_1.closeApiRequest.call(this, 'POST', '/task/', body);
}
}
if (resource === 'note') {
if (operation === 'create') {
const leadId = this.getNodeParameter('leadId', i);
const note = this.getNodeParameter('note', i);
if (!leadId) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Lead ID is required for note creation');
}
if (!note) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Note content is required');
}
const body = {
lead_id: leadId,
note,
_type: 'Note',
};
responseData = await GenericFunctions_1.closeApiRequest.call(this, 'POST', '/activity/note/', body);
}
}
if (resource === 'call') {
if (operation === 'find') {
const leadId = this.getNodeParameter('leadId', i, '');
const returnAll = this.getNodeParameter('returnAll', i);
if (leadId) {
qs.lead_id = leadId;
}
qs._type = 'Call';
if (returnAll) {
responseData = await GenericFunctions_1.closeApiRequestAllItems.call(this, 'data', 'GET', '/activity/', {}, qs);
}
else {
const limit = this.getNodeParameter('limit', i);
qs._limit = limit;
responseData = await GenericFunctions_1.closeApiRequest.call(this, 'GET', '/activity/', {}, qs);
responseData = responseData.data;
}
}
}
if (resource === 'customActivity') {
if (operation === 'getPublished') {
const returnAll = this.getNodeParameter('returnAll', i);
const dateCreated = this.getNodeParameter('dateCreated', i, '');
qs._type = 'Custom';
if (dateCreated) {
qs.date_created__gte = dateCreated;
}
if (returnAll) {
responseData = await GenericFunctions_1.closeApiRequestAllItems.call(this, 'data', 'GET', '/activity/', {}, qs);
}
else {
const limit = this.getNodeParameter('limit', i);
qs._limit = limit;
responseData = await GenericFunctions_1.closeApiRequest.call(this, 'GET', '/activity/', {}, qs);
responseData = responseData.data;
}
}
}
const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } });
returnData.push(...executionData);
}
catch (error) {
if (this.continueOnFail()) {
const executionErrorData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray({ error: error.message }), { itemData: { item: i } });
returnData.push(...executionErrorData);
continue;
}
throw error;
}
}
return [returnData];
}
}
exports.Close = Close;