UNPKG

n8n-nodes-inflow-crm

Version:

Official Inflow CRM integration for n8n. Create, update, search records and handle webhooks with dynamic field support.

677 lines (676 loc) β€’ 31.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InflowCrm = void 0; const helpers_1 = require("../lib/helpers"); const actions_1 = require("./actions"); const logger_1 = require("../lib/logger"); const fieldsAccess_1 = require("../access/fieldsAccess"); class InflowCrm { constructor() { this.description = { displayName: 'Inflow CRM', name: 'inflowCrm', icon: 'file:inflowCrm.png', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["module"]}}', description: 'Interact with Inflow CRM API', defaults: { name: 'Inflow CRM', }, inputs: ["main" /* NodeConnectionType.Main */], outputs: ["main" /* NodeConnectionType.Main */], credentials: [ { name: 'inflowCrmApi', required: true, }, ], properties: [ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, options: [ { name: 'πŸ“ Create Record', value: 'create', action: 'Create a record', }, { name: '✏️ Update Record', value: 'update', action: 'Update a record', }, { name: 'πŸ” Search Records', value: 'search', action: 'Search records', }, { name: 'πŸ”Ž Find One Record', value: 'find', action: 'Find one record', }, { name: 'πŸ†” Get By ID', value: 'getById', action: 'Get record by ID', }, { name: 'πŸ†”πŸ“‹ Get By IDs', value: 'getByIds', action: 'Get multiple records by IDs', }, { name: 'πŸ—‘οΈ Delete Record', value: 'delete', action: 'Delete a record', }, { name: 'πŸ”’ Count Records', value: 'count', action: 'Count records', }, { name: 'βš™οΈ Get Processes', value: 'getProcesses', action: 'Get available processes', }, ], default: 'create', description: 'The operation to perform on the Inflow CRM records', }, { displayName: 'πŸ“¦ Module', name: 'module', type: 'options', typeOptions: { loadOptionsMethod: 'getModules', }, default: '', required: true, description: 'The Inflow CRM module to work with', displayOptions: { show: { operation: ['create', 'update', 'search', 'find', 'getById', 'getByIds', 'delete', 'count'], }, }, }, { displayName: 'πŸ“¦ Module', name: 'processModule', type: 'options', typeOptions: { loadOptionsMethod: 'getProcessModules', }, default: 'potential', required: true, description: 'The Inflow CRM module to get processes for (only potential and order modules support processes)', displayOptions: { show: { operation: ['getProcesses'], }, }, }, { displayName: 'Record ID', name: 'updateRecordId', type: 'string', default: '', required: true, description: 'ID of the record to update', displayOptions: { show: { operation: ['update'], }, }, }, { displayName: 'Fields', name: 'mappedFields', type: 'resourceMapper', noDataExpression: true, default: { mappingMode: 'defineBelow', value: {}, }, required: false, description: 'The fields to update. Only specify the fields you want to change.', typeOptions: { loadOptionsDependsOn: ['module', 'operation'], resourceMapper: { resourceMapperMethod: 'getInflowFieldsForResourceMapper', mode: 'add', fieldWords: { singular: 'field', plural: 'fields', }, addAllFields: false, multiKeyMatch: true, }, }, displayOptions: { show: { operation: ['update'], }, }, }, { displayName: 'Fields', name: 'mappedFields', type: 'resourceMapper', noDataExpression: true, default: { mappingMode: 'defineBelow', value: {}, }, required: true, description: 'The fields and values for the operation.', typeOptions: { loadOptionsDependsOn: ['module', 'operation'], resourceMapper: { resourceMapperMethod: 'getInflowFieldsForResourceMapper', mode: 'add', fieldWords: { singular: 'field', plural: 'fields', }, addAllFields: false, multiKeyMatch: true, }, }, displayOptions: { show: { operation: ['create'], }, }, }, // File Uploads property inserted after mappedFields for create/update { displayName: 'πŸ“Ž File Uploads', name: 'fileUploads', type: 'fixedCollection', placeholder: 'Add File Upload', default: { values: [] }, typeOptions: { multipleValues: true, multipleValueButtonText: 'Add File Field', }, options: [ { name: 'values', displayName: 'File Field', values: [ { displayName: 'Field Name', name: 'fieldName', type: 'string', default: '', required: true, description: 'API field name for this file (e.g., "attachment", "document")', placeholder: 'e.g., attachment', }, { displayName: 'File Source', name: 'fileSource', type: 'options', default: 'binaryData', options: [ { name: 'Binary Data (from previous node)', value: 'binaryData', }, { name: 'File Path', value: 'filePath', }, ], }, { displayName: 'Binary Property Name', name: 'binaryPropertyName', type: 'string', default: 'data', description: 'Name of the binary property from the previous node', displayOptions: { show: { fileSource: ['binaryData'], }, }, }, { displayName: 'File Path', name: 'filePath', type: 'string', default: '', description: 'Full path to the file to upload', displayOptions: { show: { fileSource: ['filePath'], }, }, }, ], }, ], displayOptions: { show: { operation: ['create', 'update'], }, }, }, { displayName: 'Fields', name: 'mappedFields', type: 'resourceMapper', noDataExpression: true, default: { mappingMode: 'defineBelow', value: {}, }, required: false, description: 'The fields and values for filtering. Leave empty to return all records (up to limit).', typeOptions: { loadOptionsDependsOn: ['module', 'operation'], resourceMapper: { resourceMapperMethod: 'getInflowFieldsForResourceMapper', mode: 'add', fieldWords: { singular: 'field', plural: 'fields', }, addAllFields: false, multiKeyMatch: true, }, }, displayOptions: { show: { operation: ['search', 'find'], }, }, }, { displayName: 'Record ID', name: 'getByIdRecordId', type: 'string', default: '', required: true, description: 'ID of the record to retrieve', displayOptions: { show: { operation: ['getById'], }, }, }, { displayName: 'Record IDs', name: 'getByIdsRecordIds', type: 'string', default: '', required: true, description: 'Comma-separated list of record IDs to retrieve (e.g., "id1,id2,id3")', placeholder: 'e.g., 123,456,789', displayOptions: { show: { operation: ['getByIds'], }, }, }, { displayName: 'Record ID', name: 'recordId', type: 'string', default: '', required: true, description: 'ID of the record to delete', displayOptions: { show: { operation: ['delete'], }, }, }, { displayName: 'Fields', name: 'countFilter', type: 'resourceMapper', noDataExpression: true, default: { mappingMode: 'defineBelow', value: {}, }, required: false, description: 'Filter fields for count operation (optional)', typeOptions: { loadOptionsDependsOn: ['module', 'operation'], resourceMapper: { resourceMapperMethod: 'getInflowFieldsForResourceMapper', mode: 'add', fieldWords: { singular: 'field', plural: 'fields', }, addAllFields: false, multiKeyMatch: true, }, }, displayOptions: { show: { operation: ['count'], }, }, }, { displayName: 'βš™οΈ Options', name: 'options', type: 'collection', placeholder: 'Add Option', default: {}, displayOptions: { show: { operation: ['search'], }, }, options: [ { displayName: 'πŸ”’ Limit', name: 'limit', type: 'number', default: 50, description: 'Max number of results to return (perPage)', typeOptions: { minValue: 1, maxValue: 200, }, }, { displayName: 'πŸ“„ Page Number', name: 'page', type: 'number', default: 1, description: 'Page number for pagination (starts from 1)', typeOptions: { minValue: 1, }, }, { displayName: 'πŸ“Š Sort Field', name: 'sortField', type: 'string', default: '', description: 'Field to sort by (leave empty for no sorting)', }, { displayName: 'πŸ”„ Sort Direction', name: 'sortDirection', type: 'options', options: [ { name: 'Ascending', value: 'asc', }, { name: 'Descending', value: 'desc', }, ], default: 'asc', description: 'Direction to sort the results', displayOptions: { hide: { sortField: [''], }, }, }, ], }, { displayName: 'βš™οΈ Options', name: 'options', type: 'collection', placeholder: 'Add Option', default: {}, displayOptions: { show: { operation: ['getByIds'], }, }, options: [ { displayName: 'πŸ”’ Limit', name: 'limit', type: 'number', default: 50, description: 'Max number of results to return (1-200)', typeOptions: { minValue: 1, maxValue: 200, }, }, { displayName: 'πŸ“„ Page Number', name: 'page', type: 'number', default: 1, description: 'Page number for pagination (starts from 1)', typeOptions: { minValue: 1, }, }, { displayName: 'πŸ“Š Sort Field', name: 'sortField', type: 'string', default: '', description: 'Field to sort by (leave empty for no sorting)', }, { displayName: 'πŸ”„ Sort Direction', name: 'sortDirection', type: 'options', options: [ { name: 'Ascending', value: 'asc', }, { name: 'Descending', value: 'desc', }, ], default: 'asc', description: 'Direction to sort the results', displayOptions: { hide: { sortField: [''], }, }, }, ], }, ], }; // **FIXED**: Restructured methods to separate resourceMapping from loadOptions this.methods = { loadOptions: { getModules: helpers_1.getModules, getProcessModules: helpers_1.getProcessModules, // Relation loaders for dynamic dropdowns in the mapper loadUserOptions: fieldsAccess_1.loadUserOptions, }, resourceMapping: { getInflowFieldsForResourceMapper: helpers_1.getInflowFieldsForResourceMapper, }, }; } async execute() { logger_1.actionsLogger.info('===== STARTING NODE EXECUTION ====='); const items = this.getInputData(); const returnData = []; logger_1.actionsLogger.info(`Processing ${items.length} input items`); for (let i = 0; i < items.length; i++) { try { const operation = this.getNodeParameter('operation', i); // Handle different parameter names based on operation let moduleName; if (operation === 'getProcesses') { moduleName = this.getNodeParameter('processModule', i); } else { moduleName = this.getNodeParameter('module', i); } let mappedFields = undefined; if (['create', 'update', 'search', 'find'].includes(operation)) { mappedFields = this.getNodeParameter('mappedFields', i); } logger_1.actionsLogger.info(`Item ${i + 1}/${items.length}:`); logger_1.actionsLogger.info(`- Operation: ${operation}`); logger_1.actionsLogger.info(`- Module: ${moduleName}`); if (mappedFields !== undefined) { logger_1.actionsLogger.debug('- Mapped Fields:', JSON.stringify(mappedFields, null, 2)); } if (!operation) { throw new Error('Operation parameter is required but not provided'); } if (!moduleName) { throw new Error('Module name parameter is required but not provided'); } // Validate mappedFields based on operation type if (operation === 'create' || operation === 'update') { if (!mappedFields || typeof mappedFields !== 'object') { throw new Error('Mapped fields parameter is required but not provided or invalid'); } } else if (operation === 'search' || operation === 'find') { // For search and find operations, mappedFields can be empty (optional filtering) if (mappedFields && typeof mappedFields !== 'object') { throw new Error('Mapped fields parameter is invalid'); } } if (operation === 'create') { if (!mappedFields) { throw new Error('No fields provided for create operation'); } const fieldsToCreate = mappedFields.value; // Get file uploads const fileUploads = this.getNodeParameter('fileUploads', i, { values: [] }); logger_1.actionsLogger.info('Executing CREATE operation with fields:', fieldsToCreate); logger_1.actionsLogger.info('File uploads configured:', fileUploads.values.length); // Debug: Log all mapped fields and their types logger_1.actionsLogger.debug('[N8N_INFLOWCRM_NODE_DEBUG] All mappedFields:', JSON.stringify(mappedFields, null, 2)); logger_1.actionsLogger.debug('[N8N_INFLOWCRM_NODE_DEBUG] fieldsToCreate keys:', Object.keys(fieldsToCreate)); logger_1.actionsLogger.debug('[N8N_INFLOWCRM_NODE_DEBUG] fieldsToCreate values:', JSON.stringify(fieldsToCreate, null, 2)); if (!fieldsToCreate || Object.keys(fieldsToCreate).length === 0) { logger_1.actionsLogger.error('[DEBUG] fieldsToCreate is empty or missing'); throw new Error('No fields provided for create operation'); } // Note: Required field validation is now handled by the Resource Mapper // The helpers.ts ensures that required fields are properly displayed in the UI // and the API will validate required fields on the server side logger_1.actionsLogger.info('Tu jeszcze bez bledΓ³w '); logger_1.actionsLogger.debug('File uploads:', fileUploads); const result = await actions_1.executeCreateAction.call(this, i, moduleName, fieldsToCreate, fileUploads.values.length > 0 ? fileUploads.values : undefined); logger_1.actionsLogger.info('po zapisie', result); returnData.push(result); } else if (operation === 'update') { const updateRecordId = this.getNodeParameter('updateRecordId', i); let fieldsToUpdate = {}; if (mappedFields && typeof mappedFields === 'object' && 'value' in mappedFields && mappedFields.value) { fieldsToUpdate = mappedFields.value; } logger_1.actionsLogger.info('Executing UPDATE operation'); logger_1.actionsLogger.info('- Record ID:', updateRecordId); logger_1.actionsLogger.info('- Fields to update:', fieldsToUpdate); // Get file uploads const fileUploads = this.getNodeParameter('fileUploads', i, { values: [] }); logger_1.actionsLogger.info('- File uploads configured:', fileUploads.values.length); if (!updateRecordId) { throw new Error('Update operation requires a Record ID'); } const result = await actions_1.executeUpdateAction.call(this, i, moduleName, updateRecordId, fieldsToUpdate, fileUploads.values.length > 0 ? fileUploads.values : undefined); returnData.push(result); } else if (operation === 'search') { const searchFields = mappedFields && mappedFields.value ? mappedFields.value : {}; const options = this.getNodeParameter('options', i, {}); logger_1.actionsLogger.info('Executing SEARCH operation'); logger_1.actionsLogger.debug('- Search fields:', searchFields); logger_1.actionsLogger.debug('- Options:', options); const results = await actions_1.executeSearchAction.call(this, i, moduleName, searchFields, options); returnData.push(...results); } else if (operation === 'find') { const filter = mappedFields && mappedFields.value ? mappedFields.value : {}; logger_1.actionsLogger.info('Executing FIND operation with filter:', filter); const result = await actions_1.executeFindOneAction.call(this, i, moduleName, filter); returnData.push(result); } else if (operation === 'getById') { const recordId = this.getNodeParameter('getByIdRecordId', i); logger_1.actionsLogger.info('Executing GET BY ID operation'); logger_1.actionsLogger.info('- Record ID:', recordId); if (!recordId) { throw new Error('Get By ID operation requires a Record ID'); } const result = await actions_1.executeGetByIdAction.call(this, i, moduleName, recordId); returnData.push(result); } else if (operation === 'getByIds') { const recordIds = this.getNodeParameter('getByIdsRecordIds', i); const options = this.getNodeParameter('options', i, {}); logger_1.actionsLogger.info('Executing GET BY IDS operation'); logger_1.actionsLogger.info('- Record IDs:', recordIds); logger_1.actionsLogger.info('- Options:', options); if (!recordIds) { throw new Error('Get By IDs operation requires Record IDs'); } const results = await actions_1.executeGetByIdsAction.call(this, i, moduleName, recordIds, options); returnData.push(...results); } else if (operation === 'getProcesses') { logger_1.actionsLogger.info('Executing GET PROCESSES operation'); logger_1.actionsLogger.info('- Module:', moduleName); const results = await actions_1.executeGetProcessesAction.call(this, i, moduleName); returnData.push(...results); } else if (operation === 'delete') { const recordId = this.getNodeParameter('recordId', i); logger_1.actionsLogger.info('Executing DELETE operation with recordId:', recordId); const result = await actions_1.executeDeleteAction.call(this, i, moduleName, recordId); returnData.push(result); } else if (operation === 'count') { const filter = this.getNodeParameter('countFilter', i, { value: {} }); const filterValue = filter.value || {}; logger_1.actionsLogger.info('Executing COUNT operation with filter:', filterValue); const result = await actions_1.executeCountAction.call(this, i, moduleName, filterValue); returnData.push(result); } else { throw new Error(`Unknown operation: ${operation}`); } logger_1.actionsLogger.info(`Item ${i + 1} processed successfully`); } catch (error) { logger_1.actionsLogger.error(`Error processing item ${i + 1}:`, error); logger_1.actionsLogger.error('Error stack:', error.stack); if (this.continueOnFail()) { const errorData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray({ error: error.message, itemIndex: i, timestamp: new Date().toISOString(), }), { itemData: { item: i } }); returnData.push(...errorData); continue; } throw error; } } logger_1.actionsLogger.info('===== NODE EXECUTION COMPLETE ====='); logger_1.actionsLogger.info(`Returning ${returnData.length} items`); return [returnData]; } } exports.InflowCrm = InflowCrm;