tango-app-api-infra 3.9.26 → 3.9.27
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.
|
@@ -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
|
|
|
@@ -146,7 +147,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
146
147
|
const getFootfallCount = await getOpenSearchData( openSearch.footfall, getQuery );
|
|
147
148
|
const hits = getFootfallCount?.body?.hits?.hits || [];
|
|
148
149
|
if ( hits?.[0]?._source?.footfall_count <= 0 ) {
|
|
149
|
-
return res.sendError( 'You can’t create a ticket because this store has 0 footfall data' );
|
|
150
|
+
return res.sendError( 'You can’t create a ticket because this store has 0 footfall data', 400 );
|
|
150
151
|
}
|
|
151
152
|
// get category details from the client level configuration
|
|
152
153
|
const getConfig = await findOneClient( { clientId: getstoreName.clientId }, { footfallDirectoryConfigs: 1 } );
|
|
@@ -233,19 +234,11 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
233
234
|
return res.sendError( 'You don’t have any tagged images right now', 400 );
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
|
|
237
|
-
if ( inputData?.ticketType != 'internal' ) {
|
|
238
|
-
const approveDueDate = ticketData?.[0]?._source?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.dueDate;
|
|
239
|
-
|
|
240
|
-
if ( dayjs().isAfter( dayjs( approveDueDate ), 'day' ) ) {
|
|
241
|
-
res.sendError( 'Ticket approval is not allowed beyond the due date.' );
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
237
|
const tangoDueDate = ticketData?.[0]?._source?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.dueDate;
|
|
245
238
|
// const tangoAllowDueDate = getConfig?.footfallDirectoryConfigs?.allowTangoReview || 0;
|
|
246
239
|
|
|
247
240
|
if ( dayjs().isAfter( dayjs( tangoDueDate ), 'day' ) && inputData?.ticketType !== 'internal' ) {
|
|
248
|
-
return res.sendError( `Tango review is not allowed beyond the due date
|
|
241
|
+
return res.sendError( `Tango review is not allowed beyond the due date.`, 400 );
|
|
249
242
|
}
|
|
250
243
|
|
|
251
244
|
if ( inputData.ticketType === 'internal' ) {
|
|
@@ -279,6 +272,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
279
272
|
createdByEmail: req?.user?.email,
|
|
280
273
|
createdByUserName: req?.user?.userName,
|
|
281
274
|
createdByRole: req?.user?.role,
|
|
275
|
+
contactEmail: req?.user?.email,
|
|
282
276
|
createdAt: new Date(),
|
|
283
277
|
},
|
|
284
278
|
);
|
|
@@ -297,6 +291,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
297
291
|
status: parseInt( inputData?.mappingInfo?.revicedPerc || 0 ) < parseInt( getConfig?.footfallDirectoryConfigs?.tangoReview || 0 ) ? 'Open - Accuracy Issue' : 'Closed',
|
|
298
292
|
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
299
293
|
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
294
|
+
reviced: parseInt( inputData?.mappingInfo?.revicedPerc ),
|
|
300
295
|
mappingInfo: ticketData?.[0]?._source?.mappingInfo,
|
|
301
296
|
createdByEmail: req?.user?.email,
|
|
302
297
|
createdByUserName: req?.user?.userName,
|
|
@@ -314,6 +309,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
314
309
|
const tangoReview = Number( getConfig?.footfallDirectoryConfigs?.tangoReview?.split( '%' )[0] );
|
|
315
310
|
// If autoclose enabled and revisedPercentage meets/exceeds threshold, close ticket and skip revision
|
|
316
311
|
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
312
|
+
const accuracy = getConfig?.footfallDirectoryConfigs?.accuracyBreach || null;
|
|
317
313
|
if (
|
|
318
314
|
isAutoCloseEnable === true &&
|
|
319
315
|
revisedPercentage >= autoCloseAccuracyValue
|
|
@@ -329,6 +325,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
329
325
|
mode: inputData.mappingInfo?.mode,
|
|
330
326
|
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
331
327
|
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
328
|
+
reviced: parseInt( inputData?.mappingInfo?.revicedPerc ),
|
|
332
329
|
count: inputData.mappingInfo?.count,
|
|
333
330
|
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
334
331
|
status: 'Closed',
|
|
@@ -358,12 +355,14 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
358
355
|
mode: inputData.mappingInfo?.mode,
|
|
359
356
|
revicedFootfall: inputData.mappingInfo?.revicedFootfall,
|
|
360
357
|
revicedPerc: inputData.mappingInfo?.revicedPerc,
|
|
358
|
+
reviced: parseInt( inputData?.mappingInfo?.revicedPerc ),
|
|
361
359
|
count: inputData.mappingInfo?.count,
|
|
362
360
|
revisedDetail: inputData.mappingInfo?.revisedDetail,
|
|
363
361
|
status: 'Closed',
|
|
364
362
|
createdByEmail: req?.user?.email,
|
|
365
363
|
createdByUserName: req?.user?.userName,
|
|
366
364
|
createdByRole: req?.user?.role,
|
|
365
|
+
contactEmail: req?.user?.email,
|
|
367
366
|
createdAt: new Date(),
|
|
368
367
|
},
|
|
369
368
|
);
|
|
@@ -416,6 +415,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
416
415
|
createdByEmail: req?.user?.email,
|
|
417
416
|
createdByUserName: req?.user?.userName,
|
|
418
417
|
createdByRole: req?.user?.role,
|
|
418
|
+
contactEmail: req?.user?.email,
|
|
419
419
|
createdAt: new Date(),
|
|
420
420
|
},
|
|
421
421
|
|
|
@@ -467,6 +467,7 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
467
467
|
createdByEmail: req?.user?.email,
|
|
468
468
|
createdByUserName: req?.user?.userName,
|
|
469
469
|
createdByRole: req?.user?.role,
|
|
470
|
+
contactEmail: req?.user?.email,
|
|
470
471
|
createdAt: new Date(),
|
|
471
472
|
},
|
|
472
473
|
);
|
|
@@ -504,24 +505,310 @@ export async function tangoReviewTicket( req, res ) {
|
|
|
504
505
|
// return;
|
|
505
506
|
|
|
506
507
|
const insertResult = await updateOpenSearchData( openSearch.footfallDirectory, id, { doc: record } );
|
|
507
|
-
|
|
508
508
|
if ( insertResult && ( insertResult.statusCode === 201 || insertResult.statusCode === 200 ) ) {
|
|
509
|
-
if (
|
|
509
|
+
if ( accuracy && accuracy.ticketCount && accuracy.days && tangoApproved ) {
|
|
510
|
+
// Check if ticketCount exceeds breach limit within config months and revised footfall percentage > config accuracy
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
const breachDays = Number( accuracy.days );
|
|
514
|
+
const breachCount = Number( accuracy.ticketCount );
|
|
515
|
+
|
|
516
|
+
// accuracy.accuracy is a string like "95%", so remove "%" and convert to Number
|
|
517
|
+
// const breachAccuracy = Number( accuracy.accuracy.replace( '%', '' ) );
|
|
518
|
+
const ticketName = 'footfall-directory'; // adjust if ticket name variable differs
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
const formatDate = ( d ) =>
|
|
522
|
+
`${d.getFullYear()}-${String( d.getMonth() + 1 ).padStart( 2, '0' )}-${String( d.getDate() ).padStart( 2, '0' )}`;
|
|
523
|
+
|
|
524
|
+
// Compute current date object
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
// Calculate start date based on config days
|
|
528
|
+
// If 30 days: start from first day of current month
|
|
529
|
+
// If 60 days: start from first day of last month
|
|
530
|
+
const currentDateObj = new Date( inputData?.dateString ); // Declare currentDateObj as today's date
|
|
531
|
+
const startDateObj = new Date( currentDateObj );
|
|
532
|
+
|
|
533
|
+
if ( breachDays === 7 ) {
|
|
534
|
+
// Consider within this week (start from Monday)
|
|
535
|
+
const dayOfWeek = startDateObj.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
|
536
|
+
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
537
|
+
startDateObj.setDate( startDateObj.getDate() - daysFromMonday );
|
|
538
|
+
} else if ( breachDays === 30 ) {
|
|
539
|
+
// Consider within this month
|
|
540
|
+
startDateObj.setDate( 1 ); // First day of current month
|
|
541
|
+
} else if ( breachDays === 60 ) {
|
|
542
|
+
// Consider this month and last month
|
|
543
|
+
startDateObj.setMonth( startDateObj.getMonth() - 1 );
|
|
544
|
+
startDateObj.setDate( 1 ); // First day of last month
|
|
545
|
+
} else {
|
|
546
|
+
// For other values, calculate months from days
|
|
547
|
+
const breachMonths = Math.ceil( breachDays / 30 );
|
|
548
|
+
startDateObj.setMonth( startDateObj.getMonth() - breachMonths + 1 );
|
|
549
|
+
startDateObj.setDate( 1 );
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
startDateObj.setUTCHours( 0, 0, 0, 0 );
|
|
554
|
+
const startDate = formatDate( startDateObj );
|
|
555
|
+
|
|
556
|
+
// Calculate end date of the period (end of week/month) for dateString range
|
|
557
|
+
const endDateObj = new Date( currentDateObj );
|
|
558
|
+
if ( breachDays === 7 ) {
|
|
559
|
+
const dayOfWeek = endDateObj.getDay();
|
|
560
|
+
const daysUntilSunday = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
561
|
+
endDateObj.setDate( endDateObj.getDate() + daysUntilSunday );
|
|
562
|
+
} else if ( breachDays === 30 ) {
|
|
563
|
+
endDateObj.setMonth( endDateObj.getMonth() + 1, 0 );
|
|
564
|
+
} else if ( breachDays === 60 ) {
|
|
565
|
+
endDateObj.setMonth( endDateObj.getMonth() + 2, 0 );
|
|
566
|
+
} else {
|
|
567
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
568
|
+
endDateObj.setDate( endDateObj.getDate() + remainingDays );
|
|
569
|
+
}
|
|
570
|
+
const endDate = formatDate( endDateObj );
|
|
571
|
+
|
|
572
|
+
// Query for tickets within this week/month window for this store and ticket name
|
|
573
|
+
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
574
|
+
const mappingInfoFilter = [
|
|
575
|
+
{
|
|
576
|
+
nested: {
|
|
577
|
+
path: 'mappingInfo',
|
|
578
|
+
query: {
|
|
579
|
+
bool: {
|
|
580
|
+
must: [
|
|
581
|
+
{ term: { 'mappingInfo.type': 'approve' } },
|
|
582
|
+
{ term: { 'mappingInfo.status': 'Closed' } },
|
|
583
|
+
],
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
];
|
|
589
|
+
if ( tangoApproved ) {
|
|
590
|
+
mappingInfoFilter.push( {
|
|
591
|
+
nested: {
|
|
592
|
+
path: 'mappingInfo',
|
|
593
|
+
query: {
|
|
594
|
+
term: { 'mappingInfo.type': 'tangoreview' },
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
} );
|
|
598
|
+
}
|
|
599
|
+
logger.info( { startDate, endDate, storeId: inputData?.storeId } );
|
|
510
600
|
const query = {
|
|
511
|
-
|
|
512
|
-
|
|
601
|
+
query: {
|
|
602
|
+
bool: {
|
|
603
|
+
must: [
|
|
604
|
+
{ term: { 'storeId.keyword': inputData?.storeId } },
|
|
605
|
+
{ term: { 'ticketName.keyword': ticketName } },
|
|
606
|
+
{ range: { dateString: { gte: startDate, lte: endDate } } },
|
|
607
|
+
],
|
|
608
|
+
filter: [
|
|
609
|
+
{
|
|
610
|
+
bool: {
|
|
611
|
+
should: mappingInfoFilter,
|
|
612
|
+
minimum_should_match: 1,
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
],
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
};
|
|
619
|
+
// Search in OpenSearch for recent similar tickets
|
|
620
|
+
const breachTicketsResult = await getOpenSearchData( openSearch.footfallDirectory, query );
|
|
621
|
+
logger.info( { breachTicketsResult, message: 'breachTicketsResult;;;;;;;1' } );
|
|
622
|
+
const getTickets = breachTicketsResult?.body?.hits?.hits || [];
|
|
623
|
+
const tangoTaggingDuDate = getConfig?.footfallDirectoryConfigs?.tangoReview || 0;
|
|
624
|
+
const gettingTangoreview = Number( tangoTaggingDuDate.split( '%' )[0] || 0 );
|
|
625
|
+
|
|
626
|
+
const deltaBreach = getConfig?.footfallDirectoryConfigs?.deltaBreach || '>';
|
|
627
|
+
const deltaCountBreach = Number( getConfig?.footfallDirectoryConfigs?.deltaCountBreach ?? 10 );
|
|
628
|
+
const taggingBreach = getConfig?.footfallDirectoryConfigs?.taggingBreach || '>=';
|
|
629
|
+
const taggingCountBreach = Number( getConfig?.footfallDirectoryConfigs?.taggingCountBreach ?? 10 );
|
|
630
|
+
|
|
631
|
+
const compareByOperator = ( value, operator, threshold ) => {
|
|
632
|
+
switch ( operator ) {
|
|
633
|
+
case '>': return value > threshold;
|
|
634
|
+
case '>=': return value >= threshold;
|
|
635
|
+
case '<': return value < threshold;
|
|
636
|
+
case '<=': return value <= threshold;
|
|
637
|
+
case '==': return value == threshold;
|
|
638
|
+
case '===': return value === threshold;
|
|
639
|
+
default: return value > threshold;
|
|
640
|
+
}
|
|
513
641
|
};
|
|
514
|
-
const getStoreType = await countDocumnetsCamera( query );
|
|
515
642
|
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
|
|
643
|
+
// Filter tickets where revised footfall percentage > config accuracy
|
|
644
|
+
let breachTicketsCount = 0;
|
|
645
|
+
for ( const ticketInfo of getTickets ) {
|
|
646
|
+
const source = ticketInfo._source || {};
|
|
519
647
|
|
|
520
|
-
|
|
648
|
+
const ticketMappingInfo = source.mappingInfo || [];
|
|
521
649
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
650
|
+
|
|
651
|
+
// Get Store/AOM accuracy (tagging stage)
|
|
652
|
+
const taggingEntry = ticketMappingInfo.find( ( m ) => m.type === 'tagging' );
|
|
653
|
+
const storeAccuracy = Number( taggingEntry?.reviced ) || 0;
|
|
654
|
+
const storeRevisedFF = Number( taggingEntry?.revicedFootfall )|| 0;
|
|
655
|
+
|
|
656
|
+
// Get Central Ops(tangoreview stage)
|
|
657
|
+
const centralOpsEntry = ticketMappingInfo.find( ( m ) => m.type === 'approve' );
|
|
658
|
+
const centralOpsAccuracy = Number( centralOpsEntry?.reviced ) || 0;
|
|
659
|
+
const centralOpsRevisedFF = Number( centralOpsEntry?.revicedFootfall )|| 0;
|
|
660
|
+
|
|
661
|
+
// Get Central Ops/Tango accuracy (tangoreview stage)
|
|
662
|
+
const tangoEntry = ticketMappingInfo.find( ( m ) => m.type === 'tangoreview' );
|
|
663
|
+
const tangoAccuracy = Number( tangoEntry?.reviced ) || 0;
|
|
664
|
+
const tangoRevisedFF = Number( tangoEntry?.revicedFootfall )|| 0;
|
|
665
|
+
|
|
666
|
+
// Calculate delta between Store/AOM and Central Ops/Tango accuracy
|
|
667
|
+
|
|
668
|
+
const accuracyDelta1 = tangoAccuracy -storeAccuracy;
|
|
669
|
+
const accuracyDelta2 = centralOpsAccuracy - storeAccuracy;
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
// Check existing breach condition OR new accuracy delta breach conditions:
|
|
673
|
+
// Existing: revised footfall percentage > config accuracy and not Auto-Closed
|
|
674
|
+
// New conditions (all must be true):
|
|
675
|
+
// a) Delta between Store/AOM and Central Ops/Tango accuracy > deltaBreach config
|
|
676
|
+
// b) Post-review (Tango) accuracy > 85%
|
|
677
|
+
// c) Total images marked for validation > taggingBreach config
|
|
678
|
+
// const existingBreach = ticketRevisedPercentage > breachAccuracy && status !== 'Auto-Closed';
|
|
679
|
+
const isHighDelta = compareByOperator( accuracyDelta1, deltaBreach, deltaCountBreach ) || compareByOperator( accuracyDelta2, deltaBreach, deltaCountBreach );
|
|
680
|
+
const isCrossingThreshold =source?.reviced > gettingTangoreview;
|
|
681
|
+
logger.info( { source, revicedData: source?.reviced, gettingTangoreview } );
|
|
682
|
+
const isLowData = compareByOperator( tangoRevisedFF - storeRevisedFF, taggingBreach, taggingCountBreach ) || compareByOperator( centralOpsRevisedFF - storeRevisedFF, taggingBreach, taggingCountBreach );
|
|
683
|
+
|
|
684
|
+
const deltaBreachCondition =
|
|
685
|
+
isHighDelta && isCrossingThreshold && isLowData;
|
|
686
|
+
// const deltaBreachCondition = accuracyDelta > 10 || ( storeAccuracy < gettingTangoreview && tangoAccuracy > gettingTangoreview) || totalImages <= 10;
|
|
687
|
+
if ( deltaBreachCondition ) {
|
|
688
|
+
breachTicketsCount++;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// process.exit( 0 );
|
|
693
|
+
if ( breachTicketsCount > breachCount ) {
|
|
694
|
+
const getStoreName = await findOneStore( { storeId: inputData?.storeId }, { storeName: 1, _id: 0 } );
|
|
695
|
+
|
|
696
|
+
// Calculate remaining future days in the config period
|
|
697
|
+
const futureDates = [];
|
|
698
|
+
|
|
699
|
+
// Calculate end date of config period
|
|
700
|
+
const configEndDateObj = new Date( currentDateObj );
|
|
701
|
+
if ( breachDays === 7 ) {
|
|
702
|
+
// End of current week (Sunday)
|
|
703
|
+
const dayOfWeek = configEndDateObj.getDay(); // 0=Sun, 6=Sat
|
|
704
|
+
const daysUntilEndOfWeek = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
705
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + daysUntilEndOfWeek );
|
|
706
|
+
} else if ( breachDays === 30 ) {
|
|
707
|
+
// End of current month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
708
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 1, 0 ); // Last day of current month
|
|
709
|
+
} else if ( breachDays === 60 ) {
|
|
710
|
+
// End of next month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
711
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 2, 0 ); // Last day of next month
|
|
712
|
+
} else {
|
|
713
|
+
// For other values, add the remaining days
|
|
714
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
715
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + remainingDays );
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Generate future dates from the day after current date until end of config period
|
|
719
|
+
const tomorrow = new Date( currentDateObj );
|
|
720
|
+
tomorrow.setDate( tomorrow.getDate() + 1 );
|
|
721
|
+
|
|
722
|
+
const dateIterator = new Date( tomorrow );
|
|
723
|
+
while ( dateIterator <= configEndDateObj ) {
|
|
724
|
+
futureDates.push( formatDate( dateIterator ) );
|
|
725
|
+
dateIterator.setDate( dateIterator.getDate() + 1 );
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Check past days within the period (from startDate to currentDate - 1);
|
|
729
|
+
// if footfallDirectory index does not have a record for this storeId/dateString/type:"store", block that day
|
|
730
|
+
const pastIterator = new Date( startDateObj );
|
|
731
|
+
while ( pastIterator < currentDateObj ) {
|
|
732
|
+
const dateStr = formatDate( pastIterator );
|
|
733
|
+
const pastQuery = {
|
|
734
|
+
query: {
|
|
735
|
+
bool: {
|
|
736
|
+
must: [
|
|
737
|
+
{ term: { 'storeId.keyword': inputData?.storeId } },
|
|
738
|
+
{ term: { dateString: dateStr } },
|
|
739
|
+
{ term: { 'type.keyword': 'store' } },
|
|
740
|
+
],
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
746
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
747
|
+
const getStoreName = await findOneStore( { storeId: inputData?.storeId }, { storeName: 1 } );
|
|
748
|
+
|
|
749
|
+
if ( foundHits.length === 0 ) {
|
|
750
|
+
const record = {
|
|
751
|
+
clientId: inputData?.storeId?.split( '-' )[0],
|
|
752
|
+
storeId: inputData?.storeId,
|
|
753
|
+
storeName: getStoreName?.storeName,
|
|
754
|
+
dateString: dateStr,
|
|
755
|
+
status: 'block',
|
|
756
|
+
};
|
|
757
|
+
await updateOneUpsertVmsStoreRequest( { storeId: inputData?.storeId, dateString: dateStr }, record );
|
|
758
|
+
}
|
|
759
|
+
pastIterator.setDate( pastIterator.getDate() + 1 );
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Insert a block record for each future date where no store record exists
|
|
763
|
+
for ( const futureDateString of futureDates ) {
|
|
764
|
+
const pastQuery = {
|
|
765
|
+
query: {
|
|
766
|
+
bool: {
|
|
767
|
+
must: [
|
|
768
|
+
{ term: { 'storeId.keyword': inputData?.storeId } },
|
|
769
|
+
{ term: { dateString: futureDateString } },
|
|
770
|
+
{ term: { 'type.keyword': 'store' } },
|
|
771
|
+
],
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
777
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
778
|
+
|
|
779
|
+
if ( foundHits.length === 0 ) {
|
|
780
|
+
const record = {
|
|
781
|
+
clientId: inputData?.storeId?.split( '-' )[0],
|
|
782
|
+
storeId: inputData?.storeId,
|
|
783
|
+
storeName: getStoreName?.storeName,
|
|
784
|
+
dateString: futureDateString,
|
|
785
|
+
status: 'block',
|
|
786
|
+
};
|
|
787
|
+
await updateOneUpsertVmsStoreRequest( { storeId: inputData?.storeId, dateString: futureDateString }, record );
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if ( isAutoCloseEnable === true && revisedPercentage >= autoCloseAccuracyValue ) {
|
|
793
|
+
logger.info( 'Ticket auto-closed due to meeting auto-close accuracy threshold' );
|
|
794
|
+
} else {
|
|
795
|
+
if ( ( record.status === 'Closed' || record.status === 'Open - Accuracy Issue' ) && inputData.ticketType !== 'internal' && tangoApproved ) {
|
|
796
|
+
const query = {
|
|
797
|
+
storeId: inputData?.storeId,
|
|
798
|
+
isVideoStream: true,
|
|
799
|
+
};
|
|
800
|
+
const getStoreType = await countDocumnetsCamera( query );
|
|
801
|
+
|
|
802
|
+
// Get all tempIds from revopInfo response
|
|
803
|
+
const temp = [];
|
|
804
|
+
inputData?.mappingInfo?.revisedDetail?.map( ( hit ) => ( hit?.revopsType === 'duplicate' && hit?.isParent === true ) ? '':temp.push( { tempId: hit?.tempId } ) )|| [];
|
|
805
|
+
|
|
806
|
+
// Prepare management eyeZone query based on storeId and dateString
|
|
807
|
+
|
|
808
|
+
const isSendMessge = await sendSqsMessage( inputData, temp, getStoreType, inputData.storeId );
|
|
809
|
+
if ( isSendMessge === true ) {
|
|
810
|
+
logger.info( '....1' );
|
|
811
|
+
}
|
|
525
812
|
}
|
|
526
813
|
}
|
|
527
814
|
return res.sendSuccess( 'Ticket closed successfully' );
|
|
@@ -1615,7 +1902,7 @@ export async function ticketSummary( req, res ) {
|
|
|
1615
1902
|
closedTickets,
|
|
1616
1903
|
dueToday: dueToday,
|
|
1617
1904
|
Expired: expiredTickets,
|
|
1618
|
-
...( isApprover !== true? underTangoReview: undertangoTickets ),
|
|
1905
|
+
...( isApprover !== true? { underTangoReview: undertangoTickets }:'' ),
|
|
1619
1906
|
avgTicket: ticketPercentageAvg + '%',
|
|
1620
1907
|
avgAccuracy: ticketAccuracy + '%',
|
|
1621
1908
|
};
|
|
@@ -2073,7 +2360,7 @@ export async function ticketSummary( req, res ) {
|
|
|
2073
2360
|
closedTickets,
|
|
2074
2361
|
dueToday: dueToday,
|
|
2075
2362
|
Expired: expiredTickets,
|
|
2076
|
-
...( isApprover !== true? underTangoReview: undertangoTickets ),
|
|
2363
|
+
...( isApprover !== true? { underTangoReview: undertangoTickets } : '' ),
|
|
2077
2364
|
avgTicket: ticketPercentageAvg + '%',
|
|
2078
2365
|
avgAccuracy: ticketAccuracy + '%',
|
|
2079
2366
|
};
|
|
@@ -2557,7 +2844,7 @@ export async function ticketList( req, res ) {
|
|
|
2557
2844
|
const revisionArray = getConfig?.footfallDirectoryConfigs?.revision || [];
|
|
2558
2845
|
if ( Array.isArray( revisionArray ) && revisionArray.length > 0 ) {
|
|
2559
2846
|
for ( const r of revisionArray ) {
|
|
2560
|
-
if ( r.actionType === 'approver' && r.isChecked ===
|
|
2847
|
+
if ( r.actionType === 'approver' && r.isChecked === true ) {
|
|
2561
2848
|
isApprover = true;
|
|
2562
2849
|
}
|
|
2563
2850
|
}
|
|
@@ -4020,7 +4307,9 @@ export async function ticketList( req, res ) {
|
|
|
4020
4307
|
'Ticket ID': item?.ticketId,
|
|
4021
4308
|
'Store Name': item?.storeName,
|
|
4022
4309
|
'Store ID': item?.storeId,
|
|
4023
|
-
'Ticket
|
|
4310
|
+
'Ticket Created By': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4311
|
+
// ticketReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4312
|
+
'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
4313
|
'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4025
4314
|
// 'Closed Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4026
4315
|
'Due Date': ( () => {
|
|
@@ -4033,6 +4322,7 @@ export async function ticketList( req, res ) {
|
|
|
4033
4322
|
}
|
|
4034
4323
|
return '';
|
|
4035
4324
|
} )(),
|
|
4325
|
+
'Actual FF': item?.footfallCount,
|
|
4036
4326
|
...( function() {
|
|
4037
4327
|
let counts = undefined;
|
|
4038
4328
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4060,14 +4350,15 @@ export async function ticketList( req, res ) {
|
|
|
4060
4350
|
}
|
|
4061
4351
|
return {};
|
|
4062
4352
|
} )(),
|
|
4063
|
-
'
|
|
4064
|
-
// 'Revised FF': item?.revicedFootfall,
|
|
4353
|
+
'Revised FF': item?.revicedFootfall,
|
|
4065
4354
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4066
4355
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4067
4356
|
'Approver (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
4068
4357
|
'Tango (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4069
4358
|
'Status': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4070
4359
|
// 'Tango Status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4360
|
+
'Closed Date': item?.mappingInfo?.find( ( f ) => f.type === 'approve' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.updatedAt : '--',
|
|
4361
|
+
// 'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4071
4362
|
'Approved by': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4072
4363
|
|
|
4073
4364
|
} );
|
|
@@ -4080,11 +4371,41 @@ export async function ticketList( req, res ) {
|
|
|
4080
4371
|
ticketId: item?.ticketId,
|
|
4081
4372
|
storeId: item?.storeId,
|
|
4082
4373
|
storeName: item?.storeName,
|
|
4374
|
+
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4375
|
+
// ticketReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4083
4376
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
|
|
4084
4377
|
issueDate: item?.dateString,
|
|
4085
4378
|
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
|
|
4086
4379
|
footfall: item?.footfallCount,
|
|
4087
|
-
|
|
4380
|
+
...( function() {
|
|
4381
|
+
let counts = undefined;
|
|
4382
|
+
if ( Array.isArray( item.mappingInfo ) ) {
|
|
4383
|
+
// Priority: tangoreview > approve > review > tagging
|
|
4384
|
+
const targetTypes = [ 'approve', 'review', 'tagging' ];
|
|
4385
|
+
for ( let type of targetTypes ) {
|
|
4386
|
+
const found = item.mappingInfo.find( ( f ) => f && f.type === type && Array.isArray( f.count ) );
|
|
4387
|
+
if ( found ) {
|
|
4388
|
+
counts = found.count;
|
|
4389
|
+
break;
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
4393
|
+
// Fallback to item.count if not found
|
|
4394
|
+
if ( !counts && Array.isArray( item.count ) ) {
|
|
4395
|
+
counts = item.count;
|
|
4396
|
+
}
|
|
4397
|
+
if ( Array.isArray( counts ) ) {
|
|
4398
|
+
return counts.reduce( ( acc, curr ) => {
|
|
4399
|
+
if ( curr && curr.name && typeof curr.value !== 'undefined' ) {
|
|
4400
|
+
acc[curr.name.replace( /\s/g, '' )] = curr.value;
|
|
4401
|
+
// acc[curr.name] = curr.value;
|
|
4402
|
+
}
|
|
4403
|
+
return acc;
|
|
4404
|
+
}, {} );
|
|
4405
|
+
}
|
|
4406
|
+
return {};
|
|
4407
|
+
} )(),
|
|
4408
|
+
revicedFootfall: item?.revicedFootfall,
|
|
4088
4409
|
type: item.type || 'store',
|
|
4089
4410
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4090
4411
|
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
@@ -4092,6 +4413,8 @@ export async function ticketList( req, res ) {
|
|
|
4092
4413
|
tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4093
4414
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4094
4415
|
// tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4416
|
+
closedDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.updatedAt : '--',
|
|
4417
|
+
ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4095
4418
|
approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4096
4419
|
|
|
4097
4420
|
} );
|
|
@@ -4106,9 +4429,9 @@ export async function ticketList( req, res ) {
|
|
|
4106
4429
|
'Ticket ID': item?.ticketId,
|
|
4107
4430
|
'Store Name': item?.storeName,
|
|
4108
4431
|
'Store ID': item?.storeId,
|
|
4109
|
-
'Ticket
|
|
4432
|
+
'Ticket Created By': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4433
|
+
'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
4434
|
'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
4435
|
'Due Date': ( () => {
|
|
4113
4436
|
const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate;
|
|
4114
4437
|
if ( dueDate ) {
|
|
@@ -4119,6 +4442,8 @@ export async function ticketList( req, res ) {
|
|
|
4119
4442
|
}
|
|
4120
4443
|
return '';
|
|
4121
4444
|
} )(),
|
|
4445
|
+
'Actual FF': item?.footfallCount,
|
|
4446
|
+
|
|
4122
4447
|
...( function() {
|
|
4123
4448
|
let counts = undefined;
|
|
4124
4449
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4146,14 +4471,18 @@ export async function ticketList( req, res ) {
|
|
|
4146
4471
|
}
|
|
4147
4472
|
return {};
|
|
4148
4473
|
} )(),
|
|
4149
|
-
'
|
|
4150
|
-
'Revised FF': item.status === 'Closed' || item.status === 'Tango Review Done' ? item?.revicedFootfall : '--',
|
|
4474
|
+
'Revised FF': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4151
4475
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4152
4476
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4153
|
-
...( isApprover !== true?
|
|
4477
|
+
...( isApprover !== true ?
|
|
4478
|
+
{
|
|
4479
|
+
'Tango (Accuracy%)':
|
|
4480
|
+
item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4481
|
+
} :
|
|
4482
|
+
{} ),
|
|
4154
4483
|
'Status': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
4484
|
+
'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
4485
|
'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4156
|
-
'Created by': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4157
4486
|
|
|
4158
4487
|
} );
|
|
4159
4488
|
}
|
|
@@ -4167,6 +4496,7 @@ export async function ticketList( req, res ) {
|
|
|
4167
4496
|
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4168
4497
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
|
|
4169
4498
|
issueDate: item?.dateString,
|
|
4499
|
+
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4170
4500
|
footfall: item?.footfallCount,
|
|
4171
4501
|
...( function() {
|
|
4172
4502
|
let counts = undefined;
|
|
@@ -4196,15 +4526,13 @@ export async function ticketList( req, res ) {
|
|
|
4196
4526
|
}
|
|
4197
4527
|
return {};
|
|
4198
4528
|
} )(),
|
|
4199
|
-
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4200
4529
|
type: item.type || 'store',
|
|
4530
|
+
revisedFF: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4201
4531
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4202
4532
|
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4203
4533
|
...( isApprover !== true? item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc : '' ),
|
|
4204
|
-
revisedFF: item.status === 'Closed' || item.status === 'Tango Review Done' ?item?.revicedFootfall : '--',
|
|
4205
|
-
|
|
4206
4534
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
4207
|
-
closedDate: item.
|
|
4535
|
+
closedDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt : '--',
|
|
4208
4536
|
ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4209
4537
|
} );
|
|
4210
4538
|
}
|
|
@@ -4220,9 +4548,9 @@ export async function ticketList( req, res ) {
|
|
|
4220
4548
|
'Ticket ID': item?.ticketId,
|
|
4221
4549
|
'Store ID': item?.storeId,
|
|
4222
4550
|
'Store Name': item?.storeName,
|
|
4223
|
-
'Ticket
|
|
4551
|
+
'Ticket Created By': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4552
|
+
'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
4553
|
'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
4554
|
|
|
4227
4555
|
'Due Date': ( () => {
|
|
4228
4556
|
const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate;
|
|
@@ -4234,6 +4562,8 @@ export async function ticketList( req, res ) {
|
|
|
4234
4562
|
}
|
|
4235
4563
|
return '';
|
|
4236
4564
|
} )(),
|
|
4565
|
+
'Actual FF': item?.footfallCount,
|
|
4566
|
+
|
|
4237
4567
|
...( function() {
|
|
4238
4568
|
let counts = undefined;
|
|
4239
4569
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4261,14 +4591,18 @@ export async function ticketList( req, res ) {
|
|
|
4261
4591
|
}
|
|
4262
4592
|
return {};
|
|
4263
4593
|
} )(),
|
|
4264
|
-
'Actual FF': item?.footfallCount,
|
|
4265
4594
|
// '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' ) : '--',
|
|
4595
|
+
'Revised FF': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4268
4596
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4269
4597
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4270
|
-
...(
|
|
4598
|
+
...( isApprover !== true ?
|
|
4599
|
+
{
|
|
4600
|
+
'Tango (Accuracy%)':
|
|
4601
|
+
item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4602
|
+
} :
|
|
4603
|
+
{} ),
|
|
4271
4604
|
'Status': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
4605
|
+
'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
4606
|
'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4273
4607
|
|
|
4274
4608
|
} );
|
|
@@ -4284,6 +4618,7 @@ export async function ticketList( req, res ) {
|
|
|
4284
4618
|
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4285
4619
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
|
|
4286
4620
|
issueDate: item?.dateString,
|
|
4621
|
+
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4287
4622
|
footfall: item?.footfallCount,
|
|
4288
4623
|
...( function() {
|
|
4289
4624
|
let counts = undefined;
|
|
@@ -4313,23 +4648,20 @@ export async function ticketList( req, res ) {
|
|
|
4313
4648
|
}
|
|
4314
4649
|
return {};
|
|
4315
4650
|
} )(),
|
|
4316
|
-
|
|
4317
|
-
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
|
|
4651
|
+
revisedFF: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedFootfall || '--',
|
|
4318
4652
|
type: item.type || 'store',
|
|
4319
4653
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4320
4654
|
reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4321
4655
|
...( 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
4656
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
|
|
4325
|
-
closedDate: item.
|
|
4657
|
+
closedDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.updatedAt : '--',
|
|
4326
4658
|
ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4327
4659
|
|
|
4328
4660
|
} );
|
|
4329
4661
|
}
|
|
4330
4662
|
}
|
|
4331
4663
|
} else if ( ticketsApproveFeature ) {
|
|
4332
|
-
if ( inputData
|
|
4664
|
+
if ( inputData?.isExport ) {
|
|
4333
4665
|
const exportData = [];
|
|
4334
4666
|
for ( let item of ticketListData ) {
|
|
4335
4667
|
exportData.push( {
|
|
@@ -4337,7 +4669,9 @@ export async function ticketList( req, res ) {
|
|
|
4337
4669
|
'Ticket ID': item?.ticketId,
|
|
4338
4670
|
'Store Name': item?.storeName,
|
|
4339
4671
|
'Store ID': item?.storeId,
|
|
4340
|
-
'
|
|
4672
|
+
'Created by': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4673
|
+
|
|
4674
|
+
'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
4675
|
'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
|
|
4342
4676
|
'Due Date': ( () => {
|
|
4343
4677
|
const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate;
|
|
@@ -4349,6 +4683,8 @@ export async function ticketList( req, res ) {
|
|
|
4349
4683
|
}
|
|
4350
4684
|
return '';
|
|
4351
4685
|
} )(),
|
|
4686
|
+
'Actual FF': item?.footfallCount,
|
|
4687
|
+
|
|
4352
4688
|
...( function() {
|
|
4353
4689
|
let counts = undefined;
|
|
4354
4690
|
if ( Array.isArray( item.mappingInfo ) ) {
|
|
@@ -4376,14 +4712,15 @@ export async function ticketList( req, res ) {
|
|
|
4376
4712
|
}
|
|
4377
4713
|
return {};
|
|
4378
4714
|
} )(),
|
|
4379
|
-
'Actual FF': item?.footfallCount,
|
|
4380
4715
|
'Revised FF': item?.revicedFootfall,
|
|
4381
4716
|
'Store (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
4382
4717
|
'Reviewer (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
|
|
4383
4718
|
'Approver (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
4384
4719
|
'Tango (Accuracy%)': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4385
4720
|
'Status': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4386
|
-
'Tango Status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4721
|
+
// 'Tango Status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4722
|
+
'Closed Date': item?.mappingInfo?.find( ( f ) => f.type === 'approve' ).status === 'Closed' ? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.updatedAt ).format( 'DD MMM, YYYY' ) : '--',
|
|
4723
|
+
'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4387
4724
|
'Approved by': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4388
4725
|
|
|
4389
4726
|
} );
|
|
@@ -4396,10 +4733,40 @@ export async function ticketList( req, res ) {
|
|
|
4396
4733
|
ticketId: item?.ticketId,
|
|
4397
4734
|
storeId: item?.storeId,
|
|
4398
4735
|
storeName: item?.storeName,
|
|
4736
|
+
ticketCreatedBy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdByEmail || '--',
|
|
4737
|
+
// ticketReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4399
4738
|
ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
|
|
4400
4739
|
issueDate: item?.dateString,
|
|
4401
4740
|
dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
|
|
4402
4741
|
footfall: item?.footfallCount,
|
|
4742
|
+
...( function() {
|
|
4743
|
+
let counts = undefined;
|
|
4744
|
+
if ( Array.isArray( item.mappingInfo ) ) {
|
|
4745
|
+
// Priority: tangoreview > approve > review > tagging
|
|
4746
|
+
const targetTypes = [ 'approve', 'review', 'tagging' ];
|
|
4747
|
+
for ( let type of targetTypes ) {
|
|
4748
|
+
const found = item.mappingInfo.find( ( f ) => f && f.type === type && Array.isArray( f.count ) );
|
|
4749
|
+
if ( found ) {
|
|
4750
|
+
counts = found.count;
|
|
4751
|
+
break;
|
|
4752
|
+
}
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
// Fallback to item.count if not found
|
|
4756
|
+
if ( !counts && Array.isArray( item.count ) ) {
|
|
4757
|
+
counts = item.count;
|
|
4758
|
+
}
|
|
4759
|
+
if ( Array.isArray( counts ) ) {
|
|
4760
|
+
return counts.reduce( ( acc, curr ) => {
|
|
4761
|
+
if ( curr && curr.name && typeof curr.value !== 'undefined' ) {
|
|
4762
|
+
acc[curr.name.replace( /\s/g, '' )] = curr.value;
|
|
4763
|
+
// acc[curr.name] = curr.value;
|
|
4764
|
+
}
|
|
4765
|
+
return acc;
|
|
4766
|
+
}, {} );
|
|
4767
|
+
}
|
|
4768
|
+
return {};
|
|
4769
|
+
} )(),
|
|
4403
4770
|
revicedFootfall: item?.revicedFootfall,
|
|
4404
4771
|
type: item.type || 'store',
|
|
4405
4772
|
storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
|
|
@@ -4407,7 +4774,9 @@ export async function ticketList( req, res ) {
|
|
|
4407
4774
|
approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
|
|
4408
4775
|
tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4409
4776
|
status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
|
|
4410
|
-
tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4777
|
+
// tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
|
|
4778
|
+
closedDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' ).status === 'Closed' ? item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.updatedAt : '--',
|
|
4779
|
+
// ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
|
|
4411
4780
|
approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
|
|
4412
4781
|
|
|
4413
4782
|
} );
|
|
@@ -4723,6 +5092,27 @@ export async function getTickets( req, res ) {
|
|
|
4723
5092
|
mappingObj.type !== 'tangoreview'
|
|
4724
5093
|
) {
|
|
4725
5094
|
mappingObj.revisedDetail = processedRevopSources;
|
|
5095
|
+
} else if (
|
|
5096
|
+
Object.prototype.hasOwnProperty.call( mappingObj, 'revisedDetail' ) &&
|
|
5097
|
+
mappingObj.type === 'tangoreview' &&
|
|
5098
|
+
Array.isArray( mappingObj.revisedDetail )
|
|
5099
|
+
) {
|
|
5100
|
+
const url = JSON.parse( process.env.URL );
|
|
5101
|
+
const OLD_CDN = url?.tangoReviewOld;
|
|
5102
|
+
const NEW_CDN =url?.tangoReviewNew; // bucket name : v4rnd-vms-audit-input-images
|
|
5103
|
+
|
|
5104
|
+
const replaceCdnInFilePath = ( detail ) => {
|
|
5105
|
+
if ( !detail || typeof detail !== 'object' ) return detail;
|
|
5106
|
+
if ( typeof detail.filePath === 'string' && detail.filePath.startsWith( OLD_CDN ) ) {
|
|
5107
|
+
detail.filePath = detail.filePath.replace( OLD_CDN, NEW_CDN );
|
|
5108
|
+
}
|
|
5109
|
+
if ( Array.isArray( detail.duplicateImage ) ) {
|
|
5110
|
+
detail.duplicateImage.forEach( replaceCdnInFilePath );
|
|
5111
|
+
}
|
|
5112
|
+
return detail;
|
|
5113
|
+
};
|
|
5114
|
+
|
|
5115
|
+
mappingObj.revisedDetail.forEach( replaceCdnInFilePath );
|
|
4726
5116
|
}
|
|
4727
5117
|
} );
|
|
4728
5118
|
}
|
|
@@ -4743,7 +5133,14 @@ export async function getTickets( req, res ) {
|
|
|
4743
5133
|
|
|
4744
5134
|
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
5135
|
}
|
|
4746
|
-
|
|
5136
|
+
|
|
5137
|
+
// If tangoApproved is false, remove tangoreview entries from mappingInfo
|
|
5138
|
+
const clientId = finalResponse?.[0]?._source?.clientId;
|
|
5139
|
+
|
|
5140
|
+
const getConfig = clientId? await findOneClient( { clientId }, { footfallDirectoryConfigs: 1 } ): null;
|
|
5141
|
+
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
5142
|
+
|
|
5143
|
+
return res.sendSuccess( { result: finalResponse, tangoApproved: tangoApproved } );
|
|
4747
5144
|
} catch ( error ) {
|
|
4748
5145
|
const err = error.message || 'Internal Server Error';
|
|
4749
5146
|
logger.error( { error: error, messgage: req.query } );
|
|
@@ -5075,6 +5472,29 @@ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId )
|
|
|
5075
5472
|
sqsProduceQueue.MessageBody,
|
|
5076
5473
|
);
|
|
5077
5474
|
if ( sqsQueue.statusCode ) {
|
|
5475
|
+
const id = `${storeId}_${inputData.dateString.split( '-' ).reverse().join( '-' )}_${getStoreType > 0 ? 'live' : 'reduction'}_${Date.now()}`;
|
|
5476
|
+
const logs = {
|
|
5477
|
+
QueueUrl: sqsProduceQueue.QueueUrl,
|
|
5478
|
+
MessageBody: sqsProduceQueue.MessageBody,
|
|
5479
|
+
sqsQueue: sqsQueue,
|
|
5480
|
+
statusCode: sqsQueue.statusCode,
|
|
5481
|
+
store_id: storeId,
|
|
5482
|
+
store_date: inputData.dateString.split( '-' ).reverse().join( '-' ),
|
|
5483
|
+
bucket_name: '',
|
|
5484
|
+
zone_id: 'traffic_zone',
|
|
5485
|
+
process_type: 'live',
|
|
5486
|
+
revop_type: 'footfall',
|
|
5487
|
+
temp_id: tempId,
|
|
5488
|
+
tempId1: inputData?.log?.temp1 || null,
|
|
5489
|
+
tempIds: inputData?.log?.tempIds || null,
|
|
5490
|
+
mapping: JSON.stringify( inputData?.log?.mapping ) || null,
|
|
5491
|
+
managerEyeZoneHit: inputData?.log?.managerEyeZoneHit || null,
|
|
5492
|
+
managerEyeZoneQuery: inputData?.log?.managerEyeZoneQuery || null,
|
|
5493
|
+
time: Date.now(),
|
|
5494
|
+
createdAt: new Date(),
|
|
5495
|
+
createIST: dayjs().tz( 'Asia/Kolkata' ).format( 'YYYY-MM-DD' ),
|
|
5496
|
+
};
|
|
5497
|
+
await insertWithId( openSearch.revopsLog, id, logs );
|
|
5078
5498
|
logger.error( {
|
|
5079
5499
|
error: `${sqsQueue}`,
|
|
5080
5500
|
type: 'UPLOAD_ERROR',
|
|
@@ -5093,11 +5513,11 @@ export async function sendSqsMessage( inputData, tempId, getStoreType, storeId )
|
|
|
5093
5513
|
process_type: 'live',
|
|
5094
5514
|
revop_type: 'footfall',
|
|
5095
5515
|
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 ||
|
|
5516
|
+
tempId1: inputData?.log?.temp1 || null,
|
|
5517
|
+
tempIds: inputData?.log?.tempIds || null,
|
|
5518
|
+
mapping: JSON.stringify( inputData?.log?.mapping ) || null,
|
|
5519
|
+
managerEyeZoneHit: inputData?.log?.managerEyeZoneHit || null,
|
|
5520
|
+
managerEyeZoneQuery: inputData?.log?.managerEyeZoneQuery || null,
|
|
5101
5521
|
time: Date.now(),
|
|
5102
5522
|
createdAt: new Date(),
|
|
5103
5523
|
createIST: dayjs().tz( 'Asia/Kolkata' ).format( 'YYYY-MM-DD' ),
|
|
@@ -5865,7 +6285,8 @@ export async function updateTempStatus( req, res ) {
|
|
|
5865
6285
|
isChecked = status === 'approved' ? true : false;
|
|
5866
6286
|
break;
|
|
5867
6287
|
case 'approve':
|
|
5868
|
-
|
|
6288
|
+
const reviewAction = actions.find( ( item ) => item.actionType === 'review' )?.action;
|
|
6289
|
+
isChecked = ( reviewAction === 'approved' ) === ( status === 'approved' );
|
|
5869
6290
|
break;
|
|
5870
6291
|
}
|
|
5871
6292
|
docsToUpdate.push( {
|
|
@@ -6102,6 +6523,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6102
6523
|
const tangoReview = Number( getConfig?.footfallDirectoryConfigs?.tangoReview?.split( '%' )[0] );
|
|
6103
6524
|
const tangoDueDate = getConfig?.footfallDirectoryConfigs?.allowTangoReview || 0;
|
|
6104
6525
|
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
6526
|
+
const accuracy = getConfig?.footfallDirectoryConfigs?.accuracyBreach || null;
|
|
6105
6527
|
|
|
6106
6528
|
// inputData structure should include an array of items to close
|
|
6107
6529
|
// Accept both array or single ticket update
|
|
@@ -6211,6 +6633,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6211
6633
|
createdByEmail: req?.user?.email,
|
|
6212
6634
|
createdByUserName: req?.user?.userName,
|
|
6213
6635
|
createdByRole: req?.user?.role,
|
|
6636
|
+
contactEmail: req?.user?.email,
|
|
6214
6637
|
createdAt: new Date(),
|
|
6215
6638
|
updatedAt: new Date(),
|
|
6216
6639
|
},
|
|
@@ -6255,6 +6678,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6255
6678
|
createdByEmail: req?.user?.email,
|
|
6256
6679
|
createdByUserName: req?.user?.userName,
|
|
6257
6680
|
createdByRole: req?.user?.role,
|
|
6681
|
+
contactEmail: req?.user?.email,
|
|
6258
6682
|
createdAt: new Date(),
|
|
6259
6683
|
updatedAt: new Date(),
|
|
6260
6684
|
},
|
|
@@ -6270,7 +6694,7 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6270
6694
|
const ticketUpdatePayload = {
|
|
6271
6695
|
doc: {
|
|
6272
6696
|
mappingInfo: newMappingInfoArray,
|
|
6273
|
-
status: ( revised < tangoReview && tangoApproved )? 'Under Tango Review':
|
|
6697
|
+
status: ( revised < tangoReview && tangoApproved )? 'Under Tango Review': 'Closed', // status updated at top level as well
|
|
6274
6698
|
updatedAt: new Date(),
|
|
6275
6699
|
},
|
|
6276
6700
|
};
|
|
@@ -6282,6 +6706,291 @@ export async function multiCloseTicket( req, res ) {
|
|
|
6282
6706
|
results.push( { storeId, dateString, success: false, error: `Failed to update ticket this store ${storeId} for this date ${dateString}` } );
|
|
6283
6707
|
continue;
|
|
6284
6708
|
} else {
|
|
6709
|
+
if ( accuracy && accuracy.ticketCount && accuracy.days ) {
|
|
6710
|
+
// Check if ticketCount exceeds breach limit within config months and revised footfall percentage > config accuracy
|
|
6711
|
+
|
|
6712
|
+
|
|
6713
|
+
const breachDays = Number( accuracy.days );
|
|
6714
|
+
const breachCount = Number( accuracy.ticketCount );
|
|
6715
|
+
|
|
6716
|
+
// accuracy.accuracy is a string like "95%", so remove "%" and convert to Number
|
|
6717
|
+
// const breachAccuracy = Number( accuracy.accuracy.replace( '%', '' ) );
|
|
6718
|
+
const ticketName = 'footfall-directory'; // adjust if ticket name variable differs
|
|
6719
|
+
|
|
6720
|
+
|
|
6721
|
+
const formatDate = ( d ) =>
|
|
6722
|
+
`${d.getFullYear()}-${String( d.getMonth() + 1 ).padStart( 2, '0' )}-${String( d.getDate() ).padStart( 2, '0' )}`;
|
|
6723
|
+
|
|
6724
|
+
// Compute current date object
|
|
6725
|
+
|
|
6726
|
+
|
|
6727
|
+
// Calculate start date based on config days
|
|
6728
|
+
// If 30 days: start from first day of current month
|
|
6729
|
+
// If 60 days: start from first day of last month
|
|
6730
|
+
const currentDateObj = new Date( dateString ); // Declare currentDateObj as today's date
|
|
6731
|
+
const startDateObj = new Date( currentDateObj );
|
|
6732
|
+
|
|
6733
|
+
if ( breachDays === 7 ) {
|
|
6734
|
+
// Consider within this week (start from Monday)
|
|
6735
|
+
const dayOfWeek = startDateObj.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
|
6736
|
+
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
6737
|
+
startDateObj.setDate( startDateObj.getDate() - daysFromMonday );
|
|
6738
|
+
} else if ( breachDays === 30 ) {
|
|
6739
|
+
// Consider within this month
|
|
6740
|
+
startDateObj.setDate( 1 ); // First day of current month
|
|
6741
|
+
} else if ( breachDays === 60 ) {
|
|
6742
|
+
// Consider this month and last month
|
|
6743
|
+
startDateObj.setMonth( startDateObj.getMonth() - 1 );
|
|
6744
|
+
startDateObj.setDate( 1 ); // First day of last month
|
|
6745
|
+
} else {
|
|
6746
|
+
// For other values, calculate months from days
|
|
6747
|
+
const breachMonths = Math.ceil( breachDays / 30 );
|
|
6748
|
+
startDateObj.setMonth( startDateObj.getMonth() - breachMonths + 1 );
|
|
6749
|
+
startDateObj.setDate( 1 );
|
|
6750
|
+
}
|
|
6751
|
+
|
|
6752
|
+
|
|
6753
|
+
startDateObj.setUTCHours( 0, 0, 0, 0 );
|
|
6754
|
+
const startDate = formatDate( startDateObj );
|
|
6755
|
+
|
|
6756
|
+
// Calculate end date of the period (end of week/month) for dateString range
|
|
6757
|
+
const endDateObj = new Date( currentDateObj );
|
|
6758
|
+
if ( breachDays === 7 ) {
|
|
6759
|
+
const dayOfWeek = endDateObj.getDay();
|
|
6760
|
+
const daysUntilSunday = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
6761
|
+
endDateObj.setDate( endDateObj.getDate() + daysUntilSunday );
|
|
6762
|
+
} else if ( breachDays === 30 ) {
|
|
6763
|
+
endDateObj.setMonth( endDateObj.getMonth() + 1, 0 );
|
|
6764
|
+
} else if ( breachDays === 60 ) {
|
|
6765
|
+
endDateObj.setMonth( endDateObj.getMonth() + 2, 0 );
|
|
6766
|
+
} else {
|
|
6767
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
6768
|
+
endDateObj.setDate( endDateObj.getDate() + remainingDays );
|
|
6769
|
+
}
|
|
6770
|
+
const endDate = formatDate( endDateObj );
|
|
6771
|
+
|
|
6772
|
+
// Query for tickets within this week/month window for this store and ticket name
|
|
6773
|
+
const tangoApproved = getConfig?.footfallDirectoryConfigs?.tangoApproved || false;
|
|
6774
|
+
const mappingInfoFilter = [
|
|
6775
|
+
{
|
|
6776
|
+
nested: {
|
|
6777
|
+
path: 'mappingInfo',
|
|
6778
|
+
query: {
|
|
6779
|
+
bool: {
|
|
6780
|
+
must: [
|
|
6781
|
+
{ term: { 'mappingInfo.type': 'approve' } },
|
|
6782
|
+
{ term: { 'mappingInfo.status': 'Closed' } },
|
|
6783
|
+
],
|
|
6784
|
+
},
|
|
6785
|
+
},
|
|
6786
|
+
},
|
|
6787
|
+
},
|
|
6788
|
+
];
|
|
6789
|
+
if ( tangoApproved ) {
|
|
6790
|
+
mappingInfoFilter.push( {
|
|
6791
|
+
nested: {
|
|
6792
|
+
path: 'mappingInfo',
|
|
6793
|
+
query: {
|
|
6794
|
+
term: { 'mappingInfo.type': 'tangoreview' },
|
|
6795
|
+
},
|
|
6796
|
+
},
|
|
6797
|
+
} );
|
|
6798
|
+
}
|
|
6799
|
+
logger.info( { startDate, endDate, storeId } );
|
|
6800
|
+
const query = {
|
|
6801
|
+
query: {
|
|
6802
|
+
bool: {
|
|
6803
|
+
must: [
|
|
6804
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
6805
|
+
{ term: { 'ticketName.keyword': ticketName } },
|
|
6806
|
+
{ range: { dateString: { gte: startDate, lte: endDate } } },
|
|
6807
|
+
],
|
|
6808
|
+
filter: [
|
|
6809
|
+
{
|
|
6810
|
+
bool: {
|
|
6811
|
+
should: mappingInfoFilter,
|
|
6812
|
+
minimum_should_match: 1,
|
|
6813
|
+
},
|
|
6814
|
+
},
|
|
6815
|
+
],
|
|
6816
|
+
},
|
|
6817
|
+
},
|
|
6818
|
+
};
|
|
6819
|
+
// Search in OpenSearch for recent similar tickets
|
|
6820
|
+
const breachTicketsResult = await getOpenSearchData( openSearch.footfallDirectory, query );
|
|
6821
|
+
logger.info( { breachTicketsResult, message: 'breachTicketsResult;;;;;;;1' } );
|
|
6822
|
+
const getTickets = breachTicketsResult?.body?.hits?.hits || [];
|
|
6823
|
+
const tangoTaggingDuDate = getConfig?.footfallDirectoryConfigs?.tangoReview || 0;
|
|
6824
|
+
const gettingTangoreview = Number( tangoTaggingDuDate.split( '%' )[0] || 0 );
|
|
6825
|
+
|
|
6826
|
+
const deltaBreach = getConfig?.footfallDirectoryConfigs?.deltaBreach || '>';
|
|
6827
|
+
const deltaCountBreach = Number( getConfig?.footfallDirectoryConfigs?.deltaCountBreach ?? 10 );
|
|
6828
|
+
const taggingBreach = getConfig?.footfallDirectoryConfigs?.taggingBreach || '>=';
|
|
6829
|
+
const taggingCountBreach = Number( getConfig?.footfallDirectoryConfigs?.taggingCountBreach ?? 10 );
|
|
6830
|
+
|
|
6831
|
+
const compareByOperator = ( value, operator, threshold ) => {
|
|
6832
|
+
switch ( operator ) {
|
|
6833
|
+
case '>': return value > threshold;
|
|
6834
|
+
case '>=': return value >= threshold;
|
|
6835
|
+
case '<': return value < threshold;
|
|
6836
|
+
case '<=': return value <= threshold;
|
|
6837
|
+
case '==': return value == threshold;
|
|
6838
|
+
case '===': return value === threshold;
|
|
6839
|
+
default: return value > threshold;
|
|
6840
|
+
}
|
|
6841
|
+
};
|
|
6842
|
+
|
|
6843
|
+
// Filter tickets where revised footfall percentage > config accuracy
|
|
6844
|
+
let breachTicketsCount = 0;
|
|
6845
|
+
logger.info( { getTickets, message: 'tickets;;;;;;;1' } );
|
|
6846
|
+
for ( const ticketInfo of getTickets ) {
|
|
6847
|
+
const source = ticketInfo._source || {};
|
|
6848
|
+
|
|
6849
|
+
const ticketMappingInfo = source.mappingInfo || [];
|
|
6850
|
+
|
|
6851
|
+
|
|
6852
|
+
// Get Store/AOM accuracy (tagging stage)
|
|
6853
|
+
const taggingEntry = ticketMappingInfo.find( ( m ) => m.type === 'tagging' );
|
|
6854
|
+
const storeAccuracy = Number( taggingEntry?.reviced ) || 0;
|
|
6855
|
+
const storeRevisedFF = Number( taggingEntry?.revicedFootfall )|| 0;
|
|
6856
|
+
|
|
6857
|
+
// Get Central Ops(tangoreview stage)
|
|
6858
|
+
const centralOpsEntry = ticketMappingInfo.find( ( m ) => m.type === 'approve' );
|
|
6859
|
+
const centralOpsAccuracy = Number( centralOpsEntry?.reviced ) || 0;
|
|
6860
|
+
const centralOpsRevisedFF = Number( centralOpsEntry?.revicedFootfall )|| 0;
|
|
6861
|
+
|
|
6862
|
+
// Get Central Ops/Tango accuracy (tangoreview stage)
|
|
6863
|
+
const tangoEntry = ticketMappingInfo.find( ( m ) => m.type === 'tangoreview' );
|
|
6864
|
+
const tangoAccuracy = Number( tangoEntry?.reviced ) || 0;
|
|
6865
|
+
const tangoRevisedFF = Number( tangoEntry?.revicedFootfall )|| 0;
|
|
6866
|
+
|
|
6867
|
+
// Calculate delta between Store/AOM and Central Ops/Tango accuracy
|
|
6868
|
+
|
|
6869
|
+
const accuracyDelta1 = tangoAccuracy -storeAccuracy;
|
|
6870
|
+
const accuracyDelta2 = centralOpsAccuracy - storeAccuracy;
|
|
6871
|
+
|
|
6872
|
+
|
|
6873
|
+
// Check existing breach condition OR new accuracy delta breach conditions:
|
|
6874
|
+
// Existing: revised footfall percentage > config accuracy and not Auto-Closed
|
|
6875
|
+
// New conditions (all must be true):
|
|
6876
|
+
// a) Delta between Store/AOM and Central Ops/Tango accuracy > deltaBreach config
|
|
6877
|
+
// b) Post-review (Tango) accuracy > 85%
|
|
6878
|
+
// c) Total images marked for validation > taggingBreach config
|
|
6879
|
+
// const existingBreach = ticketRevisedPercentage > breachAccuracy && status !== 'Auto-Closed';
|
|
6880
|
+
const isHighDelta = compareByOperator( accuracyDelta1, deltaBreach, deltaCountBreach ) || compareByOperator( accuracyDelta2, deltaBreach, deltaCountBreach );
|
|
6881
|
+
const isCrossingThreshold =source?.reviced > gettingTangoreview;
|
|
6882
|
+
logger.info( { source, revicedData: source?.reviced, gettingTangoreview } );
|
|
6883
|
+
const isLowData = compareByOperator( tangoRevisedFF - storeRevisedFF, taggingBreach, taggingCountBreach ) || compareByOperator( centralOpsRevisedFF - storeRevisedFF, taggingBreach, taggingCountBreach );
|
|
6884
|
+
|
|
6885
|
+
const deltaBreachCondition =
|
|
6886
|
+
isHighDelta && isCrossingThreshold && isLowData;
|
|
6887
|
+
// const deltaBreachCondition = accuracyDelta > 10 || ( storeAccuracy < gettingTangoreview && tangoAccuracy > gettingTangoreview) || totalImages <= 10;
|
|
6888
|
+
logger.info( { isHighDelta, isCrossingThreshold, isLowData } );
|
|
6889
|
+
if ( deltaBreachCondition ) {
|
|
6890
|
+
breachTicketsCount++;
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6893
|
+
logger.info( { breachTicketsCount, breachCount }, 'total Breach Tickets Count1' );
|
|
6894
|
+
|
|
6895
|
+
// process.exit( 0 );
|
|
6896
|
+
if ( breachTicketsCount > breachCount ) {
|
|
6897
|
+
const getStoreName = await findOneStore( { storeId: storeId }, { storeName: 1, _id: 0 } );
|
|
6898
|
+
|
|
6899
|
+
// Calculate remaining future days in the config period
|
|
6900
|
+
const futureDates = [];
|
|
6901
|
+
|
|
6902
|
+
// Calculate end date of config period
|
|
6903
|
+
const configEndDateObj = new Date( currentDateObj );
|
|
6904
|
+
if ( breachDays === 7 ) {
|
|
6905
|
+
// End of current week (Sunday)
|
|
6906
|
+
const dayOfWeek = configEndDateObj.getDay(); // 0=Sun, 6=Sat
|
|
6907
|
+
const daysUntilEndOfWeek = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
6908
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + daysUntilEndOfWeek );
|
|
6909
|
+
} else if ( breachDays === 30 ) {
|
|
6910
|
+
// End of current month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
6911
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 1, 0 ); // Last day of current month
|
|
6912
|
+
} else if ( breachDays === 60 ) {
|
|
6913
|
+
// End of next month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
6914
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 2, 0 ); // Last day of next month
|
|
6915
|
+
} else {
|
|
6916
|
+
// For other values, add the remaining days
|
|
6917
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
6918
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + remainingDays );
|
|
6919
|
+
}
|
|
6920
|
+
|
|
6921
|
+
// Generate future dates from the day after current date until end of config period
|
|
6922
|
+
const tomorrow = new Date( currentDateObj );
|
|
6923
|
+
tomorrow.setDate( tomorrow.getDate() + 1 );
|
|
6924
|
+
|
|
6925
|
+
const dateIterator = new Date( tomorrow );
|
|
6926
|
+
while ( dateIterator <= configEndDateObj ) {
|
|
6927
|
+
futureDates.push( formatDate( dateIterator ) );
|
|
6928
|
+
dateIterator.setDate( dateIterator.getDate() + 1 );
|
|
6929
|
+
}
|
|
6930
|
+
|
|
6931
|
+
// Check past days within the period (from startDate to currentDate - 1);
|
|
6932
|
+
// if footfallDirectory index does not have a record for this storeId/dateString/type:"store", block that day
|
|
6933
|
+
const pastIterator = new Date( startDateObj );
|
|
6934
|
+
while ( pastIterator < currentDateObj ) {
|
|
6935
|
+
const dateStr = formatDate( pastIterator );
|
|
6936
|
+
const pastQuery = {
|
|
6937
|
+
query: {
|
|
6938
|
+
bool: {
|
|
6939
|
+
must: [
|
|
6940
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
6941
|
+
{ term: { dateString: dateStr } },
|
|
6942
|
+
{ term: { 'type.keyword': 'store' } },
|
|
6943
|
+
],
|
|
6944
|
+
},
|
|
6945
|
+
},
|
|
6946
|
+
};
|
|
6947
|
+
|
|
6948
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
6949
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
6950
|
+
|
|
6951
|
+
if ( foundHits.length === 0 ) {
|
|
6952
|
+
const record = {
|
|
6953
|
+
clientId: inputData?.ticketList?.[0]?.storeId?.split( '-' )[0],
|
|
6954
|
+
storeId: storeId,
|
|
6955
|
+
storeName: getStoreName?.storeName,
|
|
6956
|
+
dateString: dateStr,
|
|
6957
|
+
status: 'block',
|
|
6958
|
+
};
|
|
6959
|
+
await updateOneUpsertVmsStoreRequest( { storeId: storeId, dateString: dateStr }, record );
|
|
6960
|
+
}
|
|
6961
|
+
pastIterator.setDate( pastIterator.getDate() + 1 );
|
|
6962
|
+
}
|
|
6963
|
+
|
|
6964
|
+
// Insert a block record for each future date where no store record exists
|
|
6965
|
+
for ( const futureDateString of futureDates ) {
|
|
6966
|
+
const pastQuery = {
|
|
6967
|
+
query: {
|
|
6968
|
+
bool: {
|
|
6969
|
+
must: [
|
|
6970
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
6971
|
+
{ term: { dateString: futureDateString } },
|
|
6972
|
+
{ term: { 'type.keyword': 'store' } },
|
|
6973
|
+
],
|
|
6974
|
+
},
|
|
6975
|
+
},
|
|
6976
|
+
};
|
|
6977
|
+
|
|
6978
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
6979
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
6980
|
+
|
|
6981
|
+
if ( foundHits.length === 0 ) {
|
|
6982
|
+
const record = {
|
|
6983
|
+
clientId: inputData?.ticketList?.[0]?.storeId?.split( '-' )[0],
|
|
6984
|
+
storeId: storeId,
|
|
6985
|
+
storeName: getStoreName?.storeName,
|
|
6986
|
+
dateString: futureDateString,
|
|
6987
|
+
status: 'block',
|
|
6988
|
+
};
|
|
6989
|
+
await updateOneUpsertVmsStoreRequest( { storeId: storeId, dateString: futureDateString }, record );
|
|
6990
|
+
}
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
}
|
|
6285
6994
|
const query = {
|
|
6286
6995
|
storeId: storeId,
|
|
6287
6996
|
isVideoStream: true,
|
|
@@ -6512,3 +7221,376 @@ export async function updateAccuracyIssues( req, res ) {
|
|
|
6512
7221
|
return res.sendError( err, 500 );
|
|
6513
7222
|
}
|
|
6514
7223
|
}
|
|
7224
|
+
|
|
7225
|
+
export async function getBadTicket( req, res ) {
|
|
7226
|
+
try {
|
|
7227
|
+
if ( accuracy && accuracy.ticketCount && accuracy.days && accuracy.accuracy ) {
|
|
7228
|
+
// Check if ticketCount exceeds breach limit within config months and revised footfall percentage > config accuracy
|
|
7229
|
+
|
|
7230
|
+
const breachDays = Number( accuracy.days );
|
|
7231
|
+
const breachCount = Number( accuracy.ticketCount );
|
|
7232
|
+
|
|
7233
|
+
// accuracy.accuracy is a string like "95%", so remove "%" and convert to Number
|
|
7234
|
+
const breachAccuracy = Number( accuracy.accuracy.replace( '%', '' ) );
|
|
7235
|
+
const storeId = inputData.storeId;
|
|
7236
|
+
const ticketName = inputData.ticketName || 'footfall-directory'; // adjust if ticket name variable differs
|
|
7237
|
+
|
|
7238
|
+
|
|
7239
|
+
const formatDate = ( d ) =>
|
|
7240
|
+
`${d.getFullYear()}-${String( d.getMonth() + 1 ).padStart( 2, '0' )}-${String( d.getDate() ).padStart( 2, '0' )}`;
|
|
7241
|
+
|
|
7242
|
+
// Compute current date object
|
|
7243
|
+
|
|
7244
|
+
|
|
7245
|
+
// Calculate start date based on config days
|
|
7246
|
+
// If 30 days: start from first day of current month
|
|
7247
|
+
// If 60 days: start from first day of last month
|
|
7248
|
+
const currentDateObj = new Date( inputData?.dateString ); // Declare currentDateObj as today's date
|
|
7249
|
+
const startDateObj = new Date( currentDateObj );
|
|
7250
|
+
|
|
7251
|
+
if ( breachDays === 7 ) {
|
|
7252
|
+
// Consider within this week (start from Monday)
|
|
7253
|
+
const dayOfWeek = startDateObj.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
|
7254
|
+
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
7255
|
+
startDateObj.setDate( startDateObj.getDate() - daysFromMonday );
|
|
7256
|
+
} else if ( breachDays === 30 ) {
|
|
7257
|
+
// Consider within this month
|
|
7258
|
+
startDateObj.setDate( 1 ); // First day of current month
|
|
7259
|
+
} else if ( breachDays === 60 ) {
|
|
7260
|
+
// Consider this month and last month
|
|
7261
|
+
startDateObj.setMonth( startDateObj.getMonth() - 1 );
|
|
7262
|
+
startDateObj.setDate( 1 ); // First day of last month
|
|
7263
|
+
} else {
|
|
7264
|
+
// For other values, calculate months from days
|
|
7265
|
+
const breachMonths = Math.ceil( breachDays / 30 );
|
|
7266
|
+
startDateObj.setMonth( startDateObj.getMonth() - breachMonths + 1 );
|
|
7267
|
+
startDateObj.setDate( 1 );
|
|
7268
|
+
}
|
|
7269
|
+
|
|
7270
|
+
|
|
7271
|
+
startDateObj.setUTCHours( 0, 0, 0, 0 );
|
|
7272
|
+
const startDate = formatDate( startDateObj );
|
|
7273
|
+
|
|
7274
|
+
// Calculate end date of the period (end of week/month) for dateString range
|
|
7275
|
+
const endDateObj = new Date( currentDateObj );
|
|
7276
|
+
if ( breachDays === 7 ) {
|
|
7277
|
+
const dayOfWeek = endDateObj.getDay();
|
|
7278
|
+
const daysUntilSunday = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
7279
|
+
endDateObj.setDate( endDateObj.getDate() + daysUntilSunday );
|
|
7280
|
+
} else if ( breachDays === 30 ) {
|
|
7281
|
+
endDateObj.setMonth( endDateObj.getMonth() + 1, 0 );
|
|
7282
|
+
} else if ( breachDays === 60 ) {
|
|
7283
|
+
endDateObj.setMonth( endDateObj.getMonth() + 2, 0 );
|
|
7284
|
+
} else {
|
|
7285
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
7286
|
+
endDateObj.setDate( endDateObj.getDate() + remainingDays );
|
|
7287
|
+
}
|
|
7288
|
+
const endDate = formatDate( endDateObj );
|
|
7289
|
+
|
|
7290
|
+
// Query for tickets within this week/month window for this store and ticket name
|
|
7291
|
+
const query = {
|
|
7292
|
+
query: {
|
|
7293
|
+
bool: {
|
|
7294
|
+
must: [
|
|
7295
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
7296
|
+
{ term: { 'ticketName.keyword': ticketName } },
|
|
7297
|
+
{ range: { dateString: { gte: startDate, lte: endDate } } },
|
|
7298
|
+
],
|
|
7299
|
+
must_not: [
|
|
7300
|
+
{
|
|
7301
|
+
terms: { 'status.keyword': [ 'Auto-Closed', 'Reviewer-Expired' ] },
|
|
7302
|
+
},
|
|
7303
|
+
],
|
|
7304
|
+
},
|
|
7305
|
+
},
|
|
7306
|
+
};
|
|
7307
|
+
|
|
7308
|
+
// Search in OpenSearch for recent similar tickets
|
|
7309
|
+
const breachTicketsResult = await getOpenSearchData( openSearch.footfallDirectory, query );
|
|
7310
|
+
logger.info( { breachTicketsResult } );
|
|
7311
|
+
const tickets = breachTicketsResult?.body?.hits?.hits || [];
|
|
7312
|
+
|
|
7313
|
+
const tangoTaggingDuDate = getConfig?.footfallDirectoryConfigs?.tangoReview || 0;
|
|
7314
|
+
const gettingTangoreview = Number( tangoTaggingDuDate.split( '%' )[0] || 0 );
|
|
7315
|
+
|
|
7316
|
+
const deltaBreach = getConfig?.footfallDirectoryConfigs?.deltaBreach || '>';
|
|
7317
|
+
const deltaCountBreach = Number( getConfig?.footfallDirectoryConfigs?.deltaCountBreach ?? 10 );
|
|
7318
|
+
const taggingBreach = getConfig?.footfallDirectoryConfigs?.taggingBreach || '>=';
|
|
7319
|
+
const taggingCountBreach = Number( getConfig?.footfallDirectoryConfigs?.taggingCountBreach ?? 10 );
|
|
7320
|
+
|
|
7321
|
+
const compareByOperator = ( value, operator, threshold ) => {
|
|
7322
|
+
switch ( operator ) {
|
|
7323
|
+
case '>': return value > threshold;
|
|
7324
|
+
case '>=': return value >= threshold;
|
|
7325
|
+
case '<': return value < threshold;
|
|
7326
|
+
case '<=': return value <= threshold;
|
|
7327
|
+
case '==': return value == threshold;
|
|
7328
|
+
case '===': return value === threshold;
|
|
7329
|
+
default: return value > threshold;
|
|
7330
|
+
}
|
|
7331
|
+
};
|
|
7332
|
+
|
|
7333
|
+
// Filter tickets where revised footfall percentage > config accuracy
|
|
7334
|
+
|
|
7335
|
+
let breachTicketsCount = 0;
|
|
7336
|
+
for ( const ticket of tickets ) {
|
|
7337
|
+
const source = ticket._source || {};
|
|
7338
|
+
|
|
7339
|
+
const ticketFootfallCount = source.footfallCount || 0;
|
|
7340
|
+
const status = source.status || '';
|
|
7341
|
+
const ticketMappingInfo = source.mappingInfo || [];
|
|
7342
|
+
// Calculate revised footfall percentage
|
|
7343
|
+
const ticketRevisedPercentage = source.reviced;
|
|
7344
|
+
// Get Store/AOM accuracy (tagging stage)
|
|
7345
|
+
const taggingEntry = ticketMappingInfo.find( ( m ) => m.type === 'tagging' );
|
|
7346
|
+
const storeAccuracy = Number( taggingEntry?.reviced ) || 0;
|
|
7347
|
+
// Get AOM accuracy (tagging stage)
|
|
7348
|
+
const reviewEntry = ticketMappingInfo.find( ( m ) => m.type === 'review' );
|
|
7349
|
+
const AOMAccuracy = Number( reviewEntry?.reviced ) || 0;
|
|
7350
|
+
|
|
7351
|
+
// Get Central Ops(tangoreview stage)
|
|
7352
|
+
const centralOpsEntry = ticketMappingInfo.find( ( m ) => m.type === 'approve' );
|
|
7353
|
+
const centralOpsAccuracy = Number( centralOpsEntry?.reviced ) || 0;
|
|
7354
|
+
|
|
7355
|
+
// Get Central Ops/Tango accuracy (tangoreview stage)
|
|
7356
|
+
const tangoEntry = ticketMappingInfo.find( ( m ) => m.type === 'tangoreview' );
|
|
7357
|
+
const tangoAccuracy = Number( tangoEntry?.reviced ) || 0;
|
|
7358
|
+
|
|
7359
|
+
// Calculate delta between Store/AOM and Central Ops/Tango accuracy
|
|
7360
|
+
const accuracyDelta1 = AOMAccuracy - storeAccuracy;
|
|
7361
|
+
const accuracyDelta2 = tangoAccuracy - centralOpsAccuracy;
|
|
7362
|
+
const accuracyDelta3 = tangoAccuracy -storeAccuracy;
|
|
7363
|
+
const accuracyDelta4 = centralOpsAccuracy - AOMAccuracy;
|
|
7364
|
+
const accuracyDelta5 = tangoAccuracy -AOMAccuracy;
|
|
7365
|
+
const accuracyDelta6 = centralOpsAccuracy - storeAccuracy;
|
|
7366
|
+
// Get total images (footfall) marked for validation
|
|
7367
|
+
const totalImages =ticketFootfallCount || 0;
|
|
7368
|
+
|
|
7369
|
+
// Check existing breach condition OR new accuracy delta breach conditions:
|
|
7370
|
+
// Existing: revised footfall percentage > config accuracy and not Auto-Closed
|
|
7371
|
+
// New conditions (all must be true):
|
|
7372
|
+
// a) Delta between Store/AOM and Central Ops/Tango accuracy > deltaBreach config
|
|
7373
|
+
// b) Post-review (Tango) accuracy > 85%
|
|
7374
|
+
// c) Total images marked for validation > taggingBreach config
|
|
7375
|
+
const existingBreach = ticketRevisedPercentage > breachAccuracy && status !== 'Auto-Closed';
|
|
7376
|
+
// const deltaBreachCondition = accuracyDelta > 10 || ( storeAccuracy < gettingTangoreview && tangoAccuracy > gettingTangoreview )|| totalImages <= 10;
|
|
7377
|
+
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 );
|
|
7378
|
+
|
|
7379
|
+
const isCrossingThreshold =
|
|
7380
|
+
storeAccuracy < gettingTangoreview &&(
|
|
7381
|
+
tangoAccuracy > gettingTangoreview || centralOpsAccuracy > gettingTangoreview || AOMAccuracy > gettingTangoreview );
|
|
7382
|
+
const isLowData = compareByOperator( totalImages, taggingBreach, taggingCountBreach );
|
|
7383
|
+
|
|
7384
|
+
const deltaBreachCondition =
|
|
7385
|
+
isHighDelta || isCrossingThreshold || isLowData;
|
|
7386
|
+
if ( existingBreach || deltaBreachCondition ) {
|
|
7387
|
+
breachTicketsCount++;
|
|
7388
|
+
}
|
|
7389
|
+
}
|
|
7390
|
+
logger.info( { breachTicketsCount, breachCount } );
|
|
7391
|
+
if ( breachTicketsCount > breachCount ) {
|
|
7392
|
+
// Calculate remaining future days in the config period
|
|
7393
|
+
const futureDates = [];
|
|
7394
|
+
|
|
7395
|
+
// Calculate end date of config period
|
|
7396
|
+
const configEndDateObj = new Date( currentDateObj );
|
|
7397
|
+
if ( breachDays === 7 ) {
|
|
7398
|
+
// End of current week (Sunday)
|
|
7399
|
+
const dayOfWeek = configEndDateObj.getDay(); // 0=Sun, 6=Sat
|
|
7400
|
+
const daysUntilEndOfWeek = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
|
|
7401
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + daysUntilEndOfWeek );
|
|
7402
|
+
} else if ( breachDays === 30 ) {
|
|
7403
|
+
// End of current month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
7404
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 1, 0 ); // Last day of current month
|
|
7405
|
+
} else if ( breachDays === 60 ) {
|
|
7406
|
+
// End of next month (use 2-arg setMonth to avoid month rollover on dates like 31st)
|
|
7407
|
+
configEndDateObj.setMonth( configEndDateObj.getMonth() + 2, 0 ); // Last day of next month
|
|
7408
|
+
} else {
|
|
7409
|
+
// For other values, add the remaining days
|
|
7410
|
+
const remainingDays = breachDays - ( Math.floor( ( currentDateObj - startDateObj ) / ( 1000 * 60 * 60 * 24 ) ) );
|
|
7411
|
+
configEndDateObj.setDate( configEndDateObj.getDate() + remainingDays );
|
|
7412
|
+
}
|
|
7413
|
+
|
|
7414
|
+
// Generate future dates from the day after current date until end of config period
|
|
7415
|
+
const tomorrow = new Date( currentDateObj );
|
|
7416
|
+
tomorrow.setDate( tomorrow.getDate() + 1 );
|
|
7417
|
+
const dateIterator = new Date( tomorrow );
|
|
7418
|
+
while ( dateIterator <= configEndDateObj ) {
|
|
7419
|
+
futureDates.push( formatDate( dateIterator ) );
|
|
7420
|
+
dateIterator.setDate( dateIterator.getDate() + 1 );
|
|
7421
|
+
}
|
|
7422
|
+
|
|
7423
|
+
// Check past days within the period (from startDate to currentDate - 1);
|
|
7424
|
+
// if footfallDirectory index does not have a record for this storeId/dateString/type:"store", block that day
|
|
7425
|
+
const pastIterator = new Date( startDateObj );
|
|
7426
|
+
while ( pastIterator < currentDateObj ) {
|
|
7427
|
+
const dateStr = formatDate( pastIterator );
|
|
7428
|
+
const pastQuery = {
|
|
7429
|
+
query: {
|
|
7430
|
+
bool: {
|
|
7431
|
+
must: [
|
|
7432
|
+
{ term: { 'storeId.keyword': inputData?.storeId } },
|
|
7433
|
+
{ term: { dateString: dateStr } },
|
|
7434
|
+
{ term: { 'type.keyword': 'store' } },
|
|
7435
|
+
],
|
|
7436
|
+
},
|
|
7437
|
+
},
|
|
7438
|
+
};
|
|
7439
|
+
|
|
7440
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
7441
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
7442
|
+
|
|
7443
|
+
if ( foundHits.length === 0 ) {
|
|
7444
|
+
const record = {
|
|
7445
|
+
clientId: getstoreName?.clientId,
|
|
7446
|
+
storeId: storeId,
|
|
7447
|
+
storeName: getstoreName?.storeName,
|
|
7448
|
+
dateString: dateStr,
|
|
7449
|
+
status: 'block',
|
|
7450
|
+
};
|
|
7451
|
+
await updateOneUpsertVmsStoreRequest( { storeId: inputData?.storeId, dateString: dateStr }, record );
|
|
7452
|
+
}
|
|
7453
|
+
pastIterator.setDate( pastIterator.getDate() + 1 );
|
|
7454
|
+
}
|
|
7455
|
+
|
|
7456
|
+
// Insert a block record for each future date where no store record exists
|
|
7457
|
+
for ( const futureDateString of futureDates ) {
|
|
7458
|
+
const pastQuery = {
|
|
7459
|
+
query: {
|
|
7460
|
+
bool: {
|
|
7461
|
+
must: [
|
|
7462
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
7463
|
+
{ term: { dateString: futureDateString } },
|
|
7464
|
+
{ term: { 'type.keyword': 'store' } },
|
|
7465
|
+
],
|
|
7466
|
+
},
|
|
7467
|
+
},
|
|
7468
|
+
};
|
|
7469
|
+
|
|
7470
|
+
const searchRes = await getOpenSearchData( openSearch.footfallDirectory, pastQuery );
|
|
7471
|
+
const foundHits = searchRes?.body?.hits?.hits || [];
|
|
7472
|
+
|
|
7473
|
+
if ( foundHits.length === 0 ) {
|
|
7474
|
+
const record = {
|
|
7475
|
+
clientId: getstoreName?.clientId,
|
|
7476
|
+
storeId: storeId,
|
|
7477
|
+
storeName: getstoreName?.storeName,
|
|
7478
|
+
dateString: futureDateString,
|
|
7479
|
+
status: 'block',
|
|
7480
|
+
};
|
|
7481
|
+
await updateOneUpsertVmsStoreRequest( { storeId: inputData?.storeId, dateString: futureDateString }, record );
|
|
7482
|
+
}
|
|
7483
|
+
}
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
} catch ( error ) {
|
|
7487
|
+
const err = error.message || 'Internal Server Error';
|
|
7488
|
+
logger.error( { error: error, funtion: 'ticketreview' } );
|
|
7489
|
+
return res.sendError( err, 500 );
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
|
|
7493
|
+
// Returns footfall-directory-tagging records whose audit-input-images S3 folder
|
|
7494
|
+
// (v4rnd-vms-audit-input-images/${storeId}/${dateString}/) is empty or missing.
|
|
7495
|
+
// Query params: fromDate, toDate (YYYY-MM-DD). Defaults to Jan 1 - Apr 30 of the current year.
|
|
7496
|
+
// export async function getUnmatchedAuditFolders( req, res ) {
|
|
7497
|
+
// try {
|
|
7498
|
+
// const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
7499
|
+
// const bucketConfig = JSON.parse( process.env.BUCKET );
|
|
7500
|
+
// // Audit input images bucket; folder layout is ${storeId}/${dateString}/ with dateString as DD-MM-YYYY.
|
|
7501
|
+
// const auditInputBucket = bucketConfig.tangoReviewInput || 'v4rnd-vms-audit-input-images';
|
|
7502
|
+
|
|
7503
|
+
// // dateString is stored as YYYY-MM-DD. Default window: Jan 1 - Apr 30 of the current year.
|
|
7504
|
+
// const currentYear = new Date().getFullYear();
|
|
7505
|
+
// const fromDate = req.query.fromDate || `${currentYear}-02-02`;
|
|
7506
|
+
// const toDate = req.query.toDate || `${currentYear}-03-01`;
|
|
7507
|
+
|
|
7508
|
+
// // 1. Pull every record in the date range (scroll to get past the 10k window).
|
|
7509
|
+
// const scrollQuery = {
|
|
7510
|
+
// size: 1000,
|
|
7511
|
+
// _source: [ 'storeId', 'dateString' ],
|
|
7512
|
+
// query: {
|
|
7513
|
+
// range: { dateString: { gte: fromDate, lte: toDate } },
|
|
7514
|
+
// },
|
|
7515
|
+
// };
|
|
7516
|
+
|
|
7517
|
+
// let scrollResult = await searchOpenSearchData( openSearch.footfallDirectory, scrollQuery );
|
|
7518
|
+
// const totalCount = scrollResult?.body?.hits?.total?.value || 0;
|
|
7519
|
+
// if ( totalCount === 0 ) {
|
|
7520
|
+
// return res.sendError( 'No footfall-directory-tagging records found for the selected dates', 204 );
|
|
7521
|
+
// }
|
|
7522
|
+
|
|
7523
|
+
// let scrollId = scrollResult?.body?._scroll_id;
|
|
7524
|
+
// let hits = scrollResult?.body?.hits?.hits || [];
|
|
7525
|
+
// let allHits = [ ...hits ];
|
|
7526
|
+
// while ( hits.length > 0 ) {
|
|
7527
|
+
// const scrollResp = await scrollResponse( scrollId );
|
|
7528
|
+
// hits = scrollResp?.body?.hits?.hits || [];
|
|
7529
|
+
// scrollId = scrollResp?.body?._scroll_id;
|
|
7530
|
+
// allHits = [ ...allHits, ...hits ];
|
|
7531
|
+
// }
|
|
7532
|
+
// await clearScroll( scrollId );
|
|
7533
|
+
|
|
7534
|
+
// // 2. Keep only the main tagging docs (exclude *_internal_* records) and dedupe by storeId+dateString.
|
|
7535
|
+
// const uniqueRecords = new Map();
|
|
7536
|
+
// for ( const hit of allHits ) {
|
|
7537
|
+
// const docId = hit?._id || '';
|
|
7538
|
+
// if ( !docId.endsWith( '_footfall-directory-tagging' ) || docId.includes( '_internal_' ) ) {
|
|
7539
|
+
// continue;
|
|
7540
|
+
// }
|
|
7541
|
+
// const storeId = hit?._source?.storeId;
|
|
7542
|
+
// const dateString = hit?._source?.dateString;
|
|
7543
|
+
// if ( !storeId || !dateString ) {
|
|
7544
|
+
// continue;
|
|
7545
|
+
// }
|
|
7546
|
+
// uniqueRecords.set( `${storeId}_${dateString}`, { storeId, dateString } );
|
|
7547
|
+
// }
|
|
7548
|
+
|
|
7549
|
+
// const records = [ ...uniqueRecords.values() ];
|
|
7550
|
+
// logger.info( { records } );
|
|
7551
|
+
// // 3. For each record, check whether its audit-input-images folder exists.
|
|
7552
|
+
// // Images live in nested subfolders under ${storeId}/${dateString}/, so we use a recursive
|
|
7553
|
+
// // prefix check (checkFileExist has no delimiter) instead of listing direct children only.
|
|
7554
|
+
// // dateString (YYYY-MM-DD) -> folder (DD-MM-YYYY).
|
|
7555
|
+
// const unmatched = [];
|
|
7556
|
+
// const errored = [];
|
|
7557
|
+
// const batches = await chunkArray( records, 25 );
|
|
7558
|
+
// logger.info( { batches } );
|
|
7559
|
+
// for ( const batch of batches ) {
|
|
7560
|
+
// await Promise.all(
|
|
7561
|
+
// batch.map( async ( record ) => {
|
|
7562
|
+
// const folderDate = dayjs( record.dateString ).format( 'DD-MM-YYYY' );
|
|
7563
|
+
// const folderPath = `/${record.storeId}/${folderDate}/`;
|
|
7564
|
+
// const exists = await checkFileExist( {
|
|
7565
|
+
// Bucket: auditInputBucket,
|
|
7566
|
+
// Key: folderPath,
|
|
7567
|
+
// } );
|
|
7568
|
+
// logger.info( { exists, auditInputBucket, folderPath } );
|
|
7569
|
+
// // checkFileExist returns true/false on success, or the error object on failure.
|
|
7570
|
+
// if ( exists === false ) {
|
|
7571
|
+
// unmatched.push( { ...record, folderPath } );
|
|
7572
|
+
// } else if ( exists !== true ) {
|
|
7573
|
+
// errored.push( { ...record, folderPath } );
|
|
7574
|
+
// }
|
|
7575
|
+
// } ),
|
|
7576
|
+
// );
|
|
7577
|
+
// }
|
|
7578
|
+
|
|
7579
|
+
// return res.sendSuccess( {
|
|
7580
|
+
// fromDate,
|
|
7581
|
+
// toDate,
|
|
7582
|
+
// bucket: auditInputBucket,
|
|
7583
|
+
// totalRecords: records.length,
|
|
7584
|
+
// unmatchedCount: unmatched.length,
|
|
7585
|
+
// unmatched,
|
|
7586
|
+
// erroredCount: errored.length,
|
|
7587
|
+
// errored,
|
|
7588
|
+
// } );
|
|
7589
|
+
// } catch ( error ) {
|
|
7590
|
+
// const err = error.message || 'Internal Server Error';
|
|
7591
|
+
// logger.error( { error: error, function: 'getUnmatchedAuditFolders' } );
|
|
7592
|
+
// return res.sendError( err, 500 );
|
|
7593
|
+
// }
|
|
7594
|
+
// }
|
|
7595
|
+
|
|
7596
|
+
|