tango-app-api-analysis-traffic 3.8.7-vms.2 → 3.8.7-vms.21
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 +2 -2
- package/src/controllers/revop.controller.js +252 -36
- package/src/controllers/tangoTrafficV3.controllers.js +27 -26
- package/src/dtos/revop.dtos.js +3 -2
- package/src/dtos/validation.dtos.js +11 -0
- package/src/routes/revop.routes.js +0 -1
- package/src/routes/traffic.routes.js +3 -1
- package/src/validations/revop.validation.js +80 -109
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tango-app-api-analysis-traffic",
|
|
3
|
-
"version": "3.8.7-vms.
|
|
3
|
+
"version": "3.8.7-vms.21",
|
|
4
4
|
"description": "Traffic Analysis",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"mongodb": "^6.8.0",
|
|
24
24
|
"nodemon": "^3.1.4",
|
|
25
25
|
"swagger-ui-express": "^5.0.1",
|
|
26
|
-
"tango-api-schema": "^2.4.
|
|
26
|
+
"tango-api-schema": "^2.4.29",
|
|
27
27
|
"tango-app-api-middleware": "^3.6.5",
|
|
28
28
|
"winston": "^3.13.1",
|
|
29
29
|
"winston-daily-rotate-file": "^5.0.0"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logger, insertOpenSearchData, getOpenSearchData, updateOpenSearchData } from 'tango-app-api-middleware';
|
|
2
2
|
import { findOnerevopConfig } from '../services/revopConfig.service.js';
|
|
3
3
|
import * as clientService from '../services/clients.services.js';
|
|
4
|
-
import { bulkUpdate, upsertWithScript } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
4
|
+
import { bulkUpdate, insertWithId, upsertWithScript } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
5
5
|
import { findOneVmsStoreRequest } from '../services/vmsStoreRequest.service.js';
|
|
6
6
|
// import dayjs from 'dayjs';
|
|
7
7
|
// Lamda Service Call //
|
|
@@ -221,6 +221,9 @@ export async function storeProcessedData( req, res ) {
|
|
|
221
221
|
dayjs.extend( isSameOrBefore );
|
|
222
222
|
|
|
223
223
|
let start = dayjs( fromDate );
|
|
224
|
+
// get start value from the before day one
|
|
225
|
+
// Move start one day back so we can access "day before" when looping
|
|
226
|
+
start = start.subtract( 1, 'day' );
|
|
224
227
|
let end = dayjs( toDate );
|
|
225
228
|
|
|
226
229
|
if ( !start.isValid() || !end.isValid() ) {
|
|
@@ -267,10 +270,12 @@ export async function storeProcessedData( req, res ) {
|
|
|
267
270
|
|
|
268
271
|
const responseArray = [];
|
|
269
272
|
|
|
270
|
-
for ( let i =
|
|
273
|
+
for ( let i = 1; i < orderedDates.length; i++ ) {
|
|
271
274
|
const currentDate = orderedDates[i];
|
|
272
275
|
const currentId = `${storeId}_${currentDate}`;
|
|
276
|
+
logger.info( { currentId, currentDate } );
|
|
273
277
|
const processedData = hitsMap.get( currentId );
|
|
278
|
+
logger.info( { processedData } );
|
|
274
279
|
if ( !processedData ) {
|
|
275
280
|
responseArray.push( {
|
|
276
281
|
date: currentDate,
|
|
@@ -283,13 +288,17 @@ export async function storeProcessedData( req, res ) {
|
|
|
283
288
|
|
|
284
289
|
const prevDate = dayjs( currentDate ).subtract( 1, 'day' ).format( 'YYYY-MM-DD' );
|
|
285
290
|
const prevId = `${storeId}_${prevDate}`;
|
|
291
|
+
logger.info( { prevId, prevDate } );
|
|
286
292
|
const previousData = hitsMap.get( prevId );
|
|
287
293
|
|
|
288
294
|
let footfallCountTrend = 0;
|
|
295
|
+
logger.info( { previousData, previoucubr1: previousData?.footfall_count } );
|
|
289
296
|
if ( previousData && previousData.footfall_count ) {
|
|
297
|
+
logger.info( { previousData, previoucubr: previousData?.footfall_count } );
|
|
290
298
|
footfallCountTrend = Math.round(
|
|
291
|
-
( ( processedData.footfall_count - previousData
|
|
299
|
+
( ( processedData.footfall_count - previousData?.footfall_count ) / previousData.footfall_count ) * 100,
|
|
292
300
|
);
|
|
301
|
+
logger.info( { footfallCountTrend } );
|
|
293
302
|
}
|
|
294
303
|
// Add ticket status from openSearch.footfallDirectory (_source.status)
|
|
295
304
|
let ticketStatus = null;
|
|
@@ -302,16 +311,28 @@ export async function storeProcessedData( req, res ) {
|
|
|
302
311
|
{ term: { 'storeId.keyword': storeId } },
|
|
303
312
|
{ term: { 'dateString': currentDate } },
|
|
304
313
|
{ term: { 'ticketName.keyword': 'footfall-directory' } },
|
|
314
|
+
{
|
|
315
|
+
'term': {
|
|
316
|
+
'type.keyword': 'store',
|
|
317
|
+
},
|
|
318
|
+
},
|
|
305
319
|
],
|
|
306
320
|
},
|
|
307
321
|
},
|
|
308
322
|
size: 1,
|
|
309
|
-
_source: [ 'status' ],
|
|
323
|
+
_source: [ 'status', 'mappingInfo' ],
|
|
310
324
|
};
|
|
311
325
|
try {
|
|
312
326
|
const footfallDirRes = await getOpenSearchData( openSearch.footfallDirectory, footfallDirQuery );
|
|
313
327
|
const hit = footfallDirRes?.body?.hits?.hits?.[0];
|
|
314
|
-
|
|
328
|
+
if ( hit?._source?.mappingInfo && Array.isArray( hit._source.mappingInfo ) ) {
|
|
329
|
+
for ( let i = 0; i < hit._source.mappingInfo.length; i++ ) {
|
|
330
|
+
if ( hit._source.mappingInfo[i].type === 'tagging' ) {
|
|
331
|
+
ticketStatus = hit._source.mappingInfo[i].status;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
315
336
|
} catch ( err ) {
|
|
316
337
|
logger.warn( { message: 'Could not get ticket status from footfallDirectory', error: err } );
|
|
317
338
|
}
|
|
@@ -335,7 +356,27 @@ export async function storeProcessedData( req, res ) {
|
|
|
335
356
|
downtime: processedData?.down_time || 0,
|
|
336
357
|
ticketStatus,
|
|
337
358
|
raisedStatusEnabled,
|
|
359
|
+
|
|
338
360
|
} );
|
|
361
|
+
|
|
362
|
+
if ( raisedStatusEnabled === 'block' ) {
|
|
363
|
+
// Calculate the number of days from currentDate + 1 to the end of the month
|
|
364
|
+
// Assume currentDate is in format 'YYYY-MM-DD'
|
|
365
|
+
const currentDateObj = new Date( currentDate );
|
|
366
|
+
// Move to next day
|
|
367
|
+
const nextDay = new Date( currentDateObj );
|
|
368
|
+
nextDay.setDate( currentDateObj.getDate() + 1 );
|
|
369
|
+
|
|
370
|
+
// Get the last date of the current month
|
|
371
|
+
const endOfMonth = new Date( currentDateObj.getFullYear(), currentDateObj.getMonth() + 1, 0 );
|
|
372
|
+
|
|
373
|
+
// Calculate number of days (inclusive of end date, exclusive of nextDay)
|
|
374
|
+
let noOfBlockedDays = Math.floor( ( endOfMonth - nextDay ) / ( 1000 * 60 * 60 * 24 ) ) + 1;
|
|
375
|
+
if ( noOfBlockedDays < 0 ) noOfBlockedDays = 0;
|
|
376
|
+
|
|
377
|
+
// Add to response
|
|
378
|
+
responseArray[responseArray.length - 1].noOfBlockedDays = noOfBlockedDays;
|
|
379
|
+
}
|
|
339
380
|
}
|
|
340
381
|
|
|
341
382
|
return res.sendSuccess( responseArray );
|
|
@@ -376,43 +417,176 @@ export async function footFallImages( req, res ) {
|
|
|
376
417
|
'ticketName.keyword': 'footfall-directory',
|
|
377
418
|
},
|
|
378
419
|
},
|
|
420
|
+
{
|
|
421
|
+
'term': {
|
|
422
|
+
'type.keyword': 'store',
|
|
423
|
+
},
|
|
424
|
+
},
|
|
379
425
|
],
|
|
380
426
|
},
|
|
381
427
|
},
|
|
382
|
-
|
|
428
|
+
'_source': [ 'dateString', 'storeId', 'mappingInfo', 'revicedFootfall', 'revicedPerc', 'createdAt', 'updatedAt', 'footfallCount' ],
|
|
383
429
|
|
|
384
430
|
};
|
|
385
431
|
|
|
386
432
|
const getData = await getOpenSearchData( opensearch.footfallDirectory, query );
|
|
387
433
|
const ticketDetails = getData?.body?.hits?.hits[0];
|
|
388
434
|
let temp = [];
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
435
|
+
const footfallValue = ticketDetails?._source?.footfallCount ?? 0;
|
|
436
|
+
const mappingInfoArray = ticketDetails?._source?.mappingInfo ?? [];
|
|
437
|
+
// Helper to get mappingInfo for an actionType
|
|
438
|
+
function getMappingForType( type ) {
|
|
439
|
+
return mappingInfoArray.find( ( m ) => m.type === type ) || {};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// List of actionTypes to process in sequence
|
|
443
|
+
|
|
444
|
+
if ( req.user.userType !== 'tango' && req.user.role !== 'superadmin' ) {
|
|
445
|
+
switch ( req.user.role ) {
|
|
446
|
+
case 'user':
|
|
447
|
+
const actionTypesUser = [ 'tagging', 'finalRevision' ];
|
|
448
|
+
|
|
449
|
+
temp = [];
|
|
450
|
+
actionTypesUser.forEach( ( type ) => {
|
|
451
|
+
const mapping = getMappingForType( type );
|
|
452
|
+
if ( type === 'tagging' ) {
|
|
453
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
454
|
+
const revisedPerc =
|
|
455
|
+
footfallValue > 0 ?
|
|
456
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
457
|
+
'0';
|
|
458
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
459
|
+
temp.push( {
|
|
460
|
+
actionType: type,
|
|
461
|
+
footfall: footfallValue,
|
|
462
|
+
revicedFootfall: revisedFootfall,
|
|
463
|
+
revicedPerc: revisedPerc,
|
|
464
|
+
count: countObj,
|
|
465
|
+
createdAt: mapping.createdAt ?? '',
|
|
466
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
467
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
468
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
469
|
+
isUp: false,
|
|
470
|
+
} );
|
|
471
|
+
} else if ( type !== 'tagging' && mapping.status === 'Closed' ) {
|
|
472
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
473
|
+
const revisedPerc =
|
|
474
|
+
footfallValue > 0 ?
|
|
475
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
476
|
+
'0';
|
|
477
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
478
|
+
temp.push( {
|
|
479
|
+
actionType: type,
|
|
480
|
+
footfall: footfallValue,
|
|
481
|
+
revicedFootfall: revisedFootfall,
|
|
482
|
+
revicedPerc: revisedPerc,
|
|
483
|
+
count: countObj,
|
|
484
|
+
createdAt: mapping.createdAt ?? '',
|
|
485
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
486
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
487
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
488
|
+
} );
|
|
489
|
+
}
|
|
490
|
+
} );
|
|
491
|
+
break;
|
|
492
|
+
case 'admin':
|
|
493
|
+
const actionTypesAdmin = [ 'tagging', 'review', 'finalRevision' ];
|
|
494
|
+
temp = [];
|
|
495
|
+
actionTypesAdmin.forEach( ( type ) => {
|
|
496
|
+
const mapping = getMappingForType( type );
|
|
497
|
+
if ( type === 'tagging' ) {
|
|
498
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
499
|
+
const revisedPerc =
|
|
500
|
+
footfallValue > 0 ?
|
|
501
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
502
|
+
'0';
|
|
503
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
504
|
+
temp.push( {
|
|
505
|
+
actionType: type,
|
|
506
|
+
footfall: footfallValue,
|
|
507
|
+
revicedFootfall: revisedFootfall,
|
|
508
|
+
revicedPerc: revisedPerc,
|
|
509
|
+
count: countObj,
|
|
510
|
+
createdAt: mapping.createdAt ?? '',
|
|
511
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
512
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
513
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
514
|
+
isUp: false,
|
|
515
|
+
} );
|
|
516
|
+
} else if ( type !== 'tagging' && mapping.status === 'Closed' ) {
|
|
517
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
518
|
+
const revisedPerc =
|
|
519
|
+
footfallValue > 0 ?
|
|
520
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
521
|
+
'0';
|
|
522
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
523
|
+
temp.push( {
|
|
524
|
+
actionType: type,
|
|
525
|
+
footfall: footfallValue,
|
|
526
|
+
revicedFootfall: revisedFootfall,
|
|
527
|
+
revicedPerc: revisedPerc,
|
|
528
|
+
count: countObj,
|
|
529
|
+
createdAt: mapping.createdAt ?? '',
|
|
530
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
531
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
532
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
533
|
+
} );
|
|
534
|
+
}
|
|
535
|
+
} );
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
const actionTypes = [ 'tagging', 'review', 'approve', 'tangoreview', 'finalRevision' ];
|
|
539
|
+
|
|
540
|
+
// Dynamically add to temp only if actionType matches and status is 'closed',
|
|
541
|
+
// except for 'tagging' where status must be 'raised'
|
|
542
|
+
temp = [];
|
|
543
|
+
actionTypes.forEach( ( type ) => {
|
|
544
|
+
const mapping = getMappingForType( type );
|
|
545
|
+
if ( type === 'tagging' ) {
|
|
546
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
547
|
+
temp.push( {
|
|
548
|
+
actionType: type,
|
|
549
|
+
footfall: footfallValue,
|
|
550
|
+
revicedFootfall: mapping.revicedFootfall ?? 0,
|
|
551
|
+
revicedPerc: mapping.revicedPerc ?? '--',
|
|
552
|
+
count: countObj,
|
|
553
|
+
createdAt: mapping.createdAt ?? '',
|
|
554
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
555
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
556
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
557
|
+
isUp: false,
|
|
558
|
+
} );
|
|
559
|
+
}
|
|
560
|
+
if ( type !== 'tagging' && mapping.status === 'Closed' ) {
|
|
561
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
562
|
+
temp.push( {
|
|
563
|
+
actionType: type,
|
|
564
|
+
footfall: footfallValue,
|
|
565
|
+
revicedFootfall: mapping.revicedFootfall ?? 0,
|
|
566
|
+
revicedPerc: mapping.revicedPerc ?? '--',
|
|
567
|
+
count: countObj,
|
|
568
|
+
createdAt: mapping.createdAt ?? '',
|
|
569
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
570
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
571
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
572
|
+
} );
|
|
573
|
+
}
|
|
574
|
+
} );
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
const LamdaURL = revop.getImages;
|
|
579
|
+
let resultData = await LamdaServiceCall( LamdaURL, inputData );
|
|
580
|
+
if ( resultData ) {
|
|
581
|
+
// temp.length? temp[0].status = 'open': null;
|
|
582
|
+
if ( resultData.status_code == '200' ) {
|
|
583
|
+
return res.sendSuccess( { ...resultData, ticketStatus: temp?.length > 0 && ticketDetails? temp : null, config: req?.store?.footfallDirectoryConfigs } );
|
|
584
|
+
} else {
|
|
585
|
+
return res.sendError( 'No Content', 204 );
|
|
586
|
+
}
|
|
587
|
+
} else {
|
|
588
|
+
return res.sendError( 'No Content', 204 );
|
|
589
|
+
}
|
|
416
590
|
} catch ( error ) {
|
|
417
591
|
logger.error( { message: error, data: req.query, function: 'storeProcessedData' } );
|
|
418
592
|
const err = error.message || 'Internal Server Error';
|
|
@@ -437,9 +611,21 @@ export async function tagTempId( req, res ) {
|
|
|
437
611
|
exitTime: inputData.exitTime,
|
|
438
612
|
filePath: inputData.filePath,
|
|
439
613
|
status: inputData?.revopsType == 'non-tagging' ?'':'submitted',
|
|
440
|
-
description: '',
|
|
614
|
+
description: inputData.comments || '',
|
|
441
615
|
isChecked: inputData.isChecked,
|
|
442
|
-
|
|
616
|
+
// Add id to each object in duplicateImage if it exists and is an array
|
|
617
|
+
duplicateImage: Array.isArray( inputData?.duplicateImage ) ?
|
|
618
|
+
inputData.duplicateImage.map( ( img ) => ( {
|
|
619
|
+
...img,
|
|
620
|
+
id: `${inputData?.storeId}_${inputData?.dateString}_${img?.tempId}`,
|
|
621
|
+
actions: [
|
|
622
|
+
{
|
|
623
|
+
actionType: 'tagging',
|
|
624
|
+
action: 'submitted',
|
|
625
|
+
},
|
|
626
|
+
],
|
|
627
|
+
} ) ) :
|
|
628
|
+
[],
|
|
443
629
|
type: 'tagging-reflect',
|
|
444
630
|
ticketStatus: 'submitted',
|
|
445
631
|
isParent: inputData?.revopsType === 'duplicate'? true : false,
|
|
@@ -449,6 +635,7 @@ export async function tagTempId( req, res ) {
|
|
|
449
635
|
action: 'submitted',
|
|
450
636
|
},
|
|
451
637
|
],
|
|
638
|
+
comments: inputData.comments || '',
|
|
452
639
|
createdAt: new Date(),
|
|
453
640
|
updatedAt: new Date(),
|
|
454
641
|
|
|
@@ -464,8 +651,10 @@ export async function tagTempId( req, res ) {
|
|
|
464
651
|
ctx._source.description = params.description;
|
|
465
652
|
ctx._source.isChecked = params.isChecked;
|
|
466
653
|
ctx._source.duplicateImage = params.duplicateImage;
|
|
654
|
+
ctx._source.isParent = params.isParent;
|
|
467
655
|
ctx._source.updatedAt = params.updatedAt;
|
|
468
656
|
ctx._source.timeRange = params.timeRange;
|
|
657
|
+
ctx._source.comments = params.comments;
|
|
469
658
|
if (ctx._source.createdAt == null) {
|
|
470
659
|
ctx._source.createdAt = params.createdAt;
|
|
471
660
|
ctx._source.clientId = params.clientId;
|
|
@@ -480,6 +669,32 @@ export async function tagTempId( req, res ) {
|
|
|
480
669
|
};
|
|
481
670
|
const id = `${inputData.storeId}_${inputData.dateString}_${inputData.timeRange}_${inputData.tempId}`;
|
|
482
671
|
await upsertWithScript( openSearch.revop, id, { script, upsert: upsertRecord } );
|
|
672
|
+
if ( inputData?.comments && inputData?.comments !== '' ) {
|
|
673
|
+
const id = `${inputData.storeId}_${inputData.dateString}_${Date.now()}`;
|
|
674
|
+
const logs = {
|
|
675
|
+
type: 'tagging',
|
|
676
|
+
parent: inputData?.revopsType == 'duplicate'? inputData.tempId : null,
|
|
677
|
+
id: `${inputData?.storeId}_${inputData?.dateString}_${inputData?.tempId}`,
|
|
678
|
+
tempId: inputData.tempId,
|
|
679
|
+
timeRange: inputData.timeRange,
|
|
680
|
+
storeId: inputData.storeId,
|
|
681
|
+
dateString: inputData.dateString,
|
|
682
|
+
processType: inputData.processType,
|
|
683
|
+
category: inputData.revopsType,
|
|
684
|
+
entryTime: inputData.entryTime,
|
|
685
|
+
exitTime: inputData.exitTime,
|
|
686
|
+
filePath: inputData.filePath,
|
|
687
|
+
status: inputData?.revopsType == 'non-tagging' ?'':'submitted',
|
|
688
|
+
description: inputData.comments || '',
|
|
689
|
+
isChecked: inputData.isChecked,
|
|
690
|
+
createdByEmail: req?.user?.email,
|
|
691
|
+
createdByUserName: req?.user?.userName,
|
|
692
|
+
createdByRole: req?.user?.role,
|
|
693
|
+
message: inputData.comments || '',
|
|
694
|
+
createdAt: new Date(),
|
|
695
|
+
};
|
|
696
|
+
await insertWithId( openSearch.vmsCommentsLog, id, logs );
|
|
697
|
+
}
|
|
483
698
|
if ( inputData?.duplicateImage?.length> 0 ) {
|
|
484
699
|
let bulkBody = [];
|
|
485
700
|
for ( let item of inputData?.duplicateImage ) {
|
|
@@ -487,7 +702,7 @@ export async function tagTempId( req, res ) {
|
|
|
487
702
|
clientId: inputData.storeId.split( '-' )[0],
|
|
488
703
|
storeId: inputData.storeId,
|
|
489
704
|
tempId: item.tempId,
|
|
490
|
-
id: `${inputData?.storeId}_${inputData?.dateString}_${
|
|
705
|
+
id: `${inputData?.storeId}_${inputData?.dateString}_${item?.tempId}`,
|
|
491
706
|
dateString: inputData.dateString,
|
|
492
707
|
timeRange: item.timeRange,
|
|
493
708
|
isChecked: item.isChecked,
|
|
@@ -502,6 +717,7 @@ export async function tagTempId( req, res ) {
|
|
|
502
717
|
isParent: false,
|
|
503
718
|
type: 'tagging-reflect',
|
|
504
719
|
ticketStatus: 'submitted',
|
|
720
|
+
comments: inputData.comments || '',
|
|
505
721
|
actions: [
|
|
506
722
|
{
|
|
507
723
|
actionType: 'tagging',
|
|
@@ -3129,29 +3129,30 @@ export const managerTrafficDensityExport = async ( req, res ) => {
|
|
|
3129
3129
|
}
|
|
3130
3130
|
};
|
|
3131
3131
|
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3132
|
+
|
|
3133
|
+
export const getStoreListv2 = async ( req, res ) => {
|
|
3134
|
+
try {
|
|
3135
|
+
let reqestData = req.body;
|
|
3136
|
+
let getClientData = await getClientConfig( reqestData.clientId );
|
|
3137
|
+
if ( !getClientData ) {
|
|
3138
|
+
return res.sendError( 'Invalid Client Id', 400 );
|
|
3139
|
+
}
|
|
3140
|
+
reqestData.featureConfigs = getClientData.featureConfigs;
|
|
3141
|
+
let LamdaURL = 'https://nmnnlq3ie65bljcvxauhxmsrpa0edhfa.lambda-url.ap-south-1.on.aws/';
|
|
3142
|
+
let resultData = await LamdaServiceCall( LamdaURL, reqestData );
|
|
3143
|
+
if ( resultData ) {
|
|
3144
|
+
if ( resultData.status_code == '200' ) {
|
|
3145
|
+
return res.sendSuccess( resultData );
|
|
3146
|
+
} else {
|
|
3147
|
+
return res.sendError( 'No Content', 204 );
|
|
3148
|
+
}
|
|
3149
|
+
} else {
|
|
3150
|
+
return res.sendError( 'No Content', 204 );
|
|
3151
|
+
}
|
|
3152
|
+
} catch ( error ) {
|
|
3153
|
+
const err = error.message || 'Internal Server Error';
|
|
3154
|
+
logger.error( { error: error, message: req.body, function: 'getStoreListv2' } );
|
|
3155
|
+
return res.sendError( err, 500 );
|
|
3156
|
+
}
|
|
3157
|
+
};
|
|
3158
|
+
|
package/src/dtos/revop.dtos.js
CHANGED
|
@@ -56,8 +56,8 @@ export const tagTempIdSchema = joi.object( {
|
|
|
56
56
|
|
|
57
57
|
const diff = today.diff( inputDate, 'day' );
|
|
58
58
|
|
|
59
|
-
if ( diff >
|
|
60
|
-
return helpers.message( 'Tagging is not allowed for a period exceeding
|
|
59
|
+
if ( diff > 3 ) {
|
|
60
|
+
return helpers.message( 'Tagging is not allowed for a period exceeding 3 days' );
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
return value;
|
|
@@ -84,6 +84,7 @@ export const tagTempIdSchema = joi.object( {
|
|
|
84
84
|
entryTime: joi.string().required(),
|
|
85
85
|
exitTime: joi.string().required(),
|
|
86
86
|
filePath: joi.string().required(),
|
|
87
|
+
comments: joi.string().optional().allow( '' ),
|
|
87
88
|
|
|
88
89
|
} );
|
|
89
90
|
|
|
@@ -239,6 +239,17 @@ export const validateCountryHeaderSchemav2 = joi.object( {
|
|
|
239
239
|
export const validateCountryHeaderParamsv2 = {
|
|
240
240
|
body: validateCountryHeaderSchemav2,
|
|
241
241
|
};
|
|
242
|
+
|
|
243
|
+
export const validateGetStoreListSchemav2 = joi.object( {
|
|
244
|
+
clientId: joi.string().required(),
|
|
245
|
+
storeId: joi.array().items( joi.string().required() ).required(),
|
|
246
|
+
downtime: joi.array().items( joi.string().required() ).required(),
|
|
247
|
+
fromDate: joi.string().required(),
|
|
248
|
+
toDate: joi.string().required(),
|
|
249
|
+
} );
|
|
250
|
+
export const validateGetStoreList = {
|
|
251
|
+
body: validateGetStoreListSchemav2,
|
|
252
|
+
};
|
|
242
253
|
export const getMyProductSchema = joi.object( {
|
|
243
254
|
clientId: joi.string().required(),
|
|
244
255
|
storeId: joi.array().optional().empty(),
|
|
@@ -18,7 +18,6 @@ revopRouter
|
|
|
18
18
|
.get( '/footfall-images', isAllowedSessionHandler, validate( footfallImagesValid ), getTaggingConfig, footFallImages )
|
|
19
19
|
.post( '/tag-tempId', isAllowedSessionHandler, validate( tagTempIdValid ), deleteTaggedDuplicate, mappingConfig, tagTempId )
|
|
20
20
|
|
|
21
|
-
|
|
22
21
|
.post( '/get-categorized-images', isAllowedSessionHandler, validate( getCategorizedImagesValid ), getCategorizedImages );
|
|
23
22
|
|
|
24
23
|
|
|
@@ -92,6 +92,7 @@ import {
|
|
|
92
92
|
funnelV3,
|
|
93
93
|
getStoreMapDataV3,
|
|
94
94
|
managerTrafficDensityExport,
|
|
95
|
+
getStoreListv2,
|
|
95
96
|
} from '../controllers/tangoTrafficV3.controllers.js';
|
|
96
97
|
analysisTrafficRouter
|
|
97
98
|
.get( '/welcome', welcome )
|
|
@@ -175,5 +176,6 @@ analysisTrafficRouter
|
|
|
175
176
|
.post( '/headerCountry_v2', isAllowedSessionHandler, isAllowedClient, validate( validationDtos.validateCountryHeaderParamsv2 ), getAssinedStore, headerCountryV2 )
|
|
176
177
|
.post( '/checkTodayReportStatus', isAllowedSessionHandler, checkTodayReportStatus )
|
|
177
178
|
.post( '/headerZoneV2', isAllowedSessionHandler, headerZoneV2 )
|
|
178
|
-
.post( '/trafficDensityExport_v2', managerTrafficDensityExport )
|
|
179
|
+
.post( '/trafficDensityExport_v2', managerTrafficDensityExport )
|
|
180
|
+
.post( '/get-store-list-v2', isAllowedSessionHandler, isAllowedClient, validate( validationDtos.validateGetStoreList ), getStoreListv2 );
|
|
179
181
|
export default analysisTrafficRouter;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { getOpenSearchCount, logger } from 'tango-app-api-middleware';
|
|
2
|
-
import {
|
|
3
|
-
import { deleteByQuery } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
2
|
+
import { deleteByQuery, getOpenSearchData } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
4
3
|
import { findOne } from '../services/clients.services.js';
|
|
5
4
|
|
|
6
5
|
export async function getTaggingConfig( req, res, next ) {
|
|
@@ -65,41 +64,54 @@ export async function getFootfallCount( req, res, next ) {
|
|
|
65
64
|
export async function mappingConfig( req, res, next ) {
|
|
66
65
|
try {
|
|
67
66
|
const inputData = req.body;
|
|
67
|
+
|
|
68
68
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
term: {
|
|
77
|
-
'storeId.keyword': inputData.storeId,
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
term: {
|
|
82
|
-
'dateString': inputData.dateString,
|
|
83
|
-
},
|
|
69
|
+
const footfallQuery ={
|
|
70
|
+
query: {
|
|
71
|
+
bool: {
|
|
72
|
+
must: [
|
|
73
|
+
{
|
|
74
|
+
term: {
|
|
75
|
+
'store_id.keyword': inputData.storeId,
|
|
84
76
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
term: {
|
|
80
|
+
'date_string': inputData.dateString,
|
|
89
81
|
},
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
],
|
|
92
85
|
},
|
|
93
|
-
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
94
88
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
89
|
+
|
|
90
|
+
const footfallOutput = await getOpenSearchData( openSearch.footfall, footfallQuery );
|
|
91
|
+
if ( footfallOutput?.body?.hits?.hits?.length === 0 ) {
|
|
92
|
+
return res.sendError( 'No updated footfall for this date', 400 );
|
|
93
|
+
}
|
|
94
|
+
const getFootfallCount = footfallOutput?.body?.hits?.hits;
|
|
95
|
+
const footfall = getFootfallCount?.[0]?._source?.footfall_count;
|
|
96
|
+
|
|
97
|
+
const getConfig = await findOne( { clientId: inputData?.storeId?.split( '-' )[0] }, { footfallDirectoryConfigs: 1 } );
|
|
98
|
+
const taggingLimitation = getConfig?.footfallDirectoryConfigs?.taggingLimitation;
|
|
99
|
+
// Find the tagging limitation for the given revopsType
|
|
100
|
+
let matchedLimitation = null;
|
|
101
|
+
if ( Array.isArray( taggingLimitation ) ) {
|
|
102
|
+
matchedLimitation = taggingLimitation.find(
|
|
103
|
+
( l ) => l.type === inputData.revopsType,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if ( matchedLimitation ) {
|
|
108
|
+
// Determine the limit value
|
|
109
|
+
let limitValue = Number( matchedLimitation.value ) || 0;
|
|
110
|
+
let unit = matchedLimitation.unit;
|
|
111
|
+
|
|
112
|
+
// Assuming getData and/or getOpenSearchCount provides the actual tagged count for revopsType
|
|
113
|
+
// Query OpenSearch for current tagged count for this revopsType
|
|
114
|
+
const taggedCountQuery = {
|
|
103
115
|
query: {
|
|
104
116
|
bool: {
|
|
105
117
|
must: [
|
|
@@ -118,92 +130,51 @@ export async function mappingConfig( req, res, next ) {
|
|
|
118
130
|
'revopsType.keyword': inputData.revopsType,
|
|
119
131
|
},
|
|
120
132
|
},
|
|
133
|
+
{
|
|
134
|
+
term: {
|
|
135
|
+
'isParent': false,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
121
138
|
],
|
|
122
139
|
},
|
|
123
140
|
},
|
|
124
141
|
};
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
142
|
+
const taggedData = await getOpenSearchCount( openSearch.revop, taggedCountQuery );
|
|
143
|
+
const taggedValue = ( taggedData?.body?.count || 0 )+ ( inputData.revopsType == 'duplicate'? inputData?.duplicateImage?.length : 1 );
|
|
144
|
+
// If the unit is %, compare percentage of taggedValue/footfall, otherwise compare taggedValue to limitValue directly
|
|
145
|
+
let isLimitExceeded = false;
|
|
146
|
+
if ( unit === '%' ) {
|
|
147
|
+
// footfall may be undefined, treat as 0 (avoid division by zero)
|
|
148
|
+
const totalFootfall = Number( footfall ) || 0;
|
|
149
|
+
const taggedPercent = totalFootfall > 0 ? ( taggedValue / totalFootfall ) * 100 : 0;
|
|
150
|
+
isLimitExceeded = taggedPercent > limitValue;
|
|
151
|
+
logger.info( {
|
|
152
|
+
limitType: 'PERCENT',
|
|
153
|
+
taggedValue,
|
|
154
|
+
totalFootfall,
|
|
155
|
+
taggedPercent,
|
|
156
|
+
limitValue,
|
|
157
|
+
isLimitExceeded,
|
|
158
|
+
forRevopsType: inputData.revopsType,
|
|
159
|
+
} );
|
|
128
160
|
} else {
|
|
129
|
-
|
|
161
|
+
// Non-percent, treat limitValue as an absolute number
|
|
162
|
+
isLimitExceeded = taggedValue > limitValue;
|
|
163
|
+
logger.info( {
|
|
164
|
+
limitType: 'ABSOLUTE',
|
|
165
|
+
taggedValue,
|
|
166
|
+
limitValue,
|
|
167
|
+
isLimitExceeded,
|
|
168
|
+
forRevopsType: inputData.revopsType,
|
|
169
|
+
} );
|
|
130
170
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
next();
|
|
134
|
-
return;
|
|
135
|
-
} else {
|
|
136
|
-
return res.sendError( 'Forbidden to junk mapping', 500 );
|
|
171
|
+
if ( isLimitExceeded ) {
|
|
172
|
+
return res.sendError( `Limit exceed: Only ${limitValue}${unit || ''} items allowed for ${inputData.revopsType}`, 400 );
|
|
137
173
|
}
|
|
174
|
+
return next();
|
|
138
175
|
} else {
|
|
139
|
-
next();
|
|
176
|
+
return next();
|
|
140
177
|
}
|
|
141
|
-
// else if ( inputData.revopsType == 'duplicate' ) {
|
|
142
|
-
// const getFootfallQuery = {
|
|
143
|
-
// query: {
|
|
144
|
-
// terms: {
|
|
145
|
-
// _id: [ inputData?.dateString ],
|
|
146
|
-
// },
|
|
147
|
-
// },
|
|
148
|
-
// _source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
|
|
149
|
-
// sort: [
|
|
150
|
-
// {
|
|
151
|
-
// date_iso: {
|
|
152
|
-
// order: 'desc',
|
|
153
|
-
// },
|
|
154
|
-
// },
|
|
155
|
-
// ],
|
|
156
|
-
// };
|
|
157
|
-
|
|
158
|
-
// const getFootfall = await getOpenSearchData( openSearch.footfall, getFootfallQuery );
|
|
159
|
-
// const footfall = getFootfall?.body?.hites?.hits?.[0]?._source?.footfall_count;
|
|
160
|
-
// const getQuery = {
|
|
161
|
-
// query: {
|
|
162
|
-
// bool: {
|
|
163
|
-
// must: [
|
|
164
|
-
// {
|
|
165
|
-
// term: {
|
|
166
|
-
// 'storeId.keyword': inputData.storeId,
|
|
167
|
-
// },
|
|
168
|
-
// },
|
|
169
|
-
// {
|
|
170
|
-
// term: {
|
|
171
|
-
// 'dateString': inputData.dateString,
|
|
172
|
-
// },
|
|
173
|
-
// },
|
|
174
|
-
// {
|
|
175
|
-
// term: {
|
|
176
|
-
// 'revopsType.keyword': inputData.revopsType,
|
|
177
|
-
// },
|
|
178
|
-
// },
|
|
179
|
-
// {
|
|
180
|
-
// term: {
|
|
181
|
-
// 'parent.keyword': null,
|
|
182
|
-
// },
|
|
183
|
-
// },
|
|
184
|
-
// ],
|
|
185
|
-
// },
|
|
186
|
-
// },
|
|
187
|
-
// };
|
|
188
|
-
// const getData = await getOpenSearchCount( openSearch.revop, getQuery );
|
|
189
|
-
// logger.info( { getData: getData, footfall: footfall, duplicate: config?.revopTagging?.duplicate } );
|
|
190
|
-
// if ( getData && footfall && config?.revopTagging?.duplicate ) {
|
|
191
|
-
// const data = config?.revopTagging?.duplicate;
|
|
192
|
-
// // Convert "20%" → 0.2 (handle both "20%" and 20)
|
|
193
|
-
// const percentStr = typeof data === 'string' ? data.replace( '%', '' ) : data;
|
|
194
|
-
// logger.info( { percentStr: percentStr } );
|
|
195
|
-
// const percentValue =percentStr / 100;
|
|
196
|
-
|
|
197
|
-
// const result = percentValue * footfall;
|
|
198
|
-
|
|
199
|
-
// logger.info( { result: result, footfall: footfall } );
|
|
200
|
-
// }
|
|
201
|
-
// if ( getData && getData?.body?.count >= Math.round( result ) ) {
|
|
202
|
-
// return res.sendError( `Select up to ${config?.revopTagging?.duplicate} items only`, 400 );
|
|
203
|
-
// } else {
|
|
204
|
-
// next();
|
|
205
|
-
// }
|
|
206
|
-
// }
|
|
207
178
|
} catch ( error ) {
|
|
208
179
|
logger.error( { error: error, message: req.body, function: 'traffic-revop-getTaggingConfig' } );
|
|
209
180
|
next();
|