UNPKG

@tuanltntu/n8n-nodes-bitrix24

Version:

Comprehensive n8n community node for Bitrix24 API integration with CRM, Tasks, Chat, Telephony, and more

636 lines 28.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UserFieldConfigResourceHandler = void 0; const n8n_workflow_1 = require("n8n-workflow"); const ResourceHandlerBase_1 = require("./ResourceHandlerBase"); /** * Class for handling Bitrix24 CRM user field configuration operations */ class UserFieldConfigResourceHandler extends ResourceHandlerBase_1.ResourceHandlerBase { constructor(executeFunctions, returnData, options = {}) { super(executeFunctions, returnData, options); } /** * Process all operations for user field configuration */ async process() { const operation = this.getNodeParameter("operation", 0); for (let i = 0; i < this.items.length; i++) { try { const continueOnFail = this.getNodeParameter("continueOnFail", i, false); // Process items according to the operation if (operation === "add") { await this.handleAdd(i); } else if (operation === "addMultiple") { await this.handleAddMultiple(i); } else if (operation === "delete") { await this.handleDelete(i); } else if (operation === "get") { await this.handleGet(i); } else if (operation === "getList") { await this.handleGetList(i); } else if (operation === "update") { await this.handleUpdate(i); } else { throw new n8n_workflow_1.NodeOperationError(this.executeFunctions.getNode(), `The operation "${operation}" is not supported for user field configuration!`, { itemIndex: i }); } } catch (error) { if (this.continueOnFail()) { this.addErrorToReturnData(error, i); } else { throw error; } } } return this.returnData; } // Add this method to generate the correct prefix based on entity type getFieldNamePrefix(entityId) { // Smart Process uses format UF_CRM_[ID]_ if (entityId.startsWith("CRM_")) { const dynamicId = entityId.replace("CRM_", ""); return `UF_CRM_${dynamicId}_`; } // For standard CRM entities if (entityId === "DEAL" || entityId === "LEAD" || entityId === "CONTACT" || entityId === "COMPANY" || entityId === "QUOTE") { return "UF_CRM_"; } // Default CRM entities use UF_CRM_ return "UF_CRM_"; } // Add this helper method to convert entity type to module ID getModuleId(entityId) { console.log("Getting moduleId for entityId:", entityId); // For Smart Process entities (starting with CRM_) if (entityId.startsWith("CRM_")) { return "crm"; } // For standard CRM entities if (entityId === "DEAL" || entityId === "LEAD" || entityId === "CONTACT" || entityId === "COMPANY" || entityId === "QUOTE") { return "crm"; } // For tasks if (entityId === "TASK") { return "tasks"; } // Default to CRM module return "crm"; } // Convert field data keys to camelCase format convertToCamelCase(fieldData) { const camelCaseData = {}; // Map of uppercase keys to API expected format (based on Bitrix24 docs) const keyMap = { ENTITY_ID: "entityId", FIELD_NAME: "fieldName", USER_TYPE_ID: "userTypeId", EDIT_FORM_LABEL: "editFormLabel", LIST_COLUMN_LABEL: "listColumnLabel", LIST_FILTER_LABEL: "listFilterLabel", XML_ID: "xmlId", SORT: "sort", MULTIPLE: "multiple", MANDATORY: "mandatory", SHOW_IN_LIST: "showInList", EDIT_IN_LIST: "editInList", SHOW_FILTER: "showFilter", IS_SEARCHABLE: "isSearchable", SETTINGS: "settings", DEFAULT_VALUE: "defaultValue", USER_TYPE_SETTINGS: "userTypeSettings", // Keep enum as-is (lowercase) enum: "enum", }; // Process each field for (const [key, value] of Object.entries(fieldData)) { // Handle special cases first if (key === "enum") { // Keep enum as-is for enumeration fields camelCaseData.enum = value; continue; } // Convert key using mapping or default camelCase conversion const camelKey = keyMap[key] || key.toLowerCase().replace(/_([a-z])/g, (m, p1) => p1.toUpperCase()); camelCaseData[camelKey] = value; } return camelCaseData; } /** * Handle the add operation */ async handleAdd(itemIndex) { // Check if using structured builder or direct JSON const useStructureBuilder = this.getNodeParameter("useStructureBuilder", itemIndex, true); let fieldData = {}; if (!useStructureBuilder) { // Get fields as direct JSON input const fields = this.getNodeParameter("fields", itemIndex); // Parse fields fieldData = this.parseJsonParameter(fields, 'The "fields" parameter must be valid JSON', itemIndex); } else { // Process the structured fields - now using the flat structure // Get entity type and determine the proper prefix const entityType = this.getNodeParameter("entityType", itemIndex); // Map entity types to Bitrix24 API expected values const entityTypeMapping = { LEAD: "CRM_LEAD", DEAL: "CRM_DEAL", CONTACT: "CRM_CONTACT", COMPANY: "CRM_COMPANY", QUOTE: "CRM_QUOTE", }; // Use mapped value if available, otherwise use original fieldData.ENTITY_ID = entityTypeMapping[entityType] || entityType; // Handle custom entity type if (entityType === "CUSTOM") { const customEntityType = this.getNodeParameter("customEntityType", itemIndex, ""); if (customEntityType) { fieldData.ENTITY_ID = customEntityType; } } // Handle Smart Process Type if (entityType === "DYNAMIC_ENTITY") { const smartProcessType = this.getNodeParameter("smartProcessType", itemIndex, ""); if (smartProcessType) { console.log("Using Smart Process Type:", smartProcessType); // CRM_ format is already correct from the smartProcessType parameter // No need for conversion, just use it directly fieldData.ENTITY_ID = smartProcessType; console.log("Set ENTITY_ID to:", fieldData.ENTITY_ID); } } // Get basic field properties fieldData.USER_TYPE_ID = this.getNodeParameter("fieldType", itemIndex); // Get the field name and add the appropriate prefix const fieldName = this.getNodeParameter("fieldName", itemIndex); const prefix = this.getFieldNamePrefix(fieldData.ENTITY_ID); fieldData.FIELD_NAME = fieldName.startsWith(prefix) ? fieldName.toUpperCase() : `${prefix}${fieldName.toUpperCase()}`; // Get the field label and set all form labels const label = this.getNodeParameter("fieldLabel", itemIndex); // Create label object with both "en" and "vn" keys const labelObject = { en: label, vn: label, }; // Set all three label fields with the same format fieldData.EDIT_FORM_LABEL = labelObject; fieldData.LIST_COLUMN_LABEL = labelObject; fieldData.LIST_FILTER_LABEL = labelObject; // Process field behavior properties const fieldBehavior = this.getNodeParameter("fieldBehavior", itemIndex, { values: {}, }); if (fieldBehavior.values) { const behaviorValues = fieldBehavior.values; Object.entries(behaviorValues).forEach(([key, value]) => { // Convert boolean values to 'Y'/'N' if (typeof value === "boolean") { fieldData[key] = value ? "Y" : "N"; } else { // If the value exists and is not empty, add it if (value !== undefined && value !== "") { fieldData[key] = value; } } }); } // Process type-specific settings const fieldTypeSettings = this.getNodeParameter("fieldTypeSettings", itemIndex, { values: {} }); if (fieldTypeSettings.values) { const settings = {}; const typeValues = fieldTypeSettings.values; // Handle special case for default value if (typeValues.DEFAULT_VALUE !== undefined && typeValues.DEFAULT_VALUE !== "") { fieldData.DEFAULT_VALUE = typeValues.DEFAULT_VALUE; } // Add all other type-specific settings Object.entries(typeValues).forEach(([key, value]) => { if (key !== "DEFAULT_VALUE" && value !== undefined && value !== "" && value !== 0) { settings[key] = value; } }); // If we have settings, add them to fieldData if (Object.keys(settings).length > 0) { fieldData.SETTINGS = settings; } } // Process enumeration values const enumValues = this.getNodeParameter("enumValues", itemIndex, { values: [], }); if (enumValues.values && Array.isArray(enumValues.values) && enumValues.values.length > 0 && fieldData.USER_TYPE_ID === "enumeration") { // Create enum array directly (not in SETTINGS) const enumItems = []; // Process each enum value for (const enumValue of enumValues.values) { if (enumValue.VALUE) { enumItems.push({ value: enumValue.VALUE, def: "N", sort: enumValue.SORT || 100, }); } } if (enumItems.length > 0) { // Set first item as default if no default is specified if (enumItems.length > 0 && !enumItems.some((item) => item.def === "Y")) { enumItems[0].def = "Y"; } fieldData.enum = enumItems; // Use 'enum' directly, not in SETTINGS } } // Special handling for CRM field types if (fieldData.USER_TYPE_ID === "crm") { // Set default value to lead if not set if (!fieldData.DEFAULT_VALUE) { fieldData.DEFAULT_VALUE = "lead"; } console.log("Adding CRM field type settings for field", { fieldName, entityId: fieldData.ENTITY_ID, }); // Create USER_TYPE_SETTINGS with standard CRM entity types fieldData.USER_TYPE_SETTINGS = { LEAD: "Y", DEAL: "Y", CONTACT: "Y", COMPANY: "Y", }; console.log("Added CRM field settings:", JSON.stringify(fieldData.USER_TYPE_SETTINGS)); } } // API call for single field console.log("Full fieldData object for API call:", fieldData); // Convert to camelCase once for both logging and API call const camelCaseData = this.convertToCamelCase(fieldData); console.log("Making API call with converted parameters:", { moduleId: this.getModuleId(fieldData.ENTITY_ID), field: camelCaseData, }); try { console.log("Making API call to userfieldconfig.add with parameters", { moduleId: this.getModuleId(fieldData.ENTITY_ID), fieldEntityId: camelCaseData.entityId, fieldName: camelCaseData.fieldName, userTypeId: camelCaseData.userTypeId, }); const responseData = await this.makeApiCall("userfieldconfig.add", { moduleId: this.getModuleId(fieldData.ENTITY_ID), field: camelCaseData, // Use the converted data }, {}, itemIndex); console.log("API call successful, response:", JSON.stringify(responseData, null, 2)); console.log("Bitrix24 API Response Structure:", { status: responseData.status, result: typeof responseData.result === "object" ? "object" : responseData.result, error: responseData.error, time: responseData.time, }); if (responseData.error) { console.error("Bitrix24 API Error:", responseData.error); } this.addResponseToReturnData(responseData, itemIndex); } catch (error) { console.error("API call failed with error:", error); if (error instanceof Error) { console.error("Error message:", error.message); console.error("Error stack:", error.stack); } throw error; } } /** * Handle the addMultiple operation */ async handleAddMultiple(itemIndex) { // Get entity type const entityType = this.getNodeParameter("multipleEntityType", itemIndex); // Map entity types to Bitrix24 API expected values const entityTypeMapping = { LEAD: "CRM_LEAD", DEAL: "CRM_DEAL", CONTACT: "CRM_CONTACT", COMPANY: "CRM_COMPANY", QUOTE: "CRM_QUOTE", }; // Use mapped value if available, otherwise use original let finalEntityId = entityTypeMapping[entityType] || entityType; // Handle custom entity type if (entityType === "CUSTOM") { const customEntityType = this.getNodeParameter("multipleCustomEntityType", itemIndex, ""); if (customEntityType) { finalEntityId = customEntityType; } } // Handle Smart Process Type if (entityType === "DYNAMIC_ENTITY") { const smartProcessType = this.getNodeParameter("multipleSmartProcessType", itemIndex, ""); if (smartProcessType) { console.log("Using Smart Process Type (multiple):", smartProcessType); // CRM_ format is already correct from the smartProcessType parameter // No need for conversion, just use it directly finalEntityId = smartProcessType; console.log("Set finalEntityId to:", finalEntityId); } } // Get the field name prefix based on entity type const fieldNamePrefix = this.getFieldNamePrefix(finalEntityId); // Get field definitions const fieldDefinitions = this.getNodeParameter("fieldDefinitions.fields", itemIndex, []); const results = []; // Process each field definition for (const fieldDef of fieldDefinitions) { try { const fieldData = { ENTITY_ID: finalEntityId, ...fieldDef, }; // Add appropriate prefix to field name if not already present if (fieldData.FIELD_NAME && typeof fieldData.FIELD_NAME === "string") { const fieldName = fieldData.FIELD_NAME; fieldData.FIELD_NAME = fieldName.startsWith(fieldNamePrefix) ? fieldName.toUpperCase() : `${fieldNamePrefix}${fieldName.toUpperCase()}`; } // Handle labels properly // Make sure editFormLabel is set if (!fieldData.EDIT_FORM_LABEL && fieldData.FIELD_NAME) { // Create object with both "en" and "vn" keys fieldData.EDIT_FORM_LABEL = { en: fieldData.FIELD_NAME, vn: fieldData.FIELD_NAME, }; } else if (typeof fieldData.EDIT_FORM_LABEL === "string") { // Convert string label to object format fieldData.EDIT_FORM_LABEL = { en: fieldData.EDIT_FORM_LABEL, vn: fieldData.EDIT_FORM_LABEL, }; } // Set LIST_COLUMN_LABEL and LIST_FILTER_LABEL to same format as EDIT_FORM_LABEL if not provided if (fieldData.EDIT_FORM_LABEL) { if (!fieldData.LIST_COLUMN_LABEL) { fieldData.LIST_COLUMN_LABEL = fieldData.EDIT_FORM_LABEL; } else if (typeof fieldData.LIST_COLUMN_LABEL === "string") { // Convert string to object format fieldData.LIST_COLUMN_LABEL = { en: fieldData.LIST_COLUMN_LABEL, vn: fieldData.LIST_COLUMN_LABEL, }; } if (!fieldData.LIST_FILTER_LABEL) { fieldData.LIST_FILTER_LABEL = fieldData.EDIT_FORM_LABEL; } else if (typeof fieldData.LIST_FILTER_LABEL === "string") { // Convert string to object format fieldData.LIST_FILTER_LABEL = { en: fieldData.LIST_FILTER_LABEL, vn: fieldData.LIST_FILTER_LABEL, }; } } // Set default values for SHOW_FILTER and IS_SEARCHABLE if not provided if (fieldData.SHOW_FILTER === undefined) { fieldData.SHOW_FILTER = true; } if (fieldData.IS_SEARCHABLE === undefined) { fieldData.IS_SEARCHABLE = true; } // Convert boolean values to Y/N for (const [key, value] of Object.entries(fieldData)) { if (typeof value === "boolean") { fieldData[key] = value ? "Y" : "N"; } } // Special handling for CRM field types if (fieldData.USER_TYPE_ID === "crm") { // Set default value to lead if not set if (!fieldData.DEFAULT_VALUE) { fieldData.DEFAULT_VALUE = "lead"; } console.log("Adding CRM field type settings for field", { fieldName: fieldData.FIELD_NAME, entityId: finalEntityId, }); // Create USER_TYPE_SETTINGS with standard CRM entity types fieldData.USER_TYPE_SETTINGS = { LEAD: "Y", DEAL: "Y", CONTACT: "Y", COMPANY: "Y", }; console.log("Added CRM field settings:", JSON.stringify(fieldData.USER_TYPE_SETTINGS)); } // Process additional settings if provided if (fieldData.additionalSettings) { try { let settings = {}; if (typeof fieldData.additionalSettings === "string") { settings = JSON.parse(fieldData.additionalSettings); } else if (typeof fieldData.additionalSettings === "object") { settings = fieldData.additionalSettings; } // Add settings to fieldData for (const [key, value] of Object.entries(settings)) { if (value !== undefined) { fieldData[key] = value; } } // Remove the additionalSettings property delete fieldData.additionalSettings; } catch (err) { console.error("Failed to parse additional settings:", err); } } // Log the request parameters for debugging const camelCaseData = this.convertToCamelCase(fieldData); console.log("Adding field with parameters:", { moduleId: this.getModuleId(finalEntityId), field: camelCaseData, }); // API call for multiple fields - call API once per field instead of batching try { const responseData = await this.makeApiCall("userfieldconfig.add", { moduleId: this.getModuleId(finalEntityId), field: camelCaseData, }, {}, itemIndex); console.log("API call successful, response for field " + fieldData.FIELD_NAME + ":", JSON.stringify(responseData, null, 2)); console.log("Bitrix24 API Response Structure:", { status: responseData.status, result: typeof responseData.result === "object" ? "object" : responseData.result, error: responseData.error, time: responseData.time, }); if (responseData.error) { console.error("Bitrix24 API Error:", responseData.error); } results.push({ fieldName: fieldData.FIELD_NAME, success: true, response: responseData, }); } catch (apiError) { // Log detailed API error for debugging console.error("API Error:", apiError); results.push({ fieldName: fieldData.FIELD_NAME || "unknown", success: false, error: apiError instanceof Error ? apiError.message : String(apiError), }); } } catch (error) { // Always continue on failures for multiple field creation results.push({ fieldName: fieldDef.FIELD_NAME || "unknown", success: false, error: error instanceof Error ? error.message : String(error), }); } } // Add all results to return data this.addResponseToReturnData({ result: results }, itemIndex); } /** * Handle the delete operation */ async handleDelete(itemIndex) { const id = this.getNodeParameter("id", itemIndex); // Get entity ID directly from the parameter const entityId = this.getNodeParameter("entityId", itemIndex); // Log the delete operation parameters for debugging console.log("Deleting user field with parameters:", { moduleId: this.getModuleId(entityId), id: id, entityType: entityId, }); // API call with moduleId included and id parameter only // The id parameter is the full ID of the user field, not entityTypeId const responseData = await this.makeApiCall("userfieldconfig.delete", { moduleId: this.getModuleId(entityId), id: id, // Ensure id is passed correctly as the field ID }, {}, itemIndex); this.addResponseToReturnData(responseData, itemIndex); } /** * Handle the get operation */ async handleGet(itemIndex) { const id = this.getNodeParameter("id", itemIndex); // Get entity ID - required for moduleId parameter const entityId = this.getNodeParameter("entityId", itemIndex, "CRM"); // Log the get operation parameters for debugging console.log("Getting user field with parameters:", { moduleId: this.getModuleId(entityId), id: id, entityType: entityId, }); // API call with moduleId included as required by userfieldconfig.get const responseData = await this.makeApiCall("userfieldconfig.get", { moduleId: this.getModuleId(entityId), id, }, {}, itemIndex); this.addResponseToReturnData(responseData, itemIndex); } /** * Handle the get list operation */ async handleGetList(itemIndex) { const filter = this.getNodeParameter("filter", itemIndex, "{}"); const order = this.getNodeParameter("order", itemIndex, "{}"); const select = this.getNodeParameter("select", itemIndex, []); const start = this.getNodeParameter("start", itemIndex, 0); // Parse filter and order const filterData = this.parseJsonParameter(filter, 'The "filter" parameter must be valid JSON', itemIndex); const orderData = this.parseJsonParameter(order, 'The "order" parameter must be valid JSON', itemIndex); const body = {}; if (Object.keys(filterData).length) { body.filter = filterData; } if (Object.keys(orderData).length) { body.order = orderData; } if (select.length) { body.select = select; } if (start) { body.start = start; } // API call const responseData = await this.makeApiCall("userfieldconfig.list", body, {}, itemIndex); this.addResponseToReturnData(responseData, itemIndex); } /** * Handle the update operation */ async handleUpdate(itemIndex) { const id = this.getNodeParameter("id", itemIndex); // Get update fields from the collection const updateFieldsCollection = this.getNodeParameter("updateFields", itemIndex, { fields: [] }); // Process updates const fieldData = {}; if (updateFieldsCollection.fields && Array.isArray(updateFieldsCollection.fields)) { for (const field of updateFieldsCollection.fields) { if (field.name) { const fieldName = field.name; // Check if this is a boolean field if (field.isBoolean === true) { fieldData[fieldName] = field.boolValue ? "Y" : "N"; } else { fieldData[fieldName] = field.value; } } } } // API call for update - simplified parameter structure const responseData = await this.makeApiCall("userfieldconfig.update", { id, field: this.convertToCamelCase(fieldData), }, {}, itemIndex); this.addResponseToReturnData(responseData, itemIndex); } } exports.UserFieldConfigResourceHandler = UserFieldConfigResourceHandler; //# sourceMappingURL=UserFieldConfigResourceHandler.js.map