tango-app-api-infra 3.9.25-vmsbug.1 → 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 +1045 -104
- 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,18 +2852,18 @@ 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
|
}
|
|
2564
2859
|
}
|
|
2565
2860
|
const searchQuery = {
|
|
2566
2861
|
|
|
2567
|
-
size: limit, // or use parseInt(req.query.limit) for dynamic
|
|
2568
|
-
from: offset, // or use parseInt(req.query.offset) for dynamic
|
|
2569
|
-
sort: [ { 'createdAt': { order: 'desc' } } ],
|
|
2570
|
-
|
|
2571
|
-
query: {
|
|
2862
|
+
'size': limit, // or use parseInt(req.query.limit) for dynamic
|
|
2863
|
+
'from': offset, // or use parseInt(req.query.offset) for dynamic
|
|
2864
|
+
'sort': [ { 'createdAt': { order: 'desc' } } ],
|
|
2865
|
+
'track_total_hits': true,
|
|
2866
|
+
'query': {
|
|
2572
2867
|
bool: {
|
|
2573
2868
|
must: [
|
|
2574
2869
|
{
|
|
@@ -2817,52 +3112,6 @@ export async function ticketList( req, res ) {
|
|
|
2817
3112
|
},
|
|
2818
3113
|
];
|
|
2819
3114
|
break;
|
|
2820
|
-
// case 'revicedPerc':
|
|
2821
|
-
// searchQuery.sort = [
|
|
2822
|
-
// {
|
|
2823
|
-
// _script: {
|
|
2824
|
-
// type: 'string',
|
|
2825
|
-
// script: {
|
|
2826
|
-
// lang: 'painless',
|
|
2827
|
-
// source: `
|
|
2828
|
-
// try {
|
|
2829
|
-
// if (params._source != null && params._source.mappingInfo != null) {
|
|
2830
|
-
// String found = null;
|
|
2831
|
-
|
|
2832
|
-
// for (def item : params._source.mappingInfo) {
|
|
2833
|
-
// if (item != null && item.type == params.miType && item.revicedPerc != null) {
|
|
2834
|
-
// String value = item.revicedPerc.toString();
|
|
2835
|
-
|
|
2836
|
-
// if (found == null || value.compareTo(found) > 0) {
|
|
2837
|
-
// found = value;
|
|
2838
|
-
// }
|
|
2839
|
-
// }
|
|
2840
|
-
// }
|
|
2841
|
-
|
|
2842
|
-
// if (found != null) {
|
|
2843
|
-
// return found;
|
|
2844
|
-
// }
|
|
2845
|
-
// }
|
|
2846
|
-
// } catch (Exception e) {
|
|
2847
|
-
// // ignore and fallback
|
|
2848
|
-
// }
|
|
2849
|
-
|
|
2850
|
-
// // fallback to root-level revicedPerc
|
|
2851
|
-
// if (params._source != null && params._source.revicedPerc != null) {
|
|
2852
|
-
// return params._source.revicedPerc.toString();
|
|
2853
|
-
// }
|
|
2854
|
-
|
|
2855
|
-
// return "";
|
|
2856
|
-
// `,
|
|
2857
|
-
// params: {
|
|
2858
|
-
// miType: requestedSortType,
|
|
2859
|
-
// },
|
|
2860
|
-
// },
|
|
2861
|
-
// order: sortOrder,
|
|
2862
|
-
// },
|
|
2863
|
-
// },
|
|
2864
|
-
// ];
|
|
2865
|
-
// break;
|
|
2866
3115
|
case 'revicedPerc':
|
|
2867
3116
|
searchQuery.sort = [
|
|
2868
3117
|
{
|
|
@@ -4066,7 +4315,9 @@ export async function ticketList( req, res ) {
|
|
|
4066
4315
|
'Ticket ID': item?.ticketId,
|
|
4067
4316
|
'Store Name': item?.storeName,
|
|
4068
4317
|
'Store ID': item?.storeId,
|
|
4069
|
-
'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' ) : '',
|
|
4070
4321
|
'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4071
4322
|
// 'Closed Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4072
4323
|
'Due Date': ( () => {
|
|
@@ -4079,6 +4330,7 @@ export async function ticketList( req, res ) {
|
|
|
4079
4330
|
}
|
|
4080
4331
|
return '';
|
|
4081
4332
|
} )(),
|
|
4333
|
+
'Actual FF': item?.footfallCount,
|
|
4082
4334
|
...( function() {
|
|
4083
4335
|
let counts = undefined;
|
|
4084
4336
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4106,14 +4358,15 @@ export async function ticketList( req, res ) {
|
|
|
4106
4358
|
}
|
|
4107
4359
|
return {};
|
|
4108
4360
|
} )(),
|
|
4109
|
-
'
|
|
4110
|
-
// 'Revised FF': item?.revicedFootfall,
|
|
4361
|
+
'Revised FF': item?.revicedFootfall,
|
|
4111
4362
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4112
4363
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4113
4364
|
'Approver (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
4114
4365
|
'Tango (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4115
4366
|
'Status': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4116
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 || '--',
|
|
4117
4370
|
'Approved by': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4118
4371
|
|
|
4119
4372
|
} );
|
|
@@ -4126,11 +4379,41 @@ export async function ticketList( req, res ) {
|
|
|
4126
4379
|
ticketId: item?.ticketId,
|
|
4127
4380
|
storeId: item?.storeId,
|
|
4128
4381
|
storeName: item?.storeName,
|
|
4382
|
+
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4383
|
+
// ticketReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4129
4384
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
|
|
4130
4385
|
issueDate: item?.dateString,
|
|
4131
4386
|
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
|
|
4132
4387
|
footfall: item?.footfallCount,
|
|
4133
|
-
|
|
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,
|
|
4134
4417
|
type: item.type || 'store',
|
|
4135
4418
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4136
4419
|
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
@@ -4138,6 +4421,8 @@ export async function ticketList( req, res ) {
|
|
|
4138
4421
|
tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4139
4422
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4140
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 || '--',
|
|
4141
4426
|
approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4142
4427
|
|
|
4143
4428
|
} );
|
|
@@ -4152,9 +4437,9 @@ export async function ticketList( req, res ) {
|
|
|
4152
4437
|
'Ticket ID': item?.ticketId,
|
|
4153
4438
|
'Store Name': item?.storeName,
|
|
4154
4439
|
'Store ID': item?.storeId,
|
|
4155
|
-
'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' ) : '--',
|
|
4156
4442
|
'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4157
|
-
'Ticket Closed Date': item.status === 'Closed' || item.status === 'Tango Review Done' ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt ).format( 'DD MMM, YYYY' ) : '--',
|
|
4158
4443
|
'Due Date': ( () => {
|
|
4159
4444
|
const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate;
|
|
4160
4445
|
if ( dueDate ) {
|
|
@@ -4165,6 +4450,8 @@ export async function ticketList( req, res ) {
|
|
|
4165
4450
|
}
|
|
4166
4451
|
return '';
|
|
4167
4452
|
} )(),
|
|
4453
|
+
'Actual FF': item?.footfallCount,
|
|
4454
|
+
|
|
4168
4455
|
...( function() {
|
|
4169
4456
|
let counts = undefined;
|
|
4170
4457
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4192,14 +4479,18 @@ export async function ticketList( req, res ) {
|
|
|
4192
4479
|
}
|
|
4193
4480
|
return {};
|
|
4194
4481
|
} )(),
|
|
4195
|
-
'
|
|
4196
|
-
'Revised FF': item.status === 'Closed' || item.status === 'Tango Review Done' ? item?.revicedFootfall : '--',
|
|
4482
|
+
'Revised FF': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4197
4483
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4198
4484
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4199
|
-
...( isApprover !== true?
|
|
4485
|
+
...( isApprover !== true ?
|
|
4486
|
+
{
|
|
4487
|
+
'Tango (Accuracy%)':
|
|
4488
|
+
item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4489
|
+
} :
|
|
4490
|
+
{} ),
|
|
4200
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' ) : '--',
|
|
4201
4493
|
'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4202
|
-
'Created by': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4203
4494
|
|
|
4204
4495
|
} );
|
|
4205
4496
|
}
|
|
@@ -4213,6 +4504,7 @@ export async function ticketList( req, res ) {
|
|
|
4213
4504
|
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4214
4505
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
|
|
4215
4506
|
issueDate: item?.dateString,
|
|
4507
|
+
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4216
4508
|
footfall: item?.footfallCount,
|
|
4217
4509
|
...( function() {
|
|
4218
4510
|
let counts = undefined;
|
|
@@ -4242,15 +4534,13 @@ export async function ticketList( req, res ) {
|
|
|
4242
4534
|
}
|
|
4243
4535
|
return {};
|
|
4244
4536
|
} )(),
|
|
4245
|
-
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4246
4537
|
type: item.type || 'store',
|
|
4538
|
+
revisedFF: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4247
4539
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4248
4540
|
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4249
4541
|
...( isApprover !== true? item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc : '' ),
|
|
4250
|
-
revisedFF: item.status === 'Closed' || item.status === 'Tango Review Done' ?item?.revicedFootfall : '--',
|
|
4251
|
-
|
|
4252
4542
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
4253
|
-
closedDate: item.
|
|
4543
|
+
closedDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt : '--',
|
|
4254
4544
|
ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4255
4545
|
} );
|
|
4256
4546
|
}
|
|
@@ -4266,9 +4556,9 @@ export async function ticketList( req, res ) {
|
|
|
4266
4556
|
'Ticket ID': item?.ticketId,
|
|
4267
4557
|
'Store ID': item?.storeId,
|
|
4268
4558
|
'Store Name': item?.storeName,
|
|
4269
|
-
'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' ) : '',
|
|
4270
4561
|
'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4271
|
-
'Ticket Closed Date': item.status === 'Closed' || item.status === 'Tango Review Done' ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt ).format( 'DD MMM, YYYY' ) : '--',
|
|
4272
4562
|
|
|
4273
4563
|
'Due Date': ( () => {
|
|
4274
4564
|
const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate;
|
|
@@ -4280,6 +4570,8 @@ export async function ticketList( req, res ) {
|
|
|
4280
4570
|
}
|
|
4281
4571
|
return '';
|
|
4282
4572
|
} )(),
|
|
4573
|
+
'Actual FF': item?.footfallCount,
|
|
4574
|
+
|
|
4283
4575
|
...( function() {
|
|
4284
4576
|
let counts = undefined;
|
|
4285
4577
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4307,14 +4599,18 @@ export async function ticketList( req, res ) {
|
|
|
4307
4599
|
}
|
|
4308
4600
|
return {};
|
|
4309
4601
|
} )(),
|
|
4310
|
-
'Actual FF': item?.footfallCount,
|
|
4311
4602
|
// 'Revised FF': item?.revicedFootfall,
|
|
4312
|
-
'Revised FF': item
|
|
4313
|
-
'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 || '--',
|
|
4314
4604
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4315
4605
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4316
|
-
...(
|
|
4606
|
+
...( isApprover !== true ?
|
|
4607
|
+
{
|
|
4608
|
+
'Tango (Accuracy%)':
|
|
4609
|
+
item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4610
|
+
} :
|
|
4611
|
+
{} ),
|
|
4317
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' ) : '--',
|
|
4318
4614
|
'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4319
4615
|
|
|
4320
4616
|
} );
|
|
@@ -4330,6 +4626,7 @@ export async function ticketList( req, res ) {
|
|
|
4330
4626
|
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4331
4627
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
|
|
4332
4628
|
issueDate: item?.dateString,
|
|
4629
|
+
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4333
4630
|
footfall: item?.footfallCount,
|
|
4334
4631
|
...( function() {
|
|
4335
4632
|
let counts = undefined;
|
|
@@ -4359,23 +4656,20 @@ export async function ticketList( req, res ) {
|
|
|
4359
4656
|
}
|
|
4360
4657
|
return {};
|
|
4361
4658
|
} )(),
|
|
4362
|
-
|
|
4363
|
-
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4659
|
+
revisedFF: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4364
4660
|
type: item.type || 'store',
|
|
4365
4661
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4366
4662
|
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4367
4663
|
...( isApprover !== true? tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '' ),
|
|
4368
|
-
revisedFF: item.status === 'Closed' || item.status === 'Tango Review Done' ?item?.revicedFootfall : '--',
|
|
4369
|
-
|
|
4370
4664
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
4371
|
-
closedDate: item.
|
|
4665
|
+
closedDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt : '--',
|
|
4372
4666
|
ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4373
4667
|
|
|
4374
4668
|
} );
|
|
4375
4669
|
}
|
|
4376
4670
|
}
|
|
4377
4671
|
} else if ( ticketsApproveFeature ) {
|
|
4378
|
-
if ( inputData
|
|
4672
|
+
if ( inputData?.isExport ) {
|
|
4379
4673
|
const exportData = [];
|
|
4380
4674
|
for ( let item of ticketListData ) {
|
|
4381
4675
|
exportData.push( {
|
|
@@ -4383,7 +4677,9 @@ export async function ticketList( req, res ) {
|
|
|
4383
4677
|
'Ticket ID': item?.ticketId,
|
|
4384
4678
|
'Store Name': item?.storeName,
|
|
4385
4679
|
'Store ID': item?.storeId,
|
|
4386
|
-
'
|
|
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' ) : '',
|
|
4387
4683
|
'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4388
4684
|
'Due Date': ( () => {
|
|
4389
4685
|
const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate;
|
|
@@ -4395,6 +4691,8 @@ export async function ticketList( req, res ) {
|
|
|
4395
4691
|
}
|
|
4396
4692
|
return '';
|
|
4397
4693
|
} )(),
|
|
4694
|
+
'Actual FF': item?.footfallCount,
|
|
4695
|
+
|
|
4398
4696
|
...( function() {
|
|
4399
4697
|
let counts = undefined;
|
|
4400
4698
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4422,14 +4720,15 @@ export async function ticketList( req, res ) {
|
|
|
4422
4720
|
}
|
|
4423
4721
|
return {};
|
|
4424
4722
|
} )(),
|
|
4425
|
-
'Actual FF': item?.footfallCount,
|
|
4426
4723
|
'Revised FF': item?.revicedFootfall,
|
|
4427
4724
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4428
4725
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4429
4726
|
'Approver (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
4430
4727
|
'Tango (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4431
4728
|
'Status': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4432
|
-
'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 || '--',
|
|
4433
4732
|
'Approved by': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4434
4733
|
|
|
4435
4734
|
} );
|
|
@@ -4442,10 +4741,40 @@ export async function ticketList( req, res ) {
|
|
|
4442
4741
|
ticketId: item?.ticketId,
|
|
4443
4742
|
storeId: item?.storeId,
|
|
4444
4743
|
storeName: item?.storeName,
|
|
4744
|
+
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4745
|
+
// ticketReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4445
4746
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
|
|
4446
4747
|
issueDate: item?.dateString,
|
|
4447
4748
|
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
|
|
4448
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
|
+
} )(),
|
|
4449
4778
|
revicedFootfall: item?.revicedFootfall,
|
|
4450
4779
|
type: item.type || 'store',
|
|
4451
4780
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
@@ -4453,7 +4782,9 @@ export async function ticketList( req, res ) {
|
|
|
4453
4782
|
approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
4454
4783
|
tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4455
4784
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4456
|
-
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 || '--',
|
|
4457
4788
|
approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4458
4789
|
|
|
4459
4790
|
} );
|
|
@@ -4769,6 +5100,27 @@ export async function getTickets( req, res ) {
|
|
|
4769
5100
|
mappingObj.type !== 'tangoreview'
|
|
4770
5101
|
) {
|
|
4771
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 );
|
|
4772
5124
|
}
|
|
4773
5125
|
} );
|
|
4774
5126
|
}
|
|
@@ -4789,7 +5141,14 @@ export async function getTickets( req, res ) {
|
|
|
4789
5141
|
|
|
4790
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 );
|
|
4791
5143
|
}
|
|
4792
|
-
|
|
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 } );
|
|
4793
5152
|
} catch ( error ) {
|
|
4794
5153
|
const err = error.message || 'Internal Server Error';
|
|
4795
5154
|
logger.error( { error: error, messgage: req.query } );
|
|
@@ -5121,6 +5480,29 @@ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId )
|
|
|
5121
5480
|
sqsProduceQueue.MessageBody,
|
|
5122
5481
|
);
|
|
5123
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 );
|
|
5124
5506
|
logger.error( {
|
|
5125
5507
|
error: `${sqsQueue}`,
|
|
5126
5508
|
type: 'UPLOAD_ERROR',
|
|
@@ -5139,11 +5521,11 @@ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId )
|
|
|
5139
5521
|
process_type: 'live',
|
|
5140
5522
|
revop_type: 'footfall',
|
|
5141
5523
|
temp_id: tempId,
|
|
5142
|
-
tempId1: inputData?.log?.temp1 ||
|
|
5143
|
-
tempIds: inputData?.log?.tempIds ||
|
|
5144
|
-
mapping: JSON.stringify( inputData?.log?.mapping ) ||
|
|
5145
|
-
managerEyeZoneHit: inputData?.log?.managerEyeZoneHit ||
|
|
5146
|
-
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,
|
|
5147
5529
|
time: Date.now(),
|
|
5148
5530
|
createdAt: new Date(),
|
|
5149
5531
|
createIST: dayjs().tz( 'Asia/Kolkata' ).format( 'YYYY-MM-DD' ),
|
|
@@ -5911,7 +6293,8 @@ export async function updateTempStatus( req, res ) {
|
|
|
5911
6293
|
isChecked = status === 'approved' ? true : false;
|
|
5912
6294
|
break;
|
|
5913
6295
|
case 'approve':
|
|
5914
|
-
|
|
6296
|
+
const reviewAction = actions.find( ( item ) => item.actionType === 'review' )?.action;
|
|
6297
|
+
isChecked = ( reviewAction === 'approved' ) === ( status === 'approved' );
|
|
5915
6298
|
break;
|
|
5916
6299
|
}
|
|
5917
6300
|
docsToUpdate.push( {
|
|
@@ -6148,6 +6531,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6148
6531
|
const tangoReview = Number( getConfig?.footfallDirectoryConfigs?.tangoReview?.split( '%' )[0] );
|
|
6149
6532
|
const tangoDueDate = getConfig?.footfallDirectoryConfigs?.allowTangoReview || 0;
|
|
6150
6533
|
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
6534
|
+
const accuracy = getConfig?.footfallDirectoryConfigs?.accuracyBreach || null;
|
|
6151
6535
|
|
|
6152
6536
|
// inputData structure should include an array of items to close
|
|
6153
6537
|
// Accept both array or single ticket update
|
|
@@ -6257,6 +6641,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6257
6641
|
createdByEmail: req?.user?.email,
|
|
6258
6642
|
createdByUserName: req?.user?.userName,
|
|
6259
6643
|
createdByRole: req?.user?.role,
|
|
6644
|
+
contactEmail: req?.user?.email,
|
|
6260
6645
|
createdAt: new Date(),
|
|
6261
6646
|
updatedAt: new Date(),
|
|
6262
6647
|
},
|
|
@@ -6301,6 +6686,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6301
6686
|
createdByEmail: req?.user?.email,
|
|
6302
6687
|
createdByUserName: req?.user?.userName,
|
|
6303
6688
|
createdByRole: req?.user?.role,
|
|
6689
|
+
contactEmail: req?.user?.email,
|
|
6304
6690
|
createdAt: new Date(),
|
|
6305
6691
|
updatedAt: new Date(),
|
|
6306
6692
|
},
|
|
@@ -6316,7 +6702,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6316
6702
|
const ticketUpdatePayload = {
|
|
6317
6703
|
doc: {
|
|
6318
6704
|
mappingInfo: newMappingInfoArray,
|
|
6319
|
-
status: ( revised < tangoReview && tangoApproved )? 'Under Tango Review':
|
|
6705
|
+
status: ( revised < tangoReview && tangoApproved )? 'Under Tango Review': 'Closed', // status updated at top level as well
|
|
6320
6706
|
updatedAt: new Date(),
|
|
6321
6707
|
},
|
|
6322
6708
|
};
|
|
@@ -6328,6 +6714,291 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6328
6714
|
results.push( { storeId, dateString, success: false, error: `Failed to update ticket this store ${storeId} for this date ${dateString}` } );
|
|
6329
6715
|
continue;
|
|
6330
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
|
+
}
|
|
6331
7002
|
const query = {
|
|
6332
7003
|
storeId: storeId,
|
|
6333
7004
|
isVideoStream: true,
|
|
@@ -6558,3 +7229,273 @@ export async function updateAccuracyIssues( req, res ) {
|
|
|
6558
7229
|
return res.sendError( err, 500 );
|
|
6559
7230
|
}
|
|
6560
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
|
+
|