tango-app-api-store-zone 3.3.1-beta.9 → 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.
- package/package.json +6 -3
- package/src/controllers/zoneTagging.controller.js +1781 -64
- package/src/dtos/validation.dtos.js +143 -1
- package/src/routes/zoneTagging.routes.js +17 -1
- package/src/services/checklistconfig.services.js +31 -0
- package/src/services/customzonegrouping.service.js +55 -0
- package/src/services/customzonetag.service.js +56 -0
- package/src/services/tagging.service.js +3 -0
- package/src/validations/zone.validations.js +110 -0
- package/src/services/processedchecklistconfig.services.js +0 -35
|
@@ -1,17 +1,21 @@
|
|
|
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';
|
|
8
|
-
import * as
|
|
9
|
-
|
|
9
|
+
import { signedUrl, listFileByPath, fileUpload, insertOpenSearchData, download } from 'tango-app-api-middleware';
|
|
10
|
+
import * as checklistconfigService from '../services/checklistconfig.services.js';
|
|
10
11
|
import axios from 'axios';
|
|
12
|
+
import _ from 'lodash';
|
|
13
|
+
|
|
11
14
|
export const addCustomTag = async ( req, res ) => {
|
|
12
15
|
try {
|
|
13
16
|
let inputData = req.body;
|
|
14
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 } );
|
|
15
19
|
if ( !taggingDetails ) {
|
|
16
20
|
let data = {
|
|
17
21
|
clientId: inputData.clientId,
|
|
@@ -20,6 +24,7 @@ export const addCustomTag = async ( req, res ) => {
|
|
|
20
24
|
rgbColor: inputData.rgbColor,
|
|
21
25
|
productName: inputData.productName,
|
|
22
26
|
rgbBorderColor: inputData.rgbBorderColor,
|
|
27
|
+
groupName: findgroup.groupName,
|
|
23
28
|
};
|
|
24
29
|
await taggingService.deleteMany( { clientId: inputData.clientId, storeId: inputData.storeId, tagName: inputData.tagName, isDeleted: true } );
|
|
25
30
|
await taggingService.create( data );
|
|
@@ -174,12 +179,12 @@ export const customTagListv2 = async ( req, res ) => {
|
|
|
174
179
|
let customTagList = [
|
|
175
180
|
{ tagName: 'Front', productName: 'tangoTraffic' },
|
|
176
181
|
{ tagName: 'Back', productName: 'tangoTraffic' },
|
|
177
|
-
{ tagName: 'Tracker-in', productName: '
|
|
178
|
-
{ tagName: 'Tracker-out', productName: '
|
|
182
|
+
{ tagName: 'Tracker-in', productName: 'tangoTracker' },
|
|
183
|
+
{ tagName: 'Tracker-out', productName: 'tangoTracker' },
|
|
179
184
|
];
|
|
180
185
|
|
|
181
186
|
// Step 2: Fetch store & client details in parallel
|
|
182
|
-
const [ storeDetails, clientDetails ] = await Promise.all( [
|
|
187
|
+
const [ storeDetails, clientDetails, camList ] = await Promise.all( [
|
|
183
188
|
storeService.findOne(
|
|
184
189
|
{ storeId: req.query.storeId },
|
|
185
190
|
{ product: 1 },
|
|
@@ -188,6 +193,10 @@ export const customTagListv2 = async ( req, res ) => {
|
|
|
188
193
|
{ clientId: req.query.clientId },
|
|
189
194
|
{ featureConfigs: 1 },
|
|
190
195
|
),
|
|
196
|
+
cameraService.find(
|
|
197
|
+
{ storeId: req.query.storeId, isActivated: true, isUp: true },
|
|
198
|
+
{ streamName: 1 },
|
|
199
|
+
),
|
|
191
200
|
] );
|
|
192
201
|
|
|
193
202
|
// Step 3: Add conditional tags
|
|
@@ -209,7 +218,7 @@ export const customTagListv2 = async ( req, res ) => {
|
|
|
209
218
|
// Step 4: Fetch existing tags
|
|
210
219
|
const tagInfo = await taggingService.find(
|
|
211
220
|
{ clientId: req.query.clientId, productName: req.query.selectedProduct },
|
|
212
|
-
{ tagName: 1, rgbColor: 1, rgbBorderColor: 1, productName: 1 },
|
|
221
|
+
{ tagName: 1, rgbColor: 1, rgbBorderColor: 1, productName: 1, groupName: 1 },
|
|
213
222
|
);
|
|
214
223
|
|
|
215
224
|
// Merge all tags
|
|
@@ -220,7 +229,7 @@ export const customTagListv2 = async ( req, res ) => {
|
|
|
220
229
|
|
|
221
230
|
// Step 6: Query counts per tag
|
|
222
231
|
const tagNames = uniqueTags.map( ( t ) => t.tagName );
|
|
223
|
-
|
|
232
|
+
let activeCam = camList.map( ( data ) => data.streamName );
|
|
224
233
|
const taggingDetails = await taggingService.aggregate( [
|
|
225
234
|
{
|
|
226
235
|
$match: {
|
|
@@ -229,6 +238,7 @@ export const customTagListv2 = async ( req, res ) => {
|
|
|
229
238
|
storeId: req.query.storeId,
|
|
230
239
|
coordinates: { $exists: true, $ne: [] },
|
|
231
240
|
tagName: { $in: tagNames },
|
|
241
|
+
streamName: { $in: activeCam },
|
|
232
242
|
},
|
|
233
243
|
},
|
|
234
244
|
{ $group: { _id: '$tagName', count: { $sum: 1 } } },
|
|
@@ -248,28 +258,56 @@ export const customTagListv2 = async ( req, res ) => {
|
|
|
248
258
|
'Tracker-out': { rgbColor: 'rgba(12, 195, 111, 0.5)', rgbBorderColor: 'rgb(22, 205, 125)' },
|
|
249
259
|
};
|
|
250
260
|
|
|
261
|
+
|
|
251
262
|
// Step 8: Final processing
|
|
252
263
|
const finalTags = uniqueTags.map( ( tag ) => ( {
|
|
253
264
|
tagName: tag.tagName,
|
|
254
265
|
productName: tag.productName,
|
|
266
|
+
groupName: tag.groupName ? tag.groupName : '',
|
|
255
267
|
count: countMap.get( tag.tagName ) || 0,
|
|
256
268
|
rgbColor: tag.rgbColor || tagColors[tag.tagName]?.rgbColor,
|
|
257
269
|
rgbBorderColor: tag.rgbBorderColor || tagColors[tag.tagName]?.rgbBorderColor,
|
|
258
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
|
+
}, [] );
|
|
259
295
|
|
|
260
296
|
|
|
261
297
|
// Step 9: Sort by count (desc)
|
|
262
|
-
|
|
298
|
+
groupedData.sort( ( a, b ) => b.count - a.count );
|
|
299
|
+
|
|
263
300
|
if ( req.query.selectedProduct && req.query.selectedProduct === 'tangoTrax' ) {
|
|
264
301
|
let Query = [ {
|
|
265
302
|
$match: {
|
|
266
303
|
client_id: req.query.clientId,
|
|
304
|
+
publish: true,
|
|
267
305
|
checkListType: { $ne: 'custom' },
|
|
268
306
|
},
|
|
269
307
|
}, {
|
|
270
308
|
$group: {
|
|
271
|
-
_id: '$
|
|
272
|
-
sourceCheckList_id: { $last: '$
|
|
309
|
+
_id: '$_id',
|
|
310
|
+
sourceCheckList_id: { $last: '$_id' },
|
|
273
311
|
tagName: { $last: '$checkListName' },
|
|
274
312
|
},
|
|
275
313
|
},
|
|
@@ -299,11 +337,25 @@ export const customTagListv2 = async ( req, res ) => {
|
|
|
299
337
|
], as: 'cameraList',
|
|
300
338
|
},
|
|
301
339
|
},
|
|
302
|
-
|
|
303
340
|
{
|
|
304
341
|
$project: {
|
|
305
342
|
tagName: 1,
|
|
306
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,
|
|
307
359
|
count: { $size: '$cameraList' },
|
|
308
360
|
},
|
|
309
361
|
},
|
|
@@ -314,8 +366,8 @@ export const customTagListv2 = async ( req, res ) => {
|
|
|
314
366
|
},
|
|
315
367
|
|
|
316
368
|
];
|
|
317
|
-
let getChecklistData = await
|
|
318
|
-
if ( finalTags&&finalTags.length>0 ) {
|
|
369
|
+
let getChecklistData = await checklistconfigService.aggregate( Query );
|
|
370
|
+
if ( finalTags && finalTags.length > 0 ) {
|
|
319
371
|
let merged = getChecklistData.map( ( item ) => {
|
|
320
372
|
let match = finalTags.find( ( a ) => a.tagName === item.tagName );
|
|
321
373
|
if ( match ) {
|
|
@@ -323,7 +375,305 @@ export const customTagListv2 = async ( req, res ) => {
|
|
|
323
375
|
}
|
|
324
376
|
return item;
|
|
325
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
|
+
},
|
|
326
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
|
+
} );
|
|
327
677
|
// also include any arr1 items not in arr2
|
|
328
678
|
finalTags.forEach( ( a ) => {
|
|
329
679
|
if ( !merged.find( ( m ) => m.tagName === a.tagName ) ) {
|
|
@@ -402,12 +752,12 @@ export const tagging = async ( req, res ) => {
|
|
|
402
752
|
...taggingDetails.toObject(),
|
|
403
753
|
},
|
|
404
754
|
oldData: {
|
|
405
|
-
|
|
406
|
-
|
|
755
|
+
TagName: taggingDetails.tagName,
|
|
756
|
+
StreamName: taggingDetails.streamName,
|
|
407
757
|
},
|
|
408
758
|
newData: {
|
|
409
|
-
|
|
410
|
-
|
|
759
|
+
TagName: taggingDetails.tagName,
|
|
760
|
+
StreamName: taggingDetails.streamName,
|
|
411
761
|
},
|
|
412
762
|
showTo: [ 'tango', 'client' ],
|
|
413
763
|
};
|
|
@@ -485,7 +835,7 @@ export const tagging = async ( req, res ) => {
|
|
|
485
835
|
'reidParameterType': 'default',
|
|
486
836
|
'reidParameterValue': [ 0.6, 1, 0.6 ],
|
|
487
837
|
'reidSuggesteParameter': false,
|
|
488
|
-
'reidSuggestedReduction':
|
|
838
|
+
'reidSuggestedReduction': true,
|
|
489
839
|
'reidThresholdHoliday': 0,
|
|
490
840
|
'reidThresholdWeekDay': 0,
|
|
491
841
|
'stage': '',
|
|
@@ -497,11 +847,25 @@ export const tagging = async ( req, res ) => {
|
|
|
497
847
|
'walkInTimeSpent': 3,
|
|
498
848
|
'isWalkInOneImg': false,
|
|
499
849
|
'isAudit': true,
|
|
500
|
-
'isForceCombine':
|
|
850
|
+
'isForceCombine': true,
|
|
501
851
|
};
|
|
502
852
|
|
|
503
853
|
await externalService.create( data );
|
|
504
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
|
+
}
|
|
505
869
|
await updatezoneTagging( req, res );
|
|
506
870
|
} catch ( e ) {
|
|
507
871
|
logger.error( { error: e, function: 'tagging' } );
|
|
@@ -592,17 +956,17 @@ export const getCameraList = async ( req, res ) => {
|
|
|
592
956
|
};
|
|
593
957
|
export const getCameraListv2 = async ( req, res ) => {
|
|
594
958
|
try {
|
|
595
|
-
let cameraDetails = await cameraService.find( { clientId: req.
|
|
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 } );
|
|
596
960
|
if ( !cameraDetails.length ) {
|
|
597
961
|
return res.sendError( 'no data found', 204 );
|
|
598
962
|
}
|
|
599
963
|
const folderPath = {
|
|
600
|
-
file_path: `${req.
|
|
964
|
+
file_path: `${req.body.storeId}/zone_base_images/`,
|
|
601
965
|
Bucket: JSON.parse( process.env.BUCKET ).zoneBaseImage, MaxKeys: 1000,
|
|
602
966
|
};
|
|
603
967
|
let fileList = await listFileByPath( folderPath );
|
|
604
968
|
const TaggedfolderPath = {
|
|
605
|
-
file_path: `${req.
|
|
969
|
+
file_path: `${req.body.storeId}/zone_tagged_image/`,
|
|
606
970
|
Bucket: JSON.parse( process.env.BUCKET ).zoneTaggingImage, MaxKeys: 1000,
|
|
607
971
|
};
|
|
608
972
|
let tagFileList = await listFileByPath( TaggedfolderPath );
|
|
@@ -615,7 +979,7 @@ export const getCameraListv2 = async ( req, res ) => {
|
|
|
615
979
|
baseImg: '',
|
|
616
980
|
tagImg: '',
|
|
617
981
|
};
|
|
618
|
-
let taggingDetails = await taggingService.find( { cameraId: camera._id, streamName: camera.streamName, clientId: req.
|
|
982
|
+
let taggingDetails = await taggingService.find( { cameraId: camera._id, streamName: camera.streamName, clientId: req.body.clientId, isDeleted: false }, { tagName: 1, coordinates: 1 } );
|
|
619
983
|
if ( taggingDetails.length ) {
|
|
620
984
|
tagList = taggingDetails.filter( ( item ) => item.coordinates.length ).map( ( item ) => {
|
|
621
985
|
if ( item.coordinates.length ) {
|
|
@@ -623,14 +987,13 @@ export const getCameraListv2 = async ( req, res ) => {
|
|
|
623
987
|
}
|
|
624
988
|
} );
|
|
625
989
|
}
|
|
626
|
-
|
|
627
990
|
if ( tagFileList.data.length ) {
|
|
628
991
|
tagFileList.data.forEach( ( item ) => {
|
|
629
992
|
if ( item.Key.length > 1 ) {
|
|
630
993
|
let splitStream = item.Key.split( '/' );
|
|
631
994
|
let getStream = splitStream[splitStream.length - 1].split( '.' );
|
|
632
995
|
|
|
633
|
-
if ( getStream && getStream[0] == `${req.
|
|
996
|
+
if ( getStream && getStream[0] == `${req.body.storeId}_${camera.streamName}` ) {
|
|
634
997
|
tagPath = item.Key;
|
|
635
998
|
}
|
|
636
999
|
}
|
|
@@ -640,7 +1003,7 @@ export const getCameraListv2 = async ( req, res ) => {
|
|
|
640
1003
|
fileList.data.forEach( ( ele ) => {
|
|
641
1004
|
let splitStream = ele.Key.split( '/' );
|
|
642
1005
|
let getStream = splitStream[splitStream.length - 1].split( '.' );
|
|
643
|
-
if ( getStream && getStream[0] == `${req.
|
|
1006
|
+
if ( getStream && getStream[0] == `${req.body.storeId}_${camera.streamName}` ) {
|
|
644
1007
|
imgPath = ele.Key;
|
|
645
1008
|
}
|
|
646
1009
|
} );
|
|
@@ -662,6 +1025,7 @@ export const getCameraListv2 = async ( req, res ) => {
|
|
|
662
1025
|
camera.baseImg = cameraBaseImage;
|
|
663
1026
|
}
|
|
664
1027
|
camera.tagging = tagList;
|
|
1028
|
+
camera.taggedCount = tagList.filter( ( ele ) => ele.tagName === req.body.tagName );
|
|
665
1029
|
cameraDetails[index] = camera;
|
|
666
1030
|
}
|
|
667
1031
|
cameraDetails.sort( ( a, b ) => a.tagging?.length > b.tagging?.length ? -1 : 1 );
|
|
@@ -717,10 +1081,10 @@ export const updateTag = async ( req, res ) => {
|
|
|
717
1081
|
tagName: req.body.tagName,
|
|
718
1082
|
},
|
|
719
1083
|
oldData: {
|
|
720
|
-
|
|
1084
|
+
TagName: req.body.existTag,
|
|
721
1085
|
},
|
|
722
1086
|
newData: {
|
|
723
|
-
|
|
1087
|
+
TagName: req.body.tagName,
|
|
724
1088
|
},
|
|
725
1089
|
showTo: [ 'tango', 'client' ],
|
|
726
1090
|
};
|
|
@@ -746,19 +1110,24 @@ export const deleteTag = async ( req, res ) => {
|
|
|
746
1110
|
if ( !taggingDetails.length ) {
|
|
747
1111
|
return res.sendError( 'no data found', 204 );
|
|
748
1112
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if ( permission.featureName == 'analytics' ) {
|
|
754
|
-
let product = permission.product.find( ( item ) => item.name == 'tangoZone' );
|
|
755
|
-
if ( product ) {
|
|
756
|
-
enableDelete = product.isDelete;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
} );
|
|
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 );
|
|
760
1117
|
}
|
|
761
|
-
|
|
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' ) ) {
|
|
762
1131
|
await taggingService.deleteMany( { clientId: req.body.clientId, tagName: req.body.tagName } );
|
|
763
1132
|
await externalService.deleteMany( { zoneName: req.body.tagName, clientId: req.body.clientId } );
|
|
764
1133
|
} else {
|
|
@@ -946,44 +1315,51 @@ export const updatezoneTagging = async ( req, res ) => {
|
|
|
946
1315
|
|
|
947
1316
|
export const updateOldData = async ( req, res ) => {
|
|
948
1317
|
try {
|
|
949
|
-
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
|
+
|
|
950
1321
|
if ( tagDetails.length ) {
|
|
951
1322
|
for ( let [ index, item ] of tagDetails.entries() ) {
|
|
952
1323
|
req.body.storeId = item.storeId;
|
|
953
1324
|
let camDetails = await getCamTaggingDetails( req, res );
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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 );
|
|
972
1345
|
}
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
|
|
1346
|
+
}
|
|
1347
|
+
if ( index == tagDetails.length-1 ) {
|
|
1348
|
+
res.sendSuccess( 'Zone Updated Successfully' );
|
|
976
1349
|
}
|
|
977
1350
|
}
|
|
1351
|
+
} else {
|
|
1352
|
+
res.sendSuccess( 'Zone Updated Successfully' );
|
|
978
1353
|
}
|
|
979
1354
|
} catch ( e ) {
|
|
1355
|
+
console.log( '🚀 ~ updateOldData ~ e:', e );
|
|
980
1356
|
logger.error( { error: e, function: 'updateOldData' } );
|
|
981
1357
|
return res.sendError( e, 500 );
|
|
982
1358
|
}
|
|
983
1359
|
};
|
|
984
1360
|
export async function updateCamera( req, res ) {
|
|
985
1361
|
try {
|
|
986
|
-
let findoneCheckList = await
|
|
1362
|
+
let findoneCheckList = await checklistconfigService.findOne( {
|
|
987
1363
|
client_id: req.body.clientId, checkListName: req.body.selectedZone,
|
|
988
1364
|
} );
|
|
989
1365
|
|
|
@@ -1041,3 +1417,1344 @@ async function updateJsonFile( req, res ) {
|
|
|
1041
1417
|
}
|
|
1042
1418
|
}
|
|
1043
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
|
+
|