tango-app-api-infra 3.9.5-vms.91 → 3.9.5-vms.95

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-infra",
3
- "version": "3.9.5-vms.91",
3
+ "version": "3.9.5-vms.95",
4
4
  "description": "infra",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -151,6 +151,7 @@ export async function tangoReviewTicket( req, res ) {
151
151
  query: {
152
152
  bool: {
153
153
  must: [
154
+
154
155
  {
155
156
  term: {
156
157
  'storeId.keyword': inputData.storeId,
@@ -177,6 +178,11 @@ export async function tangoReviewTicket( req, res ) {
177
178
  query: {
178
179
  bool: {
179
180
  must: [
181
+ {
182
+ term: {
183
+ 'type.keyword': 'store',
184
+ },
185
+ },
180
186
  {
181
187
  term: {
182
188
  'storeId.keyword': inputData.storeId,
@@ -505,6 +511,12 @@ export async function tangoReviewAccuracyClosedTicket( req, res ) {
505
511
  query: {
506
512
  bool: {
507
513
  must: [
514
+ {
515
+ term: {
516
+ 'type.keyword': 'store',
517
+ },
518
+ },
519
+
508
520
  {
509
521
  term: {
510
522
  'storeId.keyword': inputData.storeId,
@@ -572,36 +584,17 @@ export async function tangoReviewAccuracyClosedTicket( req, res ) {
572
584
  subComments: inputData?.subComments ||'',
573
585
  } ) );
574
586
 
575
- record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
576
- ...temp ];
577
- // if ( Array.isArray( record.mappingInfo ) ) {
578
- // record.mappingInfo = record.mappingInfo.map( ( item ) => {
579
- // return {
580
- // ...item,
581
- // status: 'Closed',
582
- // };
583
- // } );
584
- // }
585
-
586
- record.mappingInfo.push(
587
- {
588
- type: 'finalRevision',
589
- mode: 'web',
590
- revicedFootfall: temp?.[0]?.revicedFootfall,
591
- revicedPerc: temp?.[0].revicedPerc,
592
- count: temp?.[0].count,
593
- revisedDetail: temp?.[0]?.revisedDetail,
594
- status: 'Closed',
595
- createdByEmail: req?.user?.email,
596
- createdByUserName: req?.user?.userName,
597
- createdByRole: req?.user?.role,
598
- createdAt: new Date(),
587
+ const temp2 = record.mappingInfo
588
+ .filter( ( item ) => item.type === 'finalRevision' )
589
+ .map( ( item ) => ( {
590
+ ...item,
599
591
  comments: inputData?.comments || '',
600
- subComments: inputData?.subComments || '',
601
- },
602
- );
603
- }
592
+ subComments: inputData?.subComments ||'',
593
+ } ) );
604
594
 
595
+ record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -2 ),
596
+ ...temp, ...temp2 ];
597
+ }
605
598
 
606
599
  // return;
607
600
 
@@ -610,84 +603,6 @@ export async function tangoReviewAccuracyClosedTicket( req, res ) {
610
603
  const insertResult = await updateOpenSearchData( openSearch.footfallDirectory, id, { doc: record } );
611
604
 
612
605
  if ( insertResult && ( insertResult.statusCode === 201 || insertResult.statusCode === 200 ) ) {
613
- const query = {
614
- storeId: inputData?.storeId,
615
- isVideoStream: true,
616
- };
617
- const getStoreType = await countDocumnetsCamera( query );
618
- const revopInfoQuery = {
619
- size: 10000,
620
- query: {
621
- bool: {
622
- must: [
623
- {
624
- term: {
625
- 'storeId.keyword': inputData.storeId,
626
- },
627
- },
628
- {
629
- term: {
630
- 'dateString': inputData.dateString,
631
- },
632
- },
633
- {
634
- term: {
635
- 'isParent': false,
636
- },
637
- },
638
- {
639
- term: {
640
- isChecked: true,
641
- },
642
- },
643
- ],
644
- },
645
- },
646
- _source: [ 'tempId' ],
647
-
648
- };
649
- const revopInfo = await getOpenSearchData( openSearch.revop, revopInfoQuery );
650
- // Get all tempIds from revopInfo response
651
- const tempIds = revopInfo?.body?.hits?.hits?.map( ( hit ) => hit?._source?.tempId ).filter( Boolean ) || [];
652
- // Prepare management eyeZone query based on storeId and dateString
653
- const managerEyeZoneQuery = {
654
- size: 1,
655
- query: {
656
- bool: {
657
- must: [
658
- {
659
- term: {
660
- 'storeId.keyword': inputData.storeId,
661
- },
662
- },
663
- {
664
- term: {
665
- 'storeDate': inputData.dateString,
666
- },
667
- },
668
- ],
669
- },
670
- },
671
- _source: [ 'originalToTrackerCustomerMapping' ],
672
- };
673
-
674
- // Query the managerEyeZone index for the matching document
675
- const managerEyeZoneResp = await getOpenSearchData( openSearch.managerEyeZone, managerEyeZoneQuery );
676
- const managerEyeZoneHit = managerEyeZoneResp?.body?.hits?.hits?.[0]?._source;
677
- // Extract originalToTrackerCustomerMapping if it exists
678
- const mapping =
679
- managerEyeZoneHit && managerEyeZoneHit.originalToTrackerCustomerMapping ?
680
- managerEyeZoneHit.originalToTrackerCustomerMapping :
681
- {};
682
-
683
- // Find tempIds that exist in both revopInfo results and manager mapping
684
- const temp = [];
685
- tempIds.filter( ( tid ) => mapping[tid] !== null ? temp.push( { tempId: mapping[tid] } ) :'' );
686
- const isSendMessge = await sendSqsMessage( inputData, temp, getStoreType, inputData.storeId );
687
- if ( isSendMessge == true ) {
688
- logger.info( '....1' );
689
- }
690
-
691
606
  return res.sendSuccess( 'Ticket closed successfully' );
692
607
  } else {
693
608
  return res.sendError( 'Internal Server Error', 500 );
@@ -1511,6 +1426,220 @@ export async function ticketSummary( req, res ) {
1511
1426
  avgTicket: ticketPercentageAvg+'%',
1512
1427
  avgAccuracy: ticketAccuracy+'%',
1513
1428
  };
1429
+ } else if ( !ticketsFeature && ticketsApproveFeature ) {
1430
+ const storeQuery = {
1431
+ size: 0,
1432
+ query: {
1433
+ bool: {
1434
+ must: [
1435
+ {
1436
+ 'range': {
1437
+ 'dateString': {
1438
+ 'gte': inputData?.fromDate,
1439
+ 'lte': inputData?.toDate,
1440
+ 'format': 'yyyy-MM-dd',
1441
+ },
1442
+ },
1443
+ },
1444
+ {
1445
+ terms: {
1446
+ 'clientId.keyword': Array.isArray( inputData.clientId ) ?
1447
+ inputData.clientId :
1448
+ [ inputData.clientId ],
1449
+ },
1450
+
1451
+ },
1452
+ {
1453
+ nested: {
1454
+ path: 'mappingInfo',
1455
+ query: {
1456
+ bool: {
1457
+ must: [
1458
+ {
1459
+ term: {
1460
+ 'mappingInfo.type': inputData.permissionType === 'review'? 'review':'approve',
1461
+ },
1462
+ },
1463
+ ],
1464
+ },
1465
+ },
1466
+ },
1467
+ },
1468
+
1469
+ ],
1470
+ },
1471
+ },
1472
+ };
1473
+
1474
+ // Helper function to clone deep and replace mappingInfo.status for openTickets/closed/etc
1475
+ function buildStoreQueryWithStatus( baseQuery, statusValue ) {
1476
+ let q = JSON.parse( JSON.stringify( baseQuery ) );
1477
+ // Remove any previous mappingInfo.status term
1478
+ let nested = q.query.bool.must.find( ( m ) => m.nested );
1479
+ if ( nested ) {
1480
+ // filter out all mappingInfo.status
1481
+ nested.nested.query.bool.must = nested?.nested?.query?.bool?.must.filter( ( mustItem ) => {
1482
+ return !( mustItem.term && mustItem.term['mappingInfo.status'] );
1483
+ } );
1484
+ // add desired status
1485
+ nested.nested.query.bool.must.push( {
1486
+ term: {
1487
+ 'mappingInfo.status': statusValue,
1488
+ },
1489
+ } );
1490
+ }
1491
+ return q;
1492
+ }
1493
+
1494
+ const buildAggStoreQuery = ( baseQuery, filters = [] ) => {
1495
+ const q = JSON.parse( JSON.stringify( baseQuery ) );
1496
+
1497
+ // locate nested section
1498
+ const nested = q.query.bool.must.find( ( m ) => m.nested );
1499
+
1500
+ if ( nested ) {
1501
+ // remove old status filters
1502
+ nested.nested.query.bool.must =
1503
+ nested.nested.query.bool.must.filter( ( m ) => !( m.term && m.term['mappingInfo.status'] ) );
1504
+
1505
+ // add new filters
1506
+ nested.nested.query.bool.must.push( ...filters );
1507
+ }
1508
+
1509
+ return {
1510
+ ...q,
1511
+ size: 0,
1512
+ aggs: {
1513
+ avg_value: {
1514
+ avg: 'mappingInfp.reviced',
1515
+ },
1516
+ },
1517
+ };
1518
+ };
1519
+
1520
+
1521
+ // Get OpenSearch connection
1522
+
1523
+ const baseStoreQuery = JSON.parse( JSON.stringify( storeQuery ) );
1524
+
1525
+ // Total Tickets (all tickets with mappingInfo.type == tangoreview)
1526
+ let totalTickets = 0;
1527
+
1528
+ let allQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1529
+
1530
+ allQuery.size = 0;
1531
+ const totalResp = await getOpenSearchData( openSearch.footfallDirectory, allQuery );
1532
+ totalTickets = totalResp?.body?.hits?.total?.value || 0;
1533
+
1534
+ // openTickets: mappingInfo.status: 'Open'
1535
+ let openTickets = 0;
1536
+
1537
+ let otQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open' );
1538
+ otQ.size = 0;
1539
+ const openResp = await getOpenSearchData( openSearch.footfallDirectory, otQ );
1540
+ openTickets = openResp?.body?.hits?.total?.value || 0;
1541
+ // logger.info( { msd: '..............2', openResp } );
1542
+
1543
+
1544
+ // inprogress: mappingInfo.status: 'in-Progress'
1545
+ let inprogress = 0;
1546
+
1547
+ let ipQ = buildStoreQueryWithStatus( baseStoreQuery, 'In-Progress' );
1548
+ ipQ.size = 0;
1549
+ const ipResp = await getOpenSearchData( openSearch.footfallDirectory, ipQ );
1550
+ inprogress = ipResp?.body?.hits?.total?.value || 0;
1551
+
1552
+ // closedTickets: mappingInfo.status: 'closed'
1553
+ let closedTickets = 0;
1554
+
1555
+ let clQ = buildStoreQueryWithStatus( baseStoreQuery, 'Closed' );
1556
+ clQ.size = 0;
1557
+ const clResp = await getOpenSearchData( openSearch.footfallDirectory, clQ );
1558
+ closedTickets = clResp?.body?.hits?.total?.value || 0;
1559
+ // dueToday: Tickets whose dueDate is today (format 'yyyy-MM-dd')
1560
+ let dueToday = 0;
1561
+ let todayDateString = new Date().toISOString().slice( 0, 10 );
1562
+
1563
+ // Build a query for tickets with mappingInfo.dueDate == todayDateString
1564
+ let dueTodayQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1565
+ // Locate nested mappingInfo query
1566
+ let nestedDue = dueTodayQuery.query.bool.must.find( ( m ) => m.nested );
1567
+ if ( nestedDue ) {
1568
+ // Remove any previous mappingInfo.dueDate term
1569
+ nestedDue.nested.query.bool.must = nestedDue.nested.query.bool.must.filter(
1570
+ ( mustItem ) => !( mustItem.term && mustItem.term['mappingInfo.dueDate'] ),
1571
+ );
1572
+ // Add new dueDate filter
1573
+ nestedDue.nested.query.bool.must.push( {
1574
+ term: { 'mappingInfo.dueDate': todayDateString },
1575
+ } );
1576
+ }
1577
+ dueTodayQuery.size = 0;
1578
+ const dueTodayResp = await getOpenSearchData( openSearch.footfallDirectory, dueTodayQuery );
1579
+ dueToday = dueTodayResp?.body?.hits?.total?.value || 0;
1580
+
1581
+
1582
+ // filter expired Tickets
1583
+ let expiredTickets = 0;
1584
+
1585
+ let eQ = buildStoreQueryWithStatus( baseStoreQuery, 'Under Tango Review' );
1586
+ eQ.size = 0;
1587
+ const eResp = await getOpenSearchData( openSearch.footfallDirectory, eQ );
1588
+ expiredTickets = eResp?.body?.hits?.total?.value || 0;
1589
+
1590
+ // filter under tango review
1591
+
1592
+ let undertangoTickets = 0;
1593
+
1594
+ let utrQ = buildStoreQueryWithStatus( baseStoreQuery, 'Under Tango Review' );
1595
+ utrQ.size = 0;
1596
+ const utrResp = await getOpenSearchData( openSearch.footfallDirectory, utrQ );
1597
+ undertangoTickets = utrResp?.body?.hits?.total?.value || 0;
1598
+
1599
+ let ticketPercentageAvg =0;
1600
+ let avgTicketPercentageQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1601
+ avgTicketPercentageQuery.size = 0;
1602
+ avgTicketPercentageQuery.aggs = {
1603
+ avg_ticket_percentage: {
1604
+ avg: {
1605
+ script: {
1606
+ source: `
1607
+ if (doc.containsKey('reviced') && doc['reviced'].size()!=0 &&
1608
+ doc.containsKey('footfallCount') && doc['footfallCount'].size()!=0 && doc['footfallCount'].value != 0) {
1609
+ return (doc['reviced'].value / doc['footfallCount'].value) * 100;
1610
+ } else {
1611
+ return null;
1612
+ }
1613
+ `,
1614
+ lang: 'painless',
1615
+ },
1616
+ },
1617
+ },
1618
+ };
1619
+
1620
+ const avgTicketPercentageResp = await getOpenSearchData( openSearch.footfallDirectory, avgTicketPercentageQuery );
1621
+
1622
+ ticketPercentageAvg = avgTicketPercentageResp?.body?.aggregations?.avg_ticket_percentage?.value?.toFixed( 2 ) || '0';
1623
+ logger.info( { avgTicketPercentageResp } );
1624
+ let ticketAccuracy = 0;
1625
+
1626
+ let belowQ = buildAggStoreQuery( baseStoreQuery );
1627
+ logger.info( { belowQ } );
1628
+ const accuracyResp = await getOpenSearchData( openSearch.footfallDirectory, belowQ );
1629
+ ticketAccuracy = accuracyResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1630
+ logger.info( { accuracyResp } );
1631
+ // Final result object
1632
+ result = {
1633
+ totalTickets,
1634
+ openTickets,
1635
+ inprogress,
1636
+ closedTickets,
1637
+ dueToday: dueToday,
1638
+ Expired: expiredTickets,
1639
+ underTangoReview: undertangoTickets,
1640
+ avgTicket: ticketPercentageAvg+'%',
1641
+ avgAccuracy: ticketAccuracy+'%',
1642
+ };
1514
1643
  } else if ( ticketsFeature && ticketsApproveFeature ) {
1515
1644
  if ( inputData?.permissionType === 'review' ) {
1516
1645
  const storeQuery = {
@@ -2212,6 +2341,20 @@ export async function ticketList( req, res ) {
2212
2341
  },
2213
2342
  };
2214
2343
 
2344
+ if ( req?.user?.userType !== 'tango', req?.user?.role !== 'superadmin' ) {
2345
+ if ( req?.body?.assignedStores?.length > 0 ) {
2346
+ searchQuery.query.bool.must.push( {
2347
+ terms: {
2348
+ 'storeId.keyword': Array.isArray( req?.body?.assignedStores ) ?
2349
+ req?.body?.assignedStores :
2350
+ [ req?.body?.assignedStores ],
2351
+ },
2352
+ } );
2353
+ } else {
2354
+ return res.sendError( 'no data', 204 );
2355
+ }
2356
+ }
2357
+
2215
2358
  if ( inputData.sortBy ) {
2216
2359
  let sortOrder = inputData.sortOrder === 1 ? 'asc' : 'desc';
2217
2360
 
@@ -2423,6 +2566,28 @@ export async function ticketList( req, res ) {
2423
2566
  },
2424
2567
  },
2425
2568
  } );
2569
+ } else if ( !ticketsFeature && ticketsApproveFeature ) {
2570
+ searchQuery.query.bool.must.push( {
2571
+ nested: {
2572
+ path: 'mappingInfo',
2573
+ query: {
2574
+ bool: {
2575
+ must: [
2576
+ {
2577
+ term: {
2578
+ 'mappingInfo.type': 'approve',
2579
+ },
2580
+ },
2581
+ {
2582
+ terms: {
2583
+ 'mappingInfo.status': inputData?.filterByStatus,
2584
+ },
2585
+ },
2586
+ ],
2587
+ },
2588
+ },
2589
+ },
2590
+ } );
2426
2591
  } else if ( ticketsFeature && ticketsApproveFeature ) {
2427
2592
  switch ( inputData.permisisionType ) {
2428
2593
  case 'review':
@@ -2992,9 +3157,18 @@ export async function ticketList( req, res ) {
2992
3157
  'Ticket ID': item?.ticketId,
2993
3158
  'store Name': item?.storeName,
2994
3159
  'store ID': item?.storeId,
2995
- 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.createdAt,
2996
- 'Issue Date': item?.dateString,
2997
- 'Due Date': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.dueDate,
3160
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.createdAt? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.createdAt ).format( 'DD MMM, YYYY' ) : '',
3161
+ 'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
3162
+ 'Due Date': ( () => {
3163
+ const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.dueDate;
3164
+ if ( dueDate ) {
3165
+ if ( dayjs( dueDate ).isSame( dayjs(), 'day' ) ) {
3166
+ return 'Due Today';
3167
+ }
3168
+ return dayjs( dueDate ).format( 'DD MMM, YYYY' );
3169
+ }
3170
+ return '';
3171
+ } )(),
2998
3172
  'Actual FF': item?.footfallCount,
2999
3173
  'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3000
3174
  'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
@@ -3011,20 +3185,29 @@ export async function ticketList( req, res ) {
3011
3185
  for ( let item of ticketListData ) {
3012
3186
  temp.push( {
3013
3187
 
3014
- ticketId: item?.ticketId,
3015
- storeId: item?.storeId,
3016
- storeName: item?.storeName,
3017
- ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.createdAt,
3018
- issueDate: item?.dateString,
3019
- dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.dueDate,
3020
- footfall: item?.footfallCount,
3021
- storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3022
- reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3023
- approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
3024
- tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3025
- status: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
3026
- comments: item?.mappingInfo?.find( ( f ) => f.type === 'finalRevision' )?.comments || '--',
3027
- subComments: item?.mappingInfo?.find( ( f ) => f.type === 'finalRevision' )?.subComments || '--',
3188
+ 'ticketId': item?.ticketId,
3189
+ 'storeId': item?.storeId,
3190
+ 'storeName': item?.storeName,
3191
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.createdAt? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.createdAt ).format( 'DD MMM, YYYY' ) : '',
3192
+ 'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
3193
+ 'Due Date': ( () => {
3194
+ const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.dueDate;
3195
+ if ( dueDate ) {
3196
+ if ( dayjs( dueDate ).isSame( dayjs(), 'day' ) ) {
3197
+ return 'Due Today';
3198
+ }
3199
+ return dayjs( dueDate ).format( 'DD MMM, YYYY' );
3200
+ }
3201
+ return '';
3202
+ } )(),
3203
+ 'footfall': item?.footfallCount,
3204
+ 'storeRevisedAccuracy': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3205
+ 'reviewerRevisedAccuracy': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3206
+ 'approverRevisedAccuracy': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
3207
+ 'tangoRevisedAccuracy': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3208
+ 'status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
3209
+ 'comments': item?.mappingInfo?.find( ( f ) => f.type === 'finalRevision' )?.comments || '--',
3210
+ 'subComments': item?.mappingInfo?.find( ( f ) => f.type === 'finalRevision' )?.subComments || '--',
3028
3211
 
3029
3212
  } );
3030
3213
  }
@@ -3039,18 +3222,38 @@ export async function ticketList( req, res ) {
3039
3222
  'Ticket ID': item?.ticketId,
3040
3223
  'Store Name': item?.storeName,
3041
3224
  'Store ID': item?.storeId,
3042
- 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
3043
- 'Issue Date': item?.dateString,
3225
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt ).format( 'DD MMM, YYYY' ) : '',
3226
+ 'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
3044
3227
  'Ticket Type': item?.type,
3045
3228
  'Actual FF': item?.footfallCount,
3046
- // Use the dueDate from the last mappingInfo item whose type is NOT 'finalRevisoon'
3229
+ // Use the dueDate from the last mappingInfo item whose type is NOT 'finalRevison'
3230
+
3047
3231
  'Due Date': ( () => {
3048
3232
  if ( Array.isArray( item?.mappingInfo ) ) {
3049
- const filtered = item.mappingInfo.filter( ( f ) => f.dueDate && f.type !== 'finalRevisoon' );
3233
+ const filtered = item.mappingInfo.filter( ( f ) => f.dueDate && f.type !== 'finalRevison' );
3050
3234
  return filtered.length > 0 ? filtered[filtered.length - 1].dueDate : '';
3051
3235
  }
3052
3236
  return '';
3053
3237
  } )(),
3238
+ 'Due Date (Formatted)': ( () => {
3239
+ if ( Array.isArray( item?.mappingInfo ) ) {
3240
+ // Find last mappingInfo whose type is NOT 'finalRevison'
3241
+ const filtered = item.mappingInfo.filter(
3242
+ ( f ) => f.dueDate && f.type !== 'finalRevison',
3243
+ );
3244
+ if ( filtered.length > 0 ) {
3245
+ const lastDueDate = filtered[filtered.length - 1].dueDate;
3246
+ if ( lastDueDate ) {
3247
+ if ( dayjs( lastDueDate ).isSame( dayjs(), 'day' ) ) {
3248
+ return 'Due Today';
3249
+ }
3250
+ return dayjs( lastDueDate ).format( 'DD MMM, YYYY' );
3251
+ }
3252
+ }
3253
+ }
3254
+ return '';
3255
+ } )(),
3256
+
3054
3257
  'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3055
3258
  'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3056
3259
  'Approver (%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
@@ -3063,7 +3266,6 @@ export async function ticketList( req, res ) {
3063
3266
  return await download( exportData, res );
3064
3267
  } else {
3065
3268
  for ( let item of ticketListData ) {
3066
- console.log( '🚀 ~ ticketList ~ item:', item );
3067
3269
  temp.push( {
3068
3270
 
3069
3271
  ticketId: item?.ticketId,
@@ -3101,9 +3303,18 @@ export async function ticketList( req, res ) {
3101
3303
  'Ticket ID': item?.ticketId,
3102
3304
  'Store Name': item?.storeName,
3103
3305
  'Store ID': item?.storeId,
3104
- 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
3105
- 'Issue Date': item?.dateString,
3106
- 'Due Date': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
3306
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt ).format( 'DD MMM, YYYY' ) : '',
3307
+ 'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
3308
+ 'Due Date': ( () => {
3309
+ const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate;
3310
+ if ( dueDate ) {
3311
+ if ( dayjs( dueDate ).isSame( dayjs(), 'day' ) ) {
3312
+ return 'Due Today';
3313
+ }
3314
+ return dayjs( dueDate ).format( 'DD MMM, YYYY' );
3315
+ }
3316
+ return '';
3317
+ } )(),
3107
3318
  'Actual FF': item?.footfallCount,
3108
3319
  'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3109
3320
  'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
@@ -3148,10 +3359,19 @@ export async function ticketList( req, res ) {
3148
3359
  'Ticket ID': item?.ticketId,
3149
3360
  'Store Name': item?.storeName,
3150
3361
  'Store ID': item?.storeId,
3151
- 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
3152
- 'Issue Date': item?.dateString,
3362
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt ).format( 'DD MMM, YYYY' ) : '',
3363
+ 'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
3364
+ 'Due Date': ( () => {
3365
+ const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate;
3366
+ if ( dueDate ) {
3367
+ if ( dayjs( dueDate ).isSame( dayjs(), 'day' ) ) {
3368
+ return 'Due Today';
3369
+ }
3370
+ return dayjs( dueDate ).format( 'DD MMM, YYYY' );
3371
+ }
3372
+ return '';
3373
+ } )(),
3153
3374
  'Actual FF': item?.footfallCount,
3154
- 'Due Date': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
3155
3375
  'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3156
3376
  'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3157
3377
  'Status': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
@@ -3191,10 +3411,20 @@ export async function ticketList( req, res ) {
3191
3411
  'Ticket ID': item?.ticketId,
3192
3412
  'Store ID': item?.storeId,
3193
3413
  'Store Name': item?.storeName,
3194
- 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
3195
- 'Issue Date': item?.dateString,
3414
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt ).format( 'DD MMM, YYYY' ) : '',
3415
+ 'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
3416
+ 'Due Date': ( () => {
3417
+ const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate;
3418
+ if ( dueDate ) {
3419
+ if ( dayjs( dueDate ).isSame( dayjs(), 'day' ) ) {
3420
+ return 'Due Today';
3421
+ }
3422
+ return dayjs( dueDate ).format( 'DD MMM, YYYY' );
3423
+ }
3424
+ return '';
3425
+ } )(),
3196
3426
  'Actual FF': item?.footfallCount,
3197
- 'Due Date': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
3427
+
3198
3428
  'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3199
3429
  'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3200
3430
  'Status': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
@@ -3232,9 +3462,18 @@ export async function ticketList( req, res ) {
3232
3462
  'Ticket ID': item?.ticketId,
3233
3463
  'Store Name': item?.storeName,
3234
3464
  'Store ID': item?.storeId,
3235
- 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
3236
- 'Issue Date': item?.dateString,
3237
- 'Due Date': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
3465
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt? dayjs( item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt ).format( 'DD MMM, YYYY' ) : '',
3466
+ 'Issue Date': item?.dateString ? dayjs( item.dateString ).format( 'DD MMM, YYYY' ) : '',
3467
+ 'Due Date': ( () => {
3468
+ const dueDate = item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate;
3469
+ if ( dueDate ) {
3470
+ if ( dayjs( dueDate ).isSame( dayjs(), 'day' ) ) {
3471
+ return 'Due Today';
3472
+ }
3473
+ return dayjs( dueDate ).format( 'DD MMM, YYYY' );
3474
+ }
3475
+ return '';
3476
+ } )(),
3238
3477
  'Actual FF': item?.footfallCount,
3239
3478
  'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3240
3479
  'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
@@ -3331,88 +3570,9 @@ export async function getTickets( req, res ) {
3331
3570
  if ( !response || response.length == 0 ) {
3332
3571
  return res.sendError( `No Pending items in the selected dates for the store`, 400 );
3333
3572
  }
3334
- let temp = [];
3335
- if ( inputData?.action ) {
3336
- response.map( ( hit ) => {
3337
- const defaultData = {
3338
- storeId: hit._source.storeId,
3339
- createdByEmail: hit?._source?.createdByEmail,
3340
- dateString: hit?._source?.dateString,
3341
- ticketName: hit?._source?.ticketName,
3342
- status: hit?._source?.status?.revicedFootfall,
3343
- revicedPerc: hit?._source?.revicedPerc,
3344
- revicedFootfall: hit?._source?.revicedFootfall,
3345
- mappingInfo: hit?._source?.mappingInfo,
3346
- employeeStatus: hit?._source?.employeeStatus,
3347
- houseKeepingStatus: hit?._source?.houseKeepingStatus,
3348
- duplicateStatus: hit?._source?.duplicateStatus,
3349
- junkStatus: hit?._source?.junkStatus,
3350
- storeName: hit?._source?.storeName,
3351
- ticketId: hit?._source?.ticketId,
3352
- footfallCount: hit?._source?.footfallCount,
3353
- comments: hit?._source?.comments,
3354
- email: hit?._source?.comments,
3355
- userName: hit?._source?.userName,
3356
- role: hit?._source?.role,
3357
- createdAt: hit?._source?.createdAt,
3358
- updatedAt: hit?._source?.updatedAt,
3359
- tempId: hit?._source?.tempId,
3360
- houseKeepingCount: hit?._source?.houseKeepingCount,
3361
- employeeCount: hit?._source?.employeeCount,
3362
- duplicateCount: hit?._source?.duplicateCount,
3363
- junkCount: hit?._source?.junkCount,
3364
- houseKeepingACCount: hit?._source?.houseKeepingACCount,
3365
- employeeACCount: hit?._source?.employeeACCount,
3366
- duplicateACCount: hit?._source?.duplicateACCount,
3367
- junkACCount: hit?._source?.junkACCount,
3368
- approverUserName: hit?._source?.approverUserName,
3369
- approverEmail: hit?._source?.approverEmail,
3370
- approverRole: hit?._source?.approverRole,
3371
- type: hit?._source?.type,
3372
- };
3373
- let result;
3374
3573
 
3375
3574
 
3376
- const matched = hit.matched_queries;
3377
- // Add only matched data array
3378
- if ( matched.includes( 'matched_employee' ) && ( !inputData.revopsType || inputData.revopsType == 'employee' ) ) {
3379
- result = defaultData;
3380
- result.employee = hit?._source?.employee;
3381
- // result.type = 'employee';
3382
- result.matched = matched;
3383
- }
3384
- if ( matched.includes( 'matched_housekeeping' ) && ( !inputData.revopsType || inputData.revopsType == 'houseKeeping' ) ) {
3385
- result = defaultData;
3386
- result.houseKeeping = hit?._source?.houseKeeping;
3387
- result.matched = matched;
3388
- }
3389
- if ( matched.includes( 'matched_duplicate' ) && ( !inputData.revopsType || inputData.revopsType == 'duplicateImages' ) ) {
3390
- result = defaultData;
3391
- result.duplicateImages = hit?._source?.duplicateImages;
3392
- result.matched = matched;
3393
- }
3394
-
3395
- if ( matched.includes( 'matched_junk' ) && ( !inputData.revopsType || inputData.revopsType == 'junk' ) ) {
3396
- result = defaultData;
3397
- result.junk = hit?._source?.junk;
3398
- result.matched = matched;
3399
- }
3400
-
3401
- if ( result ) {
3402
- const nested = [
3403
- {
3404
- _id: hit._id,
3405
- _index: hit._index,
3406
- _score: 0,
3407
- _source: result,
3408
- },
3409
- ];
3410
- temp.push( ...nested );
3411
- }
3412
- } );
3413
- }
3414
-
3415
- const finalResponse = inputData.action ? temp : response;
3575
+ const finalResponse = response;
3416
3576
  const getRevopQuery = {
3417
3577
  size: 10000,
3418
3578
  query: {
@@ -3655,9 +3815,10 @@ export async function getTickets( req, res ) {
3655
3815
  mappingObj.revisedDetail = processedRevopSources;
3656
3816
  }
3657
3817
  } );
3658
- } else {
3659
- item._source.mappingInfo.revisedDetail = processedRevopSources;
3660
3818
  }
3819
+ // else {
3820
+ // item._source.mappingInfo.revisedDetail = processedRevopSources;
3821
+ // }
3661
3822
  }
3662
3823
  }
3663
3824
  } else if (
@@ -3667,7 +3828,11 @@ export async function getTickets( req, res ) {
3667
3828
  ) {
3668
3829
  finalResponse._source.mappingInfo.revisedDetail = processedRevopSources;
3669
3830
  }
3831
+ if ( inputData.action && inputData.action !== '' ) {
3832
+ const isCheck = inputData.action === 'approved'? true : false;
3670
3833
 
3834
+ finalResponse[0]._source.mappingInfo[finalResponse?.[0]?._source.mappingInfo?.length -1].revisedDetail = filterRevisedDetailByIsChecked( finalResponse?.[0]?._source?.mappingInfo[finalResponse?.[0]?._source?.mappingInfo?.length -1].revisedDetail, isCheck );
3835
+ }
3671
3836
  return res.sendSuccess( { result: finalResponse } );
3672
3837
  } catch ( error ) {
3673
3838
  const err = error.message || 'Internal Server Error';
@@ -3676,6 +3841,35 @@ export async function getTickets( req, res ) {
3676
3841
  }
3677
3842
  }
3678
3843
 
3844
+ function filterRevisedDetailByIsChecked( revisedDetailArr, isCheckedValue ) {
3845
+ if ( !Array.isArray( revisedDetailArr ) ) return [];
3846
+
3847
+ // Helper function to filter duplicateImage recursively, only for revopsType === "duplicate"
3848
+ function filterDuplicateImages( duplicateImages, isChecked ) {
3849
+ if ( !Array.isArray( duplicateImages ) ) return [];
3850
+ return duplicateImages
3851
+ .filter( ( img ) => img.isChecked === isChecked )
3852
+ .map( ( img ) => ( {
3853
+ ...img,
3854
+ duplicateImage: filterDuplicateImages( img.duplicateImage, isChecked ),
3855
+ } ) );
3856
+ }
3857
+
3858
+ return revisedDetailArr
3859
+ .filter( ( item ) => item.isChecked === isCheckedValue )
3860
+ .map( ( item ) => {
3861
+ if ( item.revopsType === 'duplicate' && Array.isArray( item.duplicateImage ) ) {
3862
+ // Filter the duplicateImage recursively for duplicates only
3863
+ return {
3864
+ ...item,
3865
+ duplicateImage: filterDuplicateImages( item.duplicateImage, isCheckedValue ),
3866
+ };
3867
+ } else {
3868
+ return { ...item };
3869
+ }
3870
+ } );
3871
+ }
3872
+
3679
3873
  export async function updateStatus( req, res ) {
3680
3874
  try {
3681
3875
  const openSearch = JSON.parse( process.env.OPENSEARCH );
@@ -5062,7 +5256,7 @@ export async function checkTicketExists( req, res ) {
5062
5256
  },
5063
5257
  };
5064
5258
  let findTicket = await getOpenSearchData( openSearch.footfallDirectory, findQuery );
5065
- let Ticket = findTicket.body?.hits?.hits;
5259
+ let Ticket = findTicket?.body?.hits?.hits;
5066
5260
 
5067
5261
 
5068
5262
  res.sendSuccess( Ticket );
@@ -264,7 +264,7 @@ export const getTicketsSchema = Joi.object().keys( {
264
264
  // } ),
265
265
  ticketId: Joi.string().required(),
266
266
  // status: Joi.string().optional(),
267
- action: Joi.string().optional(),
267
+ action: Joi.string().optional().allow( '' ),
268
268
  // revopsType: Joi.string().optional(),
269
269
  // limit: Joi.number().required(),
270
270
  // offset: Joi.number().optional(),
@@ -15,7 +15,7 @@ footfallDirectoryRouter.post( '/tango-review-accuracy-ticket', isAllowedSessionH
15
15
 
16
16
  footfallDirectoryRouter.get( '/ticket-summary', isAllowedSessionHandler, bulkValidate( ticketSummaryValid ), ticketSummary );
17
17
 
18
- footfallDirectoryRouter.get( '/ticket-list', isAllowedSessionHandler, bulkValidate( ticketListValid ), ticketList );
18
+ footfallDirectoryRouter.get( '/ticket-list', isAllowedSessionHandler, bulkValidate( ticketListValid ), getAssinedStore, ticketList );
19
19
  footfallDirectoryRouter.get( '/get-tickets', isAllowedSessionHandler, bulkValidate( getTicketsValid ), getTickets );
20
20
  footfallDirectoryRouter.get( '/get-tagged-stores', isAllowedSessionHandler, bulkValidate( getTaggedStoresValid ), getAssinedStore, getClusters, getTaggedStores );
21
21
  footfallDirectoryRouter.put( '/update-status', isAllowedSessionHandler, bulkValidate( updateStatusValid ), updateStatus );
@@ -359,6 +359,7 @@ export async function ticketCreation( req, res, next ) {
359
359
  // Get taggingLimitation from config (check both possible paths)
360
360
  const taggingLimitation = getConfig?.effectiveLimitation?.values;
361
361
  // Initialize count object from taggingLimitation
362
+
362
363
  const tempAcc = [];
363
364
  taggingLimitation.reduce( ( acc, item ) => {
364
365
  if ( item?.type ) {
@@ -409,11 +410,6 @@ export async function ticketCreation( req, res, next ) {
409
410
  'isParent': false,
410
411
  },
411
412
  },
412
- {
413
- term: {
414
- isChecked: true,
415
- },
416
- },
417
413
  ],
418
414
  },
419
415
  },
@@ -421,7 +417,7 @@ export async function ticketCreation( req, res, next ) {
421
417
  type_counts: {
422
418
  terms: {
423
419
  field: 'revopsType.keyword',
424
- size: 100,
420
+ size: 10000,
425
421
  },
426
422
  },
427
423
  },
@@ -430,7 +426,6 @@ export async function ticketCreation( req, res, next ) {
430
426
 
431
427
  const revopData = await getOpenSearchData( openSearch.revop, revopQuery );
432
428
  const buckets = revopData?.body?.aggregations?.type_counts?.buckets || [];
433
-
434
429
  // Map OpenSearch revopsType values to count object keys
435
430
  buckets.forEach( ( bucket ) => {
436
431
  const revopsType = bucket.key;
@@ -458,13 +453,13 @@ export async function ticketCreation( req, res, next ) {
458
453
  const totalCount = Array.isArray( tempAcc ) ?
459
454
  tempAcc.reduce( ( sum, acc ) => sum + ( acc.value || 0 ), 0 ) :
460
455
  0;
461
-
456
+ logger.info( { totalCount, tempAcc } );
462
457
  const footfallCount = hits?.[0]?._source?.footfall_count || 0;
463
458
  const revisedFootfall = Math.max( 0, footfallCount - totalCount );
464
459
  if ( footfallCount - revisedFootfall == 0 ) {
465
460
  return res.sendError( 'Cannot create a ticket because footfall hasn’t changed', 400 );
466
461
  }
467
-
462
+ logger.info( { footfallCount, revisedFootfall } );
468
463
  const taggingData = {
469
464
  size: 10000,
470
465
  query: {