@x0333/bitrix24-mcp-server 1.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +265 -0
- package/package.json +8 -28
- package/README.md +0 -330
- package/build/bitrix24/client.d.ts +0 -224
- package/build/bitrix24/client.d.ts.map +0 -1
- package/build/bitrix24/client.js +0 -1098
- package/build/bitrix24/client.js.map +0 -1
- package/build/config/index.d.ts +0 -6
- package/build/config/index.d.ts.map +0 -1
- package/build/config/index.js +0 -8
- package/build/config/index.js.map +0 -1
- package/build/index.d.ts +0 -3
- package/build/index.d.ts.map +0 -1
- package/build/index.js +0 -56
- package/build/index.js.map +0 -1
- package/build/tools/index.d.ts +0 -67
- package/build/tools/index.d.ts.map +0 -1
- package/build/tools/index.js +0 -1377
- package/build/tools/index.js.map +0 -1
- package/build/utils/logger.d.ts +0 -7
- package/build/utils/logger.d.ts.map +0 -1
- package/build/utils/logger.js +0 -17
- package/build/utils/logger.js.map +0 -1
package/build/bitrix24/client.js
DELETED
|
@@ -1,1098 +0,0 @@
|
|
|
1
|
-
import fetch from 'node-fetch';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
// Environment configuration
|
|
4
|
-
const BITRIX24_WEBHOOK_URL = process.env.BITRIX24_WEBHOOK_URL ||
|
|
5
|
-
'https://sviluppofranchising.bitrix24.it/rest/27/wwugdez6m774803q/';
|
|
6
|
-
// Validation schemas
|
|
7
|
-
const BitrixResponseSchema = z.object({
|
|
8
|
-
result: z.any(),
|
|
9
|
-
error: z.optional(z.object({
|
|
10
|
-
error: z.string(),
|
|
11
|
-
error_description: z.string()
|
|
12
|
-
})),
|
|
13
|
-
total: z.optional(z.number()),
|
|
14
|
-
next: z.optional(z.number()),
|
|
15
|
-
time: z.optional(z.object({
|
|
16
|
-
start: z.number(),
|
|
17
|
-
finish: z.number(),
|
|
18
|
-
duration: z.number()
|
|
19
|
-
}))
|
|
20
|
-
});
|
|
21
|
-
export class Bitrix24Client {
|
|
22
|
-
baseUrl;
|
|
23
|
-
requestCount = 0;
|
|
24
|
-
lastRequestTime = 0;
|
|
25
|
-
RATE_LIMIT_DELAY = 500; // 500ms between requests (2 requests/second)
|
|
26
|
-
constructor(webhookUrl = BITRIX24_WEBHOOK_URL) {
|
|
27
|
-
this.baseUrl = webhookUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
28
|
-
}
|
|
29
|
-
async enforceRateLimit() {
|
|
30
|
-
const now = Date.now();
|
|
31
|
-
const timeSinceLastRequest = now - this.lastRequestTime;
|
|
32
|
-
if (timeSinceLastRequest < this.RATE_LIMIT_DELAY) {
|
|
33
|
-
await new Promise(resolve => setTimeout(resolve, this.RATE_LIMIT_DELAY - timeSinceLastRequest));
|
|
34
|
-
}
|
|
35
|
-
this.lastRequestTime = Date.now();
|
|
36
|
-
this.requestCount++;
|
|
37
|
-
}
|
|
38
|
-
async makeRequest(method, params = {}) {
|
|
39
|
-
await this.enforceRateLimit();
|
|
40
|
-
const url = `${this.baseUrl}/${method}`;
|
|
41
|
-
try {
|
|
42
|
-
let response;
|
|
43
|
-
if (Object.keys(params).length === 0) {
|
|
44
|
-
// GET request for methods without parameters
|
|
45
|
-
response = await fetch(url, {
|
|
46
|
-
method: 'GET',
|
|
47
|
-
headers: {
|
|
48
|
-
'Accept': 'application/json',
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
// POST request with form data
|
|
54
|
-
const body = new URLSearchParams();
|
|
55
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
56
|
-
if (key === 'fields' && typeof value === 'object' && value !== null) {
|
|
57
|
-
// For fields parameter, we need to send each field as a separate parameter
|
|
58
|
-
Object.entries(value).forEach(([fieldKey, fieldValue]) => {
|
|
59
|
-
if (Array.isArray(fieldValue)) {
|
|
60
|
-
// Handle arrays (like phone/email arrays)
|
|
61
|
-
fieldValue.forEach((item, index) => {
|
|
62
|
-
if (typeof item === 'object') {
|
|
63
|
-
Object.entries(item).forEach(([subKey, subValue]) => {
|
|
64
|
-
body.append(`fields[${fieldKey}][${index}][${subKey}]`, String(subValue));
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
body.append(`fields[${fieldKey}][${index}]`, String(item));
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
else if (typeof fieldValue === 'object' && fieldValue !== null) {
|
|
73
|
-
Object.entries(fieldValue).forEach(([subKey, subValue]) => {
|
|
74
|
-
body.append(`fields[${fieldKey}][${subKey}]`, String(subValue));
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
78
|
-
body.append(`fields[${fieldKey}]`, String(fieldValue));
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
else if (key === 'order' && typeof value === 'object' && value !== null) {
|
|
83
|
-
// Handle order parameter specially - Bitrix24 expects it as individual parameters
|
|
84
|
-
Object.entries(value).forEach(([orderKey, orderValue]) => {
|
|
85
|
-
body.append(`order[${orderKey}]`, String(orderValue));
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
else if (key === 'filter' && typeof value === 'object' && value !== null) {
|
|
89
|
-
// Handle filter parameter specially
|
|
90
|
-
Object.entries(value).forEach(([filterKey, filterValue]) => {
|
|
91
|
-
body.append(`filter[${filterKey}]`, String(filterValue));
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
else if (typeof value === 'object' && value !== null) {
|
|
95
|
-
body.append(key, JSON.stringify(value));
|
|
96
|
-
}
|
|
97
|
-
else if (value !== undefined && value !== null) {
|
|
98
|
-
body.append(key, String(value));
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
response = await fetch(url, {
|
|
102
|
-
method: 'POST',
|
|
103
|
-
headers: {
|
|
104
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
105
|
-
'Accept': 'application/json',
|
|
106
|
-
},
|
|
107
|
-
body: body.toString(),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
if (!response.ok) {
|
|
111
|
-
const errorText = await response.text();
|
|
112
|
-
console.error(`HTTP Error ${response.status}:`, errorText);
|
|
113
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
114
|
-
}
|
|
115
|
-
const data = await response.json();
|
|
116
|
-
const parsed = BitrixResponseSchema.parse(data);
|
|
117
|
-
if (parsed.error) {
|
|
118
|
-
throw new Error(`Bitrix24 API Error: ${parsed.error.error} - ${parsed.error.error_description}`);
|
|
119
|
-
}
|
|
120
|
-
return parsed.result;
|
|
121
|
-
}
|
|
122
|
-
catch (error) {
|
|
123
|
-
console.error(`Bitrix24 API Error [${method}]:`, error);
|
|
124
|
-
throw error;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// CRM Contact Methods
|
|
128
|
-
async createContact(contact) {
|
|
129
|
-
const result = await this.makeRequest('crm.contact.add', { fields: contact });
|
|
130
|
-
return result.toString();
|
|
131
|
-
}
|
|
132
|
-
async getContact(id) {
|
|
133
|
-
return await this.makeRequest('crm.contact.get', { id });
|
|
134
|
-
}
|
|
135
|
-
async updateContact(id, contact) {
|
|
136
|
-
const result = await this.makeRequest('crm.contact.update', { id, fields: contact });
|
|
137
|
-
return result === true;
|
|
138
|
-
}
|
|
139
|
-
async listContacts(params = {}) {
|
|
140
|
-
return await this.makeRequest('crm.contact.list', params);
|
|
141
|
-
}
|
|
142
|
-
// Helper method to get latest contacts with proper ordering
|
|
143
|
-
async getLatestContacts(limit = 20) {
|
|
144
|
-
// Use Bitrix24's built-in ordering which works correctly
|
|
145
|
-
const contacts = await this.makeRequest('crm.contact.list', {
|
|
146
|
-
start: 0,
|
|
147
|
-
order: { 'DATE_CREATE': 'DESC' },
|
|
148
|
-
select: ['*']
|
|
149
|
-
});
|
|
150
|
-
return contacts.slice(0, limit);
|
|
151
|
-
}
|
|
152
|
-
// CRM Deal Methods
|
|
153
|
-
async createDeal(deal) {
|
|
154
|
-
const result = await this.makeRequest('crm.deal.add', { fields: deal });
|
|
155
|
-
return result.toString();
|
|
156
|
-
}
|
|
157
|
-
async getDeal(id) {
|
|
158
|
-
return await this.makeRequest('crm.deal.get', { id });
|
|
159
|
-
}
|
|
160
|
-
async updateDeal(id, deal) {
|
|
161
|
-
const result = await this.makeRequest('crm.deal.update', { id, fields: deal });
|
|
162
|
-
return result === true;
|
|
163
|
-
}
|
|
164
|
-
async listDeals(params = {}) {
|
|
165
|
-
return await this.makeRequest('crm.deal.list', params);
|
|
166
|
-
}
|
|
167
|
-
// Helper method to get latest deals with proper ordering
|
|
168
|
-
async getLatestDeals(limit = 20) {
|
|
169
|
-
// Use Bitrix24's built-in ordering which works correctly
|
|
170
|
-
const deals = await this.makeRequest('crm.deal.list', {
|
|
171
|
-
start: 0,
|
|
172
|
-
order: { 'DATE_CREATE': 'DESC' },
|
|
173
|
-
select: ['*']
|
|
174
|
-
});
|
|
175
|
-
return deals.slice(0, limit);
|
|
176
|
-
}
|
|
177
|
-
// Helper method to get deals from a specific date range
|
|
178
|
-
async getDealsFromDateRange(startDate, endDate, limit = 50) {
|
|
179
|
-
const filter = {
|
|
180
|
-
'>=DATE_CREATE': startDate
|
|
181
|
-
};
|
|
182
|
-
if (endDate) {
|
|
183
|
-
filter['<=DATE_CREATE'] = endDate;
|
|
184
|
-
}
|
|
185
|
-
const deals = await this.makeRequest('crm.deal.list', {
|
|
186
|
-
start: 0,
|
|
187
|
-
select: ['*'],
|
|
188
|
-
filter
|
|
189
|
-
});
|
|
190
|
-
// Sort by DATE_CREATE in JavaScript for consistency
|
|
191
|
-
const sortedDeals = deals.sort((a, b) => {
|
|
192
|
-
const dateA = new Date(a.DATE_CREATE || '1970-01-01');
|
|
193
|
-
const dateB = new Date(b.DATE_CREATE || '1970-01-01');
|
|
194
|
-
return dateB.getTime() - dateA.getTime(); // DESC order (newest first)
|
|
195
|
-
});
|
|
196
|
-
return sortedDeals.slice(0, limit);
|
|
197
|
-
}
|
|
198
|
-
// CRM Lead Methods
|
|
199
|
-
async createLead(lead) {
|
|
200
|
-
const result = await this.makeRequest('crm.lead.add', { fields: lead });
|
|
201
|
-
return result.toString();
|
|
202
|
-
}
|
|
203
|
-
async getLead(id) {
|
|
204
|
-
return await this.makeRequest('crm.lead.get', { id });
|
|
205
|
-
}
|
|
206
|
-
async updateLead(id, lead) {
|
|
207
|
-
const result = await this.makeRequest('crm.lead.update', { id, fields: lead });
|
|
208
|
-
return result === true;
|
|
209
|
-
}
|
|
210
|
-
async listLeads(params = {}) {
|
|
211
|
-
return await this.makeRequest('crm.lead.list', params);
|
|
212
|
-
}
|
|
213
|
-
// Helper method to get latest leads with proper ordering
|
|
214
|
-
async getLatestLeads(limit = 20) {
|
|
215
|
-
// Use Bitrix24's built-in ordering which works correctly
|
|
216
|
-
const leads = await this.makeRequest('crm.lead.list', {
|
|
217
|
-
start: 0,
|
|
218
|
-
order: { 'DATE_CREATE': 'DESC' },
|
|
219
|
-
select: ['*']
|
|
220
|
-
});
|
|
221
|
-
return leads.slice(0, limit);
|
|
222
|
-
}
|
|
223
|
-
// Helper method to get leads from a specific date range
|
|
224
|
-
async getLeadsFromDateRange(startDate, endDate, limit = 50) {
|
|
225
|
-
const filter = {
|
|
226
|
-
'>=DATE_CREATE': startDate
|
|
227
|
-
};
|
|
228
|
-
if (endDate) {
|
|
229
|
-
filter['<=DATE_CREATE'] = endDate;
|
|
230
|
-
}
|
|
231
|
-
const leads = await this.makeRequest('crm.lead.list', {
|
|
232
|
-
start: 0,
|
|
233
|
-
select: ['*'],
|
|
234
|
-
filter
|
|
235
|
-
});
|
|
236
|
-
// Sort by DATE_CREATE in JavaScript
|
|
237
|
-
const sortedLeads = leads.sort((a, b) => {
|
|
238
|
-
const dateA = new Date(a.DATE_CREATE || '1970-01-01');
|
|
239
|
-
const dateB = new Date(b.DATE_CREATE || '1970-01-01');
|
|
240
|
-
return dateB.getTime() - dateA.getTime(); // DESC order (newest first)
|
|
241
|
-
});
|
|
242
|
-
return sortedLeads.slice(0, limit);
|
|
243
|
-
}
|
|
244
|
-
// CRM Company Methods
|
|
245
|
-
async createCompany(company) {
|
|
246
|
-
const result = await this.makeRequest('crm.company.add', { fields: company });
|
|
247
|
-
return result.toString();
|
|
248
|
-
}
|
|
249
|
-
async getCompany(id) {
|
|
250
|
-
return await this.makeRequest('crm.company.get', { id });
|
|
251
|
-
}
|
|
252
|
-
async updateCompany(id, company) {
|
|
253
|
-
const result = await this.makeRequest('crm.company.update', { id, fields: company });
|
|
254
|
-
return result === true;
|
|
255
|
-
}
|
|
256
|
-
async listCompanies(params = {}) {
|
|
257
|
-
return await this.makeRequest('crm.company.list', params);
|
|
258
|
-
}
|
|
259
|
-
// Helper method to get latest companies with proper ordering
|
|
260
|
-
async getLatestCompanies(limit = 20) {
|
|
261
|
-
const companies = await this.makeRequest('crm.company.list', {
|
|
262
|
-
start: 0,
|
|
263
|
-
order: { 'DATE_CREATE': 'DESC' },
|
|
264
|
-
select: ['*']
|
|
265
|
-
});
|
|
266
|
-
return companies.slice(0, limit);
|
|
267
|
-
}
|
|
268
|
-
// Helper method to get companies from a specific date range
|
|
269
|
-
async getCompaniesFromDateRange(startDate, endDate, limit = 50) {
|
|
270
|
-
const filter = {
|
|
271
|
-
'>=DATE_CREATE': startDate
|
|
272
|
-
};
|
|
273
|
-
if (endDate) {
|
|
274
|
-
filter['<=DATE_CREATE'] = endDate;
|
|
275
|
-
}
|
|
276
|
-
const companies = await this.makeRequest('crm.company.list', {
|
|
277
|
-
start: 0,
|
|
278
|
-
select: ['*'],
|
|
279
|
-
filter
|
|
280
|
-
});
|
|
281
|
-
// Sort by DATE_CREATE in JavaScript for consistency
|
|
282
|
-
const sortedCompanies = companies.sort((a, b) => {
|
|
283
|
-
const dateA = new Date(a.DATE_CREATE || '1970-01-01');
|
|
284
|
-
const dateB = new Date(b.DATE_CREATE || '1970-01-01');
|
|
285
|
-
return dateB.getTime() - dateA.getTime(); // DESC order (newest first)
|
|
286
|
-
});
|
|
287
|
-
return sortedCompanies.slice(0, limit);
|
|
288
|
-
}
|
|
289
|
-
// Deal Pipeline and Stage Methods
|
|
290
|
-
async getDealPipelines() {
|
|
291
|
-
return await this.makeRequest('crm.dealcategory.list');
|
|
292
|
-
}
|
|
293
|
-
async getDealStages(pipelineId) {
|
|
294
|
-
const params = {};
|
|
295
|
-
if (pipelineId) {
|
|
296
|
-
params.id = pipelineId;
|
|
297
|
-
}
|
|
298
|
-
return await this.makeRequest('crm.dealcategory.stage.list', params);
|
|
299
|
-
}
|
|
300
|
-
// Enhanced Deal Filtering Methods
|
|
301
|
-
async filterDealsByPipeline(pipelineId, options = {}) {
|
|
302
|
-
const filter = {
|
|
303
|
-
'CATEGORY_ID': pipelineId
|
|
304
|
-
};
|
|
305
|
-
const order = {};
|
|
306
|
-
if (options.orderBy) {
|
|
307
|
-
order[options.orderBy] = options.orderDirection || 'DESC';
|
|
308
|
-
}
|
|
309
|
-
const deals = await this.makeRequest('crm.deal.list', {
|
|
310
|
-
start: 0,
|
|
311
|
-
filter,
|
|
312
|
-
order: Object.keys(order).length > 0 ? order : { 'DATE_CREATE': 'DESC' },
|
|
313
|
-
select: options.select || ['*']
|
|
314
|
-
});
|
|
315
|
-
return deals.slice(0, options.limit || 50);
|
|
316
|
-
}
|
|
317
|
-
async filterDealsByBudget(minBudget, maxBudget, currency = 'EUR', options = {}) {
|
|
318
|
-
const filter = {
|
|
319
|
-
'>=OPPORTUNITY': minBudget.toString()
|
|
320
|
-
};
|
|
321
|
-
if (maxBudget) {
|
|
322
|
-
filter['<=OPPORTUNITY'] = maxBudget.toString();
|
|
323
|
-
}
|
|
324
|
-
// Add currency filter if specified
|
|
325
|
-
if (currency) {
|
|
326
|
-
filter['CURRENCY_ID'] = currency;
|
|
327
|
-
}
|
|
328
|
-
const order = {};
|
|
329
|
-
if (options.orderBy) {
|
|
330
|
-
order[options.orderBy] = options.orderDirection || 'DESC';
|
|
331
|
-
}
|
|
332
|
-
const deals = await this.makeRequest('crm.deal.list', {
|
|
333
|
-
start: 0,
|
|
334
|
-
filter,
|
|
335
|
-
order: Object.keys(order).length > 0 ? order : { 'OPPORTUNITY': 'DESC' },
|
|
336
|
-
select: options.select || ['*']
|
|
337
|
-
});
|
|
338
|
-
return deals.slice(0, options.limit || 50);
|
|
339
|
-
}
|
|
340
|
-
async filterDealsByStatus(stageIds, pipelineId, options = {}) {
|
|
341
|
-
const filter = {};
|
|
342
|
-
// Handle multiple stage IDs
|
|
343
|
-
if (stageIds.length === 1) {
|
|
344
|
-
filter['STAGE_ID'] = stageIds[0];
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
// For multiple stages, we need to use the @STAGE_ID syntax
|
|
348
|
-
stageIds.forEach((stageId, index) => {
|
|
349
|
-
filter[`@STAGE_ID[${index}]`] = stageId;
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
// Add pipeline filter if specified
|
|
353
|
-
if (pipelineId) {
|
|
354
|
-
filter['CATEGORY_ID'] = pipelineId;
|
|
355
|
-
}
|
|
356
|
-
const order = {};
|
|
357
|
-
if (options.orderBy) {
|
|
358
|
-
order[options.orderBy] = options.orderDirection || 'DESC';
|
|
359
|
-
}
|
|
360
|
-
const deals = await this.makeRequest('crm.deal.list', {
|
|
361
|
-
start: 0,
|
|
362
|
-
filter,
|
|
363
|
-
order: Object.keys(order).length > 0 ? order : { 'DATE_CREATE': 'DESC' },
|
|
364
|
-
select: options.select || ['*']
|
|
365
|
-
});
|
|
366
|
-
return deals.slice(0, options.limit || 50);
|
|
367
|
-
}
|
|
368
|
-
// Task Methods
|
|
369
|
-
async createTask(task) {
|
|
370
|
-
const result = await this.makeRequest('tasks.task.add', { fields: task });
|
|
371
|
-
return result.task.id.toString();
|
|
372
|
-
}
|
|
373
|
-
async getTask(id) {
|
|
374
|
-
const result = await this.makeRequest('tasks.task.get', { taskId: id });
|
|
375
|
-
return result.task;
|
|
376
|
-
}
|
|
377
|
-
async updateTask(id, task) {
|
|
378
|
-
const result = await this.makeRequest('tasks.task.update', { taskId: id, fields: task });
|
|
379
|
-
return result === true;
|
|
380
|
-
}
|
|
381
|
-
async listTasks(params = {}) {
|
|
382
|
-
const result = await this.makeRequest('tasks.task.list', params);
|
|
383
|
-
return result.tasks || [];
|
|
384
|
-
}
|
|
385
|
-
// Utility Methods
|
|
386
|
-
async getCurrentUser() {
|
|
387
|
-
return await this.makeRequest('user.current');
|
|
388
|
-
}
|
|
389
|
-
// User Management Methods
|
|
390
|
-
async getUser(userId) {
|
|
391
|
-
return await this.makeRequest('user.get', { ID: userId });
|
|
392
|
-
}
|
|
393
|
-
async getAllUsers() {
|
|
394
|
-
return await this.makeRequest('user.get');
|
|
395
|
-
}
|
|
396
|
-
async getUsersByIds(userIds) {
|
|
397
|
-
const results = [];
|
|
398
|
-
for (const userId of userIds) {
|
|
399
|
-
try {
|
|
400
|
-
const user = await this.getUser(userId);
|
|
401
|
-
results.push(user);
|
|
402
|
-
}
|
|
403
|
-
catch (error) {
|
|
404
|
-
console.error(`Error fetching user ${userId}:`, error);
|
|
405
|
-
results.push({ ID: userId, NAME: 'Unknown', LAST_NAME: 'User', ERROR: error instanceof Error ? error.message : String(error) });
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
return results;
|
|
409
|
-
}
|
|
410
|
-
async resolveUserNames(userIds) {
|
|
411
|
-
const userMap = {};
|
|
412
|
-
try {
|
|
413
|
-
const users = await this.getUsersByIds(userIds);
|
|
414
|
-
for (const user of users) {
|
|
415
|
-
const fullName = `${user.NAME || ''} ${user.LAST_NAME || ''}`.trim();
|
|
416
|
-
userMap[user.ID] = fullName || `User ${user.ID}`;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
catch (error) {
|
|
420
|
-
console.error('Error resolving user names:', error);
|
|
421
|
-
// Fallback: return IDs as names
|
|
422
|
-
userIds.forEach(id => {
|
|
423
|
-
userMap[id] = `User ${id}`;
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
return userMap;
|
|
427
|
-
}
|
|
428
|
-
async enhanceWithUserNames(items, userIdFields = ['ASSIGNED_BY_ID', 'CREATED_BY_ID', 'MODIFY_BY_ID']) {
|
|
429
|
-
// Collect all unique user IDs
|
|
430
|
-
const allUserIds = new Set();
|
|
431
|
-
items.forEach(item => {
|
|
432
|
-
userIdFields.forEach(field => {
|
|
433
|
-
if (item[field] && typeof item[field] === 'string') {
|
|
434
|
-
allUserIds.add(item[field]);
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
});
|
|
438
|
-
// Resolve user names
|
|
439
|
-
const userNames = await this.resolveUserNames(Array.from(allUserIds));
|
|
440
|
-
// Enhance items with user names
|
|
441
|
-
return items.map(item => {
|
|
442
|
-
const enhanced = { ...item };
|
|
443
|
-
userIdFields.forEach(field => {
|
|
444
|
-
if (item[field] && userNames[item[field]]) {
|
|
445
|
-
enhanced[`${field}_NAME`] = userNames[item[field]];
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
return enhanced;
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
async searchCRM(query, entityTypes = ['contact', 'company', 'deal', 'lead']) {
|
|
452
|
-
return await this.makeRequest('crm.duplicate.findbycomm', {
|
|
453
|
-
entity_type: entityTypes.join(','),
|
|
454
|
-
type: 'EMAIL',
|
|
455
|
-
values: [query]
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
async validateWebhook() {
|
|
459
|
-
try {
|
|
460
|
-
// Try a simple method that requires minimal permissions
|
|
461
|
-
await this.makeRequest('app.info');
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
catch (error) {
|
|
465
|
-
console.error('Webhook validation failed:', error);
|
|
466
|
-
// Try alternative validation methods
|
|
467
|
-
try {
|
|
468
|
-
await this.listContacts({ start: 0 });
|
|
469
|
-
return true;
|
|
470
|
-
}
|
|
471
|
-
catch (secondError) {
|
|
472
|
-
console.error('Alternative validation also failed:', secondError);
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
// Diagnostic Methods
|
|
478
|
-
async diagnosePermissions() {
|
|
479
|
-
const results = {
|
|
480
|
-
webhook_valid: false,
|
|
481
|
-
app_info: null,
|
|
482
|
-
permissions: null,
|
|
483
|
-
crm_access: false,
|
|
484
|
-
leads_access: false,
|
|
485
|
-
contacts_access: false,
|
|
486
|
-
deals_access: false,
|
|
487
|
-
error_details: []
|
|
488
|
-
};
|
|
489
|
-
try {
|
|
490
|
-
// Test basic webhook
|
|
491
|
-
results.app_info = await this.makeRequest('app.info');
|
|
492
|
-
results.webhook_valid = true;
|
|
493
|
-
}
|
|
494
|
-
catch (error) {
|
|
495
|
-
results.error_details.push(`app.info failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
496
|
-
}
|
|
497
|
-
try {
|
|
498
|
-
// Test permissions endpoint
|
|
499
|
-
results.permissions = await this.makeRequest('user.access');
|
|
500
|
-
}
|
|
501
|
-
catch (error) {
|
|
502
|
-
results.error_details.push(`user.access failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
503
|
-
}
|
|
504
|
-
// Test CRM access
|
|
505
|
-
try {
|
|
506
|
-
await this.listContacts({ start: 0 });
|
|
507
|
-
results.contacts_access = true;
|
|
508
|
-
results.crm_access = true;
|
|
509
|
-
}
|
|
510
|
-
catch (error) {
|
|
511
|
-
results.error_details.push(`contacts access failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
512
|
-
}
|
|
513
|
-
try {
|
|
514
|
-
await this.listDeals({ start: 0 });
|
|
515
|
-
results.deals_access = true;
|
|
516
|
-
}
|
|
517
|
-
catch (error) {
|
|
518
|
-
results.error_details.push(`deals access failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
519
|
-
}
|
|
520
|
-
// Test leads access specifically
|
|
521
|
-
try {
|
|
522
|
-
await this.makeRequest('crm.lead.list', { start: 0 });
|
|
523
|
-
results.leads_access = true;
|
|
524
|
-
}
|
|
525
|
-
catch (error) {
|
|
526
|
-
results.error_details.push(`leads access failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
527
|
-
}
|
|
528
|
-
return results;
|
|
529
|
-
}
|
|
530
|
-
async checkCRMSettings() {
|
|
531
|
-
const results = {
|
|
532
|
-
lead_fields: null,
|
|
533
|
-
lead_statuses: null,
|
|
534
|
-
crm_mode: null,
|
|
535
|
-
error_details: []
|
|
536
|
-
};
|
|
537
|
-
try {
|
|
538
|
-
// Get lead fields to check if leads are available
|
|
539
|
-
results.lead_fields = await this.makeRequest('crm.lead.fields');
|
|
540
|
-
}
|
|
541
|
-
catch (error) {
|
|
542
|
-
results.error_details.push(`crm.lead.fields failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
543
|
-
}
|
|
544
|
-
try {
|
|
545
|
-
// Try to get lead statuses
|
|
546
|
-
results.lead_statuses = await this.makeRequest('crm.status.list', { filter: { ENTITY_ID: 'STATUS' } });
|
|
547
|
-
}
|
|
548
|
-
catch (error) {
|
|
549
|
-
results.error_details.push(`lead statuses failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
550
|
-
}
|
|
551
|
-
try {
|
|
552
|
-
// Try to get CRM settings (might not be available via API)
|
|
553
|
-
results.crm_mode = await this.makeRequest('crm.settings.mode.get');
|
|
554
|
-
}
|
|
555
|
-
catch (error) {
|
|
556
|
-
results.error_details.push(`CRM mode check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
557
|
-
}
|
|
558
|
-
return results;
|
|
559
|
-
}
|
|
560
|
-
async testLeadsAPI() {
|
|
561
|
-
const results = {
|
|
562
|
-
list_test: null,
|
|
563
|
-
fields_test: null,
|
|
564
|
-
count_test: null,
|
|
565
|
-
simple_list: null,
|
|
566
|
-
error_details: []
|
|
567
|
-
};
|
|
568
|
-
// Test 1: Basic list with minimal parameters
|
|
569
|
-
try {
|
|
570
|
-
results.simple_list = await this.makeRequest('crm.lead.list', { start: 0 });
|
|
571
|
-
}
|
|
572
|
-
catch (error) {
|
|
573
|
-
results.error_details.push(`simple list failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
574
|
-
}
|
|
575
|
-
// Test 2: Get lead fields
|
|
576
|
-
try {
|
|
577
|
-
results.fields_test = await this.makeRequest('crm.lead.fields');
|
|
578
|
-
}
|
|
579
|
-
catch (error) {
|
|
580
|
-
results.error_details.push(`fields test failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
581
|
-
}
|
|
582
|
-
// Test 3: List with specific parameters
|
|
583
|
-
try {
|
|
584
|
-
results.list_test = await this.makeRequest('crm.lead.list', {
|
|
585
|
-
select: ['ID', 'TITLE', 'DATE_CREATE'],
|
|
586
|
-
start: 0,
|
|
587
|
-
order: { 'ID': 'DESC' }
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
catch (error) {
|
|
591
|
-
results.error_details.push(`parameterized list failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
592
|
-
}
|
|
593
|
-
// Test 4: Try to get count
|
|
594
|
-
try {
|
|
595
|
-
const countResult = await this.makeRequest('crm.lead.list', {
|
|
596
|
-
select: ['ID'],
|
|
597
|
-
start: 0,
|
|
598
|
-
filter: {}
|
|
599
|
-
});
|
|
600
|
-
results.count_test = Array.isArray(countResult) ? countResult.length : 'Not an array';
|
|
601
|
-
}
|
|
602
|
-
catch (error) {
|
|
603
|
-
results.error_details.push(`count test failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
604
|
-
}
|
|
605
|
-
return results;
|
|
606
|
-
}
|
|
607
|
-
async batchRequest(requests) {
|
|
608
|
-
// Implement batch processing for multiple operations
|
|
609
|
-
const results = [];
|
|
610
|
-
for (const req of requests) {
|
|
611
|
-
const result = await this.makeRequest(req.method, req.params);
|
|
612
|
-
results.push(result);
|
|
613
|
-
}
|
|
614
|
-
return results;
|
|
615
|
-
}
|
|
616
|
-
// Sales Team Monitoring Methods
|
|
617
|
-
async monitorUserActivities(userId, startDate, endDate, options = {}) {
|
|
618
|
-
const endDateToUse = endDate || new Date().toISOString().split('T')[0];
|
|
619
|
-
const results = {
|
|
620
|
-
userId: userId || 'all_users',
|
|
621
|
-
period: { startDate, endDate: endDateToUse },
|
|
622
|
-
metrics: {}
|
|
623
|
-
};
|
|
624
|
-
try {
|
|
625
|
-
// Get users to monitor
|
|
626
|
-
const users = userId ? [{ ID: userId }] : await this.makeRequest('user.get');
|
|
627
|
-
for (const user of users) {
|
|
628
|
-
const userMetrics = {
|
|
629
|
-
userId: user.ID,
|
|
630
|
-
userName: `${user.NAME || ''} ${user.LAST_NAME || ''}`.trim(),
|
|
631
|
-
activities: {}
|
|
632
|
-
};
|
|
633
|
-
// Monitor call volume
|
|
634
|
-
if (options.includeCallVolume) {
|
|
635
|
-
try {
|
|
636
|
-
const callActivities = await this.makeRequest('crm.activity.list', {
|
|
637
|
-
filter: {
|
|
638
|
-
TYPE_ID: 2, // Call activities
|
|
639
|
-
RESPONSIBLE_ID: user.ID,
|
|
640
|
-
'>=DATE_CREATE': startDate,
|
|
641
|
-
'<=DATE_CREATE': endDateToUse
|
|
642
|
-
},
|
|
643
|
-
select: ['ID', 'DATE_CREATE', 'DIRECTION', 'SUBJECT']
|
|
644
|
-
});
|
|
645
|
-
userMetrics.activities.calls = {
|
|
646
|
-
total: callActivities.length,
|
|
647
|
-
incoming: callActivities.filter((a) => a.DIRECTION === '1').length,
|
|
648
|
-
outgoing: callActivities.filter((a) => a.DIRECTION === '2').length,
|
|
649
|
-
details: callActivities
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
catch (error) {
|
|
653
|
-
userMetrics.activities.calls = { error: `Failed to get call data: ${error}` };
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
// Monitor email activity
|
|
657
|
-
if (options.includeEmailActivity) {
|
|
658
|
-
try {
|
|
659
|
-
const emailActivities = await this.makeRequest('crm.activity.list', {
|
|
660
|
-
filter: {
|
|
661
|
-
TYPE_ID: 4, // Email activities
|
|
662
|
-
RESPONSIBLE_ID: user.ID,
|
|
663
|
-
'>=DATE_CREATE': startDate,
|
|
664
|
-
'<=DATE_CREATE': endDateToUse
|
|
665
|
-
},
|
|
666
|
-
select: ['ID', 'DATE_CREATE', 'DIRECTION', 'SUBJECT']
|
|
667
|
-
});
|
|
668
|
-
userMetrics.activities.emails = {
|
|
669
|
-
total: emailActivities.length,
|
|
670
|
-
incoming: emailActivities.filter((a) => a.DIRECTION === '1').length,
|
|
671
|
-
outgoing: emailActivities.filter((a) => a.DIRECTION === '2').length,
|
|
672
|
-
details: emailActivities
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
catch (error) {
|
|
676
|
-
userMetrics.activities.emails = { error: `Failed to get email data: ${error}` };
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
// Monitor timeline activity
|
|
680
|
-
if (options.includeTimelineActivity) {
|
|
681
|
-
try {
|
|
682
|
-
const timelineEntries = await this.makeRequest('crm.timeline.comment.list', {
|
|
683
|
-
filter: {
|
|
684
|
-
AUTHOR_ID: user.ID,
|
|
685
|
-
'>=DATE_CREATE': startDate,
|
|
686
|
-
'<=DATE_CREATE': endDateToUse
|
|
687
|
-
}
|
|
688
|
-
});
|
|
689
|
-
userMetrics.activities.timeline = {
|
|
690
|
-
total: timelineEntries.length,
|
|
691
|
-
details: timelineEntries
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
catch (error) {
|
|
695
|
-
userMetrics.activities.timeline = { error: `Failed to get timeline data: ${error}` };
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
// Calculate response times
|
|
699
|
-
if (options.includeResponseTimes) {
|
|
700
|
-
try {
|
|
701
|
-
const allActivities = await this.makeRequest('crm.activity.list', {
|
|
702
|
-
filter: {
|
|
703
|
-
RESPONSIBLE_ID: user.ID,
|
|
704
|
-
'>=DATE_CREATE': startDate,
|
|
705
|
-
'<=DATE_CREATE': endDateToUse
|
|
706
|
-
},
|
|
707
|
-
select: ['ID', 'DATE_CREATE', 'DIRECTION', 'TYPE_ID'],
|
|
708
|
-
order: { DATE_CREATE: 'ASC' }
|
|
709
|
-
});
|
|
710
|
-
const responseTimes = this.calculateResponseTimes(allActivities);
|
|
711
|
-
userMetrics.activities.responseTimes = responseTimes;
|
|
712
|
-
}
|
|
713
|
-
catch (error) {
|
|
714
|
-
userMetrics.activities.responseTimes = { error: `Failed to calculate response times: ${error}` };
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
results.metrics[user.ID] = userMetrics;
|
|
718
|
-
}
|
|
719
|
-
return results;
|
|
720
|
-
}
|
|
721
|
-
catch (error) {
|
|
722
|
-
console.error('Error monitoring user activities:', error);
|
|
723
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
async getUserPerformanceSummary(userId, startDate, endDate, options = {}) {
|
|
727
|
-
const endDateToUse = endDate || new Date().toISOString().split('T')[0];
|
|
728
|
-
const results = {
|
|
729
|
-
userId: userId || 'all_users',
|
|
730
|
-
period: { startDate, endDate: endDateToUse },
|
|
731
|
-
performance: {}
|
|
732
|
-
};
|
|
733
|
-
try {
|
|
734
|
-
const users = userId ? [{ ID: userId }] : await this.makeRequest('user.get');
|
|
735
|
-
for (const user of users) {
|
|
736
|
-
const userPerformance = {
|
|
737
|
-
userId: user.ID,
|
|
738
|
-
userName: `${user.NAME || ''} ${user.LAST_NAME || ''}`.trim(),
|
|
739
|
-
metrics: {}
|
|
740
|
-
};
|
|
741
|
-
// Deal metrics
|
|
742
|
-
if (options.includeDealMetrics) {
|
|
743
|
-
try {
|
|
744
|
-
const deals = await this.makeRequest('crm.deal.list', {
|
|
745
|
-
filter: {
|
|
746
|
-
ASSIGNED_BY_ID: user.ID,
|
|
747
|
-
'>=DATE_CREATE': startDate,
|
|
748
|
-
'<=DATE_CREATE': endDateToUse
|
|
749
|
-
},
|
|
750
|
-
select: ['ID', 'TITLE', 'OPPORTUNITY', 'STAGE_ID', 'DATE_CREATE', 'CLOSEDATE']
|
|
751
|
-
});
|
|
752
|
-
const wonDeals = deals.filter((d) => d.STAGE_ID?.includes('WON') || d.STAGE_ID?.includes('SUCCESS'));
|
|
753
|
-
const lostDeals = deals.filter((d) => d.STAGE_ID?.includes('LOST') || d.STAGE_ID?.includes('FAIL'));
|
|
754
|
-
userPerformance.metrics.deals = {
|
|
755
|
-
total: deals.length,
|
|
756
|
-
won: wonDeals.length,
|
|
757
|
-
lost: lostDeals.length,
|
|
758
|
-
inProgress: deals.length - wonDeals.length - lostDeals.length,
|
|
759
|
-
totalValue: deals.reduce((sum, d) => sum + (parseFloat(d.OPPORTUNITY) || 0), 0),
|
|
760
|
-
wonValue: wonDeals.reduce((sum, d) => sum + (parseFloat(d.OPPORTUNITY) || 0), 0),
|
|
761
|
-
winRate: deals.length > 0 ? (wonDeals.length / deals.length * 100).toFixed(2) : '0'
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
catch (error) {
|
|
765
|
-
userPerformance.metrics.deals = { error: `Failed to get deal metrics: ${error}` };
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
// Activity ratios
|
|
769
|
-
if (options.includeActivityRatios) {
|
|
770
|
-
try {
|
|
771
|
-
const activities = await this.makeRequest('crm.activity.list', {
|
|
772
|
-
filter: {
|
|
773
|
-
RESPONSIBLE_ID: user.ID,
|
|
774
|
-
'>=DATE_CREATE': startDate,
|
|
775
|
-
'<=DATE_CREATE': endDateToUse
|
|
776
|
-
},
|
|
777
|
-
select: ['ID', 'TYPE_ID', 'DIRECTION']
|
|
778
|
-
});
|
|
779
|
-
const activityCounts = activities.reduce((acc, activity) => {
|
|
780
|
-
const type = activity.TYPE_ID;
|
|
781
|
-
acc[type] = (acc[type] || 0) + 1;
|
|
782
|
-
return acc;
|
|
783
|
-
}, {});
|
|
784
|
-
userPerformance.metrics.activityRatios = {
|
|
785
|
-
total: activities.length,
|
|
786
|
-
breakdown: activityCounts,
|
|
787
|
-
callsToEmails: activityCounts['2'] && activityCounts['4'] ?
|
|
788
|
-
(activityCounts['2'] / activityCounts['4']).toFixed(2) : 'N/A'
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
catch (error) {
|
|
792
|
-
userPerformance.metrics.activityRatios = { error: `Failed to get activity ratios: ${error}` };
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
// Conversion rates
|
|
796
|
-
if (options.includeConversionRates) {
|
|
797
|
-
try {
|
|
798
|
-
const leads = await this.makeRequest('crm.lead.list', {
|
|
799
|
-
filter: {
|
|
800
|
-
ASSIGNED_BY_ID: user.ID,
|
|
801
|
-
'>=DATE_CREATE': startDate,
|
|
802
|
-
'<=DATE_CREATE': endDateToUse
|
|
803
|
-
},
|
|
804
|
-
select: ['ID', 'STATUS_ID']
|
|
805
|
-
});
|
|
806
|
-
const deals = await this.makeRequest('crm.deal.list', {
|
|
807
|
-
filter: {
|
|
808
|
-
ASSIGNED_BY_ID: user.ID,
|
|
809
|
-
'>=DATE_CREATE': startDate,
|
|
810
|
-
'<=DATE_CREATE': endDateToUse
|
|
811
|
-
},
|
|
812
|
-
select: ['ID', 'STAGE_ID']
|
|
813
|
-
});
|
|
814
|
-
const convertedLeads = leads.filter((l) => l.STATUS_ID === 'CONVERTED').length;
|
|
815
|
-
const leadToDealConversion = leads.length > 0 ?
|
|
816
|
-
(convertedLeads / leads.length * 100).toFixed(2) : '0';
|
|
817
|
-
userPerformance.metrics.conversionRates = {
|
|
818
|
-
totalLeads: leads.length,
|
|
819
|
-
convertedLeads: convertedLeads,
|
|
820
|
-
leadToDealConversion: leadToDealConversion + '%',
|
|
821
|
-
totalDeals: deals.length
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
catch (error) {
|
|
825
|
-
userPerformance.metrics.conversionRates = { error: `Failed to get conversion rates: ${error}` };
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
results.performance[user.ID] = userPerformance;
|
|
829
|
-
}
|
|
830
|
-
return results;
|
|
831
|
-
}
|
|
832
|
-
catch (error) {
|
|
833
|
-
console.error('Error getting user performance summary:', error);
|
|
834
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
async analyzeAccountPerformance(accountId, accountType, startDate, endDate, options = {}) {
|
|
838
|
-
const endDateToUse = endDate || new Date().toISOString().split('T')[0];
|
|
839
|
-
const results = {
|
|
840
|
-
accountId,
|
|
841
|
-
accountType,
|
|
842
|
-
period: { startDate, endDate: endDateToUse },
|
|
843
|
-
analysis: {}
|
|
844
|
-
};
|
|
845
|
-
try {
|
|
846
|
-
// Get account details
|
|
847
|
-
const accountData = accountType === 'company'
|
|
848
|
-
? await this.getCompany(accountId)
|
|
849
|
-
: await this.getContact(accountId);
|
|
850
|
-
results.accountDetails = accountData;
|
|
851
|
-
// Get all interactions
|
|
852
|
-
if (options.includeAllInteractions) {
|
|
853
|
-
const filterKey = accountType === 'company' ? 'COMPANY_ID' : 'CONTACT_ID';
|
|
854
|
-
const activities = await this.makeRequest('crm.activity.list', {
|
|
855
|
-
filter: {
|
|
856
|
-
[filterKey]: accountId,
|
|
857
|
-
'>=DATE_CREATE': startDate,
|
|
858
|
-
'<=DATE_CREATE': endDateToUse
|
|
859
|
-
},
|
|
860
|
-
select: ['ID', 'TYPE_ID', 'SUBJECT', 'DATE_CREATE', 'RESPONSIBLE_ID', 'DIRECTION']
|
|
861
|
-
});
|
|
862
|
-
results.analysis.interactions = {
|
|
863
|
-
total: activities.length,
|
|
864
|
-
byType: activities.reduce((acc, activity) => {
|
|
865
|
-
const type = activity.TYPE_ID;
|
|
866
|
-
acc[type] = (acc[type] || 0) + 1;
|
|
867
|
-
return acc;
|
|
868
|
-
}, {}),
|
|
869
|
-
byUser: activities.reduce((acc, activity) => {
|
|
870
|
-
const userId = activity.RESPONSIBLE_ID;
|
|
871
|
-
acc[userId] = (acc[userId] || 0) + 1;
|
|
872
|
-
return acc;
|
|
873
|
-
}, {}),
|
|
874
|
-
details: activities
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
// Deal progression
|
|
878
|
-
if (options.includeDealProgression) {
|
|
879
|
-
const filterKey = accountType === 'company' ? 'COMPANY_ID' : 'CONTACT_ID';
|
|
880
|
-
const deals = await this.makeRequest('crm.deal.list', {
|
|
881
|
-
filter: {
|
|
882
|
-
[filterKey]: accountId,
|
|
883
|
-
'>=DATE_CREATE': startDate,
|
|
884
|
-
'<=DATE_CREATE': endDateToUse
|
|
885
|
-
},
|
|
886
|
-
select: ['ID', 'TITLE', 'STAGE_ID', 'OPPORTUNITY', 'DATE_CREATE', 'CLOSEDATE', 'ASSIGNED_BY_ID']
|
|
887
|
-
});
|
|
888
|
-
results.analysis.dealProgression = {
|
|
889
|
-
total: deals.length,
|
|
890
|
-
totalValue: deals.reduce((sum, d) => sum + (parseFloat(d.OPPORTUNITY) || 0), 0),
|
|
891
|
-
byStage: deals.reduce((acc, deal) => {
|
|
892
|
-
const stage = deal.STAGE_ID;
|
|
893
|
-
acc[stage] = (acc[stage] || 0) + 1;
|
|
894
|
-
return acc;
|
|
895
|
-
}, {}),
|
|
896
|
-
details: deals
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
// Timeline history
|
|
900
|
-
if (options.includeTimelineHistory) {
|
|
901
|
-
const entityType = accountType === 'company' ? 'COMPANY' : 'CONTACT';
|
|
902
|
-
try {
|
|
903
|
-
const timelineEntries = await this.makeRequest('crm.timeline.comment.list', {
|
|
904
|
-
filter: {
|
|
905
|
-
ENTITY_TYPE: entityType,
|
|
906
|
-
ENTITY_ID: accountId,
|
|
907
|
-
'>=DATE_CREATE': startDate,
|
|
908
|
-
'<=DATE_CREATE': endDateToUse
|
|
909
|
-
}
|
|
910
|
-
});
|
|
911
|
-
results.analysis.timelineHistory = {
|
|
912
|
-
total: timelineEntries.length,
|
|
913
|
-
details: timelineEntries
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
catch (error) {
|
|
917
|
-
results.analysis.timelineHistory = { error: `Failed to get timeline: ${error}` };
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
return results;
|
|
921
|
-
}
|
|
922
|
-
catch (error) {
|
|
923
|
-
console.error('Error analyzing account performance:', error);
|
|
924
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
async compareUserPerformance(userIds, startDate, endDate, options = {}) {
|
|
928
|
-
const endDateToUse = endDate || new Date().toISOString().split('T')[0];
|
|
929
|
-
const results = {
|
|
930
|
-
period: { startDate, endDate: endDateToUse },
|
|
931
|
-
comparison: {},
|
|
932
|
-
rankings: {}
|
|
933
|
-
};
|
|
934
|
-
try {
|
|
935
|
-
const users = userIds?.length ?
|
|
936
|
-
userIds.map(id => ({ ID: id })) :
|
|
937
|
-
await this.makeRequest('user.get');
|
|
938
|
-
const metricsToCompare = options.metrics || ['activities', 'deals', 'conversions'];
|
|
939
|
-
for (const user of users) {
|
|
940
|
-
const userComparison = {
|
|
941
|
-
userId: user.ID,
|
|
942
|
-
userName: `${user.NAME || ''} ${user.LAST_NAME || ''}`.trim(),
|
|
943
|
-
metrics: {}
|
|
944
|
-
};
|
|
945
|
-
// Activities comparison
|
|
946
|
-
if (metricsToCompare.includes('activities')) {
|
|
947
|
-
const activities = await this.makeRequest('crm.activity.list', {
|
|
948
|
-
filter: {
|
|
949
|
-
RESPONSIBLE_ID: user.ID,
|
|
950
|
-
'>=DATE_CREATE': startDate,
|
|
951
|
-
'<=DATE_CREATE': endDateToUse
|
|
952
|
-
},
|
|
953
|
-
select: ['ID', 'TYPE_ID']
|
|
954
|
-
});
|
|
955
|
-
userComparison.metrics.activities = {
|
|
956
|
-
total: activities.length,
|
|
957
|
-
calls: activities.filter((a) => a.TYPE_ID === '2').length,
|
|
958
|
-
emails: activities.filter((a) => a.TYPE_ID === '4').length,
|
|
959
|
-
meetings: activities.filter((a) => a.TYPE_ID === '1').length
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
// Deals comparison
|
|
963
|
-
if (metricsToCompare.includes('deals')) {
|
|
964
|
-
const deals = await this.makeRequest('crm.deal.list', {
|
|
965
|
-
filter: {
|
|
966
|
-
ASSIGNED_BY_ID: user.ID,
|
|
967
|
-
'>=DATE_CREATE': startDate,
|
|
968
|
-
'<=DATE_CREATE': endDateToUse
|
|
969
|
-
},
|
|
970
|
-
select: ['ID', 'OPPORTUNITY', 'STAGE_ID']
|
|
971
|
-
});
|
|
972
|
-
const wonDeals = deals.filter((d) => d.STAGE_ID?.includes('WON') || d.STAGE_ID?.includes('SUCCESS'));
|
|
973
|
-
userComparison.metrics.deals = {
|
|
974
|
-
total: deals.length,
|
|
975
|
-
won: wonDeals.length,
|
|
976
|
-
totalValue: deals.reduce((sum, d) => sum + (parseFloat(d.OPPORTUNITY) || 0), 0),
|
|
977
|
-
wonValue: wonDeals.reduce((sum, d) => sum + (parseFloat(d.OPPORTUNITY) || 0), 0),
|
|
978
|
-
winRate: deals.length > 0 ? (wonDeals.length / deals.length * 100).toFixed(2) : '0'
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
// Conversions comparison
|
|
982
|
-
if (metricsToCompare.includes('conversions')) {
|
|
983
|
-
const leads = await this.makeRequest('crm.lead.list', {
|
|
984
|
-
filter: {
|
|
985
|
-
ASSIGNED_BY_ID: user.ID,
|
|
986
|
-
'>=DATE_CREATE': startDate,
|
|
987
|
-
'<=DATE_CREATE': endDateToUse
|
|
988
|
-
},
|
|
989
|
-
select: ['ID', 'STATUS_ID']
|
|
990
|
-
});
|
|
991
|
-
const convertedLeads = leads.filter((l) => l.STATUS_ID === 'CONVERTED').length;
|
|
992
|
-
userComparison.metrics.conversions = {
|
|
993
|
-
totalLeads: leads.length,
|
|
994
|
-
convertedLeads: convertedLeads,
|
|
995
|
-
conversionRate: leads.length > 0 ? (convertedLeads / leads.length * 100).toFixed(2) : '0'
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
results.comparison[user.ID] = userComparison;
|
|
999
|
-
}
|
|
1000
|
-
// Generate rankings
|
|
1001
|
-
if (options.includeRankings) {
|
|
1002
|
-
results.rankings = this.generateUserRankings(results.comparison, metricsToCompare);
|
|
1003
|
-
}
|
|
1004
|
-
return results;
|
|
1005
|
-
}
|
|
1006
|
-
catch (error) {
|
|
1007
|
-
console.error('Error comparing user performance:', error);
|
|
1008
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
// Helper methods
|
|
1012
|
-
calculateResponseTimes(activities) {
|
|
1013
|
-
const incomingActivities = activities.filter(a => a.DIRECTION === '1');
|
|
1014
|
-
const outgoingActivities = activities.filter(a => a.DIRECTION === '2');
|
|
1015
|
-
const responseTimes = [];
|
|
1016
|
-
incomingActivities.forEach(incoming => {
|
|
1017
|
-
const nextOutgoing = outgoingActivities.find(outgoing => new Date(outgoing.DATE_CREATE) > new Date(incoming.DATE_CREATE));
|
|
1018
|
-
if (nextOutgoing) {
|
|
1019
|
-
const responseTime = new Date(nextOutgoing.DATE_CREATE).getTime() - new Date(incoming.DATE_CREATE).getTime();
|
|
1020
|
-
responseTimes.push(responseTime / (1000 * 60 * 60)); // Convert to hours
|
|
1021
|
-
}
|
|
1022
|
-
});
|
|
1023
|
-
return {
|
|
1024
|
-
averageResponseTime: responseTimes.length > 0 ?
|
|
1025
|
-
(responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length).toFixed(2) + ' hours' :
|
|
1026
|
-
'N/A',
|
|
1027
|
-
totalResponses: responseTimes.length,
|
|
1028
|
-
fastestResponse: responseTimes.length > 0 ? Math.min(...responseTimes).toFixed(2) + ' hours' : 'N/A',
|
|
1029
|
-
slowestResponse: responseTimes.length > 0 ? Math.max(...responseTimes).toFixed(2) + ' hours' : 'N/A'
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
generateUserRankings(comparison, metrics) {
|
|
1033
|
-
const rankings = {};
|
|
1034
|
-
metrics.forEach(metric => {
|
|
1035
|
-
const users = Object.values(comparison);
|
|
1036
|
-
switch (metric) {
|
|
1037
|
-
case 'activities':
|
|
1038
|
-
rankings.activities = users
|
|
1039
|
-
.sort((a, b) => (b.metrics.activities?.total || 0) - (a.metrics.activities?.total || 0))
|
|
1040
|
-
.map((user, index) => ({
|
|
1041
|
-
rank: index + 1,
|
|
1042
|
-
userId: user.userId,
|
|
1043
|
-
userName: user.userName,
|
|
1044
|
-
value: user.metrics.activities?.total || 0
|
|
1045
|
-
}));
|
|
1046
|
-
break;
|
|
1047
|
-
case 'deals':
|
|
1048
|
-
rankings.deals = users
|
|
1049
|
-
.sort((a, b) => (b.metrics.deals?.wonValue || 0) - (a.metrics.deals?.wonValue || 0))
|
|
1050
|
-
.map((user, index) => ({
|
|
1051
|
-
rank: index + 1,
|
|
1052
|
-
userId: user.userId,
|
|
1053
|
-
userName: user.userName,
|
|
1054
|
-
value: user.metrics.deals?.wonValue || 0
|
|
1055
|
-
}));
|
|
1056
|
-
break;
|
|
1057
|
-
case 'conversions':
|
|
1058
|
-
rankings.conversions = users
|
|
1059
|
-
.sort((a, b) => (parseFloat(b.metrics.conversions?.conversionRate) || 0) - (parseFloat(a.metrics.conversions?.conversionRate) || 0))
|
|
1060
|
-
.map((user, index) => ({
|
|
1061
|
-
rank: index + 1,
|
|
1062
|
-
userId: user.userId,
|
|
1063
|
-
userName: user.userName,
|
|
1064
|
-
value: user.metrics.conversions?.conversionRate || '0'
|
|
1065
|
-
}));
|
|
1066
|
-
break;
|
|
1067
|
-
}
|
|
1068
|
-
});
|
|
1069
|
-
return rankings;
|
|
1070
|
-
}
|
|
1071
|
-
// Placeholder methods for remaining monitoring tools
|
|
1072
|
-
async trackDealProgression(dealId, userId, pipelineId, startDate, endDate, options = {}) {
|
|
1073
|
-
// Implementation for deal progression tracking
|
|
1074
|
-
return { message: 'Deal progression tracking - implementation in progress' };
|
|
1075
|
-
}
|
|
1076
|
-
async monitorSalesActivities(userId, startDate, endDate, options = {}) {
|
|
1077
|
-
// Implementation for sales activities monitoring
|
|
1078
|
-
return { message: 'Sales activities monitoring - implementation in progress' };
|
|
1079
|
-
}
|
|
1080
|
-
async generateSalesReport(reportType, startDate, endDate, options = {}) {
|
|
1081
|
-
// Implementation for sales report generation
|
|
1082
|
-
return { message: 'Sales report generation - implementation in progress' };
|
|
1083
|
-
}
|
|
1084
|
-
async getTeamDashboard(options = {}) {
|
|
1085
|
-
// Implementation for team dashboard
|
|
1086
|
-
return { message: 'Team dashboard - implementation in progress' };
|
|
1087
|
-
}
|
|
1088
|
-
async analyzeCustomerEngagement(accountId, accountType, userId, startDate, endDate, options = {}) {
|
|
1089
|
-
// Implementation for customer engagement analysis
|
|
1090
|
-
return { message: 'Customer engagement analysis - implementation in progress' };
|
|
1091
|
-
}
|
|
1092
|
-
async forecastPerformance(forecastType, userId, options = {}) {
|
|
1093
|
-
// Implementation for performance forecasting
|
|
1094
|
-
return { message: 'Performance forecasting - implementation in progress' };
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
export const bitrix24Client = new Bitrix24Client();
|
|
1098
|
-
//# sourceMappingURL=client.js.map
|