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

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,12 @@ 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
+
298
856
  await updatezoneTagging( req, res );
299
857
  } catch ( e ) {
300
858
  logger.error( { error: e, function: 'tagging' } );
@@ -308,11 +866,13 @@ export const getCameraList = async ( req, res ) => {
308
866
  if ( !cameraDetails.length ) {
309
867
  return res.sendError( 'no data found', 204 );
310
868
  }
311
- const folderPath = { file_path: `${req.query.storeId}/zone_base_images/`,
869
+ const folderPath = {
870
+ file_path: `${req.query.storeId}/zone_base_images/`,
312
871
  Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage, MaxKeys: 1000,
313
872
  };
314
873
  let fileList = await listFileByPath( folderPath );
315
- const TaggedfolderPath = { file_path: `${req.query.storeId}/zone_tagged_image/`,
874
+ const TaggedfolderPath = {
875
+ file_path: `${req.query.storeId}/zone_tagged_image/`,
316
876
  Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage, MaxKeys: 1000,
317
877
  };
318
878
  let tagFileList = await listFileByPath( TaggedfolderPath );
@@ -338,7 +898,7 @@ export const getCameraList = async ( req, res ) => {
338
898
  tagFileList.data.forEach( ( item ) => {
339
899
  if ( item.Key.length > 1 ) {
340
900
  let splitStream = item.Key.split( '/' );
341
- let getStream = splitStream[splitStream.length -1].split( '.' );
901
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
342
902
 
343
903
  if ( getStream && getStream[0] == `${req.query.storeId}_${camera.streamName}` ) {
344
904
  tagPath = item.Key;
@@ -349,21 +909,23 @@ export const getCameraList = async ( req, res ) => {
349
909
  if ( fileList?.data.length ) {
350
910
  fileList.data.forEach( ( ele ) => {
351
911
  let splitStream = ele.Key.split( '/' );
352
- let getStream = splitStream[splitStream.length -1].split( '.' );
912
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
353
913
  if ( getStream && getStream[0] == `${req.query.storeId}_${camera.streamName}` ) {
354
914
  imgPath = ele.Key;
355
915
  }
356
916
  } );
357
917
  }
358
918
  if ( tagPath ) {
359
- const params = { file_path: tagPath,
919
+ const params = {
920
+ file_path: tagPath,
360
921
  Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage,
361
922
  };
362
923
  const cameraTagImage = await signedUrl( params );
363
924
  camera.tagImg = cameraTagImage;
364
925
  }
365
926
  if ( imgPath ) {
366
- const baseParams = { file_path: imgPath,
927
+ const baseParams = {
928
+ file_path: imgPath,
367
929
  Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage,
368
930
  };
369
931
  const cameraBaseImage = await signedUrl( baseParams );
@@ -379,6 +941,87 @@ export const getCameraList = async ( req, res ) => {
379
941
  return res.sendError( e, 500 );
380
942
  }
381
943
  };
944
+ export const getCameraListv2 = async ( req, res ) => {
945
+ try {
946
+ 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 } );
947
+ if ( !cameraDetails.length ) {
948
+ return res.sendError( 'no data found', 204 );
949
+ }
950
+ const folderPath = {
951
+ file_path: `${req.body.storeId}/zone_base_images/`,
952
+ Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage, MaxKeys: 1000,
953
+ };
954
+ let fileList = await listFileByPath( folderPath );
955
+ const TaggedfolderPath = {
956
+ file_path: `${req.body.storeId}/zone_tagged_image/`,
957
+ Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage, MaxKeys: 1000,
958
+ };
959
+ let tagFileList = await listFileByPath( TaggedfolderPath );
960
+ for ( let [ index, camera ] of cameraDetails.entries() ) {
961
+ let tagList = [];
962
+ let tagPath;
963
+ let imgPath;
964
+ camera = {
965
+ ...camera._doc,
966
+ baseImg: '',
967
+ tagImg: '',
968
+ };
969
+ let taggingDetails = await taggingService.find( { cameraId: camera._id, streamName: camera.streamName, clientId: req.body.clientId, isDeleted: false }, { tagName: 1, coordinates: 1 } );
970
+ if ( taggingDetails.length ) {
971
+ tagList = taggingDetails.filter( ( item ) => item.coordinates.length ).map( ( item ) => {
972
+ if ( item.coordinates.length ) {
973
+ return { tagName: item.tagName, color: item.coordinates[0].color };
974
+ }
975
+ } );
976
+ }
977
+ if ( tagFileList.data.length ) {
978
+ tagFileList.data.forEach( ( item ) => {
979
+ if ( item.Key.length > 1 ) {
980
+ let splitStream = item.Key.split( '/' );
981
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
982
+
983
+ if ( getStream && getStream[0] == `${req.body.storeId}_${camera.streamName}` ) {
984
+ tagPath = item.Key;
985
+ }
986
+ }
987
+ } );
988
+ }
989
+ if ( fileList?.data.length ) {
990
+ fileList.data.forEach( ( ele ) => {
991
+ let splitStream = ele.Key.split( '/' );
992
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
993
+ if ( getStream && getStream[0] == `${req.body.storeId}_${camera.streamName}` ) {
994
+ imgPath = ele.Key;
995
+ }
996
+ } );
997
+ }
998
+ if ( tagPath ) {
999
+ const params = {
1000
+ file_path: tagPath,
1001
+ Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage,
1002
+ };
1003
+ const cameraTagImage = await signedUrl( params );
1004
+ camera.tagImg = cameraTagImage;
1005
+ }
1006
+ if ( imgPath ) {
1007
+ const baseParams = {
1008
+ file_path: imgPath,
1009
+ Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage,
1010
+ };
1011
+ const cameraBaseImage = await signedUrl( baseParams );
1012
+ camera.baseImg = cameraBaseImage;
1013
+ }
1014
+ camera.tagging = tagList;
1015
+ camera.taggedCount = tagList.filter( ( ele ) => ele.tagName === req.body.tagName );
1016
+ cameraDetails[index] = camera;
1017
+ }
1018
+ cameraDetails.sort( ( a, b ) => a.tagging?.length > b.tagging?.length ? -1 : 1 );
1019
+ return res.sendSuccess( cameraDetails );
1020
+ } catch ( e ) {
1021
+ logger.error( { error: e, function: 'getCameraListv2' } );
1022
+ return res.sendError( e, 500 );
1023
+ }
1024
+ };
382
1025
 
383
1026
  export const updateTag = async ( req, res ) => {
384
1027
  try {
@@ -416,9 +1059,21 @@ export const updateTag = async ( req, res ) => {
416
1059
  date: new Date(),
417
1060
  logType: 'zone',
418
1061
  logSubType: 'updateCustomTag',
419
- changes: [ `${req.body.tagName} tagName Updated` ],
420
- eventType: '',
421
- showTo: [ 'client', 'tango' ],
1062
+ changes: [ `${req.body.tagName} tagName` ],
1063
+ eventType: 'update',
1064
+ previous: {
1065
+ tagName: req.body.existTag,
1066
+ },
1067
+ current: {
1068
+ tagName: req.body.tagName,
1069
+ },
1070
+ oldData: {
1071
+ TagName: req.body.existTag,
1072
+ },
1073
+ newData: {
1074
+ TagName: req.body.tagName,
1075
+ },
1076
+ showTo: [ 'tango', 'client' ],
422
1077
  };
423
1078
  insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
424
1079
  if ( tagUpdate.modifiedCount || tagUpdate.matchedCount ) {
@@ -442,19 +1097,24 @@ export const deleteTag = async ( req, res ) => {
442
1097
  if ( !taggingDetails.length ) {
443
1098
  return res.sendError( 'no data found', 204 );
444
1099
  }
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
- } );
1100
+
1101
+ let tagNameDetails = await taggingService.findOne( { clientId: req.body.clientId, tagName: req.body.tagName, $expr: { $ne: [ { $size: '$coordinates' }, 0 ] } } );
1102
+ if ( tagNameDetails ) {
1103
+ return res.sendError( 'This zone tag is already mapped to other stores.', 400 );
456
1104
  }
457
- if ( req.user?.role == 'superadmin' || ( req.user?.role == 'admin' && enableDelete ) ) {
1105
+ // let enableDelete = false;
1106
+ // if ( req.user.permission ) {
1107
+ // let permissions = req.user.permission;
1108
+ // permissions.forEach( ( permission ) => {
1109
+ // if ( permission.featureName == 'analytics' ) {
1110
+ // let product = permission.product.find( ( item ) => item.name == 'tangoZone' );
1111
+ // if ( product ) {
1112
+ // enableDelete = product.isDelete;
1113
+ // }
1114
+ // }
1115
+ // } );
1116
+ // }
1117
+ if ( req.user?.role == 'superadmin' || ( req.user?.role == 'admin' ) ) {
458
1118
  await taggingService.deleteMany( { clientId: req.body.clientId, tagName: req.body.tagName } );
459
1119
  await externalService.deleteMany( { zoneName: req.body.tagName, clientId: req.body.clientId } );
460
1120
  } else {
@@ -484,9 +1144,9 @@ export const deleteTag = async ( req, res ) => {
484
1144
  email: req.user?.email,
485
1145
  date: new Date(),
486
1146
  logType: 'zone',
487
- logSubType: 'deleteTag',
488
- changes: [ `${req.body.tagName} tag Deleted` ],
489
- eventType: '',
1147
+ logSubType: 'deleteCustomTag',
1148
+ changes: [ `${req.body.tagName} tag` ],
1149
+ eventType: 'delete',
490
1150
  showTo: [ 'client', 'tango' ],
491
1151
  };
492
1152
  insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
@@ -642,36 +1302,65 @@ export const updatezoneTagging = async ( req, res ) => {
642
1302
 
643
1303
  export const updateOldData = async ( req, res ) => {
644
1304
  try {
645
- let tagDetails = await taggingService.find( {}, { storeId: 1 } );
1305
+ let tagDetails = await taggingService.find( { clientId: req.body.clientId, tagName: req.body.tagName }, { storeId: 1 } );
1306
+
1307
+
646
1308
  if ( tagDetails.length ) {
647
1309
  for ( let [ index, item ] of tagDetails.entries() ) {
648
1310
  req.body.storeId = item.storeId;
649
1311
  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' );
1312
+ console.log( '🚀 ~ updateOldData ~ camDetails:', camDetails );
1313
+ if ( camDetails ) {
1314
+ const response = await axios.post( JSON.parse( process.env.URL ).zoneTaggingLamdaUrl, camDetails );
1315
+
1316
+ if ( response?.data?.status && response?.data?.status == 'success' ) {
1317
+ let fileData = {
1318
+ Key: `${req.body.storeId}/zonetagging/`,
1319
+ fileName: `${req.body.storeId}_zonetagging.json`,
1320
+ Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage,
1321
+ body: JSON.stringify( camDetails ),
1322
+ ContentType: 'application/json',
1323
+ };
1324
+
1325
+ let upload = await fileUpload( fileData );
1326
+ if ( !upload.Key ) {
1327
+ logger.error( { message: `JSON Upload Error`, store: item.storeId } );
1328
+ }
1329
+ } else {
1330
+ logger.error( { message: 'no data', store: item.storeId } );
1331
+ return res.sendError( 'something went wrong', 500 );
668
1332
  }
669
- } else {
670
- logger.error( { message: 'no data', store: item.storeId } );
671
- // return res.sendError( 'something went wrong', 500 );
1333
+ }
1334
+ if ( index == tagDetails.length-1 ) {
1335
+ res.sendSuccess( 'Zone Updated Successfully' );
672
1336
  }
673
1337
  }
1338
+ } else {
1339
+ res.sendSuccess( 'Zone Updated Successfully' );
674
1340
  }
1341
+ } catch ( e ) {
1342
+ console.log( '🚀 ~ updateOldData ~ e:', e );
1343
+ logger.error( { error: e, function: 'updateOldData' } );
1344
+ return res.sendError( e, 500 );
1345
+ }
1346
+ };
1347
+ export async function updateCamera( req, res ) {
1348
+ try {
1349
+ let findoneCheckList = await checklistconfigService.findOne( {
1350
+ client_id: req.body.clientId, checkListName: req.body.selectedZone,
1351
+ } );
1352
+
1353
+ let updateData = {
1354
+ checkListName: req.body.selectedZone,
1355
+ sourceCheckList_id: findoneCheckList?.sourceCheckList_id,
1356
+ };
1357
+ if ( req.body.type === 'tagCamera' ) {
1358
+ await cameraService.updateOne( { _id: req.body._id }, { $push: { taggedChecklist: updateData } } );
1359
+ } else {
1360
+ await cameraService.updateOne( { _id: req.body._id }, { $pull: { taggedChecklist: updateData } } );
1361
+ }
1362
+
1363
+ res.sendSuccess( 'updated Sucessfully' );
675
1364
  } catch ( e ) {
676
1365
  logger.error( { error: e, function: 'updateOldData' } );
677
1366
  return res.sendError( e, 500 );
@@ -715,3 +1404,1344 @@ async function updateJsonFile( req, res ) {
715
1404
  }
716
1405
  }
717
1406
  };
1407
+
1408
+ export const getCameraStreamList = async ( req, res ) => {
1409
+ try {
1410
+ 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 } );
1411
+ if ( !cameraDetails.length ) {
1412
+ return res.sendError( 'no data found', 204 );
1413
+ }
1414
+ const folderPath = {
1415
+ file_path: `${req.body.storeId}/zone_base_images/`,
1416
+ Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage, MaxKeys: 1000,
1417
+ };
1418
+ let fileList = await listFileByPath( folderPath );
1419
+ const TaggedfolderPath = {
1420
+ file_path: `${req.body.storeId}/zone_tagged_image/`,
1421
+ Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage, MaxKeys: 1000,
1422
+ };
1423
+ let tagFileList = await listFileByPath( TaggedfolderPath );
1424
+ for ( let [ index, camera ] of cameraDetails.entries() ) {
1425
+ let tagList = [];
1426
+ let tagPath;
1427
+ let imgPath;
1428
+ camera = {
1429
+ ...camera._doc,
1430
+ baseImg: '',
1431
+ tagImg: '',
1432
+ };
1433
+ let taggingDetails = await taggingService.find( { cameraId: camera._id, streamName: camera.streamName, clientId: req.body.clientId, isDeleted: false }, { tagName: 1, coordinates: 1 } );
1434
+ if ( taggingDetails.length ) {
1435
+ tagList = taggingDetails.filter( ( item ) => item.coordinates.length ).map( ( item ) => {
1436
+ if ( item.coordinates.length ) {
1437
+ return { tagName: item.tagName, color: item.coordinates[0].color };
1438
+ }
1439
+ } );
1440
+ }
1441
+
1442
+ if ( tagFileList.data.length ) {
1443
+ tagFileList.data.forEach( ( item ) => {
1444
+ if ( item.Key.length > 1 ) {
1445
+ let splitStream = item.Key.split( '/' );
1446
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
1447
+
1448
+ if ( getStream && getStream[0] == `${req.body.storeId}_${camera.streamName}` ) {
1449
+ tagPath = item.Key;
1450
+ }
1451
+ }
1452
+ } );
1453
+ }
1454
+ if ( fileList?.data.length ) {
1455
+ fileList.data.forEach( ( ele ) => {
1456
+ let splitStream = ele.Key.split( '/' );
1457
+ let getStream = splitStream[splitStream.length - 1].split( '.' );
1458
+ if ( getStream && getStream[0] == `${req.body.storeId}_${camera.streamName}` ) {
1459
+ imgPath = ele.Key;
1460
+ }
1461
+ } );
1462
+ }
1463
+ if ( tagPath ) {
1464
+ const params = {
1465
+ file_path: tagPath,
1466
+ Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage,
1467
+ };
1468
+ const cameraTagImage = await signedUrl( params );
1469
+ camera.tagImg = cameraTagImage;
1470
+ }
1471
+ if ( imgPath ) {
1472
+ const baseParams = {
1473
+ file_path: imgPath,
1474
+ Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage,
1475
+ };
1476
+ const cameraBaseImage = await signedUrl( baseParams );
1477
+ camera.baseImg = cameraBaseImage;
1478
+ }
1479
+ camera.tagging = tagList;
1480
+ cameraDetails[index] = camera;
1481
+ }
1482
+ cameraDetails.sort( ( a, b ) => a.tagging?.length > b.tagging?.length ? -1 : 1 );
1483
+ return res.sendSuccess( cameraDetails );
1484
+ } catch ( e ) {
1485
+ logger.error( { error: e, function: 'getCameraList' } );
1486
+ return res.sendError( e, 500 );
1487
+ }
1488
+ };
1489
+
1490
+
1491
+ // setting config - zone - get tagging details
1492
+ export const getZoneTaggingDetails = async ( req, res ) => {
1493
+ try {
1494
+ const {
1495
+ clientId,
1496
+ searchValue,
1497
+ sortColumName,
1498
+ sortBy,
1499
+ limit,
1500
+ offset = 1,
1501
+ export: isExport,
1502
+ } = req.body;
1503
+
1504
+ if ( !clientId ) {
1505
+ return res.sendError( 'clientId is required', 400 );
1506
+ }
1507
+
1508
+ const matchStage = {
1509
+ clientId,
1510
+ };
1511
+ let customTags = await customZoneTagService.findOne( { clientId: clientId } );
1512
+ const alltotalGroupCount = await customzonegrouping.count( { clientId } );
1513
+ if ( !customTags ) {
1514
+ return res.sendSuccess( { result: [], totalGroupCount: alltotalGroupCount } );
1515
+ }
1516
+ const searchStage = searchValue ?
1517
+ {
1518
+ $or: [
1519
+ { tagName: { $regex: searchValue, $options: 'i' } },
1520
+ { groupName: { $regex: searchValue, $options: 'i' } },
1521
+ ],
1522
+ } :
1523
+ null;
1524
+
1525
+ const pipeline = [
1526
+ { $match: matchStage },
1527
+ {
1528
+ $lookup: {
1529
+ from: 'taggings',
1530
+ let: { tagName: '$tagName' },
1531
+ pipeline: [
1532
+ {
1533
+ $match: {
1534
+ $expr: {
1535
+ $and: [
1536
+ { $eq: [ '$tagName', '$$tagName' ] },
1537
+ { $eq: [ '$clientId', clientId ] },
1538
+ { $ne: [ '$coordinates', [] ] },
1539
+ ],
1540
+ },
1541
+ },
1542
+ },
1543
+ {
1544
+ $group: {
1545
+ _id: '$storeId',
1546
+ count: { $sum: 1 },
1547
+ },
1548
+ },
1549
+ ],
1550
+ as: 'tagsCount',
1551
+ },
1552
+ },
1553
+ {
1554
+ $project: {
1555
+ clientId: 1,
1556
+ tagName: 1,
1557
+ groupName: 1,
1558
+ storesTaggedCount: { $size: '$tagsCount' },
1559
+ },
1560
+ },
1561
+ {
1562
+ $facet: {
1563
+ totalCount: [
1564
+ ...( searchStage ? [ { $match: searchStage } ] : [] ),
1565
+ { $count: 'count' },
1566
+ ],
1567
+
1568
+ filteredData: [
1569
+ ...( searchStage ? [ { $match: searchStage } ] : [] ),
1570
+ ...( sortColumName && sortBy ?
1571
+ [ { $sort: { [sortColumName]: sortBy } } ] :
1572
+ [] ),
1573
+ ...( isExport ?
1574
+ [] :
1575
+ limit ? [
1576
+ { $skip: ( offset - 1 ) * limit },
1577
+ { $limit: Number( limit ) },
1578
+ ] : [] ),
1579
+ ],
1580
+ },
1581
+ },
1582
+ ];
1583
+
1584
+ const [ result ] = await customZoneTagService.aggregate( pipeline );
1585
+
1586
+ const totalCount = result.totalCount[0]?.count || 0;
1587
+ if ( totalCount === 0 ) {
1588
+ return res.sendError( 'No data', 204 );
1589
+ }
1590
+ const zoneList = result.filteredData;
1591
+
1592
+ // Get total count of groups for tabs (actual count, not filtered by search)
1593
+ const totalGroupCount = await customzonegrouping.count( { clientId } );
1594
+
1595
+ if ( isExport && zoneList.length ) {
1596
+ const exportdata = zoneList.map( ( z ) => ( {
1597
+ 'Zone Name': z.tagName || '--',
1598
+ 'Zone Groups': z.groupName || '--',
1599
+ 'Stores tagged Count': z.storesTaggedCount || 0,
1600
+ } ) );
1601
+ await download( exportdata, res );
1602
+ return;
1603
+ }
1604
+
1605
+ return res.sendSuccess( {
1606
+ result: zoneList,
1607
+ totalCount,
1608
+ totalGroupCount,
1609
+ } );
1610
+ } catch ( e ) {
1611
+ logger.error( { error: e, function: 'getZoneTaggingDetails' } );
1612
+ return res.sendError( e, 500 );
1613
+ }
1614
+ // try {
1615
+ // const inputData = req.body;
1616
+ // let Query = [
1617
+ // {
1618
+ // $match: {
1619
+ // clientId: inputData.clientId,
1620
+ // },
1621
+ // },
1622
+ // {
1623
+ // $lookup: {
1624
+ // from: 'taggings',
1625
+ // let: { tagName: '$tagName' },
1626
+ // pipeline: [
1627
+ // {
1628
+ // $match: {
1629
+ // $expr: {
1630
+ // $and: [
1631
+ // {
1632
+ // $eq: [ '$tagName', '$$tagName' ],
1633
+ // },
1634
+ // {
1635
+ // $eq: [ '$clientId', inputData.clientId ],
1636
+ // },
1637
+ // ],
1638
+ // },
1639
+ // },
1640
+ // },
1641
+ // ], as: 'tagsCount',
1642
+ // },
1643
+ // },
1644
+ // {
1645
+ // $project: {
1646
+ // clientId: 1,
1647
+ // tagName: 1,
1648
+ // groupName: 1,
1649
+ // storesTaggedCount: { $size: '$tagsCount' },
1650
+ // },
1651
+ // },
1652
+ // ];
1653
+ // if ( req.body.searchValue && req.body.searchValue !== '' ) {
1654
+ // Query.push( {
1655
+ // $match: {
1656
+ // $or: [
1657
+ // { tagName: { $regex: req.body.searchValue, $options: 'i' } },
1658
+ // { groupName: { $regex: req.body.searchValue, $options: 'i' } },
1659
+ // ],
1660
+ // },
1661
+ // } );
1662
+ // }
1663
+
1664
+ // if ( req.body.sortColumName && req.body.sortColumName !== '' && req.body.sortBy ) {
1665
+ // Query.push( {
1666
+ // $sort: { [req.body.sortColumName]: req.body.sortBy },
1667
+ // } );
1668
+ // }
1669
+
1670
+ // const totalCount = await customZoneTagService.aggregate( Query );
1671
+ // if ( req.body.limit && req.body.offset && !req.body.export ) {
1672
+ // Query.push(
1673
+ // { $skip: ( req.body.offset - 1 ) * req.body.limit },
1674
+ // { $limit: Number( req.body.limit ) },
1675
+ // );
1676
+ // }
1677
+ // const zoneList = await customZoneTagService.aggregate( Query );
1678
+
1679
+ // if ( req.body.export && zoneList.length > 0 ) {
1680
+ // const exportdata = [];
1681
+ // zoneList.forEach( ( element ) => {
1682
+ // const data = {
1683
+ // 'Zone Name': element.tagName || '--',
1684
+ // 'Zone Groups': element.groupName || '--',
1685
+ // 'Stores tagged Count': element.storesTaggedCount || 0,
1686
+ // };
1687
+ // exportdata.push( data );
1688
+ // } );
1689
+ // await download( exportdata, res );
1690
+ // return;
1691
+ // }
1692
+
1693
+ // return res.sendSuccess( {
1694
+ // result: zoneList,
1695
+ // count: totalCount.length,
1696
+ // } );
1697
+ // } catch ( e ) {
1698
+ // logger.error( { error: e, function: 'getZoneTaggingDetails' } );
1699
+ // console.error( 'getZoneTaggingDetails error:', e );
1700
+ // return res.sendError( e, 500 );
1701
+ // }
1702
+ };
1703
+
1704
+ export const addZoneCustomTag = async ( req, res ) => {
1705
+ try {
1706
+ const {
1707
+ clientId,
1708
+ tagName,
1709
+ groupName,
1710
+ productName,
1711
+ isExistingGroup,
1712
+ rgbColor,
1713
+ rgbBorderColor,
1714
+ _id,
1715
+ } = req.body;
1716
+
1717
+ if ( !clientId || !tagName ) {
1718
+ return res.sendError( 'clientId and zoneName are required', 400 );
1719
+ }
1720
+
1721
+ // Escape regex special characters in tagName to avoid invalid regex patterns
1722
+ const escapedTagName = tagName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
1723
+
1724
+ // Case-insensitive check for existing tag
1725
+ const existingTag = await customZoneTagService.findOne(
1726
+ { clientId, tagName: { $regex: `^${escapedTagName}$`, $options: 'i' }, _id: { $ne: _id } },
1727
+ );
1728
+
1729
+ if ( existingTag ) {
1730
+ return res.sendError( `zoneName "${tagName}" already exists for this client`, 409 );
1731
+ }
1732
+
1733
+ let groupDoc = null;
1734
+ if ( groupName ) {
1735
+ // Escape regex special characters in groupName to avoid invalid regex patterns
1736
+ const escapedGroupName = groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
1737
+ // Case-insensitive check for existing group
1738
+ groupDoc = await customzonegrouping.findOne( { clientId, groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' } } );
1739
+ }
1740
+
1741
+ if ( !isExistingGroup && groupDoc ) {
1742
+ return res.sendError( `groupName "${groupName}" already exists for this client`, 409 );
1743
+ }
1744
+
1745
+ /** Create Zone Tag */
1746
+ await customZoneTagService.create(
1747
+ [ {
1748
+ clientId,
1749
+ tagName,
1750
+ rgbColor,
1751
+ rgbBorderColor,
1752
+ groupName: groupName || null,
1753
+ productName,
1754
+ } ],
1755
+ );
1756
+
1757
+ /** Create or Update Zone Group */
1758
+ if ( groupName ) {
1759
+ if ( !groupDoc && !isExistingGroup ) {
1760
+ await customzonegrouping.create(
1761
+ [ {
1762
+ clientId,
1763
+ groupName,
1764
+ productName,
1765
+ zonesTagged: [ tagName ],
1766
+ } ],
1767
+ );
1768
+ } else if ( groupDoc && isExistingGroup ) {
1769
+ await customzonegrouping.updateOne(
1770
+ { _id: groupDoc._id },
1771
+ { $addToSet: { zonesTagged: tagName } },
1772
+ );
1773
+ }
1774
+ }
1775
+
1776
+ await taggingService.updateMany( { clientId, tagName }, { $set: { groupName } } );
1777
+
1778
+ logger.info( 'Zone Tag Created Successfully' );
1779
+ const logObj = {
1780
+ clientId: clientId,
1781
+ userName: req.user?.userName,
1782
+ email: req.user?.email,
1783
+ date: new Date(),
1784
+ logType: 'zone',
1785
+ logSubType: 'addZoneCustomTag',
1786
+ changes: [ `${tagName} custom zone tag` ],
1787
+ eventType: 'create',
1788
+ showTo: [ 'client', 'tango' ],
1789
+ };
1790
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
1791
+
1792
+ return res.sendSuccess( 'Zone Tag Created Successfully' );
1793
+ } catch ( e ) {
1794
+ logger.error( { error: e, function: 'addZoneCustomTag' } );
1795
+ return res.sendError( e.message || e, 500 );
1796
+ }
1797
+ };
1798
+
1799
+ export const uploadBulkZoneTag = async ( req, res ) => {
1800
+ try {
1801
+ const zonesArray = req.body;
1802
+ const clientId = req.bulkZoneClientId;
1803
+
1804
+ if ( !zonesArray || !Array.isArray( zonesArray ) || zonesArray.length === 0 ) {
1805
+ return res.sendError( 'zone-config file must not be empty', 400 );
1806
+ }
1807
+
1808
+ if ( !clientId ) {
1809
+ return res.sendError( 'clientId is required', 400 );
1810
+ }
1811
+
1812
+ const zoneTagDataList = [];
1813
+ const zoneGroupDataMap = new Map();
1814
+ const createdTagNames = [];
1815
+ // Map to store user-provided groupName (any case) -> actual groupName from DB (preserving existing case)
1816
+ const groupNameMapping = new Map();
1817
+
1818
+ // First pass: Check for existing groups and create mapping
1819
+ const uniqueGroupNames = [ ...new Set( zonesArray.map( ( zone ) => zone.groupName ).filter( ( name ) => name && name !== '' && name !== null ) ) ];
1820
+
1821
+ for ( const userGroupName of uniqueGroupNames ) {
1822
+ // Escape regex special characters in group name to avoid invalid regex patterns
1823
+ const escapedUserGroupName = userGroupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
1824
+
1825
+ // Case-insensitive check for existing group
1826
+ const existingGroupDoc = await customzonegrouping.findOne( {
1827
+ clientId,
1828
+ groupName: { $regex: `^${escapedUserGroupName}$`, $options: 'i' },
1829
+ } );
1830
+
1831
+ if ( existingGroupDoc ) {
1832
+ // Group exists - use the existing group's case
1833
+ groupNameMapping.set( userGroupName.toLowerCase(), existingGroupDoc.groupName );
1834
+ } else {
1835
+ // New group - use user-provided case
1836
+ groupNameMapping.set( userGroupName.toLowerCase(), userGroupName );
1837
+ }
1838
+ }
1839
+
1840
+ // Process each zone in the array
1841
+ for ( const zone of zonesArray ) {
1842
+ // Determine the correct groupName to use (existing case if group exists, user case if new)
1843
+ let normalizedGroupName = null;
1844
+ if ( zone.groupName && zone.groupName !== '' && zone.groupName !== null ) {
1845
+ normalizedGroupName = groupNameMapping.get( zone.groupName.toLowerCase() ) || zone.groupName;
1846
+ }
1847
+
1848
+ const zoneTagData = {
1849
+ clientId: clientId,
1850
+ tagName: zone.tagName,
1851
+ rgbColor: zone.rgbColor,
1852
+ rgbBorderColor: zone.rgbBorderColor,
1853
+ groupName: normalizedGroupName,
1854
+ productName: zone.productName,
1855
+ };
1856
+ zoneTagDataList.push( zoneTagData );
1857
+ createdTagNames.push( zone.tagName );
1858
+
1859
+ // Prepare zone group data if groupName is provided
1860
+ if ( normalizedGroupName ) {
1861
+ // Use normalized groupName (existing case or user case)
1862
+ if ( !zoneGroupDataMap.has( normalizedGroupName ) ) {
1863
+ zoneGroupDataMap.set( normalizedGroupName, {
1864
+ clientId: clientId,
1865
+ groupName: normalizedGroupName,
1866
+ productName: zone.productName,
1867
+ zonesTagged: new Set(),
1868
+ } );
1869
+ }
1870
+
1871
+ // Collect all tagNames for this group so that we can append them
1872
+ const groupEntry = zoneGroupDataMap.get( normalizedGroupName );
1873
+ groupEntry.zonesTagged.add( zone.tagName );
1874
+ }
1875
+ }
1876
+
1877
+ // Create all zone tags
1878
+ await customZoneTagService.create( zoneTagDataList );
1879
+
1880
+ // Create zone groups (convert Map values to array)
1881
+ // const zoneGroupDataList = Array.from( zoneGroupDataMap.values() );
1882
+ // console.log( 'zoneGroupDataList', zoneGroupDataList );
1883
+ // if ( zoneGroupDataList.length > 0 ) {
1884
+ // for ( const groupData of zoneGroupDataList ) {
1885
+ // await customzonegrouping.create( groupData );
1886
+ // }
1887
+ // }
1888
+
1889
+ // Create or Update Zone Groups (following addZoneCustomTag pattern)
1890
+ const zoneGroupDataList = Array.from( zoneGroupDataMap.values() ).map( ( groupData ) => ( {
1891
+ clientId: groupData.clientId,
1892
+ groupName: groupData.groupName,
1893
+ productName: groupData.productName,
1894
+ zonesTagged: Array.from( groupData.zonesTagged || [] ),
1895
+ } ) );
1896
+
1897
+ if ( zoneGroupDataList.length > 0 ) {
1898
+ for ( const groupData of zoneGroupDataList ) {
1899
+ // Case-insensitive check for existing group (same as addZoneCustomTag)
1900
+ const escapedGroupName = groupData.groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
1901
+ const groupDoc = await customzonegrouping.findOne( {
1902
+ clientId,
1903
+ groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' },
1904
+ } );
1905
+
1906
+ if ( !groupDoc ) {
1907
+ // Create new group with all zones from the bulk file
1908
+ await customzonegrouping.create( [ {
1909
+ clientId: groupData.clientId,
1910
+ groupName: groupData.groupName,
1911
+ productName: groupData.productName,
1912
+ zonesTagged: groupData.zonesTagged,
1913
+ } ] );
1914
+ } else {
1915
+ // Group exists - add new zones to existing zonesTagged array (preserving existing mappings)
1916
+ await customzonegrouping.updateOne(
1917
+ { _id: groupDoc._id },
1918
+ { $addToSet: { zonesTagged: { $each: groupData.zonesTagged } } },
1919
+ );
1920
+ }
1921
+ }
1922
+ }
1923
+
1924
+ // Ensure tagging documents are also mapped to the correct groupName for newly uploaded zones
1925
+ // Use normalized groupName (existing case if group exists, user case if new)
1926
+ for ( const zone of zonesArray ) {
1927
+ if ( zone.groupName && zone.groupName !== '' && zone.groupName !== null ) {
1928
+ const normalizedGroupName = groupNameMapping.get( zone.groupName.toLowerCase() ) || zone.groupName;
1929
+ await taggingService.updateMany(
1930
+ { clientId, tagName: zone.tagName },
1931
+ { $set: { groupName: normalizedGroupName } },
1932
+ );
1933
+ }
1934
+ }
1935
+
1936
+
1937
+ logger.info( `Bulk Zone Tags Created Successfully: ${createdTagNames.length} tags` );
1938
+ const logObj = {
1939
+ clientId: clientId,
1940
+ userName: req.user?.userName,
1941
+ email: req.user?.email,
1942
+ date: new Date(),
1943
+ logType: 'zone',
1944
+ logSubType: 'addBulkZoneCustomTag',
1945
+ changes: createdTagNames.map( ( tagName ) => `${tagName} custom tag` ),
1946
+ eventType: 'create',
1947
+ showTo: [ 'client', 'tango' ],
1948
+ };
1949
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
1950
+ return res.sendSuccess( {
1951
+ message: `Zone Tags Created Successfully`,
1952
+ count: createdTagNames.length,
1953
+ } );
1954
+ } catch ( e ) {
1955
+ console.log( 'uploadBulkZoneTags error :', e );
1956
+ logger.error( { error: e, function: 'addBulkZoneCustomTag' } );
1957
+ return res.sendError( 'Failed to upload zone-config file', 500 );
1958
+ }
1959
+ };
1960
+
1961
+ export const updateZoneCustomTag = async ( req, res ) => {
1962
+ try {
1963
+ const { clientId, tagName, oldTag, isExistingGroup, oldGroupName, groupName } = req.body;
1964
+
1965
+ if ( !clientId || !tagName ) {
1966
+ return res.sendError( 'clientId, zoneName are required', 400 );
1967
+ }
1968
+
1969
+ let findQuery = {};
1970
+
1971
+ // Escape regex special characters in tag names to avoid invalid regex patterns
1972
+ const escapedTagName = tagName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
1973
+ const escapedOldTag = oldTag ? oldTag.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ) : null;
1974
+ if ( oldTag !== undefined && oldTag !== null && tagName !== oldTag ) {
1975
+ findQuery = {
1976
+ clientId,
1977
+ tagName: { $regex: `^${escapedOldTag}$`, $options: 'i' },
1978
+ };
1979
+ } else {
1980
+ findQuery = {
1981
+ clientId,
1982
+ tagName: { $regex: `^${escapedTagName}$`, $options: 'i' },
1983
+ };
1984
+ }
1985
+ await externalService.updateMany( { zoneName: oldTag, clientId: req.body.clientId }, { zoneName: req.body.tagName } );
1986
+ updateOldData( req, res );
1987
+ let customZoneTagDetails = await customZoneTagService.findOne( findQuery );
1988
+
1989
+ if ( !customZoneTagDetails ) {
1990
+ return res.sendError( 'No data found', 400 );
1991
+ }
1992
+
1993
+ // Get the actual oldGroupName from the database record
1994
+ const actualOldGroupName = customZoneTagDetails.groupName || null;
1995
+ // Get the actual tag name currently in the database
1996
+ const actualTagNameInDb = customZoneTagDetails.tagName;
1997
+
1998
+ // Validate that the new tagName does not already exist for this client (case-insensitive)
1999
+ // Check if tagName is being changed (either via oldTag or if it's different from DB value)
2000
+ const isTagNameChanging = ( oldTag && tagName.toLowerCase() !== oldTag.toLowerCase() ) || ( actualTagNameInDb && tagName.toLowerCase() !== actualTagNameInDb.toLowerCase() );
2001
+
2002
+ if ( isTagNameChanging ) {
2003
+ const existingTagWithNewName = await customZoneTagService.findOne( {
2004
+ clientId,
2005
+ tagName: { $regex: `^${escapedTagName}$`, $options: 'i' },
2006
+ } );
2007
+
2008
+ // If a tag with the new name exists and it's not the same record we're updating, return error
2009
+ if ( existingTagWithNewName && existingTagWithNewName._id.toString() !== customZoneTagDetails._id.toString() ) {
2010
+ return res.sendError( `zoneName "${tagName}" already exists for this client`, 409 );
2011
+ }
2012
+ }
2013
+
2014
+ let query = {
2015
+ clientId,
2016
+ };
2017
+ let payload = { tagName };
2018
+ if ( oldTag && tagName !== oldTag ) {
2019
+ query.tagName = oldTag;
2020
+ } else {
2021
+ query.tagName = tagName;
2022
+ }
2023
+ if ( groupName ) {
2024
+ payload.groupName = groupName || null;
2025
+ } else {
2026
+ payload.groupName = null;
2027
+ }
2028
+
2029
+ // Determine the tag name to use for group operations
2030
+ // Use the actual tag name from database for removing from old group
2031
+ const tagNameForRemoval = actualTagNameInDb;
2032
+ const newTagNameForGroup = tagName;
2033
+
2034
+ // Handle group changes: remove from old group and add to new group
2035
+ // Compare actual old group name from DB with new group name (case-insensitive)
2036
+ const oldGroupNameStr = actualOldGroupName ? String( actualOldGroupName ).trim().toLowerCase() : null;
2037
+ const newGroupNameStr = groupName ? String( groupName ).trim().toLowerCase() : null;
2038
+
2039
+ if ( oldGroupNameStr !== newGroupNameStr ) {
2040
+ // Remove tag from old group if oldGroupName exists
2041
+ if ( actualOldGroupName ) {
2042
+ // Escape regex special characters in actualOldGroupName
2043
+ const escapedActualOldGroupName = actualOldGroupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
2044
+ const oldGroupDoc = await customzonegrouping.findOne(
2045
+ { clientId, groupName: { $regex: `^${escapedActualOldGroupName}$`, $options: 'i' } },
2046
+ );
2047
+ if ( oldGroupDoc ) {
2048
+ await customzonegrouping.updateOne(
2049
+ { _id: oldGroupDoc._id },
2050
+ { $pull: { zonesTagged: tagNameForRemoval } },
2051
+ );
2052
+ } else {
2053
+ }
2054
+ }
2055
+
2056
+ // Add tag to new group if groupName exists
2057
+ if ( groupName ) {
2058
+ // Escape regex special characters in groupName
2059
+ const escapedGroupName = groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
2060
+ let newGroupDoc = await customzonegrouping.findOne(
2061
+ { clientId, groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' } },
2062
+ );
2063
+
2064
+ if ( !newGroupDoc && !isExistingGroup ) {
2065
+ await customzonegrouping.create(
2066
+ [ {
2067
+ clientId,
2068
+ groupName,
2069
+ productName: req.body.productName,
2070
+ zonesTagged: [ newTagNameForGroup ],
2071
+ } ],
2072
+ );
2073
+ } else if ( newGroupDoc ) {
2074
+ // Add to existing group
2075
+ await customzonegrouping.updateOne(
2076
+ { _id: newGroupDoc._id },
2077
+ { $addToSet: { zonesTagged: newTagNameForGroup } },
2078
+ );
2079
+ }
2080
+ }
2081
+ } else if ( groupName && oldGroupNameStr === newGroupNameStr && oldTag && tagName !== oldTag ) {
2082
+ // If groupName is same (and not null) but tagName changed, update the tagName in the group's zonesTagged
2083
+ console.log( 'Group name same but tagName changed, updating group zonesTagged' );
2084
+ // Escape regex special characters in groupName
2085
+ const escapedGroupName = groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
2086
+ const groupDoc = await customzonegrouping.findOne( { clientId, groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' } } );
2087
+ if ( groupDoc ) {
2088
+ await customzonegrouping.updateOne(
2089
+ { _id: groupDoc._id },
2090
+ [
2091
+ {
2092
+ $set: {
2093
+ zonesTagged: {
2094
+ $setUnion: [
2095
+ {
2096
+ $filter: {
2097
+ input: '$zonesTagged',
2098
+ cond: { $ne: [ '$$this', actualTagNameInDb ] },
2099
+ },
2100
+ },
2101
+ [ tagName ],
2102
+ ],
2103
+ },
2104
+ },
2105
+ },
2106
+ ],
2107
+ );
2108
+ }
2109
+ }
2110
+ // Update tagging collection if groupName changed or tagName changed (similar to addZoneCustomTag)
2111
+ if ( oldTag && tagName !== oldTag ) {
2112
+ // If tagName changed, update tagging collection records with oldTag to have new tagName and groupName
2113
+ await taggingService.updateMany(
2114
+ { clientId, tagName: oldTag },
2115
+ { $set: { tagName, 'groupName': groupName || null, 'coordinates.$[].zoneName': tagName } },
2116
+ );
2117
+ } else if ( groupName !== oldGroupName ) {
2118
+ // If only groupName changed, update tagging collection with new groupName
2119
+ await taggingService.updateMany(
2120
+ { clientId, tagName },
2121
+ { $set: { 'groupName': groupName || null } },
2122
+ );
2123
+ }
2124
+
2125
+ let zonetag = await customZoneTagService.updateOne( query, { $set: payload } );
2126
+ const logObj = {
2127
+ clientId: req.body.clientId,
2128
+ userName: req.user?.userName,
2129
+ email: req.user?.email,
2130
+ date: new Date(),
2131
+ logType: 'zone',
2132
+ logSubType: 'updateZoneCustomTag',
2133
+ changes: [ `zoneName changed from ${oldTag} to ${tagName}` ],
2134
+ eventType: 'update',
2135
+ previous: {
2136
+ tagName: oldTag,
2137
+ },
2138
+ current: {
2139
+ tagName: tagName,
2140
+ },
2141
+ oldData: {
2142
+ TagName: oldTag,
2143
+ },
2144
+ newData: {
2145
+ TagName: tagName,
2146
+ },
2147
+ showTo: [ 'tango', 'client' ],
2148
+ };
2149
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2150
+ if ( zonetag.modifiedCount || zonetag.matchedCount ) {
2151
+ logger.info( 'ZoneName Updated Successfully' );
2152
+ res.sendSuccess( 'ZoneName Updated Successfully' );
2153
+ } else {
2154
+ logger.error( { error: 'something went wrong', function: 'updateZoneCustomTag' } );
2155
+ return res.sendError( 'something went wrong', 500 );
2156
+ }
2157
+ } catch ( error ) {
2158
+ logger.error( { error: error, function: 'updateZoneCustomTag' } );
2159
+ return res.sendError( error, 500 );
2160
+ }
2161
+ };
2162
+
2163
+ export const deleteZoneCustomTag = async ( req, res ) => {
2164
+ try {
2165
+ const { clientId, tagName } = req.body;
2166
+ if ( !clientId || !tagName ) {
2167
+ return res.sendError( 'clientId and zoneName are required', 400 );
2168
+ }
2169
+
2170
+ let zoneTagDetails = await customZoneTagService.findOne( { clientId, tagName } );
2171
+ if ( !zoneTagDetails || zoneTagDetails?.length == 0 ) {
2172
+ return res.sendError( 'No data found', 400 );
2173
+ }
2174
+ updateOldData( req, res );
2175
+ await customZoneTagService.deleteOne( { clientId, tagName } );
2176
+ await taggingService.deleteMany(
2177
+ {
2178
+ clientId,
2179
+ tagName: { $in: tagName },
2180
+ },
2181
+ );
2182
+ const logObj = {
2183
+ clientId: req.body?.clientId,
2184
+ userName: req.user?.userName,
2185
+ email: req.user?.email,
2186
+ date: new Date(),
2187
+ logType: 'zone',
2188
+ logSubType: 'deleteZoneCustomTag',
2189
+ changes: [ `${tagName} zone tag` ],
2190
+ eventType: 'delete',
2191
+ showTo: [ 'client', 'tango' ],
2192
+ };
2193
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2194
+ // return res.sendSuccess( 'ZoneName Deleted Successfully' );
2195
+ } catch ( e ) {
2196
+ logger.error( { error: e, function: 'deleteZoneCustomTag' } );
2197
+ return res.sendError( e, 500 );
2198
+ }
2199
+ };
2200
+
2201
+
2202
+ // setting config - get zone grouping details
2203
+ export const getZoneGroupDetails = async ( req, res ) => {
2204
+ try {
2205
+ const {
2206
+ clientId,
2207
+ searchValue,
2208
+ sortColumName,
2209
+ sortBy,
2210
+ limit,
2211
+ offset = 1,
2212
+ export: isExport,
2213
+ } = req.body;
2214
+
2215
+ if ( !clientId ) {
2216
+ return res.sendError( 'clientId is required', 400 );
2217
+ }
2218
+
2219
+
2220
+ let groupData = await customzonegrouping.findOne( { clientId: clientId } );
2221
+ if ( !groupData ) {
2222
+ return res.sendSuccess( { result: [] } );
2223
+ }
2224
+
2225
+ const searchMatch = searchValue ?
2226
+ { groupName: { $regex: searchValue, $options: 'i' } } :
2227
+ null;
2228
+
2229
+ const pipeline = [
2230
+ { $match: { clientId } },
2231
+
2232
+ {
2233
+ $lookup: {
2234
+ from: 'taggings',
2235
+ let: { groupName: '$groupName' },
2236
+ pipeline: [
2237
+ {
2238
+ $match: {
2239
+ $expr: {
2240
+ $and: [
2241
+ { $eq: [ '$groupName', '$$groupName' ] },
2242
+ { $eq: [ '$clientId', clientId ] },
2243
+ ],
2244
+ },
2245
+ },
2246
+ },
2247
+ ],
2248
+ as: 'tagsCount',
2249
+ },
2250
+ },
2251
+
2252
+ {
2253
+ $lookup: {
2254
+ from: 'customzonetags',
2255
+ let: { groupName: '$groupName' },
2256
+ pipeline: [
2257
+ {
2258
+ $match: {
2259
+ $expr: {
2260
+ $and: [
2261
+ { $eq: [ '$groupName', '$$groupName' ] },
2262
+ { $eq: [ '$clientId', clientId ] },
2263
+ ],
2264
+ },
2265
+ },
2266
+ },
2267
+ ],
2268
+ as: 'zonesTagged',
2269
+ },
2270
+ },
2271
+
2272
+ {
2273
+ $project: {
2274
+ clientId: 1,
2275
+ groupName: 1,
2276
+ zonesTagged: 1,
2277
+ zonesCount: { $size: '$tagsCount' },
2278
+ createdAt: 1,
2279
+ },
2280
+ },
2281
+
2282
+ {
2283
+ $facet: {
2284
+ totalCount: [
2285
+ ...( searchMatch ? [ { $match: searchMatch } ] : [] ),
2286
+ { $count: 'count' },
2287
+ ],
2288
+
2289
+ data: [
2290
+ ...( searchMatch ? [ { $match: searchMatch } ] : [] ),
2291
+
2292
+ ...( sortColumName && sortBy ?
2293
+ [ { $sort: { [sortColumName]: sortBy } } ] :
2294
+ [ { $sort: { createdAt: -1 } } ] ),
2295
+
2296
+ ...( isExport ?
2297
+ [] :
2298
+ limit ? [
2299
+ { $skip: ( offset - 1 ) * limit },
2300
+ { $limit: Number( limit ) },
2301
+ ] : [] ),
2302
+ ],
2303
+ },
2304
+ },
2305
+ ];
2306
+
2307
+ const [ result ] = await customzonegrouping.aggregate( pipeline );
2308
+
2309
+ const zoneGroupList = result.data || [];
2310
+
2311
+ const totalCount = result.totalCount[0]?.count || 0;
2312
+ if ( totalCount === 0 ) {
2313
+ return res.sendError( 'No data', 204 );
2314
+ }
2315
+ // Get total count of zones for tabs (actual count, not filtered by search)
2316
+ const totalZoneCount = await customZoneTagService.count( { clientId } );
2317
+
2318
+ if ( isExport && zoneGroupList.length ) {
2319
+ const exportdata = zoneGroupList.map( ( element ) => ( {
2320
+ 'Zone Group Name': element.groupName || '--',
2321
+ 'Zones tagged': Array.isArray( element.zonesTagged ) ?
2322
+ element.zonesTagged.map( ( zone ) => zone.tagName ).filter( Boolean ).
2323
+ join( ', ' ) : '--',
2324
+ 'Zones tagged Count': element.zonesTagged.length || 0,
2325
+ } ) );
2326
+ await download( exportdata, res );
2327
+ return;
2328
+ }
2329
+
2330
+ return res.sendSuccess( {
2331
+ result: zoneGroupList,
2332
+ totalCount,
2333
+ totalZoneCount,
2334
+ } );
2335
+ } catch ( e ) {
2336
+ logger.error( { error: e, function: 'getZoneGroupDetails' } );
2337
+ return res.sendError( e, 500 );
2338
+ }
2339
+ // try {
2340
+ // const inputData = req.body;
2341
+ // if ( !inputData.clientId ) {
2342
+ // return res.sendError( 'clientId is required', 400 );
2343
+ // }
2344
+
2345
+ // let Query = [
2346
+ // {
2347
+ // $match: {
2348
+ // clientId: inputData.clientId,
2349
+ // },
2350
+ // },
2351
+ // {
2352
+ // $lookup: {
2353
+ // from: 'taggings',
2354
+ // let: { groupName: '$groupName' },
2355
+ // pipeline: [
2356
+ // {
2357
+ // $match: {
2358
+ // $expr: {
2359
+ // $and: [
2360
+ // {
2361
+ // $eq: [ '$groupName', '$$groupName' ],
2362
+ // },
2363
+ // {
2364
+ // $eq: [ '$clientId', inputData.clientId ],
2365
+ // },
2366
+ // ],
2367
+ // },
2368
+ // },
2369
+ // },
2370
+ // ], as: 'tagsCount',
2371
+ // },
2372
+ // },
2373
+ // {
2374
+ // $lookup: {
2375
+ // from: 'customzonetags',
2376
+ // let: { groupName: '$groupName' },
2377
+ // pipeline: [
2378
+ // {
2379
+ // $match: {
2380
+ // $expr: {
2381
+ // $and: [
2382
+ // {
2383
+ // $eq: [ '$groupName', '$$groupName' ],
2384
+ // },
2385
+ // {
2386
+ // $eq: [ '$clientId', inputData.clientId ],
2387
+ // },
2388
+ // ],
2389
+ // },
2390
+ // },
2391
+ // },
2392
+ // ], as: 'zonesTagged',
2393
+ // },
2394
+ // },
2395
+ // {
2396
+ // $project: {
2397
+ // clientId: 1,
2398
+ // groupName: 1,
2399
+ // zonesTagged: 1,
2400
+ // zonesCount: { $size: '$tagsCount' },
2401
+ // },
2402
+ // },
2403
+ // ];
2404
+
2405
+ // if ( req.body.searchValue && req.body.searchValue !== '' ) {
2406
+ // Query.push( {
2407
+ // $match: {
2408
+ // $or: [
2409
+ // { groupName: { $regex: req.body.searchValue, $options: 'i' } },
2410
+ // ],
2411
+ // },
2412
+ // } );
2413
+ // }
2414
+
2415
+ // if ( req.body.sortColumName && req.body.sortColumName !== '' && req.body.sortBy ) {
2416
+ // Query.push( {
2417
+ // $sort: { [req.body.sortColumName]: req.body.sortBy },
2418
+ // } );
2419
+ // } else {
2420
+ // // Default sort by createdAt descending if no sort specified
2421
+ // Query.push( {
2422
+ // $sort: { createdAt: -1 },
2423
+ // } );
2424
+ // }
2425
+
2426
+ // // Get total count before pagination
2427
+ // const countQuery = [ ...Query ];
2428
+ // const totalCountResult = await customzonegrouping.aggregate( [
2429
+ // ...countQuery,
2430
+ // { $count: 'total' },
2431
+ // ] );
2432
+ // console.log( '🚀 ~ getZoneGroupDetails ~ totalCountResult:', totalCountResult );
2433
+ // const totalCount = totalCountResult.length > 0 ? totalCountResult[0].total : 0;
2434
+
2435
+ // if ( req.body.limit && req.body.offset && !req.body.export ) {
2436
+ // Query.push(
2437
+ // { $skip: ( req.body.offset - 1 ) * req.body.limit },
2438
+ // { $limit: Number( req.body.limit ) },
2439
+ // );
2440
+ // }
2441
+
2442
+ // const zoneGroupList = await customzonegrouping.aggregate( Query );
2443
+
2444
+ // if ( req.body.export && zoneGroupList.length > 0 ) {
2445
+ // const exportdata = [];
2446
+ // zoneGroupList.forEach( ( element ) => {
2447
+ // const data = {
2448
+ // 'Zone Group Name': element.groupName || '--',
2449
+ // 'Zones tagged': Array.isArray( element.zonesTagged ) ? element.zonesTagged.join( ', ' ) : '--',
2450
+ // 'Zones tagged Count': element.zonesCount || 0,
2451
+ // // 'Stores Tagged Count': element.storesTaggedCount || 0,
2452
+ // };
2453
+ // exportdata.push( data );
2454
+ // } );
2455
+ // await download( exportdata, res );
2456
+ // return;
2457
+ // }
2458
+
2459
+ // return res.sendSuccess( {
2460
+ // result: zoneGroupList,
2461
+ // count: totalCount,
2462
+ // } );
2463
+ // } catch ( e ) {
2464
+ // logger.error( { error: e, function: 'getZoneGroupDetails' } );
2465
+ // console.error( 'getZoneGroupDetails error:', e );
2466
+ // return res.sendError( e, 500 );
2467
+ // }
2468
+ };
2469
+
2470
+ export const addZoneGroup = async ( req, res ) => {
2471
+ try {
2472
+ const { clientId, groupName, zonesTagged = [], productName = '' } = req.body;
2473
+
2474
+ if ( !clientId || !groupName ) {
2475
+ return res.sendError( 'clientId and groupName are required', 400 );
2476
+ }
2477
+
2478
+ // Escape regex special characters in groupName to avoid invalid regex patterns
2479
+ const escapedGroupName = groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
2480
+
2481
+ // Case-insensitive check for existing group
2482
+ const existingGroup = await customzonegrouping.findOne( {
2483
+ clientId,
2484
+ groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' },
2485
+ } );
2486
+
2487
+ if ( existingGroup ) {
2488
+ return res.sendError( `groupName "${groupName}" already exists for this client`, 409 );
2489
+ }
2490
+
2491
+ const payload = {
2492
+ clientId,
2493
+ groupName,
2494
+ zonesTagged: Array.isArray( zonesTagged ) ? zonesTagged : [],
2495
+ productName,
2496
+ };
2497
+
2498
+ await customzonegrouping.create( payload );
2499
+
2500
+ // Update groupName for all zone tags in zonesTagged array
2501
+ if ( Array.isArray( zonesTagged ) && zonesTagged.length > 0 ) {
2502
+ // Update all tags that are in the zonesTagged array to have this groupName
2503
+ await customZoneTagService.updateMany(
2504
+ {
2505
+ clientId,
2506
+ tagName: { $in: zonesTagged },
2507
+ },
2508
+ { $set: { groupName } },
2509
+ );
2510
+ await taggingService.updateMany(
2511
+ {
2512
+ clientId,
2513
+ tagName: { $in: zonesTagged },
2514
+ },
2515
+ { $set: { groupName } },
2516
+ );
2517
+ }
2518
+
2519
+ logger.info( 'Zone Group Created Successfully' );
2520
+
2521
+ const logObj = {
2522
+ clientId,
2523
+ userName: req.user?.userName,
2524
+ email: req.user?.email,
2525
+ date: new Date(),
2526
+ logType: 'zone',
2527
+ logSubType: 'addZoneGroup',
2528
+ changes: [ `${groupName} zone group` ],
2529
+ eventType: 'create',
2530
+ showTo: [ 'client', 'tango' ],
2531
+ };
2532
+
2533
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2534
+ return res.sendSuccess( 'Zone Group Created Successfully' );
2535
+ } catch ( e ) {
2536
+ // Duplicate key error from unique index
2537
+ if ( e.code === 11000 ) {
2538
+ return res.sendError( `groupName "${req.body.groupName}" already exists for this client`, 409 );
2539
+ }
2540
+
2541
+ logger.error( { error: e, function: 'addZoneGroup' } );
2542
+ return res.sendError( e.message || 'Internal Server Error', 500 );
2543
+ }
2544
+ };
2545
+
2546
+ export const updateZoneGroup = async ( req, res ) => {
2547
+ try {
2548
+ const { _id, clientId, groupName, productName, zonesTagged = [] } = req.body;
2549
+
2550
+ if ( !clientId || !groupName ) {
2551
+ return res.sendError( 'clientId and groupName are required', 400 );
2552
+ }
2553
+
2554
+ let findGroup = await customzonegrouping.findOne( { _id: _id } );
2555
+
2556
+ // Escape regex special characters in groupName to avoid invalid regex patterns
2557
+ const escapedGroupName = groupName.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
2558
+
2559
+ // Case-insensitive check for existing group (excluding current group)
2560
+ const existingGroup = await customzonegrouping.findOne( {
2561
+ clientId: clientId,
2562
+ groupName: { $regex: `^${escapedGroupName}$`, $options: 'i' },
2563
+ _id: { $ne: _id },
2564
+ } );
2565
+ if ( existingGroup && existingGroup.groupName ) {
2566
+ return res.sendError( `groupName "${groupName}" already exists for this client`, 409 );
2567
+ }
2568
+
2569
+ if ( findGroup ) {
2570
+ let removedTags = findGroup.zonesTagged.filter( ( zone ) => !zonesTagged.includes( zone ) ); ;
2571
+ if ( removedTags && removedTags.length > 0 ) {
2572
+ let updateQuery = { clientId, tagName: { $in: removedTags } };
2573
+ await customZoneTagService.updateMany( updateQuery, { $set: { groupName: null } } );
2574
+ await taggingService.updateMany( updateQuery, { $set: { groupName: null } } );
2575
+ }
2576
+ }
2577
+
2578
+
2579
+ await customzonegrouping.updateOne( { _id: _id }, { groupName: groupName, zonesTagged: zonesTagged } );
2580
+ let updateQuery = { clientId, tagName: { $in: zonesTagged } };
2581
+ await customZoneTagService.updateMany( updateQuery, { $set: { groupName } } );
2582
+ await taggingService.updateMany( updateQuery, { $set: { groupName } } );
2583
+
2584
+
2585
+ const logObj = {
2586
+ clientId: clientId,
2587
+ userName: req.user?.userName,
2588
+ email: req.user?.email,
2589
+ date: new Date(),
2590
+ logType: 'zone',
2591
+ logSubType: 'updateZoneGroup',
2592
+ changes: [ `zone group updated from "${findGroup.groupName || groupName}" to "${groupName}"` ],
2593
+ eventType: 'update',
2594
+ previous: {
2595
+ groupName: findGroup.groupName || groupName,
2596
+ productName: findGroup.productName,
2597
+ zonesTagged: findGroup.zonesTagged,
2598
+ },
2599
+ current: {
2600
+ groupName: groupName,
2601
+ productName: productName,
2602
+ zonesTagged: zonesTagged,
2603
+ },
2604
+ oldData: {
2605
+ GroupName: findGroup.groupName || groupName,
2606
+ },
2607
+ newData: {
2608
+ GroupName: groupName,
2609
+ },
2610
+ showTo: [ 'client', 'tango' ],
2611
+ };
2612
+
2613
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2614
+
2615
+ return res.sendSuccess( 'Zone Group Updated Successfully' );
2616
+ } catch ( e ) {
2617
+ // Duplicate key error from unique index
2618
+ if ( e.code === 11000 ) {
2619
+ return res.sendError( `groupName "${req.body.groupName}" already exists for this client`, 409 );
2620
+ }
2621
+
2622
+ logger.error( { error: e, function: 'updateZoneGroup' } );
2623
+ return res.sendError( e.message || 'Internal Server Error', 500 );
2624
+ }
2625
+ };
2626
+
2627
+ export const uploadBulkZoneGroup = async ( req, res ) => {
2628
+ try {
2629
+ const groupsArray = req.body;
2630
+ const clientId = req.bulkZoneClientId;
2631
+
2632
+ if ( !groupsArray || !Array.isArray( groupsArray ) || groupsArray.length === 0 ) {
2633
+ return res.sendError( 'group-config file must not be empty', 400 );
2634
+ }
2635
+
2636
+ if ( !clientId ) {
2637
+ return res.sendError( 'clientId is required', 400 );
2638
+ }
2639
+
2640
+ const zoneGroupDataList = [];
2641
+ const createdGroupNames = [];
2642
+
2643
+ // Process each group in the array
2644
+ for ( const group of groupsArray ) {
2645
+ if ( !group.groupName || group.groupName === '' ) {
2646
+ continue; // Skip groups without groupName
2647
+ }
2648
+
2649
+ const zoneGroupData = {
2650
+ clientId: clientId,
2651
+ groupName: group.groupName,
2652
+ productName: group.productName,
2653
+ };
2654
+ zoneGroupDataList.push( zoneGroupData );
2655
+ createdGroupNames.push( group.groupName );
2656
+ }
2657
+
2658
+ // Create all zone groups
2659
+ if ( zoneGroupDataList.length > 0 ) {
2660
+ await customzonegrouping.create( zoneGroupDataList );
2661
+ }
2662
+
2663
+ logger.info( `Bulk Zone Groups Created Successfully: ${createdGroupNames.length} groups` );
2664
+ const logObj = {
2665
+ clientId: clientId,
2666
+ userName: req.user?.userName,
2667
+ email: req.user?.email,
2668
+ date: new Date(),
2669
+ logType: 'zone',
2670
+ logSubType: 'addBulkZoneGroup',
2671
+ changes: createdGroupNames.map( ( groupName ) => `${groupName} zone group` ),
2672
+ eventType: 'create',
2673
+ showTo: [ 'client', 'tango' ],
2674
+ };
2675
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2676
+ return res.sendSuccess( {
2677
+ message: `Zone Groups Created Successfully`,
2678
+ count: createdGroupNames.length,
2679
+ } );
2680
+ } catch ( e ) {
2681
+ logger.error( { error: e, function: 'uploadBulkZoneGroup' } );
2682
+ return res.sendError( 'Failed to upload group-config file', 500 );
2683
+ }
2684
+ };
2685
+
2686
+ export const deleteZoneGroup = async ( req, res ) => {
2687
+ try {
2688
+ let zoneGroupDetails = await customzonegrouping.findOne( { _id: req.body._id, clientId: req.body.clientId, groupName: req.body.groupName } );
2689
+ if ( !zoneGroupDetails || zoneGroupDetails?.length == 0 ) {
2690
+ return res.sendError( 'no data found', 204 );
2691
+ }
2692
+
2693
+ // Remove this group mapping from all custom zone tags (zones) that are linked to it
2694
+ await customZoneTagService.updateMany(
2695
+ {
2696
+ clientId: req.body.clientId,
2697
+ groupName: req.body.groupName,
2698
+ },
2699
+ { $set: { groupName: null } },
2700
+ );
2701
+ await taggingService.updateMany(
2702
+ {
2703
+ clientId: req.body.clientId,
2704
+ groupName: req.body.groupName,
2705
+ },
2706
+ { $set: { groupName: null } },
2707
+ );
2708
+ await customzonegrouping.deleteOne( { _id: req.body._id, clientId: req.body.clientId, groupName: req.body.groupName } );
2709
+
2710
+ const logObj = {
2711
+ clientId: req.body?.clientId,
2712
+ userName: req.user?.userName,
2713
+ email: req.user?.email,
2714
+ date: new Date(),
2715
+ logType: 'zone',
2716
+ logSubType: 'deleteZoneGroup',
2717
+ changes: [ `${req.body.groupName} zone group` ],
2718
+ eventType: 'delete',
2719
+ showTo: [ 'client', 'tango' ],
2720
+ };
2721
+ insertOpenSearchData( JSON.parse( process.env.OPENSEARCH )?.activityLog, logObj );
2722
+ // await updatezoneTagging( req, res );
2723
+ return res.sendSuccess( 'Zone Group Deleted Successfully' );
2724
+ } catch ( e ) {
2725
+ logger.error( { error: e, function: 'deleteZoneGroup' } );
2726
+ return res.sendError( e, 500 );
2727
+ }
2728
+ };
2729
+
2730
+ export async function oldTagsMigration() {
2731
+ let uniqueTags = await taggingService.find( { productName: 'tangoZone', clientId: '440' } );
2732
+ const result = _.uniqBy( uniqueTags, 'tagName' );
2733
+
2734
+
2735
+ for ( let zone of result ) {
2736
+ let obj = {
2737
+ clientId: zone.clientId,
2738
+ tagName: zone.tagName,
2739
+ productName: zone.productName,
2740
+ rgbColor: zone.rgbColor,
2741
+ rgbBorderColor: zone.rgbBorderColor,
2742
+ groupName: null,
2743
+ };
2744
+ await customZoneTagService.create( [ obj ] );
2745
+ }
2746
+ }
2747
+