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
JavaScript
;
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;