tango-app-api-infra 3.9.25-vmsbug.2 → 3.9.25
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/footfallDirectory.controllers.js +1040 -53
- package/src/controllers/internalInfra.controller.js +334 -89
- package/src/dtos/footfallDirectory.dtos.js +32 -0
- package/src/routes/footfallDirectory.routes.js +4 -2
- package/src/validations/footfallDirectory.validation.js +472 -338
|
@@ -10,6 +10,7 @@ import { aggregateClient, findOneClient } from '../services/client.service.js';
|
|
|
10
10
|
import { findUser, findOneUser } from '../services/user.service.js';
|
|
11
11
|
import { sendPushNotification } from 'tango-app-api-middleware';
|
|
12
12
|
import { findStoreAccuracIssues, upsertStoreAccuracIssues } from '../services/storeAccuracyIssues.service.js';
|
|
13
|
+
import { updateOneUpsertVmsStoreRequest } from '../services/vmsStoreRequest.service.js';
|
|
13
14
|
dayjs.extend( utc );
|
|
14
15
|
dayjs.extend( timezone );
|
|
15
16
|
|
|
@@ -279,6 +280,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
279
280
|
createdByEmail: req?.user?.email,
|
|
280
281
|
createdByUserName: req?.user?.userName,
|
|
281
282
|
createdByRole: req?.user?.role,
|
|
283
|
+
contactEmail: req?.user?.email,
|
|
282
284
|
createdAt: new Date(),
|
|
283
285
|
},
|
|
284
286
|
);
|
|
@@ -297,6 +299,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
297
299
|
status: parseInt( inputData?.mappingInfo?.revicedPerc || 0 ) < parseInt( getConfig?.footfallDirectoryConfigs?.tangoReview || 0 ) ? 'Open - Accuracy Issue' : 'Closed',
|
|
298
300
|
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
299
301
|
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
302
|
+
reviced: parseInt( inputData?.mappingInfo?.revicedPerc ),
|
|
300
303
|
mappingInfo: ticketData?.[0]?._source?.mappingInfo,
|
|
301
304
|
createdByEmail: req?.user?.email,
|
|
302
305
|
createdByUserName: req?.user?.userName,
|
|
@@ -314,6 +317,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
314
317
|
const tangoReview = Number( getConfig?.footfallDirectoryConfigs?.tangoReview?.split( '%' )[0] );
|
|
315
318
|
// If autoclose enabled and revisedPercentage meets/exceeds threshold, close ticket and skip revision
|
|
316
319
|
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
320
|
+
const accuracy = getConfig?.footfallDirectoryConfigs?.accuracyBreach || null;
|
|
317
321
|
if (
|
|
318
322
|
isAutoCloseEnable === true &&
|
|
319
323
|
revisedPercentage >= autoCloseAccuracyValue
|
|
@@ -329,6 +333,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
329
333
|
mode: inputData.mappingInfo?.mode,
|
|
330
334
|
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
331
335
|
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
336
|
+
reviced: parseInt( inputData?.mappingInfo?.revicedPerc ),
|
|
332
337
|
count: inputData.mappingInfo?.count,
|
|
333
338
|
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
334
339
|
status: 'Closed',
|
|
@@ -358,12 +363,14 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
358
363
|
mode: inputData.mappingInfo?.mode,
|
|
359
364
|
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
360
365
|
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
366
|
+
reviced: parseInt( inputData?.mappingInfo?.revicedPerc ),
|
|
361
367
|
count: inputData.mappingInfo?.count,
|
|
362
368
|
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
363
369
|
status: 'Closed',
|
|
364
370
|
createdByEmail: req?.user?.email,
|
|
365
371
|
createdByUserName: req?.user?.userName,
|
|
366
372
|
createdByRole: req?.user?.role,
|
|
373
|
+
contactEmail: req?.user?.email,
|
|
367
374
|
createdAt: new Date(),
|
|
368
375
|
},
|
|
369
376
|
);
|
|
@@ -416,6 +423,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
416
423
|
createdByEmail: req?.user?.email,
|
|
417
424
|
createdByUserName: req?.user?.userName,
|
|
418
425
|
createdByRole: req?.user?.role,
|
|
426
|
+
contactEmail: req?.user?.email,
|
|
419
427
|
createdAt: new Date(),
|
|
420
428
|
},
|
|
421
429
|
|
|
@@ -467,6 +475,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
467
475
|
createdByEmail: req?.user?.email,
|
|
468
476
|
createdByUserName: req?.user?.userName,
|
|
469
477
|
createdByRole: req?.user?.role,
|
|
478
|
+
contactEmail: req?.user?.email,
|
|
470
479
|
createdAt: new Date(),
|
|
471
480
|
},
|
|
472
481
|
);
|
|
@@ -504,24 +513,310 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
504
513
|
// return;
|
|
505
514
|
|
|
506
515
|
const insertResult = await updateOpenSearchData( openSearch.footfallDirectory, id, { doc: record } );
|
|
507
|
-
|
|
508
516
|
if ( insertResult && ( insertResult.statusCode === 201 || insertResult.statusCode === 200 ) ) {
|
|
509
|
-
if (
|
|
517
|
+
if ( accuracy && accuracy.ticketCount && accuracy.days && tangoApproved ) {
|
|
518
|
+
// Check if ticketCount exceeds breach limit within config months and revised footfall percentage > config accuracy
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
const breachDays = Number( accuracy.days );
|
|
522
|
+
const breachCount = Number( accuracy.ticketCount );
|
|
523
|
+
|
|
524
|
+
// accuracy.accuracy is a string like "95%", so remove "%" and convert to Number
|
|
525
|
+
// const breachAccuracy = Number( accuracy.accuracy.replace( '%', '' ) );
|
|
526
|
+
const ticketName = 'footfall-directory'; // adjust if ticket name variable differs
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
const formatDate = ( d ) =>
|
|
530
|
+
`${d.getFullYear()}-${String( d.getMonth() + 1 ).padStart( 2, '0' )}-${String( d.getDate() ).padStart( 2, '0' )}`;
|
|
531
|
+
|
|
532
|
+
// Compute current date object
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
// Calculate start date based on config days
|
|
536
|
+
// If 30 days: start from first day of current month
|
|
537
|
+
// If 60 days: start from first day of last month
|
|
538
|
+
const currentDateObj = new Date( inputData?.dateString ); // Declare currentDateObj as today's date
|
|
539
|
+
const startDateObj = new Date( currentDateObj );
|
|
540
|
+
|
|
541
|
+
if ( breachDays === 7 ) {
|
|
542
|
+
// Consider within this week (start from Monday)
|
|
543
|
+
const dayOfWeek = startDateObj.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
|
544
|
+
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
545
|
+
startDateObj.setDate( startDateObj.getDate() - daysFromMonday );
|
|
546
|
+
} else if ( breachDays === 30 ) {
|
|
547
|
+
// Consider within this month
|
|
548
|
+
startDateObj.setDate( 1 ); // First day of current month
|
|
549
|
+
} else if ( breachDays === 60 ) {
|
|
550
|
+
// Consider this month and last month
|
|
551
|
+
startDateObj.setMonth( startDateObj.getMonth() - 1 );
|
|
552
|
+
startDateObj.setDate( 1 ); // First day of last month
|
|
553
|
+
} else {
|
|
554
|
+
// For other values, calculate months from days
|
|
555
|
+
const breachMonths = Math.ceil( breachDays / 30 );
|
|
556
|
+
startDateObj.setMonth( startDateObj.getMonth() - breachMonths + 1 );
|
|
557
|
+
startDateObj.setDate( 1 );
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
startDateObj.setUTCHours( 0, 0, 0, 0 );
|
|
562
|
+
const startDate = formatDate( startDateObj );
|
|
563
|
+
|
|
564
|
+
// Calculate end date of the period (end of week/month) for dateString range
|
|
565
|
+
const endDateObj = new Date( currentDateObj );
|
|
566
|
+
if ( breachDays === 7 ) {
|
|
567
|
+
const dayOfWeek = endDateObj.getDay();
|
|
568
|
+
const daysUntilSunday = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
569
|
+
endDateObj.setDate( endDateObj.getDate() + daysUntilSunday );
|
|
570
|
+
} else if ( breachDays === 30 ) {
|
|
571
|
+
endDateObj.setMonth( endDateObj.getMonth() + 1, 0 );
|
|
572
|
+
} else if ( breachDays === 60 ) {
|
|
573
|
+
endDateObj.setMonth( endDateObj.getMonth() + 2, 0 );
|
|
574
|
+
} else {
|
|
575
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
576
|
+
endDateObj.setDate( endDateObj.getDate() + remainingDays );
|
|
577
|
+
}
|
|
578
|
+
const endDate = formatDate( endDateObj );
|
|
579
|
+
|
|
580
|
+
// Query for tickets within this week/month window for this store and ticket name
|
|
581
|
+
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
582
|
+
const mappingInfoFilter = [
|
|
583
|
+
{
|
|
584
|
+
nested: {
|
|
585
|
+
path: 'mappingInfo',
|
|
586
|
+
query: {
|
|
587
|
+
bool: {
|
|
588
|
+
must: [
|
|
589
|
+
{ term: { 'mappingInfo.type': 'approve' } },
|
|
590
|
+
{ term: { 'mappingInfo.status': 'Closed' } },
|
|
591
|
+
],
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
];
|
|
597
|
+
if ( tangoApproved ) {
|
|
598
|
+
mappingInfoFilter.push( {
|
|
599
|
+
nested: {
|
|
600
|
+
path: 'mappingInfo',
|
|
601
|
+
query: {
|
|
602
|
+
term: { 'mappingInfo.type': 'tangoreview' },
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
} );
|
|
606
|
+
}
|
|
607
|
+
logger.info( { startDate, endDate, storeId: inputData?.storeId } );
|
|
510
608
|
const query = {
|
|
511
|
-
|
|
512
|
-
|
|
609
|
+
query: {
|
|
610
|
+
bool: {
|
|
611
|
+
must: [
|
|
612
|
+
{ term: { 'storeId.keyword': inputData?.storeId } },
|
|
613
|
+
{ term: { 'ticketName.keyword': ticketName } },
|
|
614
|
+
{ range: { dateString: { gte: startDate, lte: endDate } } },
|
|
615
|
+
],
|
|
616
|
+
filter: [
|
|
617
|
+
{
|
|
618
|
+
bool: {
|
|
619
|
+
should: mappingInfoFilter,
|
|
620
|
+
minimum_should_match: 1,
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
};
|
|
627
|
+
// Search in OpenSearch for recent similar tickets
|
|
628
|
+
const breachTicketsResult = await getOpenSearchData( openSearch.footfallDirectory, query );
|
|
629
|
+
logger.info( { breachTicketsResult, message: 'breachTicketsResult;;;;;;;1' } );
|
|
630
|
+
const getTickets = breachTicketsResult?.body?.hits?.hits || [];
|
|
631
|
+
const tangoTaggingDuDate = getConfig?.footfallDirectoryConfigs?.tangoReview || 0;
|
|
632
|
+
const gettingTangoreview = Number( tangoTaggingDuDate.split( '%' )[0] || 0 );
|
|
633
|
+
|
|
634
|
+
const deltaBreach = getConfig?.footfallDirectoryConfigs?.deltaBreach || '>';
|
|
635
|
+
const deltaCountBreach = Number( getConfig?.footfallDirectoryConfigs?.deltaCountBreach ?? 10 );
|
|
636
|
+
const taggingBreach = getConfig?.footfallDirectoryConfigs?.taggingBreach || '>=';
|
|
637
|
+
const taggingCountBreach = Number( getConfig?.footfallDirectoryConfigs?.taggingCountBreach ?? 10 );
|
|
638
|
+
|
|
639
|
+
const compareByOperator = ( value, operator, threshold ) => {
|
|
640
|
+
switch ( operator ) {
|
|
641
|
+
case '>': return value > threshold;
|
|
642
|
+
case '>=': return value >= threshold;
|
|
643
|
+
case '<': return value < threshold;
|
|
644
|
+
case '<=': return value <= threshold;
|
|
645
|
+
case '==': return value == threshold;
|
|
646
|
+
case '===': return value === threshold;
|
|
647
|
+
default: return value > threshold;
|
|
648
|
+
}
|
|
513
649
|
};
|
|
514
|
-
const getStoreType = await countDocumnetsCamera( query );
|
|
515
650
|
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
|
|
651
|
+
// Filter tickets where revised footfall percentage > config accuracy
|
|
652
|
+
let breachTicketsCount = 0;
|
|
653
|
+
for ( const ticketInfo of getTickets ) {
|
|
654
|
+
const source = ticketInfo._source || {};
|
|
519
655
|
|
|
520
|
-
|
|
656
|
+
const ticketMappingInfo = source.mappingInfo || [];
|
|
521
657
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
658
|
+
|
|
659
|
+
// Get Store/AOM accuracy (tagging stage)
|
|
660
|
+
const taggingEntry = ticketMappingInfo.find( ( m ) => m.type === 'tagging' );
|
|
661
|
+
const storeAccuracy = Number( taggingEntry?.reviced ) || 0;
|
|
662
|
+
const storeRevisedFF = Number( taggingEntry?.revicedFootfall )|| 0;
|
|
663
|
+
|
|
664
|
+
// Get Central Ops(tangoreview stage)
|
|
665
|
+
const centralOpsEntry = ticketMappingInfo.find( ( m ) => m.type === 'approve' );
|
|
666
|
+
const centralOpsAccuracy = Number( centralOpsEntry?.reviced ) || 0;
|
|
667
|
+
const centralOpsRevisedFF = Number( centralOpsEntry?.revicedFootfall )|| 0;
|
|
668
|
+
|
|
669
|
+
// Get Central Ops/Tango accuracy (tangoreview stage)
|
|
670
|
+
const tangoEntry = ticketMappingInfo.find( ( m ) => m.type === 'tangoreview' );
|
|
671
|
+
const tangoAccuracy = Number( tangoEntry?.reviced ) || 0;
|
|
672
|
+
const tangoRevisedFF = Number( tangoEntry?.revicedFootfall )|| 0;
|
|
673
|
+
|
|
674
|
+
// Calculate delta between Store/AOM and Central Ops/Tango accuracy
|
|
675
|
+
|
|
676
|
+
const accuracyDelta1 = tangoAccuracy -storeAccuracy;
|
|
677
|
+
const accuracyDelta2 = centralOpsAccuracy - storeAccuracy;
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
// Check existing breach condition OR new accuracy delta breach conditions:
|
|
681
|
+
// Existing: revised footfall percentage > config accuracy and not Auto-Closed
|
|
682
|
+
// New conditions (all must be true):
|
|
683
|
+
// a) Delta between Store/AOM and Central Ops/Tango accuracy > deltaBreach config
|
|
684
|
+
// b) Post-review (Tango) accuracy > 85%
|
|
685
|
+
// c) Total images marked for validation > taggingBreach config
|
|
686
|
+
// const existingBreach = ticketRevisedPercentage > breachAccuracy && status !== 'Auto-Closed';
|
|
687
|
+
const isHighDelta = compareByOperator( accuracyDelta1, deltaBreach, deltaCountBreach ) || compareByOperator( accuracyDelta2, deltaBreach, deltaCountBreach );
|
|
688
|
+
const isCrossingThreshold =source?.reviced > gettingTangoreview;
|
|
689
|
+
logger.info( { source, revicedData: source?.reviced, gettingTangoreview } );
|
|
690
|
+
const isLowData = compareByOperator( tangoRevisedFF - storeRevisedFF, taggingBreach, taggingCountBreach ) || compareByOperator( centralOpsRevisedFF - storeRevisedFF, taggingBreach, taggingCountBreach );
|
|
691
|
+
|
|
692
|
+
const deltaBreachCondition =
|
|
693
|
+
isHighDelta && isCrossingThreshold && isLowData;
|
|
694
|
+
// const deltaBreachCondition = accuracyDelta > 10 || ( storeAccuracy < gettingTangoreview && tangoAccuracy > gettingTangoreview) || totalImages <= 10;
|
|
695
|
+
if ( deltaBreachCondition ) {
|
|
696
|
+
breachTicketsCount++;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// process.exit( 0 );
|
|
701
|
+
if ( breachTicketsCount > breachCount ) {
|
|
702
|
+
const getStoreName = await findOneStore( { storeId: inputData?.storeId }, { storeName: 1, _id: 0 } );
|
|
703
|
+
|
|
704
|
+
// Calculate remaining future days in the config period
|
|
705
|
+
const futureDates = [];
|
|
706
|
+
|
|
707
|
+
// Calculate end date of config period
|
|
708
|
+
const configEndDateObj = new Date( currentDateObj );
|
|
709
|
+
if ( breachDays === 7 ) {
|
|
710
|
+
// End of current week (Sunday)
|
|
711
|
+
const dayOfWeek = configEndDateObj.getDay(); // 0=Sun, 6=Sat
|
|
712
|
+
const daysUntilEndOfWeek = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
713
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + daysUntilEndOfWeek );
|
|
714
|
+
} else if ( breachDays === 30 ) {
|
|
715
|
+
// End of current month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
716
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 1, 0 ); // Last day of current month
|
|
717
|
+
} else if ( breachDays === 60 ) {
|
|
718
|
+
// End of next month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
719
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 2, 0 ); // Last day of next month
|
|
720
|
+
} else {
|
|
721
|
+
// For other values, add the remaining days
|
|
722
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
723
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + remainingDays );
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Generate future dates from the day after current date until end of config period
|
|
727
|
+
const tomorrow = new Date( currentDateObj );
|
|
728
|
+
tomorrow.setDate( tomorrow.getDate() + 1 );
|
|
729
|
+
|
|
730
|
+
const dateIterator = new Date( tomorrow );
|
|
731
|
+
while ( dateIterator <= configEndDateObj ) {
|
|
732
|
+
futureDates.push( formatDate( dateIterator ) );
|
|
733
|
+
dateIterator.setDate( dateIterator.getDate() + 1 );
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Check past days within the period (from startDate to currentDate - 1);
|
|
737
|
+
// if footfallDirectory index does not have a record for this storeId/dateString/type:"store", block that day
|
|
738
|
+
const pastIterator = new Date( startDateObj );
|
|
739
|
+
while ( pastIterator < currentDateObj ) {
|
|
740
|
+
const dateStr = formatDate( pastIterator );
|
|
741
|
+
const pastQuery = {
|
|
742
|
+
query: {
|
|
743
|
+
bool: {
|
|
744
|
+
must: [
|
|
745
|
+
{ term: { 'storeId.keyword': inputData?.storeId } },
|
|
746
|
+
{ term: { dateString: dateStr } },
|
|
747
|
+
{ term: { 'type.keyword': 'store' } },
|
|
748
|
+
],
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
754
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
755
|
+
const getStoreName = await findOneStore( { storeId: inputData?.storeId }, { storeName: 1 } );
|
|
756
|
+
|
|
757
|
+
if ( foundHits.length === 0 ) {
|
|
758
|
+
const record = {
|
|
759
|
+
clientId: inputData?.storeId?.split( '-' )[0],
|
|
760
|
+
storeId: inputData?.storeId,
|
|
761
|
+
storeName: getStoreName?.storeName,
|
|
762
|
+
dateString: dateStr,
|
|
763
|
+
status: 'block',
|
|
764
|
+
};
|
|
765
|
+
await updateOneUpsertVmsStoreRequest( { storeId: inputData?.storeId, dateString: dateStr }, record );
|
|
766
|
+
}
|
|
767
|
+
pastIterator.setDate( pastIterator.getDate() + 1 );
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Insert a block record for each future date where no store record exists
|
|
771
|
+
for ( const futureDateString of futureDates ) {
|
|
772
|
+
const pastQuery = {
|
|
773
|
+
query: {
|
|
774
|
+
bool: {
|
|
775
|
+
must: [
|
|
776
|
+
{ term: { 'storeId.keyword': inputData?.storeId } },
|
|
777
|
+
{ term: { dateString: futureDateString } },
|
|
778
|
+
{ term: { 'type.keyword': 'store' } },
|
|
779
|
+
],
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
785
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
786
|
+
|
|
787
|
+
if ( foundHits.length === 0 ) {
|
|
788
|
+
const record = {
|
|
789
|
+
clientId: inputData?.storeId?.split( '-' )[0],
|
|
790
|
+
storeId: inputData?.storeId,
|
|
791
|
+
storeName: getStoreName?.storeName,
|
|
792
|
+
dateString: futureDateString,
|
|
793
|
+
status: 'block',
|
|
794
|
+
};
|
|
795
|
+
await updateOneUpsertVmsStoreRequest( { storeId: inputData?.storeId, dateString: futureDateString }, record );
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if ( isAutoCloseEnable === true && revisedPercentage >= autoCloseAccuracyValue ) {
|
|
801
|
+
logger.info( 'Ticket auto-closed due to meeting auto-close accuracy threshold' );
|
|
802
|
+
} else {
|
|
803
|
+
if ( ( record.status === 'Closed' || record.status === 'Open - Accuracy Issue' ) && inputData.ticketType !== 'internal' && tangoApproved ) {
|
|
804
|
+
const query = {
|
|
805
|
+
storeId: inputData?.storeId,
|
|
806
|
+
isVideoStream: true,
|
|
807
|
+
};
|
|
808
|
+
const getStoreType = await countDocumnetsCamera( query );
|
|
809
|
+
|
|
810
|
+
// Get all tempIds from revopInfo response
|
|
811
|
+
const temp = [];
|
|
812
|
+
inputData?.mappingInfo?.revisedDetail?.map( ( hit ) => ( hit?.revopsType === 'duplicate' && hit?.isParent === true ) ? '':temp.push( { tempId: hit?.tempId } ) )|| [];
|
|
813
|
+
|
|
814
|
+
// Prepare management eyeZone query based on storeId and dateString
|
|
815
|
+
|
|
816
|
+
const isSendMessge = await sendSqsMessage( inputData, temp, getStoreType, inputData.storeId );
|
|
817
|
+
if ( isSendMessge === true ) {
|
|
818
|
+
logger.info( '....1' );
|
|
819
|
+
}
|
|
525
820
|
}
|
|
526
821
|
}
|
|
527
822
|
return res.sendSuccess( 'Ticket closed successfully' );
|
|
@@ -1615,7 +1910,7 @@ export async function ticketSummary( req, res ) {
|
|
|
1615
1910
|
closedTickets,
|
|
1616
1911
|
dueToday: dueToday,
|
|
1617
1912
|
Expired: expiredTickets,
|
|
1618
|
-
...( isApprover !== true? underTangoReview: undertangoTickets ),
|
|
1913
|
+
...( isApprover !== true? { underTangoReview: undertangoTickets }:'' ),
|
|
1619
1914
|
avgTicket: ticketPercentageAvg + '%',
|
|
1620
1915
|
avgAccuracy: ticketAccuracy + '%',
|
|
1621
1916
|
};
|
|
@@ -2073,7 +2368,7 @@ export async function ticketSummary( req, res ) {
|
|
|
2073
2368
|
closedTickets,
|
|
2074
2369
|
dueToday: dueToday,
|
|
2075
2370
|
Expired: expiredTickets,
|
|
2076
|
-
...( isApprover !== true? underTangoReview: undertangoTickets ),
|
|
2371
|
+
...( isApprover !== true? { underTangoReview: undertangoTickets } : '' ),
|
|
2077
2372
|
avgTicket: ticketPercentageAvg + '%',
|
|
2078
2373
|
avgAccuracy: ticketAccuracy + '%',
|
|
2079
2374
|
};
|
|
@@ -2557,7 +2852,7 @@ export async function ticketList( req, res ) {
|
|
|
2557
2852
|
const revisionArray = getConfig?.footfallDirectoryConfigs?.revision || [];
|
|
2558
2853
|
if ( Array.isArray( revisionArray ) && revisionArray.length > 0 ) {
|
|
2559
2854
|
for ( const r of revisionArray ) {
|
|
2560
|
-
if ( r.actionType === 'approver' && r.isChecked ===
|
|
2855
|
+
if ( r.actionType === 'approver' && r.isChecked === true ) {
|
|
2561
2856
|
isApprover = true;
|
|
2562
2857
|
}
|
|
2563
2858
|
}
|
|
@@ -4020,7 +4315,9 @@ export async function ticketList( req, res ) {
|
|
|
4020
4315
|
'Ticket ID': item?.ticketId,
|
|
4021
4316
|
'Store Name': item?.storeName,
|
|
4022
4317
|
'Store ID': item?.storeId,
|
|
4023
|
-
'Ticket
|
|
4318
|
+
'Ticket Created By': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4319
|
+
// ticketReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4320
|
+
'Ticket Raised On': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt ).format( 'DD MMM, YYYY' ) : '',
|
|
4024
4321
|
'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4025
4322
|
// 'Closed Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4026
4323
|
'Due Date': ( () => {
|
|
@@ -4033,6 +4330,7 @@ export async function ticketList( req, res ) {
|
|
|
4033
4330
|
}
|
|
4034
4331
|
return '';
|
|
4035
4332
|
} )(),
|
|
4333
|
+
'Actual FF': item?.footfallCount,
|
|
4036
4334
|
...( function() {
|
|
4037
4335
|
let counts = undefined;
|
|
4038
4336
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4060,14 +4358,15 @@ export async function ticketList( req, res ) {
|
|
|
4060
4358
|
}
|
|
4061
4359
|
return {};
|
|
4062
4360
|
} )(),
|
|
4063
|
-
'
|
|
4064
|
-
// 'Revised FF': item?.revicedFootfall,
|
|
4361
|
+
'Revised FF': item?.revicedFootfall,
|
|
4065
4362
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4066
4363
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4067
4364
|
'Approver (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
4068
4365
|
'Tango (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4069
4366
|
'Status': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4070
4367
|
// 'Tango Status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4368
|
+
'Closed Date': item?.mappingInfo?.find( ( f ) => f.type === 'approve' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.updatedAt : '--',
|
|
4369
|
+
// 'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4071
4370
|
'Approved by': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4072
4371
|
|
|
4073
4372
|
} );
|
|
@@ -4080,11 +4379,41 @@ export async function ticketList( req, res ) {
|
|
|
4080
4379
|
ticketId: item?.ticketId,
|
|
4081
4380
|
storeId: item?.storeId,
|
|
4082
4381
|
storeName: item?.storeName,
|
|
4382
|
+
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4383
|
+
// ticketReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4083
4384
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
|
|
4084
4385
|
issueDate: item?.dateString,
|
|
4085
4386
|
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
|
|
4086
4387
|
footfall: item?.footfallCount,
|
|
4087
|
-
|
|
4388
|
+
...( function() {
|
|
4389
|
+
let counts = undefined;
|
|
4390
|
+
if ( Array.isArray( item.mappingInfo ) ) {
|
|
4391
|
+
// Priority: tangoreview > approve > review > tagging
|
|
4392
|
+
const targetTypes = [ 'approve', 'review', 'tagging' ];
|
|
4393
|
+
for ( let type of targetTypes ) {
|
|
4394
|
+
const found = item.mappingInfo.find( ( f ) => f && f.type === type && Array.isArray( f.count ) );
|
|
4395
|
+
if ( found ) {
|
|
4396
|
+
counts = found.count;
|
|
4397
|
+
break;
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
// Fallback to item.count if not found
|
|
4402
|
+
if ( !counts && Array.isArray( item.count ) ) {
|
|
4403
|
+
counts = item.count;
|
|
4404
|
+
}
|
|
4405
|
+
if ( Array.isArray( counts ) ) {
|
|
4406
|
+
return counts.reduce( ( acc, curr ) => {
|
|
4407
|
+
if ( curr && curr.name && typeof curr.value !== 'undefined' ) {
|
|
4408
|
+
acc[curr.name.replace( /\s/g, '' )] = curr.value;
|
|
4409
|
+
// acc[curr.name] = curr.value;
|
|
4410
|
+
}
|
|
4411
|
+
return acc;
|
|
4412
|
+
}, {} );
|
|
4413
|
+
}
|
|
4414
|
+
return {};
|
|
4415
|
+
} )(),
|
|
4416
|
+
revicedFootfall: item?.revicedFootfall,
|
|
4088
4417
|
type: item.type || 'store',
|
|
4089
4418
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4090
4419
|
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
@@ -4092,6 +4421,8 @@ export async function ticketList( req, res ) {
|
|
|
4092
4421
|
tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4093
4422
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4094
4423
|
// tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4424
|
+
closedDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.updatedAt : '--',
|
|
4425
|
+
ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4095
4426
|
approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4096
4427
|
|
|
4097
4428
|
} );
|
|
@@ -4106,9 +4437,9 @@ export async function ticketList( req, res ) {
|
|
|
4106
4437
|
'Ticket ID': item?.ticketId,
|
|
4107
4438
|
'Store Name': item?.storeName,
|
|
4108
4439
|
'Store ID': item?.storeId,
|
|
4109
|
-
'Ticket
|
|
4440
|
+
'Ticket Created By': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4441
|
+
'Ticket Raised On': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt ).format( 'DD MMM, YYYY' ) : '--',
|
|
4110
4442
|
'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4111
|
-
'Ticket Closed Date': item.status === 'Closed' || item.status === 'Tango Review Done' ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt ).format( 'DD MMM, YYYY' ) : '--',
|
|
4112
4443
|
'Due Date': ( () => {
|
|
4113
4444
|
const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate;
|
|
4114
4445
|
if ( dueDate ) {
|
|
@@ -4119,6 +4450,8 @@ export async function ticketList( req, res ) {
|
|
|
4119
4450
|
}
|
|
4120
4451
|
return '';
|
|
4121
4452
|
} )(),
|
|
4453
|
+
'Actual FF': item?.footfallCount,
|
|
4454
|
+
|
|
4122
4455
|
...( function() {
|
|
4123
4456
|
let counts = undefined;
|
|
4124
4457
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4146,14 +4479,18 @@ export async function ticketList( req, res ) {
|
|
|
4146
4479
|
}
|
|
4147
4480
|
return {};
|
|
4148
4481
|
} )(),
|
|
4149
|
-
'
|
|
4150
|
-
'Revised FF': item.status === 'Closed' || item.status === 'Tango Review Done' ? item?.revicedFootfall : '--',
|
|
4482
|
+
'Revised FF': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4151
4483
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4152
4484
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4153
|
-
...( isApprover !== true?
|
|
4485
|
+
...( isApprover !== true ?
|
|
4486
|
+
{
|
|
4487
|
+
'Tango (Accuracy%)':
|
|
4488
|
+
item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4489
|
+
} :
|
|
4490
|
+
{} ),
|
|
4154
4491
|
'Status': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
4492
|
+
'Closed Date': item?.mappingInfo?.find( ( f ) => f.type === 'review' ).status === 'Closed' ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt ).format( 'DD MMM, YYYY' ) : '--',
|
|
4155
4493
|
'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4156
|
-
'Created by': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4157
4494
|
|
|
4158
4495
|
} );
|
|
4159
4496
|
}
|
|
@@ -4167,6 +4504,7 @@ export async function ticketList( req, res ) {
|
|
|
4167
4504
|
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4168
4505
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
|
|
4169
4506
|
issueDate: item?.dateString,
|
|
4507
|
+
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4170
4508
|
footfall: item?.footfallCount,
|
|
4171
4509
|
...( function() {
|
|
4172
4510
|
let counts = undefined;
|
|
@@ -4196,15 +4534,13 @@ export async function ticketList( req, res ) {
|
|
|
4196
4534
|
}
|
|
4197
4535
|
return {};
|
|
4198
4536
|
} )(),
|
|
4199
|
-
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4200
4537
|
type: item.type || 'store',
|
|
4538
|
+
revisedFF: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4201
4539
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4202
4540
|
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4203
4541
|
...( isApprover !== true? item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc : '' ),
|
|
4204
|
-
revisedFF: item.status === 'Closed' || item.status === 'Tango Review Done' ?item?.revicedFootfall : '--',
|
|
4205
|
-
|
|
4206
4542
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
4207
|
-
closedDate: item.
|
|
4543
|
+
closedDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt : '--',
|
|
4208
4544
|
ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4209
4545
|
} );
|
|
4210
4546
|
}
|
|
@@ -4220,9 +4556,9 @@ export async function ticketList( req, res ) {
|
|
|
4220
4556
|
'Ticket ID': item?.ticketId,
|
|
4221
4557
|
'Store ID': item?.storeId,
|
|
4222
4558
|
'Store Name': item?.storeName,
|
|
4223
|
-
'Ticket
|
|
4559
|
+
'Ticket Created By': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4560
|
+
'Ticket Raised On': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt ).format( 'DD MMM, YYYY' ) : '',
|
|
4224
4561
|
'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4225
|
-
'Ticket Closed Date': item.status === 'Closed' || item.status === 'Tango Review Done' ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt ).format( 'DD MMM, YYYY' ) : '--',
|
|
4226
4562
|
|
|
4227
4563
|
'Due Date': ( () => {
|
|
4228
4564
|
const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate;
|
|
@@ -4234,6 +4570,8 @@ export async function ticketList( req, res ) {
|
|
|
4234
4570
|
}
|
|
4235
4571
|
return '';
|
|
4236
4572
|
} )(),
|
|
4573
|
+
'Actual FF': item?.footfallCount,
|
|
4574
|
+
|
|
4237
4575
|
...( function() {
|
|
4238
4576
|
let counts = undefined;
|
|
4239
4577
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4261,14 +4599,18 @@ export async function ticketList( req, res ) {
|
|
|
4261
4599
|
}
|
|
4262
4600
|
return {};
|
|
4263
4601
|
} )(),
|
|
4264
|
-
'Actual FF': item?.footfallCount,
|
|
4265
4602
|
// 'Revised FF': item?.revicedFootfall,
|
|
4266
|
-
'Revised FF': item
|
|
4267
|
-
'Ticket Closed Date': item.status === 'Closed' || item.status === 'Tango Review Done' ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt ).format( 'DD MMM, YYYY' ) : '--',
|
|
4603
|
+
'Revised FF': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4268
4604
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4269
4605
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4270
|
-
...(
|
|
4606
|
+
...( isApprover !== true ?
|
|
4607
|
+
{
|
|
4608
|
+
'Tango (Accuracy%)':
|
|
4609
|
+
item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4610
|
+
} :
|
|
4611
|
+
{} ),
|
|
4271
4612
|
'Status': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
4613
|
+
'Closed Date': item?.mappingInfo?.find( ( f ) => f.type === 'review' ).status === 'Closed' ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt ).format( 'DD MMM, YYYY' ) : '--',
|
|
4272
4614
|
'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4273
4615
|
|
|
4274
4616
|
} );
|
|
@@ -4284,6 +4626,7 @@ export async function ticketList( req, res ) {
|
|
|
4284
4626
|
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4285
4627
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
|
|
4286
4628
|
issueDate: item?.dateString,
|
|
4629
|
+
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4287
4630
|
footfall: item?.footfallCount,
|
|
4288
4631
|
...( function() {
|
|
4289
4632
|
let counts = undefined;
|
|
@@ -4313,23 +4656,20 @@ export async function ticketList( req, res ) {
|
|
|
4313
4656
|
}
|
|
4314
4657
|
return {};
|
|
4315
4658
|
} )(),
|
|
4316
|
-
|
|
4317
|
-
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4659
|
+
revisedFF: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4318
4660
|
type: item.type || 'store',
|
|
4319
4661
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4320
4662
|
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4321
4663
|
...( isApprover !== true? tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '' ),
|
|
4322
|
-
revisedFF: item.status === 'Closed' || item.status === 'Tango Review Done' ?item?.revicedFootfall : '--',
|
|
4323
|
-
|
|
4324
4664
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
4325
|
-
closedDate: item.
|
|
4665
|
+
closedDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt : '--',
|
|
4326
4666
|
ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4327
4667
|
|
|
4328
4668
|
} );
|
|
4329
4669
|
}
|
|
4330
4670
|
}
|
|
4331
4671
|
} else if ( ticketsApproveFeature ) {
|
|
4332
|
-
if ( inputData
|
|
4672
|
+
if ( inputData?.isExport ) {
|
|
4333
4673
|
const exportData = [];
|
|
4334
4674
|
for ( let item of ticketListData ) {
|
|
4335
4675
|
exportData.push( {
|
|
@@ -4337,7 +4677,9 @@ export async function ticketList( req, res ) {
|
|
|
4337
4677
|
'Ticket ID': item?.ticketId,
|
|
4338
4678
|
'Store Name': item?.storeName,
|
|
4339
4679
|
'Store ID': item?.storeId,
|
|
4340
|
-
'
|
|
4680
|
+
'Created by': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4681
|
+
|
|
4682
|
+
'Ticket Raised On': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt ).format( 'DD MMM, YYYY' ) : '',
|
|
4341
4683
|
'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4342
4684
|
'Due Date': ( () => {
|
|
4343
4685
|
const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate;
|
|
@@ -4349,6 +4691,8 @@ export async function ticketList( req, res ) {
|
|
|
4349
4691
|
}
|
|
4350
4692
|
return '';
|
|
4351
4693
|
} )(),
|
|
4694
|
+
'Actual FF': item?.footfallCount,
|
|
4695
|
+
|
|
4352
4696
|
...( function() {
|
|
4353
4697
|
let counts = undefined;
|
|
4354
4698
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4376,14 +4720,15 @@ export async function ticketList( req, res ) {
|
|
|
4376
4720
|
}
|
|
4377
4721
|
return {};
|
|
4378
4722
|
} )(),
|
|
4379
|
-
'Actual FF': item?.footfallCount,
|
|
4380
4723
|
'Revised FF': item?.revicedFootfall,
|
|
4381
4724
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4382
4725
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4383
4726
|
'Approver (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
4384
4727
|
'Tango (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4385
4728
|
'Status': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4386
|
-
'Tango Status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4729
|
+
// 'Tango Status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4730
|
+
'Closed Date': item?.mappingInfo?.find( ( f ) => f.type === 'approve' ).status === 'Closed' ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.updatedAt ).format( 'DD MMM, YYYY' ) : '--',
|
|
4731
|
+
'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4387
4732
|
'Approved by': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4388
4733
|
|
|
4389
4734
|
} );
|
|
@@ -4396,10 +4741,40 @@ export async function ticketList( req, res ) {
|
|
|
4396
4741
|
ticketId: item?.ticketId,
|
|
4397
4742
|
storeId: item?.storeId,
|
|
4398
4743
|
storeName: item?.storeName,
|
|
4744
|
+
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4745
|
+
// ticketReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4399
4746
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
|
|
4400
4747
|
issueDate: item?.dateString,
|
|
4401
4748
|
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
|
|
4402
4749
|
footfall: item?.footfallCount,
|
|
4750
|
+
...( function() {
|
|
4751
|
+
let counts = undefined;
|
|
4752
|
+
if ( Array.isArray( item.mappingInfo ) ) {
|
|
4753
|
+
// Priority: tangoreview > approve > review > tagging
|
|
4754
|
+
const targetTypes = [ 'approve', 'review', 'tagging' ];
|
|
4755
|
+
for ( let type of targetTypes ) {
|
|
4756
|
+
const found = item.mappingInfo.find( ( f ) => f && f.type === type && Array.isArray( f.count ) );
|
|
4757
|
+
if ( found ) {
|
|
4758
|
+
counts = found.count;
|
|
4759
|
+
break;
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
// Fallback to item.count if not found
|
|
4764
|
+
if ( !counts && Array.isArray( item.count ) ) {
|
|
4765
|
+
counts = item.count;
|
|
4766
|
+
}
|
|
4767
|
+
if ( Array.isArray( counts ) ) {
|
|
4768
|
+
return counts.reduce( ( acc, curr ) => {
|
|
4769
|
+
if ( curr && curr.name && typeof curr.value !== 'undefined' ) {
|
|
4770
|
+
acc[curr.name.replace( /\s/g, '' )] = curr.value;
|
|
4771
|
+
// acc[curr.name] = curr.value;
|
|
4772
|
+
}
|
|
4773
|
+
return acc;
|
|
4774
|
+
}, {} );
|
|
4775
|
+
}
|
|
4776
|
+
return {};
|
|
4777
|
+
} )(),
|
|
4403
4778
|
revicedFootfall: item?.revicedFootfall,
|
|
4404
4779
|
type: item.type || 'store',
|
|
4405
4780
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
@@ -4407,7 +4782,9 @@ export async function ticketList( req, res ) {
|
|
|
4407
4782
|
approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
4408
4783
|
tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4409
4784
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4410
|
-
tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4785
|
+
// tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4786
|
+
closedDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.updatedAt : '--',
|
|
4787
|
+
// ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4411
4788
|
approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4412
4789
|
|
|
4413
4790
|
} );
|
|
@@ -4723,6 +5100,27 @@ export async function getTickets( req, res ) {
|
|
|
4723
5100
|
mappingObj.type !== 'tangoreview'
|
|
4724
5101
|
) {
|
|
4725
5102
|
mappingObj.revisedDetail = processedRevopSources;
|
|
5103
|
+
} else if (
|
|
5104
|
+
Object.prototype.hasOwnProperty.call( mappingObj, 'revisedDetail' ) &&
|
|
5105
|
+
mappingObj.type === 'tangoreview' &&
|
|
5106
|
+
Array.isArray( mappingObj.revisedDetail )
|
|
5107
|
+
) {
|
|
5108
|
+
const url = JSON.parse( process.env.URL );
|
|
5109
|
+
const OLD_CDN = url?.tangoReviewOld;
|
|
5110
|
+
const NEW_CDN =url?.tangoReviewNew; // bucket name : v4rnd-vms-audit-input-images
|
|
5111
|
+
|
|
5112
|
+
const replaceCdnInFilePath = ( detail ) => {
|
|
5113
|
+
if ( !detail || typeof detail !== 'object' ) return detail;
|
|
5114
|
+
if ( typeof detail.filePath === 'string' && detail.filePath.startsWith( OLD_CDN ) ) {
|
|
5115
|
+
detail.filePath = detail.filePath.replace( OLD_CDN, NEW_CDN );
|
|
5116
|
+
}
|
|
5117
|
+
if ( Array.isArray( detail.duplicateImage ) ) {
|
|
5118
|
+
detail.duplicateImage.forEach( replaceCdnInFilePath );
|
|
5119
|
+
}
|
|
5120
|
+
return detail;
|
|
5121
|
+
};
|
|
5122
|
+
|
|
5123
|
+
mappingObj.revisedDetail.forEach( replaceCdnInFilePath );
|
|
4726
5124
|
}
|
|
4727
5125
|
} );
|
|
4728
5126
|
}
|
|
@@ -4743,7 +5141,14 @@ export async function getTickets( req, res ) {
|
|
|
4743
5141
|
|
|
4744
5142
|
finalResponse[0]._source.mappingInfo[finalResponse?.[0]?._source.mappingInfo?.length - 1].revisedDetail = filterRevisedDetailByIsChecked( finalResponse?.[0]?._source?.mappingInfo[finalResponse?.[0]?._source?.mappingInfo?.length - 1].revisedDetail, isCheck );
|
|
4745
5143
|
}
|
|
4746
|
-
|
|
5144
|
+
|
|
5145
|
+
// If tangoApproved is false, remove tangoreview entries from mappingInfo
|
|
5146
|
+
const clientId = finalResponse?.[0]?._source?.clientId;
|
|
5147
|
+
|
|
5148
|
+
const getConfig = clientId? await findOneClient( { clientId }, { footfallDirectoryConfigs: 1 } ): null;
|
|
5149
|
+
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
5150
|
+
|
|
5151
|
+
return res.sendSuccess( { result: finalResponse, tangoApproved: tangoApproved } );
|
|
4747
5152
|
} catch ( error ) {
|
|
4748
5153
|
const err = error.message || 'Internal Server Error';
|
|
4749
5154
|
logger.error( { error: error, messgage: req.query } );
|
|
@@ -5075,6 +5480,29 @@ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId )
|
|
|
5075
5480
|
sqsProduceQueue.MessageBody,
|
|
5076
5481
|
);
|
|
5077
5482
|
if ( sqsQueue.statusCode ) {
|
|
5483
|
+
const id = `${storeId}_${inputData.dateString.split( '-' ).reverse().join( '-' )}_${getStoreType > 0 ? 'live' : 'reduction'}_${Date.now()}`;
|
|
5484
|
+
const logs = {
|
|
5485
|
+
QueueUrl: sqsProduceQueue.QueueUrl,
|
|
5486
|
+
MessageBody: sqsProduceQueue.MessageBody,
|
|
5487
|
+
sqsQueue: sqsQueue,
|
|
5488
|
+
statusCode: sqsQueue.statusCode,
|
|
5489
|
+
store_id: storeId,
|
|
5490
|
+
store_date: inputData.dateString.split( '-' ).reverse().join( '-' ),
|
|
5491
|
+
bucket_name: '',
|
|
5492
|
+
zone_id: 'traffic_zone',
|
|
5493
|
+
process_type: 'live',
|
|
5494
|
+
revop_type: 'footfall',
|
|
5495
|
+
temp_id: tempId,
|
|
5496
|
+
tempId1: inputData?.log?.temp1 || null,
|
|
5497
|
+
tempIds: inputData?.log?.tempIds || null,
|
|
5498
|
+
mapping: JSON.stringify( inputData?.log?.mapping ) || null,
|
|
5499
|
+
managerEyeZoneHit: inputData?.log?.managerEyeZoneHit || null,
|
|
5500
|
+
managerEyeZoneQuery: inputData?.log?.managerEyeZoneQuery || null,
|
|
5501
|
+
time: Date.now(),
|
|
5502
|
+
createdAt: new Date(),
|
|
5503
|
+
createIST: dayjs().tz( 'Asia/Kolkata' ).format( 'YYYY-MM-DD' ),
|
|
5504
|
+
};
|
|
5505
|
+
await insertWithId( openSearch.revopsLog, id, logs );
|
|
5078
5506
|
logger.error( {
|
|
5079
5507
|
error: `${sqsQueue}`,
|
|
5080
5508
|
type: 'UPLOAD_ERROR',
|
|
@@ -5093,11 +5521,11 @@ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId )
|
|
|
5093
5521
|
process_type: 'live',
|
|
5094
5522
|
revop_type: 'footfall',
|
|
5095
5523
|
temp_id: tempId,
|
|
5096
|
-
tempId1: inputData?.log?.temp1 ||
|
|
5097
|
-
tempIds: inputData?.log?.tempIds ||
|
|
5098
|
-
mapping: JSON.stringify( inputData?.log?.mapping ) ||
|
|
5099
|
-
managerEyeZoneHit: inputData?.log?.managerEyeZoneHit ||
|
|
5100
|
-
managerEyeZoneQuery: inputData?.log?.managerEyeZoneQuery ||
|
|
5524
|
+
tempId1: inputData?.log?.temp1 || null,
|
|
5525
|
+
tempIds: inputData?.log?.tempIds || null,
|
|
5526
|
+
mapping: JSON.stringify( inputData?.log?.mapping ) || null,
|
|
5527
|
+
managerEyeZoneHit: inputData?.log?.managerEyeZoneHit || null,
|
|
5528
|
+
managerEyeZoneQuery: inputData?.log?.managerEyeZoneQuery || null,
|
|
5101
5529
|
time: Date.now(),
|
|
5102
5530
|
createdAt: new Date(),
|
|
5103
5531
|
createIST: dayjs().tz( 'Asia/Kolkata' ).format( 'YYYY-MM-DD' ),
|
|
@@ -5865,7 +6293,8 @@ export async function updateTempStatus( req, res ) {
|
|
|
5865
6293
|
isChecked = status === 'approved' ? true : false;
|
|
5866
6294
|
break;
|
|
5867
6295
|
case 'approve':
|
|
5868
|
-
|
|
6296
|
+
const reviewAction = actions.find( ( item ) => item.actionType === 'review' )?.action;
|
|
6297
|
+
isChecked = ( reviewAction === 'approved' ) === ( status === 'approved' );
|
|
5869
6298
|
break;
|
|
5870
6299
|
}
|
|
5871
6300
|
docsToUpdate.push( {
|
|
@@ -6102,6 +6531,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6102
6531
|
const tangoReview = Number( getConfig?.footfallDirectoryConfigs?.tangoReview?.split( '%' )[0] );
|
|
6103
6532
|
const tangoDueDate = getConfig?.footfallDirectoryConfigs?.allowTangoReview || 0;
|
|
6104
6533
|
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
6534
|
+
const accuracy = getConfig?.footfallDirectoryConfigs?.accuracyBreach || null;
|
|
6105
6535
|
|
|
6106
6536
|
// inputData structure should include an array of items to close
|
|
6107
6537
|
// Accept both array or single ticket update
|
|
@@ -6211,6 +6641,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6211
6641
|
createdByEmail: req?.user?.email,
|
|
6212
6642
|
createdByUserName: req?.user?.userName,
|
|
6213
6643
|
createdByRole: req?.user?.role,
|
|
6644
|
+
contactEmail: req?.user?.email,
|
|
6214
6645
|
createdAt: new Date(),
|
|
6215
6646
|
updatedAt: new Date(),
|
|
6216
6647
|
},
|
|
@@ -6255,6 +6686,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6255
6686
|
createdByEmail: req?.user?.email,
|
|
6256
6687
|
createdByUserName: req?.user?.userName,
|
|
6257
6688
|
createdByRole: req?.user?.role,
|
|
6689
|
+
contactEmail: req?.user?.email,
|
|
6258
6690
|
createdAt: new Date(),
|
|
6259
6691
|
updatedAt: new Date(),
|
|
6260
6692
|
},
|
|
@@ -6270,7 +6702,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6270
6702
|
const ticketUpdatePayload = {
|
|
6271
6703
|
doc: {
|
|
6272
6704
|
mappingInfo: newMappingInfoArray,
|
|
6273
|
-
status: ( revised < tangoReview && tangoApproved )? 'Under Tango Review':
|
|
6705
|
+
status: ( revised < tangoReview && tangoApproved )? 'Under Tango Review': 'Closed', // status updated at top level as well
|
|
6274
6706
|
updatedAt: new Date(),
|
|
6275
6707
|
},
|
|
6276
6708
|
};
|
|
@@ -6282,6 +6714,291 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6282
6714
|
results.push( { storeId, dateString, success: false, error: `Failed to update ticket this store ${storeId} for this date ${dateString}` } );
|
|
6283
6715
|
continue;
|
|
6284
6716
|
} else {
|
|
6717
|
+
if ( accuracy && accuracy.ticketCount && accuracy.days ) {
|
|
6718
|
+
// Check if ticketCount exceeds breach limit within config months and revised footfall percentage > config accuracy
|
|
6719
|
+
|
|
6720
|
+
|
|
6721
|
+
const breachDays = Number( accuracy.days );
|
|
6722
|
+
const breachCount = Number( accuracy.ticketCount );
|
|
6723
|
+
|
|
6724
|
+
// accuracy.accuracy is a string like "95%", so remove "%" and convert to Number
|
|
6725
|
+
// const breachAccuracy = Number( accuracy.accuracy.replace( '%', '' ) );
|
|
6726
|
+
const ticketName = 'footfall-directory'; // adjust if ticket name variable differs
|
|
6727
|
+
|
|
6728
|
+
|
|
6729
|
+
const formatDate = ( d ) =>
|
|
6730
|
+
`${d.getFullYear()}-${String( d.getMonth() + 1 ).padStart( 2, '0' )}-${String( d.getDate() ).padStart( 2, '0' )}`;
|
|
6731
|
+
|
|
6732
|
+
// Compute current date object
|
|
6733
|
+
|
|
6734
|
+
|
|
6735
|
+
// Calculate start date based on config days
|
|
6736
|
+
// If 30 days: start from first day of current month
|
|
6737
|
+
// If 60 days: start from first day of last month
|
|
6738
|
+
const currentDateObj = new Date( dateString ); // Declare currentDateObj as today's date
|
|
6739
|
+
const startDateObj = new Date( currentDateObj );
|
|
6740
|
+
|
|
6741
|
+
if ( breachDays === 7 ) {
|
|
6742
|
+
// Consider within this week (start from Monday)
|
|
6743
|
+
const dayOfWeek = startDateObj.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
|
6744
|
+
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
6745
|
+
startDateObj.setDate( startDateObj.getDate() - daysFromMonday );
|
|
6746
|
+
} else if ( breachDays === 30 ) {
|
|
6747
|
+
// Consider within this month
|
|
6748
|
+
startDateObj.setDate( 1 ); // First day of current month
|
|
6749
|
+
} else if ( breachDays === 60 ) {
|
|
6750
|
+
// Consider this month and last month
|
|
6751
|
+
startDateObj.setMonth( startDateObj.getMonth() - 1 );
|
|
6752
|
+
startDateObj.setDate( 1 ); // First day of last month
|
|
6753
|
+
} else {
|
|
6754
|
+
// For other values, calculate months from days
|
|
6755
|
+
const breachMonths = Math.ceil( breachDays / 30 );
|
|
6756
|
+
startDateObj.setMonth( startDateObj.getMonth() - breachMonths + 1 );
|
|
6757
|
+
startDateObj.setDate( 1 );
|
|
6758
|
+
}
|
|
6759
|
+
|
|
6760
|
+
|
|
6761
|
+
startDateObj.setUTCHours( 0, 0, 0, 0 );
|
|
6762
|
+
const startDate = formatDate( startDateObj );
|
|
6763
|
+
|
|
6764
|
+
// Calculate end date of the period (end of week/month) for dateString range
|
|
6765
|
+
const endDateObj = new Date( currentDateObj );
|
|
6766
|
+
if ( breachDays === 7 ) {
|
|
6767
|
+
const dayOfWeek = endDateObj.getDay();
|
|
6768
|
+
const daysUntilSunday = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
6769
|
+
endDateObj.setDate( endDateObj.getDate() + daysUntilSunday );
|
|
6770
|
+
} else if ( breachDays === 30 ) {
|
|
6771
|
+
endDateObj.setMonth( endDateObj.getMonth() + 1, 0 );
|
|
6772
|
+
} else if ( breachDays === 60 ) {
|
|
6773
|
+
endDateObj.setMonth( endDateObj.getMonth() + 2, 0 );
|
|
6774
|
+
} else {
|
|
6775
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
6776
|
+
endDateObj.setDate( endDateObj.getDate() + remainingDays );
|
|
6777
|
+
}
|
|
6778
|
+
const endDate = formatDate( endDateObj );
|
|
6779
|
+
|
|
6780
|
+
// Query for tickets within this week/month window for this store and ticket name
|
|
6781
|
+
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
6782
|
+
const mappingInfoFilter = [
|
|
6783
|
+
{
|
|
6784
|
+
nested: {
|
|
6785
|
+
path: 'mappingInfo',
|
|
6786
|
+
query: {
|
|
6787
|
+
bool: {
|
|
6788
|
+
must: [
|
|
6789
|
+
{ term: { 'mappingInfo.type': 'approve' } },
|
|
6790
|
+
{ term: { 'mappingInfo.status': 'Closed' } },
|
|
6791
|
+
],
|
|
6792
|
+
},
|
|
6793
|
+
},
|
|
6794
|
+
},
|
|
6795
|
+
},
|
|
6796
|
+
];
|
|
6797
|
+
if ( tangoApproved ) {
|
|
6798
|
+
mappingInfoFilter.push( {
|
|
6799
|
+
nested: {
|
|
6800
|
+
path: 'mappingInfo',
|
|
6801
|
+
query: {
|
|
6802
|
+
term: { 'mappingInfo.type': 'tangoreview' },
|
|
6803
|
+
},
|
|
6804
|
+
},
|
|
6805
|
+
} );
|
|
6806
|
+
}
|
|
6807
|
+
logger.info( { startDate, endDate, storeId } );
|
|
6808
|
+
const query = {
|
|
6809
|
+
query: {
|
|
6810
|
+
bool: {
|
|
6811
|
+
must: [
|
|
6812
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
6813
|
+
{ term: { 'ticketName.keyword': ticketName } },
|
|
6814
|
+
{ range: { dateString: { gte: startDate, lte: endDate } } },
|
|
6815
|
+
],
|
|
6816
|
+
filter: [
|
|
6817
|
+
{
|
|
6818
|
+
bool: {
|
|
6819
|
+
should: mappingInfoFilter,
|
|
6820
|
+
minimum_should_match: 1,
|
|
6821
|
+
},
|
|
6822
|
+
},
|
|
6823
|
+
],
|
|
6824
|
+
},
|
|
6825
|
+
},
|
|
6826
|
+
};
|
|
6827
|
+
// Search in OpenSearch for recent similar tickets
|
|
6828
|
+
const breachTicketsResult = await getOpenSearchData( openSearch.footfallDirectory, query );
|
|
6829
|
+
logger.info( { breachTicketsResult, message: 'breachTicketsResult;;;;;;;1' } );
|
|
6830
|
+
const getTickets = breachTicketsResult?.body?.hits?.hits || [];
|
|
6831
|
+
const tangoTaggingDuDate = getConfig?.footfallDirectoryConfigs?.tangoReview || 0;
|
|
6832
|
+
const gettingTangoreview = Number( tangoTaggingDuDate.split( '%' )[0] || 0 );
|
|
6833
|
+
|
|
6834
|
+
const deltaBreach = getConfig?.footfallDirectoryConfigs?.deltaBreach || '>';
|
|
6835
|
+
const deltaCountBreach = Number( getConfig?.footfallDirectoryConfigs?.deltaCountBreach ?? 10 );
|
|
6836
|
+
const taggingBreach = getConfig?.footfallDirectoryConfigs?.taggingBreach || '>=';
|
|
6837
|
+
const taggingCountBreach = Number( getConfig?.footfallDirectoryConfigs?.taggingCountBreach ?? 10 );
|
|
6838
|
+
|
|
6839
|
+
const compareByOperator = ( value, operator, threshold ) => {
|
|
6840
|
+
switch ( operator ) {
|
|
6841
|
+
case '>': return value > threshold;
|
|
6842
|
+
case '>=': return value >= threshold;
|
|
6843
|
+
case '<': return value < threshold;
|
|
6844
|
+
case '<=': return value <= threshold;
|
|
6845
|
+
case '==': return value == threshold;
|
|
6846
|
+
case '===': return value === threshold;
|
|
6847
|
+
default: return value > threshold;
|
|
6848
|
+
}
|
|
6849
|
+
};
|
|
6850
|
+
|
|
6851
|
+
// Filter tickets where revised footfall percentage > config accuracy
|
|
6852
|
+
let breachTicketsCount = 0;
|
|
6853
|
+
logger.info( { getTickets, message: 'tickets;;;;;;;1' } );
|
|
6854
|
+
for ( const ticketInfo of getTickets ) {
|
|
6855
|
+
const source = ticketInfo._source || {};
|
|
6856
|
+
|
|
6857
|
+
const ticketMappingInfo = source.mappingInfo || [];
|
|
6858
|
+
|
|
6859
|
+
|
|
6860
|
+
// Get Store/AOM accuracy (tagging stage)
|
|
6861
|
+
const taggingEntry = ticketMappingInfo.find( ( m ) => m.type === 'tagging' );
|
|
6862
|
+
const storeAccuracy = Number( taggingEntry?.reviced ) || 0;
|
|
6863
|
+
const storeRevisedFF = Number( taggingEntry?.revicedFootfall )|| 0;
|
|
6864
|
+
|
|
6865
|
+
// Get Central Ops(tangoreview stage)
|
|
6866
|
+
const centralOpsEntry = ticketMappingInfo.find( ( m ) => m.type === 'approve' );
|
|
6867
|
+
const centralOpsAccuracy = Number( centralOpsEntry?.reviced ) || 0;
|
|
6868
|
+
const centralOpsRevisedFF = Number( centralOpsEntry?.revicedFootfall )|| 0;
|
|
6869
|
+
|
|
6870
|
+
// Get Central Ops/Tango accuracy (tangoreview stage)
|
|
6871
|
+
const tangoEntry = ticketMappingInfo.find( ( m ) => m.type === 'tangoreview' );
|
|
6872
|
+
const tangoAccuracy = Number( tangoEntry?.reviced ) || 0;
|
|
6873
|
+
const tangoRevisedFF = Number( tangoEntry?.revicedFootfall )|| 0;
|
|
6874
|
+
|
|
6875
|
+
// Calculate delta between Store/AOM and Central Ops/Tango accuracy
|
|
6876
|
+
|
|
6877
|
+
const accuracyDelta1 = tangoAccuracy -storeAccuracy;
|
|
6878
|
+
const accuracyDelta2 = centralOpsAccuracy - storeAccuracy;
|
|
6879
|
+
|
|
6880
|
+
|
|
6881
|
+
// Check existing breach condition OR new accuracy delta breach conditions:
|
|
6882
|
+
// Existing: revised footfall percentage > config accuracy and not Auto-Closed
|
|
6883
|
+
// New conditions (all must be true):
|
|
6884
|
+
// a) Delta between Store/AOM and Central Ops/Tango accuracy > deltaBreach config
|
|
6885
|
+
// b) Post-review (Tango) accuracy > 85%
|
|
6886
|
+
// c) Total images marked for validation > taggingBreach config
|
|
6887
|
+
// const existingBreach = ticketRevisedPercentage > breachAccuracy && status !== 'Auto-Closed';
|
|
6888
|
+
const isHighDelta = compareByOperator( accuracyDelta1, deltaBreach, deltaCountBreach ) || compareByOperator( accuracyDelta2, deltaBreach, deltaCountBreach );
|
|
6889
|
+
const isCrossingThreshold =source?.reviced > gettingTangoreview;
|
|
6890
|
+
logger.info( { source, revicedData: source?.reviced, gettingTangoreview } );
|
|
6891
|
+
const isLowData = compareByOperator( tangoRevisedFF - storeRevisedFF, taggingBreach, taggingCountBreach ) || compareByOperator( centralOpsRevisedFF - storeRevisedFF, taggingBreach, taggingCountBreach );
|
|
6892
|
+
|
|
6893
|
+
const deltaBreachCondition =
|
|
6894
|
+
isHighDelta && isCrossingThreshold && isLowData;
|
|
6895
|
+
// const deltaBreachCondition = accuracyDelta > 10 || ( storeAccuracy < gettingTangoreview && tangoAccuracy > gettingTangoreview) || totalImages <= 10;
|
|
6896
|
+
logger.info( { isHighDelta, isCrossingThreshold, isLowData } );
|
|
6897
|
+
if ( deltaBreachCondition ) {
|
|
6898
|
+
breachTicketsCount++;
|
|
6899
|
+
}
|
|
6900
|
+
}
|
|
6901
|
+
logger.info( { breachTicketsCount, breachCount }, 'total Breach Tickets Count1' );
|
|
6902
|
+
|
|
6903
|
+
// process.exit( 0 );
|
|
6904
|
+
if ( breachTicketsCount > breachCount ) {
|
|
6905
|
+
const getStoreName = await findOneStore( { storeId: storeId }, { storeName: 1, _id: 0 } );
|
|
6906
|
+
|
|
6907
|
+
// Calculate remaining future days in the config period
|
|
6908
|
+
const futureDates = [];
|
|
6909
|
+
|
|
6910
|
+
// Calculate end date of config period
|
|
6911
|
+
const configEndDateObj = new Date( currentDateObj );
|
|
6912
|
+
if ( breachDays === 7 ) {
|
|
6913
|
+
// End of current week (Sunday)
|
|
6914
|
+
const dayOfWeek = configEndDateObj.getDay(); // 0=Sun, 6=Sat
|
|
6915
|
+
const daysUntilEndOfWeek = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
6916
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + daysUntilEndOfWeek );
|
|
6917
|
+
} else if ( breachDays === 30 ) {
|
|
6918
|
+
// End of current month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
6919
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 1, 0 ); // Last day of current month
|
|
6920
|
+
} else if ( breachDays === 60 ) {
|
|
6921
|
+
// End of next month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
6922
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 2, 0 ); // Last day of next month
|
|
6923
|
+
} else {
|
|
6924
|
+
// For other values, add the remaining days
|
|
6925
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
6926
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + remainingDays );
|
|
6927
|
+
}
|
|
6928
|
+
|
|
6929
|
+
// Generate future dates from the day after current date until end of config period
|
|
6930
|
+
const tomorrow = new Date( currentDateObj );
|
|
6931
|
+
tomorrow.setDate( tomorrow.getDate() + 1 );
|
|
6932
|
+
|
|
6933
|
+
const dateIterator = new Date( tomorrow );
|
|
6934
|
+
while ( dateIterator <= configEndDateObj ) {
|
|
6935
|
+
futureDates.push( formatDate( dateIterator ) );
|
|
6936
|
+
dateIterator.setDate( dateIterator.getDate() + 1 );
|
|
6937
|
+
}
|
|
6938
|
+
|
|
6939
|
+
// Check past days within the period (from startDate to currentDate - 1);
|
|
6940
|
+
// if footfallDirectory index does not have a record for this storeId/dateString/type:"store", block that day
|
|
6941
|
+
const pastIterator = new Date( startDateObj );
|
|
6942
|
+
while ( pastIterator < currentDateObj ) {
|
|
6943
|
+
const dateStr = formatDate( pastIterator );
|
|
6944
|
+
const pastQuery = {
|
|
6945
|
+
query: {
|
|
6946
|
+
bool: {
|
|
6947
|
+
must: [
|
|
6948
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
6949
|
+
{ term: { dateString: dateStr } },
|
|
6950
|
+
{ term: { 'type.keyword': 'store' } },
|
|
6951
|
+
],
|
|
6952
|
+
},
|
|
6953
|
+
},
|
|
6954
|
+
};
|
|
6955
|
+
|
|
6956
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
6957
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
6958
|
+
|
|
6959
|
+
if ( foundHits.length === 0 ) {
|
|
6960
|
+
const record = {
|
|
6961
|
+
clientId: inputData?.ticketList?.[0]?.storeId?.split( '-' )[0],
|
|
6962
|
+
storeId: storeId,
|
|
6963
|
+
storeName: getStoreName?.storeName,
|
|
6964
|
+
dateString: dateStr,
|
|
6965
|
+
status: 'block',
|
|
6966
|
+
};
|
|
6967
|
+
await updateOneUpsertVmsStoreRequest( { storeId: storeId, dateString: dateStr }, record );
|
|
6968
|
+
}
|
|
6969
|
+
pastIterator.setDate( pastIterator.getDate() + 1 );
|
|
6970
|
+
}
|
|
6971
|
+
|
|
6972
|
+
// Insert a block record for each future date where no store record exists
|
|
6973
|
+
for ( const futureDateString of futureDates ) {
|
|
6974
|
+
const pastQuery = {
|
|
6975
|
+
query: {
|
|
6976
|
+
bool: {
|
|
6977
|
+
must: [
|
|
6978
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
6979
|
+
{ term: { dateString: futureDateString } },
|
|
6980
|
+
{ term: { 'type.keyword': 'store' } },
|
|
6981
|
+
],
|
|
6982
|
+
},
|
|
6983
|
+
},
|
|
6984
|
+
};
|
|
6985
|
+
|
|
6986
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
6987
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
6988
|
+
|
|
6989
|
+
if ( foundHits.length === 0 ) {
|
|
6990
|
+
const record = {
|
|
6991
|
+
clientId: inputData?.ticketList?.[0]?.storeId?.split( '-' )[0],
|
|
6992
|
+
storeId: storeId,
|
|
6993
|
+
storeName: getStoreName?.storeName,
|
|
6994
|
+
dateString: futureDateString,
|
|
6995
|
+
status: 'block',
|
|
6996
|
+
};
|
|
6997
|
+
await updateOneUpsertVmsStoreRequest( { storeId: storeId, dateString: futureDateString }, record );
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
}
|
|
7001
|
+
}
|
|
6285
7002
|
const query = {
|
|
6286
7003
|
storeId: storeId,
|
|
6287
7004
|
isVideoStream: true,
|
|
@@ -6512,3 +7229,273 @@ export async function updateAccuracyIssues( req, res ) {
|
|
|
6512
7229
|
return res.sendError( err, 500 );
|
|
6513
7230
|
}
|
|
6514
7231
|
}
|
|
7232
|
+
|
|
7233
|
+
export async function getBadTicket( req, res ) {
|
|
7234
|
+
try {
|
|
7235
|
+
if ( accuracy && accuracy.ticketCount && accuracy.days && accuracy.accuracy ) {
|
|
7236
|
+
// Check if ticketCount exceeds breach limit within config months and revised footfall percentage > config accuracy
|
|
7237
|
+
|
|
7238
|
+
const breachDays = Number( accuracy.days );
|
|
7239
|
+
const breachCount = Number( accuracy.ticketCount );
|
|
7240
|
+
|
|
7241
|
+
// accuracy.accuracy is a string like "95%", so remove "%" and convert to Number
|
|
7242
|
+
const breachAccuracy = Number( accuracy.accuracy.replace( '%', '' ) );
|
|
7243
|
+
const storeId = inputData.storeId;
|
|
7244
|
+
const ticketName = inputData.ticketName || 'footfall-directory'; // adjust if ticket name variable differs
|
|
7245
|
+
|
|
7246
|
+
|
|
7247
|
+
const formatDate = ( d ) =>
|
|
7248
|
+
`${d.getFullYear()}-${String( d.getMonth() + 1 ).padStart( 2, '0' )}-${String( d.getDate() ).padStart( 2, '0' )}`;
|
|
7249
|
+
|
|
7250
|
+
// Compute current date object
|
|
7251
|
+
|
|
7252
|
+
|
|
7253
|
+
// Calculate start date based on config days
|
|
7254
|
+
// If 30 days: start from first day of current month
|
|
7255
|
+
// If 60 days: start from first day of last month
|
|
7256
|
+
const currentDateObj = new Date( inputData?.dateString ); // Declare currentDateObj as today's date
|
|
7257
|
+
const startDateObj = new Date( currentDateObj );
|
|
7258
|
+
|
|
7259
|
+
if ( breachDays === 7 ) {
|
|
7260
|
+
// Consider within this week (start from Monday)
|
|
7261
|
+
const dayOfWeek = startDateObj.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
|
7262
|
+
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
7263
|
+
startDateObj.setDate( startDateObj.getDate() - daysFromMonday );
|
|
7264
|
+
} else if ( breachDays === 30 ) {
|
|
7265
|
+
// Consider within this month
|
|
7266
|
+
startDateObj.setDate( 1 ); // First day of current month
|
|
7267
|
+
} else if ( breachDays === 60 ) {
|
|
7268
|
+
// Consider this month and last month
|
|
7269
|
+
startDateObj.setMonth( startDateObj.getMonth() - 1 );
|
|
7270
|
+
startDateObj.setDate( 1 ); // First day of last month
|
|
7271
|
+
} else {
|
|
7272
|
+
// For other values, calculate months from days
|
|
7273
|
+
const breachMonths = Math.ceil( breachDays / 30 );
|
|
7274
|
+
startDateObj.setMonth( startDateObj.getMonth() - breachMonths + 1 );
|
|
7275
|
+
startDateObj.setDate( 1 );
|
|
7276
|
+
}
|
|
7277
|
+
|
|
7278
|
+
|
|
7279
|
+
startDateObj.setUTCHours( 0, 0, 0, 0 );
|
|
7280
|
+
const startDate = formatDate( startDateObj );
|
|
7281
|
+
|
|
7282
|
+
// Calculate end date of the period (end of week/month) for dateString range
|
|
7283
|
+
const endDateObj = new Date( currentDateObj );
|
|
7284
|
+
if ( breachDays === 7 ) {
|
|
7285
|
+
const dayOfWeek = endDateObj.getDay();
|
|
7286
|
+
const daysUntilSunday = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
7287
|
+
endDateObj.setDate( endDateObj.getDate() + daysUntilSunday );
|
|
7288
|
+
} else if ( breachDays === 30 ) {
|
|
7289
|
+
endDateObj.setMonth( endDateObj.getMonth() + 1, 0 );
|
|
7290
|
+
} else if ( breachDays === 60 ) {
|
|
7291
|
+
endDateObj.setMonth( endDateObj.getMonth() + 2, 0 );
|
|
7292
|
+
} else {
|
|
7293
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
7294
|
+
endDateObj.setDate( endDateObj.getDate() + remainingDays );
|
|
7295
|
+
}
|
|
7296
|
+
const endDate = formatDate( endDateObj );
|
|
7297
|
+
|
|
7298
|
+
// Query for tickets within this week/month window for this store and ticket name
|
|
7299
|
+
const query = {
|
|
7300
|
+
query: {
|
|
7301
|
+
bool: {
|
|
7302
|
+
must: [
|
|
7303
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
7304
|
+
{ term: { 'ticketName.keyword': ticketName } },
|
|
7305
|
+
{ range: { dateString: { gte: startDate, lte: endDate } } },
|
|
7306
|
+
],
|
|
7307
|
+
must_not: [
|
|
7308
|
+
{
|
|
7309
|
+
terms: { 'status.keyword': [ 'Auto-Closed', 'Reviewer-Expired' ] },
|
|
7310
|
+
},
|
|
7311
|
+
],
|
|
7312
|
+
},
|
|
7313
|
+
},
|
|
7314
|
+
};
|
|
7315
|
+
|
|
7316
|
+
// Search in OpenSearch for recent similar tickets
|
|
7317
|
+
const breachTicketsResult = await getOpenSearchData( openSearch.footfallDirectory, query );
|
|
7318
|
+
logger.info( { breachTicketsResult } );
|
|
7319
|
+
const tickets = breachTicketsResult?.body?.hits?.hits || [];
|
|
7320
|
+
|
|
7321
|
+
const tangoTaggingDuDate = getConfig?.footfallDirectoryConfigs?.tangoReview || 0;
|
|
7322
|
+
const gettingTangoreview = Number( tangoTaggingDuDate.split( '%' )[0] || 0 );
|
|
7323
|
+
|
|
7324
|
+
const deltaBreach = getConfig?.footfallDirectoryConfigs?.deltaBreach || '>';
|
|
7325
|
+
const deltaCountBreach = Number( getConfig?.footfallDirectoryConfigs?.deltaCountBreach ?? 10 );
|
|
7326
|
+
const taggingBreach = getConfig?.footfallDirectoryConfigs?.taggingBreach || '>=';
|
|
7327
|
+
const taggingCountBreach = Number( getConfig?.footfallDirectoryConfigs?.taggingCountBreach ?? 10 );
|
|
7328
|
+
|
|
7329
|
+
const compareByOperator = ( value, operator, threshold ) => {
|
|
7330
|
+
switch ( operator ) {
|
|
7331
|
+
case '>': return value > threshold;
|
|
7332
|
+
case '>=': return value >= threshold;
|
|
7333
|
+
case '<': return value < threshold;
|
|
7334
|
+
case '<=': return value <= threshold;
|
|
7335
|
+
case '==': return value == threshold;
|
|
7336
|
+
case '===': return value === threshold;
|
|
7337
|
+
default: return value > threshold;
|
|
7338
|
+
}
|
|
7339
|
+
};
|
|
7340
|
+
|
|
7341
|
+
// Filter tickets where revised footfall percentage > config accuracy
|
|
7342
|
+
|
|
7343
|
+
let breachTicketsCount = 0;
|
|
7344
|
+
for ( const ticket of tickets ) {
|
|
7345
|
+
const source = ticket._source || {};
|
|
7346
|
+
|
|
7347
|
+
const ticketFootfallCount = source.footfallCount || 0;
|
|
7348
|
+
const status = source.status || '';
|
|
7349
|
+
const ticketMappingInfo = source.mappingInfo || [];
|
|
7350
|
+
// Calculate revised footfall percentage
|
|
7351
|
+
const ticketRevisedPercentage = source.reviced;
|
|
7352
|
+
// Get Store/AOM accuracy (tagging stage)
|
|
7353
|
+
const taggingEntry = ticketMappingInfo.find( ( m ) => m.type === 'tagging' );
|
|
7354
|
+
const storeAccuracy = Number( taggingEntry?.reviced ) || 0;
|
|
7355
|
+
// Get AOM accuracy (tagging stage)
|
|
7356
|
+
const reviewEntry = ticketMappingInfo.find( ( m ) => m.type === 'review' );
|
|
7357
|
+
const AOMAccuracy = Number( reviewEntry?.reviced ) || 0;
|
|
7358
|
+
|
|
7359
|
+
// Get Central Ops(tangoreview stage)
|
|
7360
|
+
const centralOpsEntry = ticketMappingInfo.find( ( m ) => m.type === 'approve' );
|
|
7361
|
+
const centralOpsAccuracy = Number( centralOpsEntry?.reviced ) || 0;
|
|
7362
|
+
|
|
7363
|
+
// Get Central Ops/Tango accuracy (tangoreview stage)
|
|
7364
|
+
const tangoEntry = ticketMappingInfo.find( ( m ) => m.type === 'tangoreview' );
|
|
7365
|
+
const tangoAccuracy = Number( tangoEntry?.reviced ) || 0;
|
|
7366
|
+
|
|
7367
|
+
// Calculate delta between Store/AOM and Central Ops/Tango accuracy
|
|
7368
|
+
const accuracyDelta1 = AOMAccuracy - storeAccuracy;
|
|
7369
|
+
const accuracyDelta2 = tangoAccuracy - centralOpsAccuracy;
|
|
7370
|
+
const accuracyDelta3 = tangoAccuracy -storeAccuracy;
|
|
7371
|
+
const accuracyDelta4 = centralOpsAccuracy - AOMAccuracy;
|
|
7372
|
+
const accuracyDelta5 = tangoAccuracy -AOMAccuracy;
|
|
7373
|
+
const accuracyDelta6 = centralOpsAccuracy - storeAccuracy;
|
|
7374
|
+
// Get total images (footfall) marked for validation
|
|
7375
|
+
const totalImages =ticketFootfallCount || 0;
|
|
7376
|
+
|
|
7377
|
+
// Check existing breach condition OR new accuracy delta breach conditions:
|
|
7378
|
+
// Existing: revised footfall percentage > config accuracy and not Auto-Closed
|
|
7379
|
+
// New conditions (all must be true):
|
|
7380
|
+
// a) Delta between Store/AOM and Central Ops/Tango accuracy > deltaBreach config
|
|
7381
|
+
// b) Post-review (Tango) accuracy > 85%
|
|
7382
|
+
// c) Total images marked for validation > taggingBreach config
|
|
7383
|
+
const existingBreach = ticketRevisedPercentage > breachAccuracy && status !== 'Auto-Closed';
|
|
7384
|
+
// const deltaBreachCondition = accuracyDelta > 10 || ( storeAccuracy < gettingTangoreview && tangoAccuracy > gettingTangoreview )|| totalImages <= 10;
|
|
7385
|
+
const isHighDelta = compareByOperator( accuracyDelta1, deltaBreach, deltaCountBreach ) || compareByOperator( accuracyDelta2, deltaBreach, deltaCountBreach ) || compareByOperator( accuracyDelta3, deltaBreach, deltaCountBreach ) || compareByOperator( accuracyDelta4, deltaBreach, deltaCountBreach ) || compareByOperator( accuracyDelta5, deltaBreach, deltaCountBreach ) || compareByOperator( accuracyDelta6, deltaBreach, deltaCountBreach );
|
|
7386
|
+
|
|
7387
|
+
const isCrossingThreshold =
|
|
7388
|
+
storeAccuracy < gettingTangoreview &&(
|
|
7389
|
+
tangoAccuracy > gettingTangoreview || centralOpsAccuracy > gettingTangoreview || AOMAccuracy > gettingTangoreview );
|
|
7390
|
+
const isLowData = compareByOperator( totalImages, taggingBreach, taggingCountBreach );
|
|
7391
|
+
|
|
7392
|
+
const deltaBreachCondition =
|
|
7393
|
+
isHighDelta || isCrossingThreshold || isLowData;
|
|
7394
|
+
if ( existingBreach || deltaBreachCondition ) {
|
|
7395
|
+
breachTicketsCount++;
|
|
7396
|
+
}
|
|
7397
|
+
}
|
|
7398
|
+
logger.info( { breachTicketsCount, breachCount } );
|
|
7399
|
+
if ( breachTicketsCount > breachCount ) {
|
|
7400
|
+
// Calculate remaining future days in the config period
|
|
7401
|
+
const futureDates = [];
|
|
7402
|
+
|
|
7403
|
+
// Calculate end date of config period
|
|
7404
|
+
const configEndDateObj = new Date( currentDateObj );
|
|
7405
|
+
if ( breachDays === 7 ) {
|
|
7406
|
+
// End of current week (Sunday)
|
|
7407
|
+
const dayOfWeek = configEndDateObj.getDay(); // 0=Sun, 6=Sat
|
|
7408
|
+
const daysUntilEndOfWeek = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
7409
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + daysUntilEndOfWeek );
|
|
7410
|
+
} else if ( breachDays === 30 ) {
|
|
7411
|
+
// End of current month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
7412
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 1, 0 ); // Last day of current month
|
|
7413
|
+
} else if ( breachDays === 60 ) {
|
|
7414
|
+
// End of next month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
7415
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 2, 0 ); // Last day of next month
|
|
7416
|
+
} else {
|
|
7417
|
+
// For other values, add the remaining days
|
|
7418
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
7419
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + remainingDays );
|
|
7420
|
+
}
|
|
7421
|
+
|
|
7422
|
+
// Generate future dates from the day after current date until end of config period
|
|
7423
|
+
const tomorrow = new Date( currentDateObj );
|
|
7424
|
+
tomorrow.setDate( tomorrow.getDate() + 1 );
|
|
7425
|
+
const dateIterator = new Date( tomorrow );
|
|
7426
|
+
while ( dateIterator <= configEndDateObj ) {
|
|
7427
|
+
futureDates.push( formatDate( dateIterator ) );
|
|
7428
|
+
dateIterator.setDate( dateIterator.getDate() + 1 );
|
|
7429
|
+
}
|
|
7430
|
+
|
|
7431
|
+
// Check past days within the period (from startDate to currentDate - 1);
|
|
7432
|
+
// if footfallDirectory index does not have a record for this storeId/dateString/type:"store", block that day
|
|
7433
|
+
const pastIterator = new Date( startDateObj );
|
|
7434
|
+
while ( pastIterator < currentDateObj ) {
|
|
7435
|
+
const dateStr = formatDate( pastIterator );
|
|
7436
|
+
const pastQuery = {
|
|
7437
|
+
query: {
|
|
7438
|
+
bool: {
|
|
7439
|
+
must: [
|
|
7440
|
+
{ term: { 'storeId.keyword': inputData?.storeId } },
|
|
7441
|
+
{ term: { dateString: dateStr } },
|
|
7442
|
+
{ term: { 'type.keyword': 'store' } },
|
|
7443
|
+
],
|
|
7444
|
+
},
|
|
7445
|
+
},
|
|
7446
|
+
};
|
|
7447
|
+
|
|
7448
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
7449
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
7450
|
+
|
|
7451
|
+
if ( foundHits.length === 0 ) {
|
|
7452
|
+
const record = {
|
|
7453
|
+
clientId: getstoreName?.clientId,
|
|
7454
|
+
storeId: storeId,
|
|
7455
|
+
storeName: getstoreName?.storeName,
|
|
7456
|
+
dateString: dateStr,
|
|
7457
|
+
status: 'block',
|
|
7458
|
+
};
|
|
7459
|
+
await updateOneUpsertVmsStoreRequest( { storeId: inputData?.storeId, dateString: dateStr }, record );
|
|
7460
|
+
}
|
|
7461
|
+
pastIterator.setDate( pastIterator.getDate() + 1 );
|
|
7462
|
+
}
|
|
7463
|
+
|
|
7464
|
+
// Insert a block record for each future date where no store record exists
|
|
7465
|
+
for ( const futureDateString of futureDates ) {
|
|
7466
|
+
const pastQuery = {
|
|
7467
|
+
query: {
|
|
7468
|
+
bool: {
|
|
7469
|
+
must: [
|
|
7470
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
7471
|
+
{ term: { dateString: futureDateString } },
|
|
7472
|
+
{ term: { 'type.keyword': 'store' } },
|
|
7473
|
+
],
|
|
7474
|
+
},
|
|
7475
|
+
},
|
|
7476
|
+
};
|
|
7477
|
+
|
|
7478
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
7479
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
7480
|
+
|
|
7481
|
+
if ( foundHits.length === 0 ) {
|
|
7482
|
+
const record = {
|
|
7483
|
+
clientId: getstoreName?.clientId,
|
|
7484
|
+
storeId: storeId,
|
|
7485
|
+
storeName: getstoreName?.storeName,
|
|
7486
|
+
dateString: futureDateString,
|
|
7487
|
+
status: 'block',
|
|
7488
|
+
};
|
|
7489
|
+
await updateOneUpsertVmsStoreRequest( { storeId: inputData?.storeId, dateString: futureDateString }, record );
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
}
|
|
7493
|
+
}
|
|
7494
|
+
} catch ( error ) {
|
|
7495
|
+
const err = error.message || 'Internal Server Error';
|
|
7496
|
+
logger.error( { error: error, funtion: 'ticketreview' } );
|
|
7497
|
+
return res.sendError( err, 500 );
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
|
|
7501
|
+
|