@tuanltntu/n8n-nodes-bitrix24
Version:
Comprehensive n8n community node for Bitrix24 API integration with CRM, Tasks, Chat, Telephony, and more
802 lines • 39.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Bitrix24 = void 0;
const FieldRegistry_1 = require("./FieldRegistry");
const ResourceHandlerFactory_1 = require("./handlers/ResourceHandlerFactory");
const GenericFunctions_1 = require("./GenericFunctions");
/**
* Bitrix24 Node Implementation
* Using FieldRegistry for clean architecture
*/
class Bitrix24 {
constructor() {
this.description = {
displayName: "Bitrix24",
name: "bitrix24",
icon: "file:bitrix24.svg",
group: ["output"],
version: 1,
subtitle: "={{ $parameter['resource'] + ': ' + $parameter['operation'] }}",
description: "Interact with Bitrix24 CRM and business platform",
defaults: {
name: "Bitrix24",
},
inputs: ["main"],
outputs: ["main"],
usableAsTool: true,
credentials: [
{
name: "bitrix24OAuth",
required: true,
displayOptions: {
show: {
authentication: ["oAuth2"],
},
},
},
{
name: "bitrix24Api",
required: true,
displayOptions: {
show: {
authentication: ["apiKey"],
},
},
},
{
name: "bitrix24Webhook",
required: true,
displayOptions: {
show: {
authentication: ["webhook"],
},
},
},
],
properties: [
{
displayName: "Authentication",
name: "authentication",
type: "options",
options: [
{
name: "OAuth2",
value: "oAuth2",
description: "Use OAuth2 authentication (recommended for production)",
},
{
name: "Webhook",
value: "webhook",
description: "Use a Bitrix24 webhook URL (simpler but less secure)",
},
{
name: "API Key",
value: "apiKey",
description: "Use Bitrix24 API key authentication",
},
],
default: "oAuth2",
},
// Resource selector
{
displayName: "Resource",
name: "resource",
type: "options",
noDataExpression: true,
required: true,
default: "crm",
description: "Select the Bitrix24 resource to work with",
options: FieldRegistry_1.FieldRegistry.getAvailableResources(),
},
// All fields from all description files
...FieldRegistry_1.FieldRegistry.getAvailableFields(),
],
};
this.methods = {
loadOptions: {
// Load CRM entity fields dynamically
async getCrmEntityFields() {
try {
const entityType = this.getCurrentNodeParameter("entityType");
if (!entityType) {
return [];
}
// Map entity types to their field endpoints
const fieldEndpoints = {
contact: "crm.contact.fields",
deal: "crm.deal.fields",
lead: "crm.lead.fields",
company: "crm.company.fields",
quote: "crm.quote.fields",
invoice: "crm.invoice.fields",
product: "crm.product.fields",
activity: "crm.activity.fields",
};
const endpoint = fieldEndpoints[entityType];
if (!endpoint) {
return [];
}
// Use the same makeStandardBitrix24Call function as other operations
const response = await GenericFunctions_1.makeStandardBitrix24Call.call(this, endpoint, {}, {});
if (!response.result) {
return [];
}
const fields = response.result;
const options = [];
// System fields that should not be set manually
const systemFields = [
"ID",
"DATE_CREATE",
"DATE_MODIFY",
"CREATED_BY_ID",
"MODIFY_BY_ID",
];
for (const [fieldId, fieldData] of Object.entries(fields)) {
if (systemFields.includes(fieldId)) {
continue;
}
const field = fieldData;
// Get display name with priority: listLabel -> formLabel -> title -> fieldId
let displayName = fieldId; // fallback to field ID
if (field.listLabel && field.listLabel.trim()) {
displayName = field.listLabel.trim();
}
else if (field.formLabel && field.formLabel.trim()) {
displayName = field.formLabel.trim();
}
else if (field.title && field.title.trim()) {
displayName = field.title.trim();
}
// Create description with field type and ID info
let description = `Field ID: ${fieldId}`;
if (field.type) {
description = `${field.type} - ${description}`;
}
if (field.isRequired) {
description = `Required - ${description}`;
}
options.push({
name: displayName,
value: fieldId,
description: description,
});
}
// Sort options by display name
return options.sort((a, b) => a.name.localeCompare(b.name));
}
catch (error) {
return [
{
name: "Error loading fields",
value: "",
description: `Failed to load CRM fields: ${error.message}`,
},
];
}
},
// Load SPA Types dynamically
async getSpaTypes() {
try {
console.log("getSpaTypes called");
// Get all SPA types
const response = await GenericFunctions_1.makeStandardBitrix24Call.call(this, "crm.type.list", {}, {});
console.log("SPA Types API response:", JSON.stringify(response, null, 2));
if (!response.result) {
return [
{
name: "No SPA types available",
value: "",
description: "No SPA types found",
},
];
}
// Handle both possible response structures
let types = [];
if (response.result.types) {
types = response.result.types;
}
else if (Array.isArray(response.result)) {
types = response.result;
}
else {
types = Object.values(response.result);
}
const options = [];
for (const type of types) {
// Use entityTypeId if available, otherwise use id
const entityTypeId = type.entityTypeId || type.id;
const title = type.title || type.name || `SPA Type ${entityTypeId}`;
options.push({
name: `${title} (ID: ${entityTypeId})`,
value: entityTypeId.toString(),
description: title,
});
}
// Sort by name
options.sort((a, b) => a.name.localeCompare(b.name));
console.log("Returning SPA types:", options.length, "types");
return options;
}
catch (error) {
console.error("Error in getSpaTypes:", error);
return [
{
name: "Error loading SPA types",
value: "",
description: `Failed to load SPA types: ${error.message}`,
},
];
}
},
// Load SPA Type IDs for Type operations (returns id field instead of entityTypeId)
async getSpaTypeIds() {
try {
console.log("getSpaTypeIds called");
// Get all SPA types
const response = await GenericFunctions_1.makeStandardBitrix24Call.call(this, "crm.type.list", {}, {});
console.log("SPA Type IDs API response:", JSON.stringify(response, null, 2));
if (!response.result) {
return [
{
name: "No SPA types available",
value: "",
description: "No SPA types found",
},
];
}
// Handle both possible response structures
let types = [];
if (response.result.types) {
types = response.result.types;
}
else if (Array.isArray(response.result)) {
types = response.result;
}
else {
types = Object.values(response.result);
}
const options = [];
for (const type of types) {
// Use id field for Type operations
const typeId = type.id;
const title = type.title || type.name || `SPA Type ${typeId}`;
options.push({
name: `${title} (ID: ${typeId})`,
value: typeId.toString(),
description: title,
});
}
// Sort by name
options.sort((a, b) => a.name.localeCompare(b.name));
console.log("Returning SPA type IDs:", options.length, "types");
return options;
}
catch (error) {
console.error("Error in getSpaTypeIds:", error);
return [
{
name: "Error loading SPA type IDs",
value: "",
description: `Failed to load SPA type IDs: ${error.message}`,
},
];
}
},
// Load Deal Categories for deal operations
async getDealCategories() {
try {
console.log("getDealCategories called");
// Get all deal categories
const response = await GenericFunctions_1.makeStandardBitrix24Call.call(this, "crm.category.list", { entityTypeId: 2 }, // 2 is the entity type ID for deals
{});
console.log("Deal Categories API response:", JSON.stringify(response, null, 2));
if (!response.result) {
return [
{
name: "No deal categories available",
value: "",
description: "No deal categories found",
},
];
}
// Handle response structure
let categories = [];
if (response.result.categories) {
categories = response.result.categories;
}
else if (Array.isArray(response.result)) {
categories = response.result;
}
else {
categories = Object.values(response.result);
}
const options = [];
for (const category of categories) {
const categoryId = category.id;
const name = category.name || `Category ${categoryId}`;
options.push({
name: name,
value: categoryId.toString(),
description: category.isDefault === "Y" ? `${name} (Default)` : name,
});
}
// Sort by name, but put default category first
options.sort((a, b) => {
var _a, _b;
if ((_a = a.description) === null || _a === void 0 ? void 0 : _a.includes("(Default)"))
return -1;
if ((_b = b.description) === null || _b === void 0 ? void 0 : _b.includes("(Default)"))
return 1;
return a.name.localeCompare(b.name);
});
console.log("Returning deal categories:", options.length, "categories");
return options;
}
catch (error) {
console.error("Error in getDealCategories:", error);
return [
{
name: "Error loading deal categories",
value: "",
description: `Failed to load deal categories: ${error.message}`,
},
];
}
},
// Load Dynamic Types for automation module
async getDynamicTypes() {
try {
console.log("getDynamicTypes called");
const options = [
// Standard CRM types
{
name: "CRM Deal",
value: "crm_deal",
description: "CRM Deal entities",
},
{
name: "CRM Lead",
value: "crm_lead",
description: "CRM Lead entities",
},
{
name: "CRM Contact",
value: "crm_contact",
description: "CRM Contact entities",
},
{
name: "CRM Company",
value: "crm_company",
description: "CRM Company entities",
},
{
name: "CRM Quote",
value: "crm_quote",
description: "CRM Quote entities",
},
{
name: "CRM Invoice",
value: "crm_invoice",
description: "CRM Invoice entities",
},
// Smart Process Automation
{
name: "Smart Process",
value: "spa_placement",
description: "Smart Process Automation entities",
},
];
// Try to get SPA types
try {
const spaResponse = await GenericFunctions_1.makeStandardBitrix24Call.call(this, "crm.type.list", {}, {});
if (spaResponse.result) {
let types = [];
if (spaResponse.result.types) {
types = spaResponse.result.types;
}
else if (Array.isArray(spaResponse.result)) {
types = spaResponse.result;
}
else {
types = Object.values(spaResponse.result);
}
for (const type of types) {
const entityTypeId = type.entityTypeId || type.id;
const title = type.title || type.name || `SPA Type ${entityTypeId}`;
options.push({
name: `SPA: ${title}`,
value: `spa_${entityTypeId}`,
description: `Smart Process: ${title}`,
});
}
}
}
catch (spaError) {
console.log("Could not load SPA types:", spaError.message);
}
console.log("Returning dynamic types:", options.length, "types");
return options.sort((a, b) => a.name.localeCompare(b.name));
}
catch (error) {
console.error("Error in getDynamicTypes:", error);
return [
{
name: "Error loading dynamic types",
value: "",
description: `Failed to load dynamic types: ${error.message}`,
},
];
}
},
// Load SPA Placement Options for automation module
async getSpaPlacementOptions() {
try {
console.log("getSpaPlacementOptions called");
// Get all SPA types
const response = await GenericFunctions_1.makeStandardBitrix24Call.call(this, "crm.type.list", {}, {});
console.log("SPA Placement Options API response:", JSON.stringify(response, null, 2));
if (!response.result) {
return [
{
name: "No SPA types available",
value: "",
description: "No Smart Process types found",
},
];
}
// Handle both possible response structures
let types = [];
if (response.result.types) {
types = response.result.types;
}
else if (Array.isArray(response.result)) {
types = response.result;
}
else {
types = Object.values(response.result);
}
const options = [];
for (const type of types) {
// Use entityTypeId if available, otherwise use id
const entityTypeId = type.entityTypeId || type.id;
const title = type.title || type.name || `SPA Type ${entityTypeId}`;
// Only include types that support automation
if (type.isAutomationEnabled === "Y" ||
type.isAutomationEnabled === true) {
options.push({
name: `${title} (ID: ${entityTypeId})`,
value: entityTypeId.toString(),
description: `${title} - Automation Enabled`,
});
}
}
// If no automation-enabled types found, show all types
if (options.length === 0) {
for (const type of types) {
const entityTypeId = type.entityTypeId || type.id;
const title = type.title || type.name || `SPA Type ${entityTypeId}`;
options.push({
name: `${title} (ID: ${entityTypeId})`,
value: entityTypeId.toString(),
description: title,
});
}
}
// Sort by name
options.sort((a, b) => a.name.localeCompare(b.name));
console.log("Returning SPA placement options:", options.length, "types");
return options;
}
catch (error) {
console.error("Error in getSpaPlacementOptions:", error);
return [
{
name: "Error loading SPA placement options",
value: "",
description: `Failed to load SPA placement options: ${error.message}`,
},
];
}
},
// Load SPA Item Fields dynamically
async getSpaItemFields() {
try {
const entityTypeId = this.getCurrentNodeParameter("entityTypeId");
console.log("getSpaItemFields called with entityTypeId:", entityTypeId);
if (!entityTypeId) {
console.log("No entityTypeId provided");
return [
{
name: "Please select a Type first",
value: "",
description: "SPA Type is required to load fields",
},
];
}
console.log("Making API call to crm.item.fields with entityTypeId:", entityTypeId);
// Use the same makeStandardBitrix24Call function as other operations
const response = await GenericFunctions_1.makeStandardBitrix24Call.call(this, "crm.item.fields", { entityTypeId: entityTypeId }, {});
console.log("Full API response:", JSON.stringify(response, null, 2));
if (!response.result) {
console.log("No result in API response");
return [
{
name: "No fields available",
value: "",
description: "No fields found for this SPA type",
},
];
}
// Handle nested fields structure
let fields = response.result;
if (response.result.fields) {
fields = response.result.fields;
}
console.log("Fields object type:", typeof fields);
console.log("Fields keys:", Object.keys(fields));
console.log("Fields values sample:", Object.values(fields).slice(0, 2));
const options = [];
console.log("Processing fields:", Object.keys(fields));
// System fields that shouldn't be set manually
const systemFields = [
"ID",
"CREATED_TIME",
"UPDATED_TIME",
"CREATED_BY",
"UPDATED_BY",
"ENTITY_TYPE_ID",
];
for (const [fieldName, fieldData] of Object.entries(fields)) {
if (systemFields.includes(fieldName)) {
continue;
}
const field = fieldData;
let displayName = field.title || fieldName;
let description = field.type || "Field";
// Add field type information to description
if (field.type) {
description = `${field.type}`;
if (field.isRequired) {
description += " (Required)";
}
if (field.isReadOnly) {
description += " (Read Only)";
}
}
// Prioritize common fields
let sortOrder = 999;
if (fieldName === "TITLE")
sortOrder = 1;
else if (fieldName === "OPPORTUNITY")
sortOrder = 2;
else if (fieldName === "CURRENCY_ID")
sortOrder = 3;
else if (fieldName === "ASSIGNED_BY_ID")
sortOrder = 4;
else if (fieldName === "STAGE_ID")
sortOrder = 5;
else if (fieldName === "CATEGORY_ID")
sortOrder = 6;
else if (fieldName.includes("DATE"))
sortOrder = 10;
else if (fieldName.includes("PHONE"))
sortOrder = 15;
else if (fieldName.includes("EMAIL"))
sortOrder = 16;
else if (fieldName.includes("WEB"))
sortOrder = 17;
else if (fieldName.includes("IM"))
sortOrder = 18;
else if (fieldName.startsWith("UF_"))
sortOrder = 50;
options.push({
name: displayName,
value: fieldName,
description,
// @ts-ignore - Adding custom property for sorting
sortOrder,
});
}
// Sort options by priority then by name
options.sort((a, b) => {
// @ts-ignore
const sortOrderA = a.sortOrder || 999;
// @ts-ignore
const sortOrderB = b.sortOrder || 999;
if (sortOrderA !== sortOrderB) {
return sortOrderA - sortOrderB;
}
return a.name.localeCompare(b.name);
});
// Remove the sortOrder property before returning
options.forEach((option) => {
// @ts-ignore
delete option.sortOrder;
});
console.log("Returning options:", options.length, "fields");
return options;
}
catch (error) {
console.error("Error in getSpaItemFields:", error);
return [
{
name: "Error loading fields",
value: "",
description: `Failed to load SPA fields: ${error.message}`,
},
];
}
},
// Load Smart Process Types for User Field Config
async getUserFieldSmartProcessTypes() {
try {
console.log("Loading Smart Process types for User Field Config");
// Use crm.type.list to get Smart Process types
const response = await GenericFunctions_1.makeStandardBitrix24Call.call(this, "crm.type.list", {}, {});
console.log("Smart Process API response:", JSON.stringify(response, null, 2));
if (!response.result) {
console.log("No result in Smart Process API response");
return [
{
name: "No Smart Process types available",
value: "",
description: "No Smart Process types found",
},
];
}
// Handle response structure - it's response.result.types, not response.result directly
let types = [];
if (response.result.types) {
types = response.result.types;
}
else if (Array.isArray(response.result)) {
types = response.result;
}
else {
types = Object.values(response.result);
}
const options = [];
console.log("Processing Smart Process types:", types.length);
for (const type of types) {
if (type.id && type.title) {
const value = `CRM_${type.id}`;
options.push({
name: `${type.title} (ID: ${type.id})`,
value: value,
description: `Smart Process: ${type.title} - Entity ID: ${value}`,
});
}
}
console.log("Returning Smart Process options:", options.length);
return options;
}
catch (error) {
console.error("Error in getUserFieldSmartProcessTypes:", error);
return [
{
name: "Error loading Smart Process types",
value: "",
description: `Failed to load Smart Process types: ${error.message}`,
},
];
}
},
// Load User Field Config List Options for select field
async getUserFieldConfigListOptions() {
try {
// Standard user field configuration options
return [
{ name: "ID", value: "ID", description: "Configuration ID" },
{
name: "Field Name",
value: "FIELD_NAME",
description: "Field name",
},
{
name: "Entity ID",
value: "ENTITY_ID",
description: "Entity identifier",
},
{
name: "User Type ID",
value: "USER_TYPE_ID",
description: "Field type",
},
{
name: "Edit Form Label",
value: "EDIT_FORM_LABEL",
description: "Form label",
},
{
name: "List Column Label",
value: "LIST_COLUMN_LABEL",
description: "List column label",
},
{
name: "List Filter Label",
value: "LIST_FILTER_LABEL",
description: "Filter label",
},
{
name: "Multiple",
value: "MULTIPLE",
description: "Multiple values allowed",
},
{
name: "Mandatory",
value: "MANDATORY",
description: "Required field",
},
{
name: "Show in List",
value: "SHOW_IN_LIST",
description: "Show in list view",
},
{
name: "Edit in List",
value: "EDIT_IN_LIST",
description: "Editable in list",
},
{
name: "Show Filter",
value: "SHOW_FILTER",
description: "Show filter option",
},
{
name: "Is Searchable",
value: "IS_SEARCHABLE",
description: "Searchable field",
},
{
name: "Settings",
value: "SETTINGS",
description: "Field settings",
},
{ name: "Sort", value: "SORT", description: "Sort order" },
{ name: "XML ID", value: "XML_ID", description: "External ID" },
];
}
catch (error) {
console.error("Error in getUserFieldConfigListOptions:", error);
return [
{
name: "Error loading options",
value: "",
description: `Failed to load options: ${error.message}`,
},
];
}
},
},
};
}
async execute() {
const resource = this.getNodeParameter("resource", 0);
try {
// Create resource handler and process all items
const handler = ResourceHandlerFactory_1.ResourceHandlerFactory.createHandler(resource, this, []);
const result = await handler.process();
return [result];
}
catch (error) {
// Handle errors gracefully
if (this.continueOnFail()) {
return [
[
{
json: {
error: error.message,
resource,
timestamp: new Date().toISOString(),
},
pairedItem: { item: 0 },
},
],
];
}
throw error;
}
}
}
exports.Bitrix24 = Bitrix24;
//# sourceMappingURL=Bitrix24.node.js.map