tango-app-api-store-zone 3.3.1 → 3.3.2

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,22 +1,30 @@
1
1
  import { logger } from 'tango-app-api-middleware';
2
2
  import * as cameraService from '../services/camera.service.js';
3
3
  import * as taggingService from '../services/tagging.service.js';
4
+ import * as customZoneTagService from '../services/customzonetag.service.js';
5
+ import * as customzonegrouping from '../services/customzonegrouping.service.js';
4
6
  import * as storeService from '../services/store.service.js';
5
7
  import * as clientService from '../services/client.service.js';
6
8
  import * as externalService from '../services/external.service.js';
7
- import { signedUrl, listFileByPath, fileUpload, insertOpenSearchData } from 'tango-app-api-middleware';
9
+ import { signedUrl, listFileByPath, fileUpload, insertOpenSearchData, download } from 'tango-app-api-middleware';
10
+ import * as checklistconfigService from '../services/checklistconfig.services.js';
8
11
  import axios from 'axios';
12
+ import _ from 'lodash';
13
+
9
14
  export const addCustomTag = async ( req, res ) => {
10
15
  try {
11
16
  let inputData = req.body;
12
17
  let taggingDetails = await taggingService.findOne( { clientId: inputData.clientId, storeId: inputData.storeId, tagName: inputData.tagName, isDeleted: false } );
18
+ let findgroup = await customZoneTagService.findOne( { clientId: inputData.clientId, storeId: inputData.storeId, tagName: inputData.tagName } );
13
19
  if ( !taggingDetails ) {
14
20
  let data = {
15
21
  clientId: inputData.clientId,
16
22
  storeId: inputData.storeId,
17
23
  tagName: inputData.tagName,
18
24
  rgbColor: inputData.rgbColor,
25
+ productName: inputData.productName,
19
26
  rgbBorderColor: inputData.rgbBorderColor,
27
+ groupName: findgroup.groupName,
20
28
  };
21
29
  await taggingService.deleteMany( { clientId: inputData.clientId, storeId: inputData.storeId, tagName: inputData.tagName, isDeleted: true } );
22
30
  await taggingService.create( data );
@@ -30,8 +38,8 @@ export const addCustomTag = async ( req, res ) => {
30
38
  date: new Date(),
31
39
  logType: 'zone',
32
40
  logSubType: 'addCustomTag',
33
- changes: [ `${inputData.tagName} customtag Created.` ],
34
- eventType: '',
41
+ changes: [ `${inputData.tagName} custom tag` ],
42
+ eventType: 'create',
35
43
  showTo: [ 'client', 'tango' ],
36
44
  };
37
45
  insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
@@ -44,7 +52,7 @@ export const addCustomTag = async ( req, res ) => {
44
52
 
45
53
  export const customTagList = async ( req, res ) => {
46
54
  try {
47
- let customTagList = [ 'Front', 'Back' ];
55
+ let customTagList = [ 'Front', 'Back', 'Tracker-in', 'Tracker-out' ];
48
56
  let storeDetails = await storeService.findOne( { storeId: req.query.storeId }, { product: 1 } );
49
57
  let clientDetails = await clientService.findOne( { clientId: req.query.clientId }, { featureConfigs: 1 } );
50
58
  if ( clientDetails && clientDetails?.featureConfigs?.isExcludedArea ) {
@@ -149,6 +157,14 @@ export const customTagList = async ( req, res ) => {
149
157
  i.rgbColor = 'rgba(193, 214, 114, 0.5)';
150
158
  i.rgbBorderColor = 'rgb(203, 224, 124)';
151
159
  }
160
+ if ( i.tagName === 'Tracker-in' ) {
161
+ i.rgbColor = 'rgba(123, 95, 105, 0.5)';
162
+ i.rgbBorderColor = 'rgb(133, 105, 115)';
163
+ }
164
+ if ( i.tagName === 'Tracker-out' ) {
165
+ i.rgbColor = 'rgba(12, 195, 111, 0.5)';
166
+ i.rgbBorderColor = 'rgb(22, 205, 125)';
167
+ }
152
168
  } );
153
169
 
154
170
  return res.sendSuccess( customTagList );
@@ -157,6 +173,526 @@ export const customTagList = async ( req, res ) => {
157
173
  return res.sendError( e, 500 );
158
174
  }
159
175
  };
176
+ export const customTagListv2 = async ( req, res ) => {
177
+ try {
178
+ // Step 1: Base tag list
179
+ let customTagList = [
180
+ { tagName: 'Front', productName: 'tangoTraffic' },
181
+ { tagName: 'Back', productName: 'tangoTraffic' },
182
+ { tagName: 'Tracker-in', productName: 'tangoTracker' },
183
+ { tagName: 'Tracker-out', productName: 'tangoTracker' },
184
+ ];
185
+
186
+ // Step 2: Fetch store & client details in parallel
187
+ const [ storeDetails, clientDetails, camList ] = await Promise.all( [
188
+ storeService.findOne(
189
+ { storeId: req.query.storeId },
190
+ { product: 1 },
191
+ ),
192
+ clientService.findOne(
193
+ { clientId: req.query.clientId },
194
+ { featureConfigs: 1 },
195
+ ),
196
+ cameraService.find(
197
+ { storeId: req.query.storeId, isActivated: true, isUp: true },
198
+ { streamName: 1 },
199
+ ),
200
+ ] );
201
+
202
+ // Step 3: Add conditional tags
203
+ if ( clientDetails?.featureConfigs?.isExcludedArea ) {
204
+ customTagList.push( { tagName: 'Excluded Area', productName: 'tangoTraffic' } );
205
+ }
206
+ if ( clientDetails?.featureConfigs?.isPasserByData ) {
207
+ customTagList.push( { tagName: 'Passer By', productName: 'tangoTraffic' } );
208
+ }
209
+ if ( storeDetails?.product?.includes( 'tangoZone' ) ) {
210
+ customTagList.push(
211
+ { tagName: 'Entry/Exit', productName: 'tangoZone' },
212
+ { tagName: 'Billing', productName: 'tangoZone' },
213
+ );
214
+ }
215
+ if ( req.query.selectedProduct && req.query.selectedProduct != '' ) {
216
+ customTagList = customTagList.filter( ( ele ) => ele.productName === req.query.selectedProduct );
217
+ }
218
+ // Step 4: Fetch existing tags
219
+ const tagInfo = await taggingService.find(
220
+ { clientId: req.query.clientId, productName: req.query.selectedProduct },
221
+ { tagName: 1, rgbColor: 1, rgbBorderColor: 1, productName: 1, groupName: 1 },
222
+ );
223
+
224
+ // Merge all tags
225
+ const mergedTags = [ ...customTagList, ...tagInfo ];
226
+
227
+ // Step 5: Build unique tag list
228
+ const uniqueTags = [ ...new Map( mergedTags.map( ( tag ) => [ tag.tagName, tag ] ) ).values() ];
229
+
230
+ // Step 6: Query counts per tag
231
+ const tagNames = uniqueTags.map( ( t ) => t.tagName );
232
+ let activeCam = camList.map( ( data ) => data.streamName );
233
+ const taggingDetails = await taggingService.aggregate( [
234
+ {
235
+ $match: {
236
+ productName: req.query.selectedProduct,
237
+ clientId: req.query.clientId,
238
+ storeId: req.query.storeId,
239
+ coordinates: { $exists: true, $ne: [] },
240
+ tagName: { $in: tagNames },
241
+ streamName: { $in: activeCam },
242
+ },
243
+ },
244
+ { $group: { _id: '$tagName', count: { $sum: 1 } } },
245
+ ] );
246
+
247
+ const countMap = new Map( taggingDetails.map( ( t ) => [ t._id, t.count ] ) );
248
+
249
+ // Step 7: Tag colors config (instead of many ifs)
250
+ const tagColors = {
251
+ 'Front': { rgbColor: 'rgba(89, 80, 5, 0.5)', rgbBorderColor: 'rgb(99, 90, 15)' },
252
+ 'Back': { rgbColor: 'rgba(94, 60, 107, 0.5)', rgbBorderColor: 'rgb(104, 70, 117)' },
253
+ 'Excluded Area': { rgbColor: 'rgba(186, 60, 214, 0.5)', rgbBorderColor: 'rgb(196, 70, 224)' },
254
+ 'Passer By': { rgbColor: 'rgba(81, 153, 247, 0.5)', rgbBorderColor: 'rgb(91, 163, 257)' },
255
+ 'Entry/Exit': { rgbColor: 'rgba(224, 43, 170, 0.5)', rgbBorderColor: 'rgb(234, 53, 180)' },
256
+ 'Billing': { rgbColor: 'rgba(193, 214, 114, 0.5)', rgbBorderColor: 'rgb(203, 224, 124)' },
257
+ 'Tracker-in': { rgbColor: 'rgba(123, 95, 105, 0.5)', rgbBorderColor: 'rgb(133, 105, 115)' },
258
+ 'Tracker-out': { rgbColor: 'rgba(12, 195, 111, 0.5)', rgbBorderColor: 'rgb(22, 205, 125)' },
259
+ };
260
+
261
+
262
+ // Step 8: Final processing
263
+ const finalTags = uniqueTags.map( ( tag ) => ( {
264
+ tagName: tag.tagName,
265
+ productName: tag.productName,
266
+ groupName: tag.groupName ? tag.groupName : '',
267
+ count: countMap.get( tag.tagName ) || 0,
268
+ rgbColor: tag.rgbColor || tagColors[tag.tagName]?.rgbColor,
269
+ rgbBorderColor: tag.rgbBorderColor || tagColors[tag.tagName]?.rgbBorderColor,
270
+ } ) );
271
+ const groupedData = finalTags.reduce( ( acc, item ) => {
272
+ if ( item.groupName === '' ) {
273
+ item.isGroup = false;
274
+ // If groupName is empty, push the item as a standalone object
275
+ acc.push( item );
276
+ } else {
277
+ // If groupName exists, find if we already started a group for it
278
+ let group = acc.find( ( i ) => i.isGroup && i.groupName === item.groupName );
279
+
280
+ if ( !group ) {
281
+ // Create a new group container
282
+ group = {
283
+ groupName: item.groupName,
284
+ isGroup: true, // flag to identify this is a group
285
+ children: [],
286
+ };
287
+ acc.push( group );
288
+ }
289
+
290
+ // Push the current item into the group's children
291
+ group.children.push( item );
292
+ }
293
+ return acc;
294
+ }, [] );
295
+
296
+
297
+ // Step 9: Sort by count (desc)
298
+ groupedData.sort( ( a, b ) => b.count - a.count );
299
+
300
+ if ( req.query.selectedProduct && req.query.selectedProduct === 'tangoTrax' ) {
301
+ let Query = [ {
302
+ $match: {
303
+ client_id: req.query.clientId,
304
+ publish: true,
305
+ checkListType: { $ne: 'custom' },
306
+ },
307
+ }, {
308
+ $group: {
309
+ _id: '$_id',
310
+ sourceCheckList_id: { $last: '$_id' },
311
+ tagName: { $last: '$checkListName' },
312
+ },
313
+ },
314
+ {
315
+ $lookup: {
316
+ from: 'cameras',
317
+ let: { checkListName: '$tagName' },
318
+ pipeline: [
319
+ {
320
+ $match: {
321
+ $expr: {
322
+ $anyElementTrue: {
323
+ $map: {
324
+ input: { $ifNull: [ '$taggedChecklist', [] ] }, // ✅ default to empty array
325
+ as: 'tc',
326
+ in: { $eq: [ '$$tc.checkListName', '$$checkListName' ] },
327
+ },
328
+ },
329
+ },
330
+ },
331
+ },
332
+ {
333
+ $project: {
334
+ storeId: 1,
335
+ },
336
+ },
337
+ ], as: 'cameraList',
338
+ },
339
+ },
340
+ {
341
+ $project: {
342
+ tagName: 1,
343
+ type: 'checklist',
344
+ cameraList: {
345
+ $filter: {
346
+ input: '$cameraList',
347
+ as: 'item',
348
+ cond: {
349
+ $eq: [ '$$item.storeId', req.query.storeId ],
350
+ },
351
+ },
352
+ },
353
+ },
354
+ },
355
+ {
356
+ $project: {
357
+ tagName: 1,
358
+ type: 1,
359
+ count: { $size: '$cameraList' },
360
+ },
361
+ },
362
+ {
363
+ $sort: {
364
+ count: -1,
365
+ },
366
+ },
367
+
368
+ ];
369
+ let getChecklistData = await checklistconfigService.aggregate( Query );
370
+ if ( finalTags && finalTags.length > 0 ) {
371
+ let merged = getChecklistData.map( ( item ) => {
372
+ let match = finalTags.find( ( a ) => a.tagName === item.tagName );
373
+ if ( match ) {
374
+ return { ...item, ...match, count: item.count + match.count }; // add counts
375
+ }
376
+ return item;
377
+ } );
378
+ // also include any arr1 items not in arr2
379
+ finalTags.forEach( ( a ) => {
380
+ if ( !merged.find( ( m ) => m.tagName === a.tagName ) ) {
381
+ merged.push( a );
382
+ }
383
+ } );
384
+ merged.sort( ( a, b ) => b.count - a.count );
385
+ return res.sendSuccess( merged );
386
+ } else {
387
+ return res.sendSuccess( getChecklistData );
388
+ }
389
+ } else {
390
+ return res.sendSuccess( groupedData );
391
+ }
392
+ } catch ( e ) {
393
+ logger.error( { error: e, function: 'customTagList' } );
394
+ return res.sendError( e, 500 );
395
+ }
396
+ };
397
+ export const customTagListv3 = async ( req, res ) => {
398
+ try {
399
+ // Step 1: Base tag list
400
+ let customTagList = [
401
+ { tagName: 'Front', productName: 'tangoTraffic' },
402
+ { tagName: 'Back', productName: 'tangoTraffic' },
403
+ { tagName: 'Tracker-in', productName: 'tangoTracker' },
404
+ { tagName: 'Tracker-out', productName: 'tangoTracker' },
405
+ ];
406
+
407
+ // Step 2: Fetch store & client details in parallel
408
+ const [ storeDetails, clientDetails, camList ] = await Promise.all( [
409
+ storeService.findOne(
410
+ { storeId: req.query.storeId },
411
+ { product: 1 },
412
+ ),
413
+ clientService.findOne(
414
+ { clientId: req.query.clientId },
415
+ { featureConfigs: 1 },
416
+ ),
417
+ cameraService.find(
418
+ { storeId: req.query.storeId, isActivated: true, isUp: true },
419
+ { streamName: 1 },
420
+ ),
421
+ ] );
422
+
423
+ // Step 3: Add conditional tags
424
+ if ( clientDetails?.featureConfigs?.isExcludedArea ) {
425
+ customTagList.push( { tagName: 'Excluded Area', productName: 'tangoTraffic' } );
426
+ }
427
+ if ( clientDetails?.featureConfigs?.isPasserByData ) {
428
+ customTagList.push( { tagName: 'Passer By', productName: 'tangoTraffic' } );
429
+ }
430
+ if ( storeDetails?.product?.includes( 'tangoZone' ) ) {
431
+ customTagList.push(
432
+ { tagName: 'Entry/Exit', productName: 'tangoZone' },
433
+ { tagName: 'Billing', productName: 'tangoZone' },
434
+ );
435
+ }
436
+ if ( req.query.selectedProduct && req.query.selectedProduct != '' ) {
437
+ customTagList = customTagList.filter( ( ele ) => ele.productName === req.query.selectedProduct );
438
+ }
439
+ // Step 4: Fetch existing tags
440
+ const tagInfo = await taggingService.find(
441
+ { clientId: req.query.clientId, productName: req.query.selectedProduct },
442
+ { tagName: 1, rgbColor: 1, rgbBorderColor: 1, productName: 1, groupName: 1 },
443
+ );
444
+
445
+ // Merge all tags
446
+ const mergedTags = [ ...customTagList, ...tagInfo ];
447
+ const uniqueTags = [ ...new Map( mergedTags.map( ( tag ) => [ tag.tagName, tag ] ) ).values() ];
448
+
449
+ // Step 6: Query counts per tag
450
+ const tagNames = uniqueTags.map( ( t ) => t.tagName );
451
+ let activeCam = camList.map( ( data ) => data.streamName );
452
+ const taggingDetails = await taggingService.aggregate( [
453
+ {
454
+ $match: {
455
+ productName: req.query.selectedProduct,
456
+ clientId: req.query.clientId,
457
+ storeId: req.query.storeId,
458
+ coordinates: { $exists: true, $ne: [] },
459
+ tagName: { $in: tagNames },
460
+ streamName: { $in: activeCam },
461
+ },
462
+ },
463
+ { $group: { _id: '$tagName', count: { $sum: 1 } } },
464
+ ] );
465
+
466
+ const countMap = new Map( taggingDetails.map( ( t ) => [ t._id, t.count ] ) );
467
+
468
+ // Step 7: Tag colors config (instead of many ifs)
469
+ const tagColors = {
470
+ 'Front': { rgbColor: 'rgba(89, 80, 5, 0.5)', rgbBorderColor: 'rgb(99, 90, 15)' },
471
+ 'Back': { rgbColor: 'rgba(94, 60, 107, 0.5)', rgbBorderColor: 'rgb(104, 70, 117)' },
472
+ 'Excluded Area': { rgbColor: 'rgba(186, 60, 214, 0.5)', rgbBorderColor: 'rgb(196, 70, 224)' },
473
+ 'Passer By': { rgbColor: 'rgba(81, 153, 247, 0.5)', rgbBorderColor: 'rgb(91, 163, 257)' },
474
+ 'Entry/Exit': { rgbColor: 'rgba(224, 43, 170, 0.5)', rgbBorderColor: 'rgb(234, 53, 180)' },
475
+ 'Billing': { rgbColor: 'rgba(193, 214, 114, 0.5)', rgbBorderColor: 'rgb(203, 224, 124)' },
476
+ 'Tracker-in': { rgbColor: 'rgba(123, 95, 105, 0.5)', rgbBorderColor: 'rgb(133, 105, 115)' },
477
+ 'Tracker-out': { rgbColor: 'rgba(12, 195, 111, 0.5)', rgbBorderColor: 'rgb(22, 205, 125)' },
478
+ };
479
+
480
+
481
+ // Step 8: Final processing
482
+ const finalTags = uniqueTags.map( ( tag ) => ( {
483
+ tagName: tag.tagName,
484
+ productName: tag.productName,
485
+ groupName: tag.groupName ? tag.groupName : '',
486
+ count: countMap.get( tag.tagName ) || 0,
487
+ rgbColor: tag.rgbColor || tagColors[tag.tagName]?.rgbColor,
488
+ rgbBorderColor: tag.rgbBorderColor || tagColors[tag.tagName]?.rgbBorderColor,
489
+ } ) );
490
+
491
+
492
+ console.log( '🚀 ~ customTagListv3 ~ mergedTags:', mergedTags );
493
+
494
+ if ( req.query.selectedProduct === 'tangoZone' ) {
495
+ const zonefinalTags = await customZoneTagService.aggregate( [
496
+ {
497
+ $match: {
498
+ clientId: req.query.clientId,
499
+ productName: req.query.selectedProduct,
500
+ },
501
+ },
502
+
503
+ // Count lookup
504
+ {
505
+ $lookup: {
506
+ from: 'taggings',
507
+ let: { tagName: '$tagName', clientId: '$clientId' },
508
+ pipeline: [
509
+ {
510
+ $match: {
511
+ $expr: {
512
+ $and: [
513
+ { $eq: [ '$tagName', '$$tagName' ] },
514
+ { $eq: [ '$clientId', '$$clientId' ] },
515
+ { $in: [ '$streamName', activeCam ] },
516
+ { $ne: [ '$coordinates', [] ] },
517
+ ],
518
+ },
519
+ },
520
+ },
521
+ { $count: 'count' },
522
+ ],
523
+ as: 'tagsCount',
524
+ },
525
+ },
526
+
527
+ // Add count
528
+ {
529
+ $addFields: {
530
+ count: {
531
+ $ifNull: [ { $arrayElemAt: [ '$tagsCount.count', 0 ] }, 0 ],
532
+ },
533
+ },
534
+ },
535
+
536
+ {
537
+ $project: {
538
+ _id: 0,
539
+ tagName: 1,
540
+ productName: 1,
541
+ groupName: 1,
542
+ rgbColor: 1,
543
+ rgbBorderColor: 1,
544
+ count: 1,
545
+ },
546
+ },
547
+ {
548
+ $sort: {
549
+ count: -1,
550
+ },
551
+ },
552
+
553
+ // Split grouped vs ungrouped
554
+ {
555
+ $facet: {
556
+ grouped: [
557
+ { $match: { groupName: { $nin: [ null, '' ] } } },
558
+ {
559
+ $group: {
560
+ _id: '$groupName',
561
+ children: { $push: '$$ROOT' },
562
+ },
563
+ },
564
+ {
565
+ $project: {
566
+ _id: 0,
567
+ groupName: '$_id',
568
+ isGroup: { $literal: true },
569
+ children: 1,
570
+ },
571
+ },
572
+ ],
573
+
574
+ ungrouped: [
575
+ { $match: { groupName: { $in: [ null, '' ] } } },
576
+ {
577
+ $addFields: { isGroup: { $literal: false } },
578
+ },
579
+ ],
580
+ },
581
+ },
582
+
583
+ // Merge results
584
+ {
585
+ $project: {
586
+ result: { $concatArrays: [ '$ungrouped', '$grouped' ] },
587
+ },
588
+ },
589
+
590
+ { $unwind: '$result' },
591
+ { $replaceRoot: { newRoot: '$result' } },
592
+ ] );
593
+
594
+
595
+ return res.sendSuccess( zonefinalTags );
596
+ }
597
+
598
+
599
+ if ( req.query.selectedProduct && req.query.selectedProduct === 'tangoTrax' ) {
600
+ let Query = [ {
601
+ $match: {
602
+ client_id: req.query.clientId,
603
+ publish: true,
604
+ checkListType: { $ne: 'custom' },
605
+ },
606
+ }, {
607
+ $group: {
608
+ _id: '$_id',
609
+ sourceCheckList_id: { $last: '$_id' },
610
+ tagName: { $last: '$checkListName' },
611
+ },
612
+ },
613
+ {
614
+ $lookup: {
615
+ from: 'cameras',
616
+ let: { checkListName: '$tagName' },
617
+ pipeline: [
618
+ {
619
+ $match: {
620
+ $expr: {
621
+ $anyElementTrue: {
622
+ $map: {
623
+ input: { $ifNull: [ '$taggedChecklist', [] ] }, // ✅ default to empty array
624
+ as: 'tc',
625
+ in: { $eq: [ '$$tc.checkListName', '$$checkListName' ] },
626
+ },
627
+ },
628
+ },
629
+ },
630
+ },
631
+ {
632
+ $project: {
633
+ storeId: 1,
634
+ },
635
+ },
636
+ ], as: 'cameraList',
637
+ },
638
+ },
639
+ {
640
+ $project: {
641
+ tagName: 1,
642
+ type: 'checklist',
643
+ cameraList: {
644
+ $filter: {
645
+ input: '$cameraList',
646
+ as: 'item',
647
+ cond: {
648
+ $eq: [ '$$item.storeId', req.query.storeId ],
649
+ },
650
+ },
651
+ },
652
+ },
653
+ },
654
+ {
655
+ $project: {
656
+ tagName: 1,
657
+ type: 1,
658
+ count: { $size: '$cameraList' },
659
+ },
660
+ },
661
+ {
662
+ $sort: {
663
+ count: -1,
664
+ },
665
+ },
666
+
667
+ ];
668
+ let getChecklistData = await checklistconfigService.aggregate( Query );
669
+ if ( finalTags && finalTags.length > 0 ) {
670
+ let merged = getChecklistData.map( ( item ) => {
671
+ let match = finalTags.find( ( a ) => a.tagName === item.tagName );
672
+ if ( match ) {
673
+ return { ...item, ...match, count: item.count + match.count }; // add counts
674
+ }
675
+ return item;
676
+ } );
677
+ // also include any arr1 items not in arr2
678
+ finalTags.forEach( ( a ) => {
679
+ if ( !merged.find( ( m ) => m.tagName === a.tagName ) ) {
680
+ merged.push( a );
681
+ }
682
+ } );
683
+ merged.sort( ( a, b ) => b.count - a.count );
684
+ return res.sendSuccess( merged );
685
+ } else {
686
+ return res.sendSuccess( getChecklistData );
687
+ }
688
+ } else {
689
+ return res.sendSuccess( finalTags );
690
+ }
691
+ } catch ( e ) {
692
+ logger.error( { error: e, function: 'customTagList' } );
693
+ return res.sendError( e, 500 );
694
+ }
695
+ };
160
696
 
161
697
  export const tagging = async ( req, res ) => {
162
698
  try {
@@ -182,8 +718,8 @@ export const tagging = async ( req, res ) => {
182
718
  date: new Date(),
183
719
  logType: 'zone',
184
720
  logSubType: 'addZoneTagging',
185
- changes: [ `${InputData.tagName} zone added tagging` ],
186
- eventType: '',
721
+ changes: [ `${InputData.tagName} zone tagging` ],
722
+ eventType: 'create',
187
723
  showTo: [ 'client', 'tango' ],
188
724
  };
189
725
  insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
@@ -206,12 +742,33 @@ export const tagging = async ( req, res ) => {
206
742
  date: new Date(),
207
743
  logType: 'zone',
208
744
  logSubType: 'updateZoneTagging',
209
- changes: [ `${InputData.tagName} zone Updated tagging` ],
210
- eventType: '',
745
+ changes: [ `${InputData.tagName} zone tagging` ],
746
+ eventType: 'update',
747
+ taggingId: taggingDetails._id,
748
+ previous: [
749
+ JSON.parse( JSON.stringify( taggingDetails ) ),
750
+ ],
751
+ current: {
752
+ ...taggingDetails.toObject(),
753
+ },
754
+ oldData: {
755
+ TagName: taggingDetails.tagName,
756
+ StreamName: taggingDetails.streamName,
757
+ },
758
+ newData: {
759
+ TagName: taggingDetails.tagName,
760
+ StreamName: taggingDetails.streamName,
761
+ },
762
+ showTo: [ 'tango', 'client' ],
211
763
  };
764
+
765
+ delete logObj?.current?.coordinates;
766
+
212
767
  insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
213
768
  taggingDetails.cameraId = InputData.cameraId;
214
769
  taggingDetails.streamName = InputData.streamName;
770
+ taggingDetails.checkListName = InputData.checkListName;
771
+ taggingDetails.productName = InputData.productName;
215
772
  // if ( req.body?.redoPoint ) {
216
773
  // taggingDetails.coordinates = InputData.coordinates[0];
217
774
  // taggingDetails.save();
@@ -244,7 +801,7 @@ export const tagging = async ( req, res ) => {
244
801
  let externalDetails = await externalService.findOne( { storeId: InputData.storeId, zoneName: InputData.tagName, clientId: InputData.clientId } );
245
802
 
246
803
  if ( !externalDetails ) {
247
- let data ={
804
+ let data = {
248
805
  'storeId': InputData.storeId,
249
806
  'clientId': InputData.clientId,
250
807
  'productName': 'tangoZone',
@@ -278,7 +835,7 @@ export const tagging = async ( req, res ) => {
278
835
  'reidParameterType': 'default',
279
836
  'reidParameterValue': [ 0.6, 1, 0.6 ],
280
837
  'reidSuggesteParameter': false,
281
- 'reidSuggestedReduction': false,
838
+ 'reidSuggestedReduction': true,
282
839
  'reidThresholdHoliday': 0,
283
840
  'reidThresholdWeekDay': 0,
284
841
  'stage': '',
@@ -290,11 +847,25 @@ export const tagging = async ( req, res ) => {
290
847
  'walkInTimeSpent': 3,
291
848
  'isWalkInOneImg': false,
292
849
  'isAudit': true,
293
- 'isForceCombine': false,
850
+ 'isForceCombine': true,
294
851
  };
295
852
 
296
853
  await externalService.create( data );
297
854
  }
855
+
856
+ let cameraDetails = await cameraService.findOne( { _id: InputData.cameraId, streamName: InputData.streamName }, { productModule: 1 } );
857
+ if ( cameraDetails ) {
858
+ if ( !cameraDetails?.productModule.includes( 'tangoZone' ) ) {
859
+ cameraDetails.productModule.push( { productName: 'tangoZone', checked: true } );
860
+ cameraDetails.save();
861
+ } else {
862
+ let camIndex = cameraDetails.productModule.findIndex( ( ele ) => ele.productName == 'tangoZone' );
863
+ if ( !cameraDetails?.productModule?.[camIndex]?.checked ) {
864
+ cameraDetails.productModule[camIndex].checked = true;
865
+ cameraDetails.save();
866
+ }
867
+ }
868
+ }
298
869
  await updatezoneTagging( req, res );
299
870
  } catch ( e ) {
300
871
  logger.error( { error: e, function: 'tagging' } );
@@ -308,11 +879,13 @@ export const getCameraList = async ( req, res ) => {
308
879
  if ( !cameraDetails.length ) {
309
880
  return res.sendError( 'no data found', 204 );
310
881
  }
311
- const folderPath = { file_path: `${req.query.storeId}/zone_base_images/`,
882
+ const folderPath = {
883
+ file_path: `${req.query.storeId}/zone_base_images/`,
312
884
  Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage, MaxKeys: 1000,
313
885
  };
314
886
  let fileList = await listFileByPath( folderPath );
315
- const TaggedfolderPath = { file_path: `${req.query.storeId}/zone_tagged_image/`,
887
+ const TaggedfolderPath = {
888
+ file_path: `${req.query.storeId}/zone_tagged_image/`,
316
889
  Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage, MaxKeys: 1000,
317
890
  };
318
891
  let tagFileList = await listFileByPath( TaggedfolderPath );
@@ -338,7 +911,7 @@ export const getCameraList = async ( req, res ) => {
338
911
  tagFileList.data.forEach( ( item ) => {
339
912
  if ( item.Key.length > 1 ) {
340
913
  let splitStream = item.Key.split( '/' );
341
- let getStream = splitStream[splitStream.length -1].split( '.' );
914
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
342
915
 
343
916
  if ( getStream && getStream[0] == `${req.query.storeId}_${camera.streamName}` ) {
344
917
  tagPath = item.Key;
@@ -349,21 +922,23 @@ export const getCameraList = async ( req, res ) => {
349
922
  if ( fileList?.data.length ) {
350
923
  fileList.data.forEach( ( ele ) => {
351
924
  let splitStream = ele.Key.split( '/' );
352
- let getStream = splitStream[splitStream.length -1].split( '.' );
925
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
353
926
  if ( getStream && getStream[0] == `${req.query.storeId}_${camera.streamName}` ) {
354
927
  imgPath = ele.Key;
355
928
  }
356
929
  } );
357
930
  }
358
931
  if ( tagPath ) {
359
- const params = { file_path: tagPath,
932
+ const params = {
933
+ file_path: tagPath,
360
934
  Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage,
361
935
  };
362
936
  const cameraTagImage = await signedUrl( params );
363
937
  camera.tagImg = cameraTagImage;
364
938
  }
365
939
  if ( imgPath ) {
366
- const baseParams = { file_path: imgPath,
940
+ const baseParams = {
941
+ file_path: imgPath,
367
942
  Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage,
368
943
  };
369
944
  const cameraBaseImage = await signedUrl( baseParams );
@@ -379,6 +954,87 @@ export const getCameraList = async ( req, res ) => {
379
954
  return res.sendError( e, 500 );
380
955
  }
381
956
  };
957
+ export const getCameraListv2 = async ( req, res ) => {
958
+ try {
959
+ let cameraDetails = await cameraService.find( { clientId: req.body.clientId, storeId: req.body.storeId, isActivated: true, isUp: true }, { cameraNumber: 1, streamName: 1, isActivated: 1, isUp: 1, cameraName: 1, taggedChecklist: 1 } );
960
+ if ( !cameraDetails.length ) {
961
+ return res.sendError( 'no data found', 204 );
962
+ }
963
+ const folderPath = {
964
+ file_path: `${req.body.storeId}/zone_base_images/`,
965
+ Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage, MaxKeys: 1000,
966
+ };
967
+ let fileList = await listFileByPath( folderPath );
968
+ const TaggedfolderPath = {
969
+ file_path: `${req.body.storeId}/zone_tagged_image/`,
970
+ Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage, MaxKeys: 1000,
971
+ };
972
+ let tagFileList = await listFileByPath( TaggedfolderPath );
973
+ for ( let [ index, camera ] of cameraDetails.entries() ) {
974
+ let tagList = [];
975
+ let tagPath;
976
+ let imgPath;
977
+ camera = {
978
+ ...camera._doc,
979
+ baseImg: '',
980
+ tagImg: '',
981
+ };
982
+ let taggingDetails = await taggingService.find( { cameraId: camera._id, streamName: camera.streamName, clientId: req.body.clientId, isDeleted: false }, { tagName: 1, coordinates: 1 } );
983
+ if ( taggingDetails.length ) {
984
+ tagList = taggingDetails.filter( ( item ) => item.coordinates.length ).map( ( item ) => {
985
+ if ( item.coordinates.length ) {
986
+ return { tagName: item.tagName, color: item.coordinates[0].color };
987
+ }
988
+ } );
989
+ }
990
+ if ( tagFileList.data.length ) {
991
+ tagFileList.data.forEach( ( item ) => {
992
+ if ( item.Key.length > 1 ) {
993
+ let splitStream = item.Key.split( '/' );
994
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
995
+
996
+ if ( getStream && getStream[0] == `${req.body.storeId}_${camera.streamName}` ) {
997
+ tagPath = item.Key;
998
+ }
999
+ }
1000
+ } );
1001
+ }
1002
+ if ( fileList?.data.length ) {
1003
+ fileList.data.forEach( ( ele ) => {
1004
+ let splitStream = ele.Key.split( '/' );
1005
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
1006
+ if ( getStream && getStream[0] == `${req.body.storeId}_${camera.streamName}` ) {
1007
+ imgPath = ele.Key;
1008
+ }
1009
+ } );
1010
+ }
1011
+ if ( tagPath ) {
1012
+ const params = {
1013
+ file_path: tagPath,
1014
+ Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage,
1015
+ };
1016
+ const cameraTagImage = await signedUrl( params );
1017
+ camera.tagImg = cameraTagImage;
1018
+ }
1019
+ if ( imgPath ) {
1020
+ const baseParams = {
1021
+ file_path: imgPath,
1022
+ Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage,
1023
+ };
1024
+ const cameraBaseImage = await signedUrl( baseParams );
1025
+ camera.baseImg = cameraBaseImage;
1026
+ }
1027
+ camera.tagging = tagList;
1028
+ camera.taggedCount = tagList.filter( ( ele ) => ele.tagName === req.body.tagName );
1029
+ cameraDetails[index] = camera;
1030
+ }
1031
+ cameraDetails.sort( ( a, b ) => a.tagging?.length > b.tagging?.length ? -1 : 1 );
1032
+ return res.sendSuccess( cameraDetails );
1033
+ } catch ( e ) {
1034
+ logger.error( { error: e, function: 'getCameraListv2' } );
1035
+ return res.sendError( e, 500 );
1036
+ }
1037
+ };
382
1038
 
383
1039
  export const updateTag = async ( req, res ) => {
384
1040
  try {
@@ -416,9 +1072,21 @@ export const updateTag = async ( req, res ) => {
416
1072
  date: new Date(),
417
1073
  logType: 'zone',
418
1074
  logSubType: 'updateCustomTag',
419
- changes: [ `${req.body.tagName} tagName Updated` ],
420
- eventType: '',
421
- showTo: [ 'client', 'tango' ],
1075
+ changes: [ `${req.body.tagName} tagName` ],
1076
+ eventType: 'update',
1077
+ previous: {
1078
+ tagName: req.body.existTag,
1079
+ },
1080
+ current: {
1081
+ tagName: req.body.tagName,
1082
+ },
1083
+ oldData: {
1084
+ TagName: req.body.existTag,
1085
+ },
1086
+ newData: {
1087
+ TagName: req.body.tagName,
1088
+ },
1089
+ showTo: [ 'tango', 'client' ],
422
1090
  };
423
1091
  insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
424
1092
  if ( tagUpdate.modifiedCount || tagUpdate.matchedCount ) {
@@ -442,19 +1110,24 @@ export const deleteTag = async ( req, res ) => {
442
1110
  if ( !taggingDetails.length ) {
443
1111
  return res.sendError( 'no data found', 204 );
444
1112
  }
445
- let enableDelete = false;
446
- if ( req.user.permission ) {
447
- let permissions = req.user.permission;
448
- permissions.forEach( ( permission ) => {
449
- if ( permission.featureName == 'analytics' ) {
450
- let product = permission.product.find( ( item ) => item.name == 'tangoZone' );
451
- if ( product ) {
452
- enableDelete = product.isDelete;
453
- }
454
- }
455
- } );
1113
+
1114
+ let tagNameDetails = await taggingService.findOne( { clientId: req.body.clientId, tagName: req.body.tagName, $expr: { $ne: [ { $size: '$coordinates' }, 0 ] } } );
1115
+ if ( tagNameDetails ) {
1116
+ return res.sendError( 'This zone tag is already mapped to other stores.', 400 );
456
1117
  }
457
- if ( req.user?.role == 'superadmin' || ( req.user?.role == 'admin' && enableDelete ) ) {
1118
+ // let enableDelete = false;
1119
+ // if ( req.user.permission ) {
1120
+ // let permissions = req.user.permission;
1121
+ // permissions.forEach( ( permission ) => {
1122
+ // if ( permission.featureName == 'analytics' ) {
1123
+ // let product = permission.product.find( ( item ) => item.name == 'tangoZone' );
1124
+ // if ( product ) {
1125
+ // enableDelete = product.isDelete;
1126
+ // }
1127
+ // }
1128
+ // } );
1129
+ // }
1130
+ if ( req.user?.role == 'superadmin' || ( req.user?.role == 'admin' ) ) {
458
1131
  await taggingService.deleteMany( { clientId: req.body.clientId, tagName: req.body.tagName } );
459
1132
  await externalService.deleteMany( { zoneName: req.body.tagName, clientId: req.body.clientId } );
460
1133
  } else {
@@ -484,9 +1157,9 @@ export const deleteTag = async ( req, res ) => {
484
1157
  email: req.user?.email,
485
1158
  date: new Date(),
486
1159
  logType: 'zone',
487
- logSubType: 'deleteTag',
488
- changes: [ `${req.body.tagName} tag Deleted` ],
489
- eventType: '',
1160
+ logSubType: 'deleteCustomTag',
1161
+ changes: [ `${req.body.tagName} tag` ],
1162
+ eventType: 'delete',
490
1163
  showTo: [ 'client', 'tango' ],
491
1164
  };
492
1165
  insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
@@ -642,36 +1315,65 @@ export const updatezoneTagging = async ( req, res ) => {
642
1315
 
643
1316
  export const updateOldData = async ( req, res ) => {
644
1317
  try {
645
- let tagDetails = await taggingService.find( {}, { storeId: 1 } );
1318
+ let tagDetails = await taggingService.find( { clientId: req.body.clientId, tagName: req.body.tagName }, { storeId: 1 } );
1319
+
1320
+
646
1321
  if ( tagDetails.length ) {
647
1322
  for ( let [ index, item ] of tagDetails.entries() ) {
648
1323
  req.body.storeId = item.storeId;
649
1324
  let camDetails = await getCamTaggingDetails( req, res );
650
- if ( !camDetails ) {
651
- logger.error( { message: 'no data', store: item.storeId } );
652
- }
653
- const response = await axios.post( JSON.parse( process.env.URL ).zoneTaggingLamdaUrl, camDetails );
654
- if ( response?.data?.status && response?.data?.status == 'success' ) {
655
- let fileData = {
656
- Key: `${req.body.storeId}/zonetagging/`,
657
- fileName: `${req.body.storeId}_zonetagging.json`,
658
- Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage,
659
- body: JSON.stringify( camDetails ),
660
- ContentType: 'application/json',
661
- };
662
- let upload = await fileUpload( fileData );
663
- if ( !upload.Key ) {
664
- logger.error( { message: `JSON Upload Error`, store: item.storeId } );
665
- }
666
- if ( index == 0 ) {
667
- res.sendSuccess( 'Zone Updated Successfully' );
1325
+ console.log( '🚀 ~ updateOldData ~ camDetails:', camDetails );
1326
+ if ( camDetails ) {
1327
+ const response = await axios.post( JSON.parse( process.env.URL ).zoneTaggingLamdaUrl, camDetails );
1328
+
1329
+ if ( response?.data?.status && response?.data?.status == 'success' ) {
1330
+ let fileData = {
1331
+ Key: `${req.body.storeId}/zonetagging/`,
1332
+ fileName: `${req.body.storeId}_zonetagging.json`,
1333
+ Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage,
1334
+ body: JSON.stringify( camDetails ),
1335
+ ContentType: 'application/json',
1336
+ };
1337
+
1338
+ let upload = await fileUpload( fileData );
1339
+ if ( !upload.Key ) {
1340
+ logger.error( { message: `JSON Upload Error`, store: item.storeId } );
1341
+ }
1342
+ } else {
1343
+ logger.error( { message: 'no data', store: item.storeId } );
1344
+ return res.sendError( 'something went wrong', 500 );
668
1345
  }
669
- } else {
670
- logger.error( { message: 'no data', store: item.storeId } );
671
- // return res.sendError( 'something went wrong', 500 );
1346
+ }
1347
+ if ( index == tagDetails.length-1 ) {
1348
+ res.sendSuccess( 'Zone Updated Successfully' );
672
1349
  }
673
1350
  }
1351
+ } else {
1352
+ res.sendSuccess( 'Zone Updated Successfully' );
1353
+ }
1354
+ } catch ( e ) {
1355
+ console.log( '🚀 ~ updateOldData ~ e:', e );
1356
+ logger.error( { error: e, function: 'updateOldData' } );
1357
+ return res.sendError( e, 500 );
1358
+ }
1359
+ };
1360
+ export async function updateCamera( req, res ) {
1361
+ try {
1362
+ let findoneCheckList = await checklistconfigService.findOne( {
1363
+ client_id: req.body.clientId, checkListName: req.body.selectedZone,
1364
+ } );
1365
+
1366
+ let updateData = {
1367
+ checkListName: req.body.selectedZone,
1368
+ sourceCheckList_id: findoneCheckList?.sourceCheckList_id,
1369
+ };
1370
+ if ( req.body.type === 'tagCamera' ) {
1371
+ await cameraService.updateOne( { _id: req.body._id }, { $push: { taggedChecklist: updateData } } );
1372
+ } else {
1373
+ await cameraService.updateOne( { _id: req.body._id }, { $pull: { taggedChecklist: updateData } } );
674
1374
  }
1375
+
1376
+ res.sendSuccess( 'updated Sucessfully' );
675
1377
  } catch ( e ) {
676
1378
  logger.error( { error: e, function: 'updateOldData' } );
677
1379
  return res.sendError( e, 500 );
@@ -715,3 +1417,1344 @@ async function updateJsonFile( req, res ) {
715
1417
  }
716
1418
  }
717
1419
  };
1420
+
1421
+ export const getCameraStreamList = async ( req, res ) => {
1422
+ try {
1423
+ let cameraDetails = await cameraService.find( { clientId: req.body.clientId, storeId: req.body.storeId, streamName: req.body.streamName }, { cameraNumber: 1, streamName: 1, isActivated: 1, isUp: 1, cameraName: 1 } );
1424
+ if ( !cameraDetails.length ) {
1425
+ return res.sendError( 'no data found', 204 );
1426
+ }
1427
+ const folderPath = {
1428
+ file_path: `${req.body.storeId}/zone_base_images/`,
1429
+ Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage, MaxKeys: 1000,
1430
+ };
1431
+ let fileList = await listFileByPath( folderPath );
1432
+ const TaggedfolderPath = {
1433
+ file_path: `${req.body.storeId}/zone_tagged_image/`,
1434
+ Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage, MaxKeys: 1000,
1435
+ };
1436
+ let tagFileList = await listFileByPath( TaggedfolderPath );
1437
+ for ( let [ index, camera ] of cameraDetails.entries() ) {
1438
+ let tagList = [];
1439
+ let tagPath;
1440
+ let imgPath;
1441
+ camera = {
1442
+ ...camera._doc,
1443
+ baseImg: '',
1444
+ tagImg: '',
1445
+ };
1446
+ let taggingDetails = await taggingService.find( { cameraId: camera._id, streamName: camera.streamName, clientId: req.body.clientId, isDeleted: false }, { tagName: 1, coordinates: 1 } );
1447
+ if ( taggingDetails.length ) {
1448
+ tagList = taggingDetails.filter( ( item ) => item.coordinates.length ).map( ( item ) => {
1449
+ if ( item.coordinates.length ) {
1450
+ return { tagName: item.tagName, color: item.coordinates[0].color };
1451
+ }
1452
+ } );
1453
+ }
1454
+
1455
+ if ( tagFileList.data.length ) {
1456
+ tagFileList.data.forEach( ( item ) => {
1457
+ if ( item.Key.length > 1 ) {
1458
+ let splitStream = item.Key.split( '/' );
1459
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
1460
+
1461
+ if ( getStream && getStream[0] == `${req.body.storeId}_${camera.streamName}` ) {
1462
+ tagPath = item.Key;
1463
+ }
1464
+ }
1465
+ } );
1466
+ }
1467
+ if ( fileList?.data.length ) {
1468
+ fileList.data.forEach( ( ele ) => {
1469
+ let splitStream = ele.Key.split( '/' );
1470
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
1471
+ if ( getStream && getStream[0] == `${req.body.storeId}_${camera.streamName}` ) {
1472
+ imgPath = ele.Key;
1473
+ }
1474
+ } );
1475
+ }
1476
+ if ( tagPath ) {
1477
+ const params = {
1478
+ file_path: tagPath,
1479
+ Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage,
1480
+ };
1481
+ const cameraTagImage = await signedUrl( params );
1482
+ camera.tagImg = cameraTagImage;
1483
+ }
1484
+ if ( imgPath ) {
1485
+ const baseParams = {
1486
+ file_path: imgPath,
1487
+ Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage,
1488
+ };
1489
+ const cameraBaseImage = await signedUrl( baseParams );
1490
+ camera.baseImg = cameraBaseImage;
1491
+ }
1492
+ camera.tagging = tagList;
1493
+ cameraDetails[index] = camera;
1494
+ }
1495
+ cameraDetails.sort( ( a, b ) => a.tagging?.length > b.tagging?.length ? -1 : 1 );
1496
+ return res.sendSuccess( cameraDetails );
1497
+ } catch ( e ) {
1498
+ logger.error( { error: e, function: 'getCameraList' } );
1499
+ return res.sendError( e, 500 );
1500
+ }
1501
+ };
1502
+
1503
+
1504
+ // setting config - zone - get tagging details
1505
+ export const getZoneTaggingDetails = async ( req, res ) => {
1506
+ try {
1507
+ const {
1508
+ clientId,
1509
+ searchValue,
1510
+ sortColumName,
1511
+ sortBy,
1512
+ limit,
1513
+ offset = 1,
1514
+ export: isExport,
1515
+ } = req.body;
1516
+
1517
+ if ( !clientId ) {
1518
+ return res.sendError( 'clientId is required', 400 );
1519
+ }
1520
+
1521
+ const matchStage = {
1522
+ clientId,
1523
+ };
1524
+ let customTags = await customZoneTagService.findOne( { clientId: clientId } );
1525
+ const alltotalGroupCount = await customzonegrouping.count( { clientId } );
1526
+ if ( !customTags ) {
1527
+ return res.sendSuccess( { result: [], totalGroupCount: alltotalGroupCount } );
1528
+ }
1529
+ const searchStage = searchValue ?
1530
+ {
1531
+ $or: [
1532
+ { tagName: { $regex: searchValue, $options: 'i' } },
1533
+ { groupName: { $regex: searchValue, $options: 'i' } },
1534
+ ],
1535
+ } :
1536
+ null;
1537
+
1538
+ const pipeline = [
1539
+ { $match: matchStage },
1540
+ {
1541
+ $lookup: {
1542
+ from: 'taggings',
1543
+ let: { tagName: '$tagName' },
1544
+ pipeline: [
1545
+ {
1546
+ $match: {
1547
+ $expr: {
1548
+ $and: [
1549
+ { $eq: [ '$tagName', '$$tagName' ] },
1550
+ { $eq: [ '$clientId', clientId ] },
1551
+ { $ne: [ '$coordinates', [] ] },
1552
+ ],
1553
+ },
1554
+ },
1555
+ },
1556
+ {
1557
+ $group: {
1558
+ _id: '$storeId',
1559
+ count: { $sum: 1 },
1560
+ },
1561
+ },
1562
+ ],
1563
+ as: 'tagsCount',
1564
+ },
1565
+ },
1566
+ {
1567
+ $project: {
1568
+ clientId: 1,
1569
+ tagName: 1,
1570
+ groupName: 1,
1571
+ storesTaggedCount: { $size: '$tagsCount' },
1572
+ },
1573
+ },
1574
+ {
1575
+ $facet: {
1576
+ totalCount: [
1577
+ ...( searchStage ? [ { $match: searchStage } ] : [] ),
1578
+ { $count: 'count' },
1579
+ ],
1580
+
1581
+ filteredData: [
1582
+ ...( searchStage ? [ { $match: searchStage } ] : [] ),
1583
+ ...( sortColumName && sortBy ?
1584
+ [ { $sort: { [sortColumName]: sortBy } } ] :
1585
+ [] ),
1586
+ ...( isExport ?
1587
+ [] :
1588
+ limit ? [
1589
+ { $skip: ( offset - 1 ) * limit },
1590
+ { $limit: Number( limit ) },
1591
+ ] : [] ),
1592
+ ],
1593
+ },
1594
+ },
1595
+ ];
1596
+
1597
+ const [ result ] = await customZoneTagService.aggregate( pipeline );
1598
+
1599
+ const totalCount = result.totalCount[0]?.count || 0;
1600
+ if ( totalCount === 0 ) {
1601
+ return res.sendError( 'No data', 204 );
1602
+ }
1603
+ const zoneList = result.filteredData;
1604
+
1605
+ // Get total count of groups for tabs (actual count, not filtered by search)
1606
+ const totalGroupCount = await customzonegrouping.count( { clientId } );
1607
+
1608
+ if ( isExport && zoneList.length ) {
1609
+ const exportdata = zoneList.map( ( z ) => ( {
1610
+ 'Zone Name': z.tagName || '--',
1611
+ 'Zone Groups': z.groupName || '--',
1612
+ 'Stores tagged Count': z.storesTaggedCount || 0,
1613
+ } ) );
1614
+ await download( exportdata, res );
1615
+ return;
1616
+ }
1617
+
1618
+ return res.sendSuccess( {
1619
+ result: zoneList,
1620
+ totalCount,
1621
+ totalGroupCount,
1622
+ } );
1623
+ } catch ( e ) {
1624
+ logger.error( { error: e, function: 'getZoneTaggingDetails' } );
1625
+ return res.sendError( e, 500 );
1626
+ }
1627
+ // try {
1628
+ // const inputData = req.body;
1629
+ // let Query = [
1630
+ // {
1631
+ // $match: {
1632
+ // clientId: inputData.clientId,
1633
+ // },
1634
+ // },
1635
+ // {
1636
+ // $lookup: {
1637
+ // from: 'taggings',
1638
+ // let: { tagName: '$tagName' },
1639
+ // pipeline: [
1640
+ // {
1641
+ // $match: {
1642
+ // $expr: {
1643
+ // $and: [
1644
+ // {
1645
+ // $eq: [ '$tagName', '$$tagName' ],
1646
+ // },
1647
+ // {
1648
+ // $eq: [ '$clientId', inputData.clientId ],
1649
+ // },
1650
+ // ],
1651
+ // },
1652
+ // },
1653
+ // },
1654
+ // ], as: 'tagsCount',
1655
+ // },
1656
+ // },
1657
+ // {
1658
+ // $project: {
1659
+ // clientId: 1,
1660
+ // tagName: 1,
1661
+ // groupName: 1,
1662
+ // storesTaggedCount: { $size: '$tagsCount' },
1663
+ // },
1664
+ // },
1665
+ // ];
1666
+ // if ( req.body.searchValue && req.body.searchValue !== '' ) {
1667
+ // Query.push( {
1668
+ // $match: {
1669
+ // $or: [
1670
+ // { tagName: { $regex: req.body.searchValue, $options: 'i' } },
1671
+ // { groupName: { $regex: req.body.searchValue, $options: 'i' } },
1672
+ // ],
1673
+ // },
1674
+ // } );
1675
+ // }
1676
+
1677
+ // if ( req.body.sortColumName && req.body.sortColumName !== '' && req.body.sortBy ) {
1678
+ // Query.push( {
1679
+ // $sort: { [req.body.sortColumName]: req.body.sortBy },
1680
+ // } );
1681
+ // }
1682
+
1683
+ // const totalCount = await customZoneTagService.aggregate( Query );
1684
+ // if ( req.body.limit && req.body.offset && !req.body.export ) {
1685
+ // Query.push(
1686
+ // { $skip: ( req.body.offset - 1 ) * req.body.limit },
1687
+ // { $limit: Number( req.body.limit ) },
1688
+ // );
1689
+ // }
1690
+ // const zoneList = await customZoneTagService.aggregate( Query );
1691
+
1692
+ // if ( req.body.export && zoneList.length > 0 ) {
1693
+ // const exportdata = [];
1694
+ // zoneList.forEach( ( element ) => {
1695
+ // const data = {
1696
+ // 'Zone Name': element.tagName || '--',
1697
+ // 'Zone Groups': element.groupName || '--',
1698
+ // 'Stores tagged Count': element.storesTaggedCount || 0,
1699
+ // };
1700
+ // exportdata.push( data );
1701
+ // } );
1702
+ // await download( exportdata, res );
1703
+ // return;
1704
+ // }
1705
+
1706
+ // return res.sendSuccess( {
1707
+ // result: zoneList,
1708
+ // count: totalCount.length,
1709
+ // } );
1710
+ // } catch ( e ) {
1711
+ // logger.error( { error: e, function: 'getZoneTaggingDetails' } );
1712
+ // console.error( 'getZoneTaggingDetails error:', e );
1713
+ // return res.sendError( e, 500 );
1714
+ // }
1715
+ };
1716
+
1717
+ export const addZoneCustomTag = async ( req, res ) => {
1718
+ try {
1719
+ const {
1720
+ clientId,
1721
+ tagName,
1722
+ groupName,
1723
+ productName,
1724
+ isExistingGroup,
1725
+ rgbColor,
1726
+ rgbBorderColor,
1727
+ _id,
1728
+ } = req.body;
1729
+
1730
+ if ( !clientId || !tagName ) {
1731
+ return res.sendError( 'clientId and zoneName are required', 400 );
1732
+ }
1733
+
1734
+ // Escape regex special characters in tagName to avoid invalid regex patterns
1735
+ const escapedTagName = tagName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
1736
+
1737
+ // Case-insensitive check for existing tag
1738
+ const existingTag = await customZoneTagService.findOne(
1739
+ { clientId, tagName: { $regex: `^${escapedTagName}$`, $options: 'i' }, _id: { $ne: _id } },
1740
+ );
1741
+
1742
+ if ( existingTag ) {
1743
+ return res.sendError( `zoneName "${tagName}" already exists for this client`, 409 );
1744
+ }
1745
+
1746
+ let groupDoc = null;
1747
+ if ( groupName ) {
1748
+ // Escape regex special characters in groupName to avoid invalid regex patterns
1749
+ const escapedGroupName = groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
1750
+ // Case-insensitive check for existing group
1751
+ groupDoc = await customzonegrouping.findOne( { clientId, groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' } } );
1752
+ }
1753
+
1754
+ if ( !isExistingGroup && groupDoc ) {
1755
+ return res.sendError( `groupName "${groupName}" already exists for this client`, 409 );
1756
+ }
1757
+
1758
+ /** Create Zone Tag */
1759
+ await customZoneTagService.create(
1760
+ [ {
1761
+ clientId,
1762
+ tagName,
1763
+ rgbColor,
1764
+ rgbBorderColor,
1765
+ groupName: groupName || null,
1766
+ productName,
1767
+ } ],
1768
+ );
1769
+
1770
+ /** Create or Update Zone Group */
1771
+ if ( groupName ) {
1772
+ if ( !groupDoc && !isExistingGroup ) {
1773
+ await customzonegrouping.create(
1774
+ [ {
1775
+ clientId,
1776
+ groupName,
1777
+ productName,
1778
+ zonesTagged: [ tagName ],
1779
+ } ],
1780
+ );
1781
+ } else if ( groupDoc && isExistingGroup ) {
1782
+ await customzonegrouping.updateOne(
1783
+ { _id: groupDoc._id },
1784
+ { $addToSet: { zonesTagged: tagName } },
1785
+ );
1786
+ }
1787
+ }
1788
+
1789
+ await taggingService.updateMany( { clientId, tagName }, { $set: { groupName } } );
1790
+
1791
+ logger.info( 'Zone Tag Created Successfully' );
1792
+ const logObj = {
1793
+ clientId: clientId,
1794
+ userName: req.user?.userName,
1795
+ email: req.user?.email,
1796
+ date: new Date(),
1797
+ logType: 'zone',
1798
+ logSubType: 'addZoneCustomTag',
1799
+ changes: [ `${tagName} custom zone tag` ],
1800
+ eventType: 'create',
1801
+ showTo: [ 'client', 'tango' ],
1802
+ };
1803
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
1804
+
1805
+ return res.sendSuccess( 'Zone Tag Created Successfully' );
1806
+ } catch ( e ) {
1807
+ logger.error( { error: e, function: 'addZoneCustomTag' } );
1808
+ return res.sendError( e.message || e, 500 );
1809
+ }
1810
+ };
1811
+
1812
+ export const uploadBulkZoneTag = async ( req, res ) => {
1813
+ try {
1814
+ const zonesArray = req.body;
1815
+ const clientId = req.bulkZoneClientId;
1816
+
1817
+ if ( !zonesArray || !Array.isArray( zonesArray ) || zonesArray.length === 0 ) {
1818
+ return res.sendError( 'zone-config file must not be empty', 400 );
1819
+ }
1820
+
1821
+ if ( !clientId ) {
1822
+ return res.sendError( 'clientId is required', 400 );
1823
+ }
1824
+
1825
+ const zoneTagDataList = [];
1826
+ const zoneGroupDataMap = new Map();
1827
+ const createdTagNames = [];
1828
+ // Map to store user-provided groupName (any case) -> actual groupName from DB (preserving existing case)
1829
+ const groupNameMapping = new Map();
1830
+
1831
+ // First pass: Check for existing groups and create mapping
1832
+ const uniqueGroupNames = [ ...new Set( zonesArray.map( ( zone ) => zone.groupName ).filter( ( name ) => name && name !== '' && name !== null ) ) ];
1833
+
1834
+ for ( const userGroupName of uniqueGroupNames ) {
1835
+ // Escape regex special characters in group name to avoid invalid regex patterns
1836
+ const escapedUserGroupName = userGroupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
1837
+
1838
+ // Case-insensitive check for existing group
1839
+ const existingGroupDoc = await customzonegrouping.findOne( {
1840
+ clientId,
1841
+ groupName: { $regex: `^${escapedUserGroupName}$`, $options: 'i' },
1842
+ } );
1843
+
1844
+ if ( existingGroupDoc ) {
1845
+ // Group exists - use the existing group's case
1846
+ groupNameMapping.set( userGroupName.toLowerCase(), existingGroupDoc.groupName );
1847
+ } else {
1848
+ // New group - use user-provided case
1849
+ groupNameMapping.set( userGroupName.toLowerCase(), userGroupName );
1850
+ }
1851
+ }
1852
+
1853
+ // Process each zone in the array
1854
+ for ( const zone of zonesArray ) {
1855
+ // Determine the correct groupName to use (existing case if group exists, user case if new)
1856
+ let normalizedGroupName = null;
1857
+ if ( zone.groupName && zone.groupName !== '' && zone.groupName !== null ) {
1858
+ normalizedGroupName = groupNameMapping.get( zone.groupName.toLowerCase() ) || zone.groupName;
1859
+ }
1860
+
1861
+ const zoneTagData = {
1862
+ clientId: clientId,
1863
+ tagName: zone.tagName,
1864
+ rgbColor: zone.rgbColor,
1865
+ rgbBorderColor: zone.rgbBorderColor,
1866
+ groupName: normalizedGroupName,
1867
+ productName: zone.productName,
1868
+ };
1869
+ zoneTagDataList.push( zoneTagData );
1870
+ createdTagNames.push( zone.tagName );
1871
+
1872
+ // Prepare zone group data if groupName is provided
1873
+ if ( normalizedGroupName ) {
1874
+ // Use normalized groupName (existing case or user case)
1875
+ if ( !zoneGroupDataMap.has( normalizedGroupName ) ) {
1876
+ zoneGroupDataMap.set( normalizedGroupName, {
1877
+ clientId: clientId,
1878
+ groupName: normalizedGroupName,
1879
+ productName: zone.productName,
1880
+ zonesTagged: new Set(),
1881
+ } );
1882
+ }
1883
+
1884
+ // Collect all tagNames for this group so that we can append them
1885
+ const groupEntry = zoneGroupDataMap.get( normalizedGroupName );
1886
+ groupEntry.zonesTagged.add( zone.tagName );
1887
+ }
1888
+ }
1889
+
1890
+ // Create all zone tags
1891
+ await customZoneTagService.create( zoneTagDataList );
1892
+
1893
+ // Create zone groups (convert Map values to array)
1894
+ // const zoneGroupDataList = Array.from( zoneGroupDataMap.values() );
1895
+ // console.log( 'zoneGroupDataList', zoneGroupDataList );
1896
+ // if ( zoneGroupDataList.length > 0 ) {
1897
+ // for ( const groupData of zoneGroupDataList ) {
1898
+ // await customzonegrouping.create( groupData );
1899
+ // }
1900
+ // }
1901
+
1902
+ // Create or Update Zone Groups (following addZoneCustomTag pattern)
1903
+ const zoneGroupDataList = Array.from( zoneGroupDataMap.values() ).map( ( groupData ) => ( {
1904
+ clientId: groupData.clientId,
1905
+ groupName: groupData.groupName,
1906
+ productName: groupData.productName,
1907
+ zonesTagged: Array.from( groupData.zonesTagged || [] ),
1908
+ } ) );
1909
+
1910
+ if ( zoneGroupDataList.length > 0 ) {
1911
+ for ( const groupData of zoneGroupDataList ) {
1912
+ // Case-insensitive check for existing group (same as addZoneCustomTag)
1913
+ const escapedGroupName = groupData.groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
1914
+ const groupDoc = await customzonegrouping.findOne( {
1915
+ clientId,
1916
+ groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' },
1917
+ } );
1918
+
1919
+ if ( !groupDoc ) {
1920
+ // Create new group with all zones from the bulk file
1921
+ await customzonegrouping.create( [ {
1922
+ clientId: groupData.clientId,
1923
+ groupName: groupData.groupName,
1924
+ productName: groupData.productName,
1925
+ zonesTagged: groupData.zonesTagged,
1926
+ } ] );
1927
+ } else {
1928
+ // Group exists - add new zones to existing zonesTagged array (preserving existing mappings)
1929
+ await customzonegrouping.updateOne(
1930
+ { _id: groupDoc._id },
1931
+ { $addToSet: { zonesTagged: { $each: groupData.zonesTagged } } },
1932
+ );
1933
+ }
1934
+ }
1935
+ }
1936
+
1937
+ // Ensure tagging documents are also mapped to the correct groupName for newly uploaded zones
1938
+ // Use normalized groupName (existing case if group exists, user case if new)
1939
+ for ( const zone of zonesArray ) {
1940
+ if ( zone.groupName && zone.groupName !== '' && zone.groupName !== null ) {
1941
+ const normalizedGroupName = groupNameMapping.get( zone.groupName.toLowerCase() ) || zone.groupName;
1942
+ await taggingService.updateMany(
1943
+ { clientId, tagName: zone.tagName },
1944
+ { $set: { groupName: normalizedGroupName } },
1945
+ );
1946
+ }
1947
+ }
1948
+
1949
+
1950
+ logger.info( `Bulk Zone Tags Created Successfully: ${createdTagNames.length} tags` );
1951
+ const logObj = {
1952
+ clientId: clientId,
1953
+ userName: req.user?.userName,
1954
+ email: req.user?.email,
1955
+ date: new Date(),
1956
+ logType: 'zone',
1957
+ logSubType: 'addBulkZoneCustomTag',
1958
+ changes: createdTagNames.map( ( tagName ) => `${tagName} custom tag` ),
1959
+ eventType: 'create',
1960
+ showTo: [ 'client', 'tango' ],
1961
+ };
1962
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
1963
+ return res.sendSuccess( {
1964
+ message: `Zone Tags Created Successfully`,
1965
+ count: createdTagNames.length,
1966
+ } );
1967
+ } catch ( e ) {
1968
+ console.log( 'uploadBulkZoneTags error :', e );
1969
+ logger.error( { error: e, function: 'addBulkZoneCustomTag' } );
1970
+ return res.sendError( 'Failed to upload zone-config file', 500 );
1971
+ }
1972
+ };
1973
+
1974
+ export const updateZoneCustomTag = async ( req, res ) => {
1975
+ try {
1976
+ const { clientId, tagName, oldTag, isExistingGroup, oldGroupName, groupName } = req.body;
1977
+
1978
+ if ( !clientId || !tagName ) {
1979
+ return res.sendError( 'clientId, zoneName are required', 400 );
1980
+ }
1981
+
1982
+ let findQuery = {};
1983
+
1984
+ // Escape regex special characters in tag names to avoid invalid regex patterns
1985
+ const escapedTagName = tagName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
1986
+ const escapedOldTag = oldTag ? oldTag.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ) : null;
1987
+ if ( oldTag !== undefined && oldTag !== null && tagName !== oldTag ) {
1988
+ findQuery = {
1989
+ clientId,
1990
+ tagName: { $regex: `^${escapedOldTag}$`, $options: 'i' },
1991
+ };
1992
+ } else {
1993
+ findQuery = {
1994
+ clientId,
1995
+ tagName: { $regex: `^${escapedTagName}$`, $options: 'i' },
1996
+ };
1997
+ }
1998
+ await externalService.updateMany( { zoneName: oldTag, clientId: req.body.clientId }, { zoneName: req.body.tagName } );
1999
+ updateOldData( req, res );
2000
+ let customZoneTagDetails = await customZoneTagService.findOne( findQuery );
2001
+
2002
+ if ( !customZoneTagDetails ) {
2003
+ return res.sendError( 'No data found', 400 );
2004
+ }
2005
+
2006
+ // Get the actual oldGroupName from the database record
2007
+ const actualOldGroupName = customZoneTagDetails.groupName || null;
2008
+ // Get the actual tag name currently in the database
2009
+ const actualTagNameInDb = customZoneTagDetails.tagName;
2010
+
2011
+ // Validate that the new tagName does not already exist for this client (case-insensitive)
2012
+ // Check if tagName is being changed (either via oldTag or if it's different from DB value)
2013
+ const isTagNameChanging = ( oldTag && tagName.toLowerCase() !== oldTag.toLowerCase() ) || ( actualTagNameInDb && tagName.toLowerCase() !== actualTagNameInDb.toLowerCase() );
2014
+
2015
+ if ( isTagNameChanging ) {
2016
+ const existingTagWithNewName = await customZoneTagService.findOne( {
2017
+ clientId,
2018
+ tagName: { $regex: `^${escapedTagName}$`, $options: 'i' },
2019
+ } );
2020
+
2021
+ // If a tag with the new name exists and it's not the same record we're updating, return error
2022
+ if ( existingTagWithNewName && existingTagWithNewName._id.toString() !== customZoneTagDetails._id.toString() ) {
2023
+ return res.sendError( `zoneName "${tagName}" already exists for this client`, 409 );
2024
+ }
2025
+ }
2026
+
2027
+ let query = {
2028
+ clientId,
2029
+ };
2030
+ let payload = { tagName };
2031
+ if ( oldTag && tagName !== oldTag ) {
2032
+ query.tagName = oldTag;
2033
+ } else {
2034
+ query.tagName = tagName;
2035
+ }
2036
+ if ( groupName ) {
2037
+ payload.groupName = groupName || null;
2038
+ } else {
2039
+ payload.groupName = null;
2040
+ }
2041
+
2042
+ // Determine the tag name to use for group operations
2043
+ // Use the actual tag name from database for removing from old group
2044
+ const tagNameForRemoval = actualTagNameInDb;
2045
+ const newTagNameForGroup = tagName;
2046
+
2047
+ // Handle group changes: remove from old group and add to new group
2048
+ // Compare actual old group name from DB with new group name (case-insensitive)
2049
+ const oldGroupNameStr = actualOldGroupName ? String( actualOldGroupName ).trim().toLowerCase() : null;
2050
+ const newGroupNameStr = groupName ? String( groupName ).trim().toLowerCase() : null;
2051
+
2052
+ if ( oldGroupNameStr !== newGroupNameStr ) {
2053
+ // Remove tag from old group if oldGroupName exists
2054
+ if ( actualOldGroupName ) {
2055
+ // Escape regex special characters in actualOldGroupName
2056
+ const escapedActualOldGroupName = actualOldGroupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
2057
+ const oldGroupDoc = await customzonegrouping.findOne(
2058
+ { clientId, groupName: { $regex: `^${escapedActualOldGroupName}$`, $options: 'i' } },
2059
+ );
2060
+ if ( oldGroupDoc ) {
2061
+ await customzonegrouping.updateOne(
2062
+ { _id: oldGroupDoc._id },
2063
+ { $pull: { zonesTagged: tagNameForRemoval } },
2064
+ );
2065
+ } else {
2066
+ }
2067
+ }
2068
+
2069
+ // Add tag to new group if groupName exists
2070
+ if ( groupName ) {
2071
+ // Escape regex special characters in groupName
2072
+ const escapedGroupName = groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
2073
+ let newGroupDoc = await customzonegrouping.findOne(
2074
+ { clientId, groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' } },
2075
+ );
2076
+
2077
+ if ( !newGroupDoc && !isExistingGroup ) {
2078
+ await customzonegrouping.create(
2079
+ [ {
2080
+ clientId,
2081
+ groupName,
2082
+ productName: req.body.productName,
2083
+ zonesTagged: [ newTagNameForGroup ],
2084
+ } ],
2085
+ );
2086
+ } else if ( newGroupDoc ) {
2087
+ // Add to existing group
2088
+ await customzonegrouping.updateOne(
2089
+ { _id: newGroupDoc._id },
2090
+ { $addToSet: { zonesTagged: newTagNameForGroup } },
2091
+ );
2092
+ }
2093
+ }
2094
+ } else if ( groupName && oldGroupNameStr === newGroupNameStr && oldTag && tagName !== oldTag ) {
2095
+ // If groupName is same (and not null) but tagName changed, update the tagName in the group's zonesTagged
2096
+ console.log( 'Group name same but tagName changed, updating group zonesTagged' );
2097
+ // Escape regex special characters in groupName
2098
+ const escapedGroupName = groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
2099
+ const groupDoc = await customzonegrouping.findOne( { clientId, groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' } } );
2100
+ if ( groupDoc ) {
2101
+ await customzonegrouping.updateOne(
2102
+ { _id: groupDoc._id },
2103
+ [
2104
+ {
2105
+ $set: {
2106
+ zonesTagged: {
2107
+ $setUnion: [
2108
+ {
2109
+ $filter: {
2110
+ input: '$zonesTagged',
2111
+ cond: { $ne: [ '$$this', actualTagNameInDb ] },
2112
+ },
2113
+ },
2114
+ [ tagName ],
2115
+ ],
2116
+ },
2117
+ },
2118
+ },
2119
+ ],
2120
+ );
2121
+ }
2122
+ }
2123
+ // Update tagging collection if groupName changed or tagName changed (similar to addZoneCustomTag)
2124
+ if ( oldTag && tagName !== oldTag ) {
2125
+ // If tagName changed, update tagging collection records with oldTag to have new tagName and groupName
2126
+ await taggingService.updateMany(
2127
+ { clientId, tagName: oldTag },
2128
+ { $set: { tagName, 'groupName': groupName || null, 'coordinates.$[].zoneName': tagName } },
2129
+ );
2130
+ } else if ( groupName !== oldGroupName ) {
2131
+ // If only groupName changed, update tagging collection with new groupName
2132
+ await taggingService.updateMany(
2133
+ { clientId, tagName },
2134
+ { $set: { 'groupName': groupName || null } },
2135
+ );
2136
+ }
2137
+
2138
+ let zonetag = await customZoneTagService.updateOne( query, { $set: payload } );
2139
+ const logObj = {
2140
+ clientId: req.body.clientId,
2141
+ userName: req.user?.userName,
2142
+ email: req.user?.email,
2143
+ date: new Date(),
2144
+ logType: 'zone',
2145
+ logSubType: 'updateZoneCustomTag',
2146
+ changes: [ `zoneName changed from ${oldTag} to ${tagName}` ],
2147
+ eventType: 'update',
2148
+ previous: {
2149
+ tagName: oldTag,
2150
+ },
2151
+ current: {
2152
+ tagName: tagName,
2153
+ },
2154
+ oldData: {
2155
+ TagName: oldTag,
2156
+ },
2157
+ newData: {
2158
+ TagName: tagName,
2159
+ },
2160
+ showTo: [ 'tango', 'client' ],
2161
+ };
2162
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2163
+ if ( zonetag.modifiedCount || zonetag.matchedCount ) {
2164
+ logger.info( 'ZoneName Updated Successfully' );
2165
+ res.sendSuccess( 'ZoneName Updated Successfully' );
2166
+ } else {
2167
+ logger.error( { error: 'something went wrong', function: 'updateZoneCustomTag' } );
2168
+ return res.sendError( 'something went wrong', 500 );
2169
+ }
2170
+ } catch ( error ) {
2171
+ logger.error( { error: error, function: 'updateZoneCustomTag' } );
2172
+ return res.sendError( error, 500 );
2173
+ }
2174
+ };
2175
+
2176
+ export const deleteZoneCustomTag = async ( req, res ) => {
2177
+ try {
2178
+ const { clientId, tagName } = req.body;
2179
+ if ( !clientId || !tagName ) {
2180
+ return res.sendError( 'clientId and zoneName are required', 400 );
2181
+ }
2182
+
2183
+ let zoneTagDetails = await customZoneTagService.findOne( { clientId, tagName } );
2184
+ if ( !zoneTagDetails || zoneTagDetails?.length == 0 ) {
2185
+ return res.sendError( 'No data found', 400 );
2186
+ }
2187
+ updateOldData( req, res );
2188
+ await customZoneTagService.deleteOne( { clientId, tagName } );
2189
+ await taggingService.deleteMany(
2190
+ {
2191
+ clientId,
2192
+ tagName: { $in: tagName },
2193
+ },
2194
+ );
2195
+ const logObj = {
2196
+ clientId: req.body?.clientId,
2197
+ userName: req.user?.userName,
2198
+ email: req.user?.email,
2199
+ date: new Date(),
2200
+ logType: 'zone',
2201
+ logSubType: 'deleteZoneCustomTag',
2202
+ changes: [ `${tagName} zone tag` ],
2203
+ eventType: 'delete',
2204
+ showTo: [ 'client', 'tango' ],
2205
+ };
2206
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2207
+ // return res.sendSuccess( 'ZoneName Deleted Successfully' );
2208
+ } catch ( e ) {
2209
+ logger.error( { error: e, function: 'deleteZoneCustomTag' } );
2210
+ return res.sendError( e, 500 );
2211
+ }
2212
+ };
2213
+
2214
+
2215
+ // setting config - get zone grouping details
2216
+ export const getZoneGroupDetails = async ( req, res ) => {
2217
+ try {
2218
+ const {
2219
+ clientId,
2220
+ searchValue,
2221
+ sortColumName,
2222
+ sortBy,
2223
+ limit,
2224
+ offset = 1,
2225
+ export: isExport,
2226
+ } = req.body;
2227
+
2228
+ if ( !clientId ) {
2229
+ return res.sendError( 'clientId is required', 400 );
2230
+ }
2231
+
2232
+
2233
+ let groupData = await customzonegrouping.findOne( { clientId: clientId } );
2234
+ if ( !groupData ) {
2235
+ return res.sendSuccess( { result: [] } );
2236
+ }
2237
+
2238
+ const searchMatch = searchValue ?
2239
+ { groupName: { $regex: searchValue, $options: 'i' } } :
2240
+ null;
2241
+
2242
+ const pipeline = [
2243
+ { $match: { clientId } },
2244
+
2245
+ {
2246
+ $lookup: {
2247
+ from: 'taggings',
2248
+ let: { groupName: '$groupName' },
2249
+ pipeline: [
2250
+ {
2251
+ $match: {
2252
+ $expr: {
2253
+ $and: [
2254
+ { $eq: [ '$groupName', '$$groupName' ] },
2255
+ { $eq: [ '$clientId', clientId ] },
2256
+ ],
2257
+ },
2258
+ },
2259
+ },
2260
+ ],
2261
+ as: 'tagsCount',
2262
+ },
2263
+ },
2264
+
2265
+ {
2266
+ $lookup: {
2267
+ from: 'customzonetags',
2268
+ let: { groupName: '$groupName' },
2269
+ pipeline: [
2270
+ {
2271
+ $match: {
2272
+ $expr: {
2273
+ $and: [
2274
+ { $eq: [ '$groupName', '$$groupName' ] },
2275
+ { $eq: [ '$clientId', clientId ] },
2276
+ ],
2277
+ },
2278
+ },
2279
+ },
2280
+ ],
2281
+ as: 'zonesTagged',
2282
+ },
2283
+ },
2284
+
2285
+ {
2286
+ $project: {
2287
+ clientId: 1,
2288
+ groupName: 1,
2289
+ zonesTagged: 1,
2290
+ zonesCount: { $size: '$tagsCount' },
2291
+ createdAt: 1,
2292
+ },
2293
+ },
2294
+
2295
+ {
2296
+ $facet: {
2297
+ totalCount: [
2298
+ ...( searchMatch ? [ { $match: searchMatch } ] : [] ),
2299
+ { $count: 'count' },
2300
+ ],
2301
+
2302
+ data: [
2303
+ ...( searchMatch ? [ { $match: searchMatch } ] : [] ),
2304
+
2305
+ ...( sortColumName && sortBy ?
2306
+ [ { $sort: { [sortColumName]: sortBy } } ] :
2307
+ [ { $sort: { createdAt: -1 } } ] ),
2308
+
2309
+ ...( isExport ?
2310
+ [] :
2311
+ limit ? [
2312
+ { $skip: ( offset - 1 ) * limit },
2313
+ { $limit: Number( limit ) },
2314
+ ] : [] ),
2315
+ ],
2316
+ },
2317
+ },
2318
+ ];
2319
+
2320
+ const [ result ] = await customzonegrouping.aggregate( pipeline );
2321
+
2322
+ const zoneGroupList = result.data || [];
2323
+
2324
+ const totalCount = result.totalCount[0]?.count || 0;
2325
+ if ( totalCount === 0 ) {
2326
+ return res.sendError( 'No data', 204 );
2327
+ }
2328
+ // Get total count of zones for tabs (actual count, not filtered by search)
2329
+ const totalZoneCount = await customZoneTagService.count( { clientId } );
2330
+
2331
+ if ( isExport && zoneGroupList.length ) {
2332
+ const exportdata = zoneGroupList.map( ( element ) => ( {
2333
+ 'Zone Group Name': element.groupName || '--',
2334
+ 'Zones tagged': Array.isArray( element.zonesTagged ) ?
2335
+ element.zonesTagged.map( ( zone ) => zone.tagName ).filter( Boolean ).
2336
+ join( ', ' ) : '--',
2337
+ 'Zones tagged Count': element.zonesTagged.length || 0,
2338
+ } ) );
2339
+ await download( exportdata, res );
2340
+ return;
2341
+ }
2342
+
2343
+ return res.sendSuccess( {
2344
+ result: zoneGroupList,
2345
+ totalCount,
2346
+ totalZoneCount,
2347
+ } );
2348
+ } catch ( e ) {
2349
+ logger.error( { error: e, function: 'getZoneGroupDetails' } );
2350
+ return res.sendError( e, 500 );
2351
+ }
2352
+ // try {
2353
+ // const inputData = req.body;
2354
+ // if ( !inputData.clientId ) {
2355
+ // return res.sendError( 'clientId is required', 400 );
2356
+ // }
2357
+
2358
+ // let Query = [
2359
+ // {
2360
+ // $match: {
2361
+ // clientId: inputData.clientId,
2362
+ // },
2363
+ // },
2364
+ // {
2365
+ // $lookup: {
2366
+ // from: 'taggings',
2367
+ // let: { groupName: '$groupName' },
2368
+ // pipeline: [
2369
+ // {
2370
+ // $match: {
2371
+ // $expr: {
2372
+ // $and: [
2373
+ // {
2374
+ // $eq: [ '$groupName', '$$groupName' ],
2375
+ // },
2376
+ // {
2377
+ // $eq: [ '$clientId', inputData.clientId ],
2378
+ // },
2379
+ // ],
2380
+ // },
2381
+ // },
2382
+ // },
2383
+ // ], as: 'tagsCount',
2384
+ // },
2385
+ // },
2386
+ // {
2387
+ // $lookup: {
2388
+ // from: 'customzonetags',
2389
+ // let: { groupName: '$groupName' },
2390
+ // pipeline: [
2391
+ // {
2392
+ // $match: {
2393
+ // $expr: {
2394
+ // $and: [
2395
+ // {
2396
+ // $eq: [ '$groupName', '$$groupName' ],
2397
+ // },
2398
+ // {
2399
+ // $eq: [ '$clientId', inputData.clientId ],
2400
+ // },
2401
+ // ],
2402
+ // },
2403
+ // },
2404
+ // },
2405
+ // ], as: 'zonesTagged',
2406
+ // },
2407
+ // },
2408
+ // {
2409
+ // $project: {
2410
+ // clientId: 1,
2411
+ // groupName: 1,
2412
+ // zonesTagged: 1,
2413
+ // zonesCount: { $size: '$tagsCount' },
2414
+ // },
2415
+ // },
2416
+ // ];
2417
+
2418
+ // if ( req.body.searchValue && req.body.searchValue !== '' ) {
2419
+ // Query.push( {
2420
+ // $match: {
2421
+ // $or: [
2422
+ // { groupName: { $regex: req.body.searchValue, $options: 'i' } },
2423
+ // ],
2424
+ // },
2425
+ // } );
2426
+ // }
2427
+
2428
+ // if ( req.body.sortColumName && req.body.sortColumName !== '' && req.body.sortBy ) {
2429
+ // Query.push( {
2430
+ // $sort: { [req.body.sortColumName]: req.body.sortBy },
2431
+ // } );
2432
+ // } else {
2433
+ // // Default sort by createdAt descending if no sort specified
2434
+ // Query.push( {
2435
+ // $sort: { createdAt: -1 },
2436
+ // } );
2437
+ // }
2438
+
2439
+ // // Get total count before pagination
2440
+ // const countQuery = [ ...Query ];
2441
+ // const totalCountResult = await customzonegrouping.aggregate( [
2442
+ // ...countQuery,
2443
+ // { $count: 'total' },
2444
+ // ] );
2445
+ // console.log( '🚀 ~ getZoneGroupDetails ~ totalCountResult:', totalCountResult );
2446
+ // const totalCount = totalCountResult.length > 0 ? totalCountResult[0].total : 0;
2447
+
2448
+ // if ( req.body.limit && req.body.offset && !req.body.export ) {
2449
+ // Query.push(
2450
+ // { $skip: ( req.body.offset - 1 ) * req.body.limit },
2451
+ // { $limit: Number( req.body.limit ) },
2452
+ // );
2453
+ // }
2454
+
2455
+ // const zoneGroupList = await customzonegrouping.aggregate( Query );
2456
+
2457
+ // if ( req.body.export && zoneGroupList.length > 0 ) {
2458
+ // const exportdata = [];
2459
+ // zoneGroupList.forEach( ( element ) => {
2460
+ // const data = {
2461
+ // 'Zone Group Name': element.groupName || '--',
2462
+ // 'Zones tagged': Array.isArray( element.zonesTagged ) ? element.zonesTagged.join( ', ' ) : '--',
2463
+ // 'Zones tagged Count': element.zonesCount || 0,
2464
+ // // 'Stores Tagged Count': element.storesTaggedCount || 0,
2465
+ // };
2466
+ // exportdata.push( data );
2467
+ // } );
2468
+ // await download( exportdata, res );
2469
+ // return;
2470
+ // }
2471
+
2472
+ // return res.sendSuccess( {
2473
+ // result: zoneGroupList,
2474
+ // count: totalCount,
2475
+ // } );
2476
+ // } catch ( e ) {
2477
+ // logger.error( { error: e, function: 'getZoneGroupDetails' } );
2478
+ // console.error( 'getZoneGroupDetails error:', e );
2479
+ // return res.sendError( e, 500 );
2480
+ // }
2481
+ };
2482
+
2483
+ export const addZoneGroup = async ( req, res ) => {
2484
+ try {
2485
+ const { clientId, groupName, zonesTagged = [], productName = '' } = req.body;
2486
+
2487
+ if ( !clientId || !groupName ) {
2488
+ return res.sendError( 'clientId and groupName are required', 400 );
2489
+ }
2490
+
2491
+ // Escape regex special characters in groupName to avoid invalid regex patterns
2492
+ const escapedGroupName = groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
2493
+
2494
+ // Case-insensitive check for existing group
2495
+ const existingGroup = await customzonegrouping.findOne( {
2496
+ clientId,
2497
+ groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' },
2498
+ } );
2499
+
2500
+ if ( existingGroup ) {
2501
+ return res.sendError( `groupName "${groupName}" already exists for this client`, 409 );
2502
+ }
2503
+
2504
+ const payload = {
2505
+ clientId,
2506
+ groupName,
2507
+ zonesTagged: Array.isArray( zonesTagged ) ? zonesTagged : [],
2508
+ productName,
2509
+ };
2510
+
2511
+ await customzonegrouping.create( payload );
2512
+
2513
+ // Update groupName for all zone tags in zonesTagged array
2514
+ if ( Array.isArray( zonesTagged ) && zonesTagged.length > 0 ) {
2515
+ // Update all tags that are in the zonesTagged array to have this groupName
2516
+ await customZoneTagService.updateMany(
2517
+ {
2518
+ clientId,
2519
+ tagName: { $in: zonesTagged },
2520
+ },
2521
+ { $set: { groupName } },
2522
+ );
2523
+ await taggingService.updateMany(
2524
+ {
2525
+ clientId,
2526
+ tagName: { $in: zonesTagged },
2527
+ },
2528
+ { $set: { groupName } },
2529
+ );
2530
+ }
2531
+
2532
+ logger.info( 'Zone Group Created Successfully' );
2533
+
2534
+ const logObj = {
2535
+ clientId,
2536
+ userName: req.user?.userName,
2537
+ email: req.user?.email,
2538
+ date: new Date(),
2539
+ logType: 'zone',
2540
+ logSubType: 'addZoneGroup',
2541
+ changes: [ `${groupName} zone group` ],
2542
+ eventType: 'create',
2543
+ showTo: [ 'client', 'tango' ],
2544
+ };
2545
+
2546
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2547
+ return res.sendSuccess( 'Zone Group Created Successfully' );
2548
+ } catch ( e ) {
2549
+ // Duplicate key error from unique index
2550
+ if ( e.code === 11000 ) {
2551
+ return res.sendError( `groupName "${req.body.groupName}" already exists for this client`, 409 );
2552
+ }
2553
+
2554
+ logger.error( { error: e, function: 'addZoneGroup' } );
2555
+ return res.sendError( e.message || 'Internal Server Error', 500 );
2556
+ }
2557
+ };
2558
+
2559
+ export const updateZoneGroup = async ( req, res ) => {
2560
+ try {
2561
+ const { _id, clientId, groupName, productName, zonesTagged = [] } = req.body;
2562
+
2563
+ if ( !clientId || !groupName ) {
2564
+ return res.sendError( 'clientId and groupName are required', 400 );
2565
+ }
2566
+
2567
+ let findGroup = await customzonegrouping.findOne( { _id: _id } );
2568
+
2569
+ // Escape regex special characters in groupName to avoid invalid regex patterns
2570
+ const escapedGroupName = groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
2571
+
2572
+ // Case-insensitive check for existing group (excluding current group)
2573
+ const existingGroup = await customzonegrouping.findOne( {
2574
+ clientId: clientId,
2575
+ groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' },
2576
+ _id: { $ne: _id },
2577
+ } );
2578
+ if ( existingGroup && existingGroup.groupName ) {
2579
+ return res.sendError( `groupName "${groupName}" already exists for this client`, 409 );
2580
+ }
2581
+
2582
+ if ( findGroup ) {
2583
+ let removedTags = findGroup.zonesTagged.filter( ( zone ) => !zonesTagged.includes( zone ) ); ;
2584
+ if ( removedTags && removedTags.length > 0 ) {
2585
+ let updateQuery = { clientId, tagName: { $in: removedTags } };
2586
+ await customZoneTagService.updateMany( updateQuery, { $set: { groupName: null } } );
2587
+ await taggingService.updateMany( updateQuery, { $set: { groupName: null } } );
2588
+ }
2589
+ }
2590
+
2591
+
2592
+ await customzonegrouping.updateOne( { _id: _id }, { groupName: groupName, zonesTagged: zonesTagged } );
2593
+ let updateQuery = { clientId, tagName: { $in: zonesTagged } };
2594
+ await customZoneTagService.updateMany( updateQuery, { $set: { groupName } } );
2595
+ await taggingService.updateMany( updateQuery, { $set: { groupName } } );
2596
+
2597
+
2598
+ const logObj = {
2599
+ clientId: clientId,
2600
+ userName: req.user?.userName,
2601
+ email: req.user?.email,
2602
+ date: new Date(),
2603
+ logType: 'zone',
2604
+ logSubType: 'updateZoneGroup',
2605
+ changes: [ `zone group updated from "${findGroup.groupName || groupName}" to "${groupName}"` ],
2606
+ eventType: 'update',
2607
+ previous: {
2608
+ groupName: findGroup.groupName || groupName,
2609
+ productName: findGroup.productName,
2610
+ zonesTagged: findGroup.zonesTagged,
2611
+ },
2612
+ current: {
2613
+ groupName: groupName,
2614
+ productName: productName,
2615
+ zonesTagged: zonesTagged,
2616
+ },
2617
+ oldData: {
2618
+ GroupName: findGroup.groupName || groupName,
2619
+ },
2620
+ newData: {
2621
+ GroupName: groupName,
2622
+ },
2623
+ showTo: [ 'client', 'tango' ],
2624
+ };
2625
+
2626
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2627
+
2628
+ return res.sendSuccess( 'Zone Group Updated Successfully' );
2629
+ } catch ( e ) {
2630
+ // Duplicate key error from unique index
2631
+ if ( e.code === 11000 ) {
2632
+ return res.sendError( `groupName "${req.body.groupName}" already exists for this client`, 409 );
2633
+ }
2634
+
2635
+ logger.error( { error: e, function: 'updateZoneGroup' } );
2636
+ return res.sendError( e.message || 'Internal Server Error', 500 );
2637
+ }
2638
+ };
2639
+
2640
+ export const uploadBulkZoneGroup = async ( req, res ) => {
2641
+ try {
2642
+ const groupsArray = req.body;
2643
+ const clientId = req.bulkZoneClientId;
2644
+
2645
+ if ( !groupsArray || !Array.isArray( groupsArray ) || groupsArray.length === 0 ) {
2646
+ return res.sendError( 'group-config file must not be empty', 400 );
2647
+ }
2648
+
2649
+ if ( !clientId ) {
2650
+ return res.sendError( 'clientId is required', 400 );
2651
+ }
2652
+
2653
+ const zoneGroupDataList = [];
2654
+ const createdGroupNames = [];
2655
+
2656
+ // Process each group in the array
2657
+ for ( const group of groupsArray ) {
2658
+ if ( !group.groupName || group.groupName === '' ) {
2659
+ continue; // Skip groups without groupName
2660
+ }
2661
+
2662
+ const zoneGroupData = {
2663
+ clientId: clientId,
2664
+ groupName: group.groupName,
2665
+ productName: group.productName,
2666
+ };
2667
+ zoneGroupDataList.push( zoneGroupData );
2668
+ createdGroupNames.push( group.groupName );
2669
+ }
2670
+
2671
+ // Create all zone groups
2672
+ if ( zoneGroupDataList.length > 0 ) {
2673
+ await customzonegrouping.create( zoneGroupDataList );
2674
+ }
2675
+
2676
+ logger.info( `Bulk Zone Groups Created Successfully: ${createdGroupNames.length} groups` );
2677
+ const logObj = {
2678
+ clientId: clientId,
2679
+ userName: req.user?.userName,
2680
+ email: req.user?.email,
2681
+ date: new Date(),
2682
+ logType: 'zone',
2683
+ logSubType: 'addBulkZoneGroup',
2684
+ changes: createdGroupNames.map( ( groupName ) => `${groupName} zone group` ),
2685
+ eventType: 'create',
2686
+ showTo: [ 'client', 'tango' ],
2687
+ };
2688
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2689
+ return res.sendSuccess( {
2690
+ message: `Zone Groups Created Successfully`,
2691
+ count: createdGroupNames.length,
2692
+ } );
2693
+ } catch ( e ) {
2694
+ logger.error( { error: e, function: 'uploadBulkZoneGroup' } );
2695
+ return res.sendError( 'Failed to upload group-config file', 500 );
2696
+ }
2697
+ };
2698
+
2699
+ export const deleteZoneGroup = async ( req, res ) => {
2700
+ try {
2701
+ let zoneGroupDetails = await customzonegrouping.findOne( { _id: req.body._id, clientId: req.body.clientId, groupName: req.body.groupName } );
2702
+ if ( !zoneGroupDetails || zoneGroupDetails?.length == 0 ) {
2703
+ return res.sendError( 'no data found', 204 );
2704
+ }
2705
+
2706
+ // Remove this group mapping from all custom zone tags (zones) that are linked to it
2707
+ await customZoneTagService.updateMany(
2708
+ {
2709
+ clientId: req.body.clientId,
2710
+ groupName: req.body.groupName,
2711
+ },
2712
+ { $set: { groupName: null } },
2713
+ );
2714
+ await taggingService.updateMany(
2715
+ {
2716
+ clientId: req.body.clientId,
2717
+ groupName: req.body.groupName,
2718
+ },
2719
+ { $set: { groupName: null } },
2720
+ );
2721
+ await customzonegrouping.deleteOne( { _id: req.body._id, clientId: req.body.clientId, groupName: req.body.groupName } );
2722
+
2723
+ const logObj = {
2724
+ clientId: req.body?.clientId,
2725
+ userName: req.user?.userName,
2726
+ email: req.user?.email,
2727
+ date: new Date(),
2728
+ logType: 'zone',
2729
+ logSubType: 'deleteZoneGroup',
2730
+ changes: [ `${req.body.groupName} zone group` ],
2731
+ eventType: 'delete',
2732
+ showTo: [ 'client', 'tango' ],
2733
+ };
2734
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2735
+ // await updatezoneTagging( req, res );
2736
+ return res.sendSuccess( 'Zone Group Deleted Successfully' );
2737
+ } catch ( e ) {
2738
+ logger.error( { error: e, function: 'deleteZoneGroup' } );
2739
+ return res.sendError( e, 500 );
2740
+ }
2741
+ };
2742
+
2743
+ export async function oldTagsMigration() {
2744
+ let uniqueTags = await taggingService.find( { productName: 'tangoZone', clientId: '440' } );
2745
+ const result = _.uniqBy( uniqueTags, 'tagName' );
2746
+
2747
+
2748
+ for ( let zone of result ) {
2749
+ let obj = {
2750
+ clientId: zone.clientId,
2751
+ tagName: zone.tagName,
2752
+ productName: zone.productName,
2753
+ rgbColor: zone.rgbColor,
2754
+ rgbBorderColor: zone.rgbBorderColor,
2755
+ groupName: null,
2756
+ };
2757
+ await customZoneTagService.create( [ obj ] );
2758
+ }
2759
+ }
2760
+