@x0333/bitrix24-mcp-server 1.0.1 → 2.1.0

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