@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.
@@ -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