tango-app-api-infra 3.9.5-vms.77 → 3.9.5-vms.79

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.
@@ -385,22 +385,97 @@ export async function tangoReviewTicket( req, res ) {
385
385
 
386
386
  let id = `${inputData.storeId}_${inputData.dateString}_footfall-directory-tagging`;
387
387
  let getExistingOne = await getOpenSearchById( openSearch.footfallDirectory, id );
388
- console.log( '🚀 ~ tangoReviewTicket ~ getExistingOne:', getExistingOne );
389
- if ( inputData.ticketType === 'internal'&&!getExistingOne?.body?._source ) {
388
+ if ( inputData.ticketType === 'internal' &&!getExistingOne?.body?._source ) {
390
389
  id = `${inputData.storeId}_${inputData.dateString}_internal_footfall-directory-tagging`;
391
390
  }
392
391
 
393
-
394
392
  const insertResult = await updateOpenSearchData( openSearch.footfallDirectory, id, { doc: record } );
395
- console.log( insertResult );
396
393
 
397
394
  if ( insertResult && ( insertResult.statusCode === 201 || insertResult.statusCode === 200 ) ) {
395
+ if ( record.status = 'Closed' && inputData.ticketType !== 'internal' ) {
396
+ const query = {
397
+ storeId: inputData?.storeId,
398
+ isVideoStream: true,
399
+ };
400
+ const getStoreType = await countDocumnetsCamera( query );
401
+ const revopInfoQuery = {
402
+ size: 10000,
403
+ query: {
404
+ bool: {
405
+ must: [
406
+ {
407
+ term: {
408
+ 'storeId.keyword': inputData.storeId,
409
+ },
410
+ },
411
+ {
412
+ term: {
413
+ 'dateString': inputData.dateString,
414
+ },
415
+ },
416
+ {
417
+ term: {
418
+ 'isParent': false,
419
+ },
420
+ },
421
+ {
422
+ term: {
423
+ isChecked: true,
424
+ },
425
+ },
426
+ ],
427
+ },
428
+ },
429
+ _source: [ 'tempId' ],
430
+
431
+ };
432
+ const revopInfo = await getOpenSearchData( openSearch.revop, revopInfoQuery );
433
+ // Get all tempIds from revopInfo response
434
+ const tempIds = revopInfo?.body?.hits?.hits?.map( ( hit ) => hit?._source?.tempId ).filter( Boolean ) || [];
435
+ // Prepare management eyeZone query based on storeId and dateString
436
+ const managerEyeZoneQuery = {
437
+ size: 1,
438
+ query: {
439
+ bool: {
440
+ must: [
441
+ {
442
+ term: {
443
+ 'storeId.keyword': inputData.storeId,
444
+ },
445
+ },
446
+ {
447
+ term: {
448
+ 'storeDate': inputData.dateString,
449
+ },
450
+ },
451
+ ],
452
+ },
453
+ },
454
+ _source: [ 'originalToTrackerCustomerMapping' ],
455
+ };
456
+
457
+ // Query the managerEyeZone index for the matching document
458
+ const managerEyeZoneResp = await getOpenSearchData( openSearch.managerEyeZone, managerEyeZoneQuery );
459
+ const managerEyeZoneHit = managerEyeZoneResp?.body?.hits?.hits?.[0]?._source;
460
+ // Extract originalToTrackerCustomerMapping if it exists
461
+ const mapping =
462
+ managerEyeZoneHit && managerEyeZoneHit.originalToTrackerCustomerMapping ?
463
+ managerEyeZoneHit.originalToTrackerCustomerMapping :
464
+ {};
465
+
466
+ // Find tempIds that exist in both revopInfo results and manager mapping
467
+ const temp = [];
468
+ tempIds.filter( ( tid ) => mapping[tid] !== null ? temp.push( { tempId: mapping[tid] } ) :'' );
469
+ const isSendMessge = await sendSqsMessage( inputData, temp, getStoreType, inputData.storeId );
470
+ if ( isSendMessge == true ) {
471
+ logger.info( '....1' );
472
+ }
473
+ }
398
474
  return res.sendSuccess( 'Ticket closed successfully' );
399
475
  } else {
400
476
  return res.sendError( 'Internal Server Error', 500 );
401
477
  }
402
478
  } catch ( error ) {
403
- console.log( '🚀 ~ tangoReviewTicket ~ error:', error );
404
479
  const err = error.message || 'Internal Server Error';
405
480
  logger.error( { error: error, funtion: 'tangoReviewTicket' } );
406
481
  return res.sendError( err, 500 );
@@ -518,22 +593,6 @@ export async function tangoReviewAccuracyClosedTicket( req, res ) {
518
593
  } );
519
594
  }
520
595
 
521
-
522
- // If no review mapping existed, push a new one
523
- // if ( record.mappingInfo.length === 0 ) {
524
- // record.mappingInfo.push( {
525
- // type: 'tangoreview',
526
- // mode: inputData.mappingInfo?.mode,
527
- // revicedFootfall: temp?.mappingInfo?.revicedFootfall,
528
- // revicedPerc: temp?.mappingInfo?.revicedPerc,
529
- // count: temp?.mappingInfo?.count,
530
- // revisedDetail: temp?.mappingInfo?.revisedDetail,
531
- // status: 'Closed',
532
- // createdByEmail: req?.user?.email,
533
- // createdByUserName: req?.user?.userName,
534
- // createdByRole: req?.user?.role,
535
- // } );
536
- // }
537
596
  record.mappingInfo.push(
538
597
  {
539
598
  type: 'finalRevision',
@@ -559,6 +618,84 @@ export async function tangoReviewAccuracyClosedTicket( req, res ) {
559
618
  const insertResult = await updateOpenSearchData( openSearch.footfallDirectory, id, { doc: record } );
560
619
 
561
620
  if ( insertResult && ( insertResult.statusCode === 201 || insertResult.statusCode === 200 ) ) {
621
+ const query = {
622
+ storeId: inputData?.storeId,
623
+ isVideoStream: true,
624
+ };
625
+ const getStoreType = await countDocumnetsCamera( query );
626
+ const revopInfoQuery = {
627
+ size: 10000,
628
+ query: {
629
+ bool: {
630
+ must: [
631
+ {
632
+ term: {
633
+ 'storeId.keyword': inputData.storeId,
634
+ },
635
+ },
636
+ {
637
+ term: {
638
+ 'dateString': inputData.dateString,
639
+ },
640
+ },
641
+ {
642
+ term: {
643
+ 'isParent': false,
644
+ },
645
+ },
646
+ {
647
+ term: {
648
+ isChecked: true,
649
+ },
650
+ },
651
+ ],
652
+ },
653
+ },
654
+ _source: [ 'tempId' ],
655
+
656
+ };
657
+ const revopInfo = await getOpenSearchData( openSearch.revop, revopInfoQuery );
658
+ // Get all tempIds from revopInfo response
659
+ const tempIds = revopInfo?.body?.hits?.hits?.map( ( hit ) => hit?._source?.tempId ).filter( Boolean ) || [];
660
+ // Prepare management eyeZone query based on storeId and dateString
661
+ const managerEyeZoneQuery = {
662
+ size: 1,
663
+ query: {
664
+ bool: {
665
+ must: [
666
+ {
667
+ term: {
668
+ 'storeId.keyword': inputData.storeId,
669
+ },
670
+ },
671
+ {
672
+ term: {
673
+ 'storeDate': inputData.dateString,
674
+ },
675
+ },
676
+ ],
677
+ },
678
+ },
679
+ _source: [ 'originalToTrackerCustomerMapping' ],
680
+ };
681
+
682
+ // Query the managerEyeZone index for the matching document
683
+ const managerEyeZoneResp = await getOpenSearchData( openSearch.managerEyeZone, managerEyeZoneQuery );
684
+ const managerEyeZoneHit = managerEyeZoneResp?.body?.hits?.hits?.[0]?._source;
685
+ // Extract originalToTrackerCustomerMapping if it exists
686
+ const mapping =
687
+ managerEyeZoneHit && managerEyeZoneHit.originalToTrackerCustomerMapping ?
688
+ managerEyeZoneHit.originalToTrackerCustomerMapping :
689
+ {};
690
+
691
+ // Find tempIds that exist in both revopInfo results and manager mapping
692
+ const temp = [];
693
+ tempIds.filter( ( tid ) => mapping[tid] !== null ? temp.push( { tempId: mapping[tid] } ) :'' );
694
+ const isSendMessge = await sendSqsMessage( inputData, temp, getStoreType, inputData.storeId );
695
+ if ( isSendMessge == true ) {
696
+ logger.info( '....1' );
697
+ }
698
+
562
699
  return res.sendSuccess( 'Ticket closed successfully' );
563
700
  } else {
564
701
  return res.sendError( 'Internal Server Error', 500 );
@@ -628,145 +765,1210 @@ async function bulkUpdateStatusToPending( indexName, inputData ) {
628
765
  }
629
766
  }
630
767
 
631
- // Final bulk call
632
- if ( bulkBody.length > 0 ) {
633
- const res = await bulkUpdate( bulkBody );
634
- if ( res?.errors ) {
635
- logger.error( 'Bulk update errors:', res.items );
636
- return { success: false, errors: res.items };
637
- } else {
638
- logger.error( 'Bulk status update successful.' );
639
- return { success: true };
640
- }
641
- } else {
642
- logger.error( 'No data to update.' );
643
- return { success: false, message: 'No updates needed' };
644
- }
645
- }
768
+ // Final bulk call
769
+ if ( bulkBody.length > 0 ) {
770
+ const res = await bulkUpdate( bulkBody );
771
+ if ( res?.errors ) {
772
+ logger.error( 'Bulk update errors:', res.items );
773
+ return { success: false, errors: res.items };
774
+ } else {
775
+ logger.error( 'Bulk status update successful.' );
776
+ return { success: true };
777
+ }
778
+ } else {
779
+ logger.error( 'No data to update.' );
780
+ return { success: false, message: 'No updates needed' };
781
+ }
782
+ }
783
+
784
+ export async function ticketSummary1( req, res ) {
785
+ try {
786
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
787
+ const inputData = req.query;
788
+ inputData.clientId = inputData.clientId.split( ',' ); // convert strig to array
789
+ const getQuery = {
790
+ size: 0,
791
+ query: {
792
+ bool: {
793
+ filter: [
794
+ { terms: { 'clientId.keyword': inputData.clientId } },
795
+ {
796
+ range: {
797
+ dateString: {
798
+ gte: inputData.fromDate,
799
+ lte: inputData.toDate,
800
+ },
801
+ },
802
+ },
803
+ ],
804
+ },
805
+ },
806
+ aggs: {
807
+ totalTicketCount: {
808
+ value_count: { field: '_id' },
809
+ },
810
+ openStatusCount: {
811
+ filter: {
812
+ term: { 'status.keyword': 'open' },
813
+ },
814
+ },
815
+ closeTicketCount: {
816
+ filter: {
817
+ term: { 'status.keyword': 'closed' },
818
+ },
819
+ },
820
+ duplicateCount: {
821
+ sum: {
822
+ field: 'duplicateCount',
823
+ },
824
+ },
825
+ employeeCount: {
826
+ sum: {
827
+ field: 'employeeCount',
828
+ },
829
+ },
830
+ houseKeepingCount: {
831
+ sum: {
832
+ field: 'houseKeepingCount',
833
+ },
834
+ },
835
+ junkCount: {
836
+ sum: {
837
+ field: 'junkCount',
838
+ },
839
+ },
840
+ },
841
+ };
842
+
843
+ const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
844
+ const aggs = getData?.body?.aggregations;
845
+
846
+ const result = {
847
+ totalTickets: aggs.totalTicketCount.value,
848
+ openTickets: aggs.openStatusCount.doc_count,
849
+ closedTickets: aggs.closeTicketCount.doc_count,
850
+ duplicateCount: aggs.duplicateCount.value,
851
+ employeeCount: aggs.employeeCount.value,
852
+ houseKeepingCount: aggs.houseKeepingCount.value,
853
+ junkCount: aggs.junkCount.value,
854
+ noShopper: aggs.duplicateCount.value + aggs.employeeCount.value + aggs.houseKeepingCount.value + aggs.junkCount.value,
855
+ };
856
+
857
+ return res.sendSuccess( { result: result } );
858
+ } catch ( error ) {
859
+ const err = error.message || 'Internal Server Error';
860
+ logger.error( { error: error, messgage: req.query } );
861
+ return res.sendSuccess( err, 500 );
862
+ }
863
+ }
864
+
865
+ export async function ticketSummary( req, res ) {
866
+ try {
867
+ const inputData = req.query;
868
+
869
+ let result = '';
870
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
871
+ const userInfo = req.user;
872
+ const ticketsFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'reviewer' && ( m.isAdd == true || m.isEdit == true ) ) ) );
873
+ const ticketsApproveFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'approver' && ( m.isAdd == true || m.isEdit == true ) ) ) );
874
+
875
+ // const ticketsApproveFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'approver' && ( m.isAdd == true || m.isEdit == true ) ) ) );
876
+ if ( req?.user?.userType === 'tango' ) {
877
+ switch ( inputData?.tangoType ) {
878
+ case 'store':
879
+ const storeQuery = {
880
+ size: 0,
881
+ query: {
882
+ bool: {
883
+ must: [
884
+ {
885
+ 'range': {
886
+ 'dateString': {
887
+ 'gte': inputData?.fromDate,
888
+ 'lte': inputData?.toDate,
889
+ 'format': 'yyyy-MM-dd',
890
+ },
891
+ },
892
+ },
893
+ {
894
+ nested: {
895
+ path: 'mappingInfo',
896
+ query: {
897
+ bool: {
898
+ must: [
899
+ {
900
+ term: {
901
+ 'mappingInfo.type': 'tangoreview',
902
+ },
903
+ },
904
+ ],
905
+ },
906
+ },
907
+ },
908
+ },
909
+
910
+ ],
911
+ },
912
+ },
913
+ };
914
+
915
+ // Helper function to clone deep and replace mappingInfo.status for openTickets/closed/etc
916
+ function buildStoreQueryWithStatus( baseQuery, statusValue ) {
917
+ let q = JSON.parse( JSON.stringify( baseQuery ) );
918
+ // Remove any previous mappingInfo.status term
919
+ let nested = q.query.bool.must.find( ( m ) => m.nested );
920
+ if ( nested ) {
921
+ // filter out all mappingInfo.status
922
+ nested.nested.query.bool.must = nested?.nested?.query?.bool?.must.filter( ( mustItem ) => {
923
+ return !( mustItem.term && mustItem.term['mappingInfo.status'] );
924
+ } );
925
+ // add desired status
926
+ nested.nested.query.bool.must.push( {
927
+ term: {
928
+ 'mappingInfo.status': statusValue,
929
+ },
930
+ } );
931
+ }
932
+ return q;
933
+ }
934
+
935
+ const buildAggStoreQuery = ( baseQuery, filters = [] ) => {
936
+ const q = JSON.parse( JSON.stringify( baseQuery ) );
937
+
938
+ // locate nested section
939
+ const nested = q.query.bool.must.find( ( m ) => m.nested );
940
+
941
+ if ( nested ) {
942
+ // remove old status filters
943
+ nested.nested.query.bool.must =
944
+ nested.nested.query.bool.must.filter( ( m ) => !( m.term && m.term['mappingInfo.status'] ) );
945
+
946
+ // add new filters
947
+ nested.nested.query.bool.must.push( ...filters );
948
+ }
949
+
950
+ return {
951
+ ...q,
952
+ size: 0,
953
+ aggs: {
954
+ avg_value: {
955
+ avg: {
956
+ script: {
957
+ lang: 'painless',
958
+ source: `
959
+ if (doc['revicedPerc.keyword'].size() == 0) return null;
960
+ String v = doc['revicedPerc.keyword'].value.replace('%','');
961
+ try {
962
+ return Double.parseDouble(v);
963
+ } catch (Exception e) {
964
+ return null;
965
+ }
966
+ `,
967
+ },
968
+ },
969
+ },
970
+ },
971
+ };
972
+ };
973
+
974
+
975
+ // Get OpenSearch connection
976
+
977
+ const baseStoreQuery = JSON.parse( JSON.stringify( storeQuery ) );
978
+
979
+ // Total Tickets (all tickets with mappingInfo.type == tangoreview)
980
+ let totalTickets = 0;
981
+
982
+ let allQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
983
+
984
+ allQuery.size = 0;
985
+ const totalResp = await getOpenSearchData( openSearch.footfallDirectory, allQuery );
986
+ totalTickets = totalResp?.body?.hits?.total?.value || 0;
987
+
988
+ // openTickets: mappingInfo.status: 'Open'
989
+ let openTickets = 0;
990
+
991
+ let otQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open' );
992
+ otQ.size = 0;
993
+ const openResp = await getOpenSearchData( openSearch.footfallDirectory, otQ );
994
+ openTickets = openResp?.body?.hits?.total?.value || 0;
995
+ // logger.info( { msd: '..............2', openResp } );
996
+
997
+
998
+ // openInfraIssues: mappingInfo.status: 'Open Accuracy Issue'
999
+ let openInfraIssues = 0;
1000
+
1001
+ let oiQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open Accuracy Issue' );
1002
+ oiQ.size = 0;
1003
+ const infraResp = await getOpenSearchData( openSearch.footfallDirectory, oiQ );
1004
+ openInfraIssues = infraResp?.body?.hits?.total?.value || 0;
1005
+
1006
+ // inprogress: mappingInfo.status: 'in-Progress'
1007
+ let inprogress = 0;
1008
+
1009
+ let ipQ = buildStoreQueryWithStatus( baseStoreQuery, 'In-Progress' );
1010
+ ipQ.size = 0;
1011
+ const ipResp = await getOpenSearchData( openSearch.footfallDirectory, ipQ );
1012
+ inprogress = ipResp?.body?.hits?.total?.value || 0;
1013
+
1014
+ // closedTickets: mappingInfo.status: 'closed'
1015
+ let closedTickets = 0;
1016
+
1017
+ let clQ = buildStoreQueryWithStatus( baseStoreQuery, 'Closed' );
1018
+ clQ.size = 0;
1019
+ const clResp = await getOpenSearchData( openSearch.footfallDirectory, clQ );
1020
+ closedTickets = clResp?.body?.hits?.total?.value || 0;
1021
+ // Average revisedPerc (for all tangoreview)
1022
+ let averageAccuracyOverAll = 0;
1023
+
1024
+ let avgQ = buildAggStoreQuery( baseStoreQuery );
1025
+ const avgResp = await getOpenSearchData( openSearch.footfallDirectory, avgQ );
1026
+ averageAccuracyOverAll = avgResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1027
+
1028
+ // ticketAccuracyAbove: avg of revicedPerc > 85%
1029
+ let ticketAccuracyAbove = 0;
1030
+
1031
+
1032
+ // For this, add a filter on revicedPerc >= 85
1033
+ let aboveQ = buildAggStoreQuery( baseStoreQuery, [
1034
+ {
1035
+ script: {
1036
+ script: {
1037
+ lang: 'painless',
1038
+ source: `
1039
+ doc['revicedPerc.keyword'].size()!=0 &&
1040
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) >= params.num
1041
+ `,
1042
+ params: { num: 85 },
1043
+ },
1044
+ },
1045
+ },
1046
+
1047
+
1048
+ ] );
1049
+ const aboveResp = await getOpenSearchData( openSearch.footfallDirectory, aboveQ );
1050
+ ticketAccuracyAbove = aboveResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1051
+
1052
+ // ticketAccuracyBelow: avg of revicedPerc < 85%
1053
+ let ticketAccuracyBelow = 0;
1054
+
1055
+ let belowQ = buildAggStoreQuery( baseStoreQuery, [
1056
+ {
1057
+ script: {
1058
+ script: {
1059
+ lang: 'painless',
1060
+ source: `
1061
+ doc['revicedPerc.keyword'].size()!=0 &&
1062
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) < params.num
1063
+ `,
1064
+ params: { num: 85 },
1065
+ },
1066
+ },
1067
+ },
1068
+
1069
+ ] );
1070
+ const belowResp = await getOpenSearchData( openSearch.footfallDirectory, belowQ );
1071
+ ticketAccuracyBelow = belowResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1072
+
1073
+ // Final result object
1074
+ result = {
1075
+ totalTickets,
1076
+ averageAccuracyOverAll: averageAccuracyOverAll+'%',
1077
+ openTickets,
1078
+ openInfraIssues,
1079
+ inprogress,
1080
+ closedTickets,
1081
+ ticketAccuracyAbove: ticketAccuracyAbove+'%',
1082
+ ticketAccuracyBelow: ticketAccuracyBelow+'%',
1083
+ };
1084
+ break;
1085
+ case 'internal':
1086
+ const internalQuery = {
1087
+ size: 0,
1088
+ query: {
1089
+ bool: {
1090
+ must: [
1091
+ {
1092
+ 'range': {
1093
+ 'dateString': {
1094
+ 'gte': inputData?.fromDate,
1095
+ 'lte': inputData?.toDate,
1096
+ 'format': 'yyyy-MM-dd',
1097
+ },
1098
+ },
1099
+ },
1100
+
1101
+ {
1102
+ nested: {
1103
+ path: 'mappingInfo',
1104
+ query: {
1105
+ bool: {
1106
+ must: [
1107
+ {
1108
+ terms: {
1109
+ 'mappingInfo.type': [ 'tagging', 'review', 'approve', 'tangoreview' ],
1110
+ },
1111
+ },
1112
+ ],
1113
+ },
1114
+ },
1115
+ },
1116
+ },
1117
+
1118
+ ],
1119
+ },
1120
+ },
1121
+ };
1122
+
1123
+ // Helper function to clone deep and replace mappingInfo.status for openTickets/closed/etc
1124
+ function buildInternalQueryWithStatus( baseQuery, statusValue ) {
1125
+ let q = JSON.parse( JSON.stringify( baseQuery ) );
1126
+ // Remove any previous mappingInfo.status term
1127
+ let nested = q.query.bool.must.find( ( m ) => m.nested );
1128
+ if ( nested ) {
1129
+ // filter out all mappingInfo.status
1130
+ nested.nested.query.bool.must = nested?.nested?.query?.bool?.must.filter( ( mustItem ) => {
1131
+ return !( mustItem.term && mustItem.term['mappingInfo.status'] );
1132
+ } );
1133
+ // add desired status
1134
+ nested.nested.query.bool.must.push( {
1135
+ term: {
1136
+ 'mappingInfo.status': statusValue,
1137
+ },
1138
+ } );
1139
+ }
1140
+ return q;
1141
+ }
1142
+
1143
+ const buildAggInternalQuery = ( baseQuery, filters = [] ) => {
1144
+ const q = JSON.parse( JSON.stringify( baseQuery ) );
1145
+
1146
+ // locate nested section
1147
+ const nested = q.query.bool.must.find( ( m ) => m.nested );
1148
+
1149
+ if ( nested ) {
1150
+ // remove old status filters
1151
+ nested.nested.query.bool.must =
1152
+ nested.nested.query.bool.must.filter( ( m ) => !( m.term && m.term['mappingInfo.status'] ) );
1153
+
1154
+ // add new filters
1155
+ nested.nested.query.bool.must.push( ...filters );
1156
+ }
1157
+
1158
+ return {
1159
+ ...q,
1160
+ size: 0,
1161
+ aggs: {
1162
+ avg_value: {
1163
+ avg: {
1164
+ script: {
1165
+ lang: 'painless',
1166
+ source: `
1167
+ if (doc['revicedPerc.keyword'].size() == 0) return null;
1168
+ String v = doc['revicedPerc.keyword'].value.replace('%','');
1169
+ try {
1170
+ return Double.parseDouble(v);
1171
+ } catch (Exception e) {
1172
+ return null;
1173
+ }
1174
+ `,
1175
+ },
1176
+ },
1177
+ },
1178
+ },
1179
+ };
1180
+ };
1181
+
1182
+
1183
+ // Get OpenSearch connection
1184
+
1185
+
1186
+ const baseInternalQuery = JSON.parse( JSON.stringify( internalQuery ) );
1187
+
1188
+ // Total Tickets (all tickets with mappingInfo.type == tangoreview)
1189
+ let totalInternalTickets = 0;
1190
+
1191
+ let allInternalQuery = JSON.parse( JSON.stringify( baseInternalQuery ) );
1192
+
1193
+ allInternalQuery.size = 0;
1194
+ const totalInternalResp = await getOpenSearchData( openSearch.footfallDirectory, allInternalQuery );
1195
+ totalInternalTickets = totalInternalResp?.body?.hits?.total?.value || 0;
1196
+
1197
+ // openTickets: mappingInfo.status: 'Open'
1198
+ let openInternalTickets = 0;
1199
+
1200
+ let otQInternal = buildInternalQueryWithStatus( baseInternalQuery, 'Open' );
1201
+ otQInternal.size = 0;
1202
+ const openInternalResp = await getOpenSearchData( openSearch.footfallDirectory, otQInternal );
1203
+ openInternalTickets = openInternalResp?.body?.hits?.total?.value || 0;
1204
+ // logger.info( { msd: '..............2', openResp } );
1205
+
1206
+
1207
+ // openInfraIssues: mappingInfo.status: 'Open Accuracy Issue'
1208
+ let openInternalInfraIssues = 0;
1209
+
1210
+ let oiQinternal = buildInternalQueryWithStatus( baseInternalQuery, 'Open Accuracy Issue' );
1211
+ oiQinternal.size = 0;
1212
+ const infraInternalResp = await getOpenSearchData( openSearch.footfallDirectory, oiQinternal );
1213
+ openInternalInfraIssues = infraInternalResp?.body?.hits?.total?.value || 0;
1214
+
1215
+ // inprogress: mappingInfo.status: 'in-Progress'
1216
+ let inprogressIntrenal = 0;
1217
+
1218
+ let ipQInternal = buildInternalQueryWithStatus( baseInternalQuery, 'In-Progress' );
1219
+ ipQInternal.size = 0;
1220
+ const ipInternalResp = await getOpenSearchData( openSearch.footfallDirectory, ipQInternal );
1221
+ inprogressIntrenal = ipInternalResp?.body?.hits?.total?.value || 0;
1222
+
1223
+ // closedTickets: mappingInfo.status: 'closed'
1224
+ let closedInternalTickets = 0;
1225
+
1226
+ let clQInternal = buildInternalQueryWithStatus( baseInternalQuery, 'Closed' );
1227
+ clQInternal.size = 0;
1228
+ const clInternalResp = await getOpenSearchData( openSearch.footfallDirectory, clQInternal );
1229
+ closedInternalTickets = clInternalResp?.body?.hits?.total?.value || 0;
1230
+ // Average revisedPerc (for all tangoreview)
1231
+ let internalAverageAccuracyOverAll = 0;
1232
+
1233
+ let avgQInternal = buildAggInternalQuery( baseInternalQuery );
1234
+ const avgInternalResp = await getOpenSearchData( openSearch.footfallDirectory, avgQInternal );
1235
+ internalAverageAccuracyOverAll = avgInternalResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1236
+
1237
+ // ticketAccuracyAbove: avg of revicedPerc > 85%
1238
+ let internalTicketAccuracyAbove = 0;
1239
+
1240
+
1241
+ // For this, add a filter on revicedPerc >= 85
1242
+ let aboveQinternal = buildAggInternalQuery( baseInternalQuery, [
1243
+ {
1244
+ script: {
1245
+ script: {
1246
+ lang: 'painless',
1247
+ source: `
1248
+ doc['revicedPerc.keyword'].size()!=0 &&
1249
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) >= params.num
1250
+ `,
1251
+ params: { num: 85 },
1252
+ },
1253
+ },
1254
+ },
1255
+
1256
+
1257
+ ] );
1258
+ const aboveRespInternal = await getOpenSearchData( openSearch.footfallDirectory, aboveQinternal );
1259
+ internalTicketAccuracyAbove = aboveRespInternal?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1260
+
1261
+ // ticketAccuracyBelow: avg of revicedPerc < 85%
1262
+ let internalTicketAccuracyBelow = 0;
1263
+
1264
+ let belowQIneranl = buildAggInternalQuery( baseInternalQuery, [
1265
+ {
1266
+ script: {
1267
+ script: {
1268
+ lang: 'painless',
1269
+ source: `
1270
+ doc['revicedPerc.keyword'].size()!=0 &&
1271
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) < params.num
1272
+ `,
1273
+ params: { num: 85 },
1274
+ },
1275
+ },
1276
+ },
1277
+
1278
+ ] );
1279
+ const belowRespInternal = await getOpenSearchData( openSearch.footfallDirectory, belowQIneranl );
1280
+ internalTicketAccuracyBelow = belowRespInternal?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1281
+
1282
+ // Final result object
1283
+ result = {
1284
+ totalTickets: totalInternalTickets,
1285
+ averageAccuracyOverAll: internalAverageAccuracyOverAll+'%',
1286
+ openTickets: openInternalTickets,
1287
+ openInfraIssues: openInternalInfraIssues,
1288
+ inprogress: inprogressIntrenal,
1289
+ closedTickets: closedInternalTickets,
1290
+ ticketAccuracyAbove: internalTicketAccuracyAbove+'%',
1291
+ ticketAccuracyBelow: internalTicketAccuracyBelow+'%',
1292
+ };
1293
+ break;
1294
+ default: '';
1295
+ }
1296
+ } else if ( req?.user?.userType === 'client' ) {
1297
+ if ( ticketsFeature && !ticketsApproveFeature ) {
1298
+ const storeQuery = {
1299
+ size: 0,
1300
+ query: {
1301
+ bool: {
1302
+ must: [
1303
+ {
1304
+ 'range': {
1305
+ 'dateString': {
1306
+ 'gte': inputData?.fromDate,
1307
+ 'lte': inputData?.toDate,
1308
+ 'format': 'yyyy-MM-dd',
1309
+ },
1310
+ },
1311
+ },
1312
+ {
1313
+ nested: {
1314
+ path: 'mappingInfo',
1315
+ query: {
1316
+ bool: {
1317
+ must: [
1318
+ {
1319
+ term: {
1320
+ 'mappingInfo.type': 'review',
1321
+ },
1322
+ },
1323
+ ],
1324
+ },
1325
+ },
1326
+ },
1327
+ },
1328
+
1329
+ ],
1330
+ },
1331
+ },
1332
+ };
1333
+
1334
+ // Helper function to clone deep and replace mappingInfo.status for openTickets/closed/etc
1335
+ function buildStoreQueryWithStatus( baseQuery, statusValue ) {
1336
+ let q = JSON.parse( JSON.stringify( baseQuery ) );
1337
+ // Remove any previous mappingInfo.status term
1338
+ let nested = q.query.bool.must.find( ( m ) => m.nested );
1339
+ if ( nested ) {
1340
+ // filter out all mappingInfo.status
1341
+ nested.nested.query.bool.must = nested?.nested?.query?.bool?.must.filter( ( mustItem ) => {
1342
+ return !( mustItem.term && mustItem.term['mappingInfo.status'] );
1343
+ } );
1344
+ // add desired status
1345
+ nested.nested.query.bool.must.push( {
1346
+ term: {
1347
+ 'mappingInfo.status': statusValue,
1348
+ },
1349
+ } );
1350
+ }
1351
+ return q;
1352
+ }
1353
+
1354
+ const buildAggStoreQuery = ( baseQuery, filters = [] ) => {
1355
+ const q = JSON.parse( JSON.stringify( baseQuery ) );
1356
+
1357
+ // locate nested section
1358
+ const nested = q.query.bool.must.find( ( m ) => m.nested );
1359
+
1360
+ if ( nested ) {
1361
+ // remove old status filters
1362
+ nested.nested.query.bool.must =
1363
+ nested.nested.query.bool.must.filter( ( m ) => !( m.term && m.term['mappingInfo.status'] ) );
1364
+
1365
+ // add new filters
1366
+ nested.nested.query.bool.must.push( ...filters );
1367
+ }
1368
+
1369
+ return {
1370
+ ...q,
1371
+ size: 0,
1372
+ aggs: {
1373
+ avg_value: {
1374
+ avg: {
1375
+ script: {
1376
+ lang: 'painless',
1377
+ source: `
1378
+ if (doc['revicedPerc.keyword'].size() == 0) return null;
1379
+ String v = doc['revicedPerc.keyword'].value.replace('%','');
1380
+ try {
1381
+ return Double.parseDouble(v);
1382
+ } catch (Exception e) {
1383
+ return null;
1384
+ }
1385
+ `,
1386
+ },
1387
+ },
1388
+ },
1389
+ },
1390
+ };
1391
+ };
1392
+
1393
+
1394
+ // Get OpenSearch connection
1395
+
1396
+ const baseStoreQuery = JSON.parse( JSON.stringify( storeQuery ) );
1397
+
1398
+ // Total Tickets (all tickets with mappingInfo.type == tangoreview)
1399
+ let totalTickets = 0;
1400
+
1401
+ let allQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1402
+
1403
+ allQuery.size = 0;
1404
+ const totalResp = await getOpenSearchData( openSearch.footfallDirectory, allQuery );
1405
+ totalTickets = totalResp?.body?.hits?.total?.value || 0;
1406
+
1407
+ // openTickets: mappingInfo.status: 'Open'
1408
+ let openTickets = 0;
1409
+
1410
+ let otQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open' );
1411
+ otQ.size = 0;
1412
+ const openResp = await getOpenSearchData( openSearch.footfallDirectory, otQ );
1413
+ openTickets = openResp?.body?.hits?.total?.value || 0;
1414
+ // logger.info( { msd: '..............2', openResp } );
1415
+
1416
+
1417
+ // inprogress: mappingInfo.status: 'in-Progress'
1418
+ let inprogress = 0;
1419
+
1420
+ let ipQ = buildStoreQueryWithStatus( baseStoreQuery, 'In-Progress' );
1421
+ ipQ.size = 0;
1422
+ const ipResp = await getOpenSearchData( openSearch.footfallDirectory, ipQ );
1423
+ inprogress = ipResp?.body?.hits?.total?.value || 0;
1424
+
1425
+ // closedTickets: mappingInfo.status: 'closed'
1426
+ let closedTickets = 0;
1427
+
1428
+ let clQ = buildStoreQueryWithStatus( baseStoreQuery, 'Closed' );
1429
+ clQ.size = 0;
1430
+ const clResp = await getOpenSearchData( openSearch.footfallDirectory, clQ );
1431
+ closedTickets = clResp?.body?.hits?.total?.value || 0;
1432
+
1433
+ let dueToday = 0;
1434
+ let todayDateString = new Date().toISOString().slice( 0, 10 );
1435
+
1436
+ // Build a query for tickets with mappingInfo.dueDate == todayDateString
1437
+ let dueTodayQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1438
+ // Locate nested mappingInfo query
1439
+ let nestedDue = dueTodayQuery.query.bool.must.find( ( m ) => m.nested );
1440
+ if ( nestedDue ) {
1441
+ // Remove any previous mappingInfo.dueDate term
1442
+ nestedDue.nested.query.bool.must = nestedDue.nested.query.bool.must.filter(
1443
+ ( mustItem ) => !( mustItem.term && mustItem.term['mappingInfo.dueDate'] ),
1444
+ );
1445
+ // Add new dueDate filter
1446
+ nestedDue.nested.query.bool.must.push( {
1447
+ term: { 'mappingInfo.dueDate': todayDateString },
1448
+ } );
1449
+ }
1450
+ dueTodayQuery.size = 0;
1451
+ const dueTodayResp = await getOpenSearchData( openSearch.footfallDirectory, dueTodayQuery );
1452
+ dueToday = dueTodayResp?.body?.hits?.total?.value || 0;
1453
+
1454
+ // filter expired Tickets
1455
+ let expiredTickets = 0;
1456
+
1457
+ let eQ = buildStoreQueryWithStatus( baseStoreQuery, 'Under Tango Review' );
1458
+ eQ.size = 0;
1459
+ const eResp = await getOpenSearchData( openSearch.footfallDirectory, eQ );
1460
+ expiredTickets = eResp?.body?.hits?.total?.value || 0;
1461
+
1462
+ let ticketAccuracyAbove = 0;
1463
+ // For this, add a filter on revicedPerc >= 85
1464
+ let aboveQ = buildAggStoreQuery( baseStoreQuery, [
1465
+ {
1466
+ script: {
1467
+ script: {
1468
+ lang: 'painless',
1469
+ source: `
1470
+ doc['revicedPerc.keyword'].size()!=0 &&
1471
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) >= params.num
1472
+ `,
1473
+ params: { num: 85 },
1474
+ },
1475
+ },
1476
+ },
1477
+
1478
+
1479
+ ] );
1480
+ const aboveResp = await getOpenSearchData( openSearch.footfallDirectory, aboveQ );
1481
+ ticketAccuracyAbove = aboveResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1482
+
1483
+ // ticketAccuracyBelow: avg of revicedPerc < 85%
1484
+ let ticketAccuracyBelow = 0;
1485
+
1486
+ let belowQ = buildAggStoreQuery( baseStoreQuery, [
1487
+ {
1488
+ script: {
1489
+ script: {
1490
+ lang: 'painless',
1491
+ source: `
1492
+ doc['revicedPerc.keyword'].size()!=0 &&
1493
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) < params.num
1494
+ `,
1495
+ params: { num: 85 },
1496
+ },
1497
+ },
1498
+ },
1499
+
1500
+ ] );
1501
+ const belowResp = await getOpenSearchData( openSearch.footfallDirectory, belowQ );
1502
+ ticketAccuracyBelow = belowResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1503
+
1504
+ // Final result object
1505
+ result = {
1506
+ totalTickets,
1507
+ openTickets,
1508
+ inprogress,
1509
+ closedTickets,
1510
+ dueToday: dueToday,
1511
+ Expired: expiredTickets,
1512
+ avgTicket: ticketAccuracyAbove+'%',
1513
+ avgAccuracy: ticketAccuracyBelow+'%',
1514
+ };
1515
+ }
1516
+ } else if ( ticketsFeature && ticketsApproveFeature ) {
1517
+ if ( inputData?.permissionType === 'review' ) {
1518
+ const storeQuery = {
1519
+ size: 0,
1520
+ query: {
1521
+ bool: {
1522
+ must: [
1523
+ {
1524
+ 'range': {
1525
+ 'dateString': {
1526
+ 'gte': inputData?.fromDate,
1527
+ 'lte': inputData?.toDate,
1528
+ 'format': 'yyyy-MM-dd',
1529
+ },
1530
+ },
1531
+ },
1532
+ {
1533
+ nested: {
1534
+ path: 'mappingInfo',
1535
+ query: {
1536
+ bool: {
1537
+ must: [
1538
+ {
1539
+ term: {
1540
+ 'mappingInfo.type': inputData.permissionType === 'review'? 'review':'approve',
1541
+ },
1542
+ },
1543
+ ],
1544
+ },
1545
+ },
1546
+ },
1547
+ },
1548
+
1549
+ ],
1550
+ },
1551
+ },
1552
+ };
1553
+
1554
+ // Helper function to clone deep and replace mappingInfo.status for openTickets/closed/etc
1555
+ function buildStoreQueryWithStatus( baseQuery, statusValue ) {
1556
+ let q = JSON.parse( JSON.stringify( baseQuery ) );
1557
+ // Remove any previous mappingInfo.status term
1558
+ let nested = q.query.bool.must.find( ( m ) => m.nested );
1559
+ if ( nested ) {
1560
+ // filter out all mappingInfo.status
1561
+ nested.nested.query.bool.must = nested?.nested?.query?.bool?.must.filter( ( mustItem ) => {
1562
+ return !( mustItem.term && mustItem.term['mappingInfo.status'] );
1563
+ } );
1564
+ // add desired status
1565
+ nested.nested.query.bool.must.push( {
1566
+ term: {
1567
+ 'mappingInfo.status': statusValue,
1568
+ },
1569
+ } );
1570
+ }
1571
+ return q;
1572
+ }
1573
+
1574
+ const buildAggStoreQuery = ( baseQuery, filters = [] ) => {
1575
+ const q = JSON.parse( JSON.stringify( baseQuery ) );
1576
+
1577
+ // locate nested section
1578
+ const nested = q.query.bool.must.find( ( m ) => m.nested );
1579
+
1580
+ if ( nested ) {
1581
+ // remove old status filters
1582
+ nested.nested.query.bool.must =
1583
+ nested.nested.query.bool.must.filter( ( m ) => !( m.term && m.term['mappingInfo.status'] ) );
1584
+
1585
+ // add new filters
1586
+ nested.nested.query.bool.must.push( ...filters );
1587
+ }
1588
+
1589
+ return {
1590
+ ...q,
1591
+ size: 0,
1592
+ aggs: {
1593
+ avg_value: {
1594
+ avg: {
1595
+ script: {
1596
+ lang: 'painless',
1597
+ source: `
1598
+ if (doc['revicedPerc.keyword'].size() == 0) return null;
1599
+ String v = doc['revicedPerc.keyword'].value.replace('%','');
1600
+ try {
1601
+ return Double.parseDouble(v);
1602
+ } catch (Exception e) {
1603
+ return null;
1604
+ }
1605
+ `,
1606
+ },
1607
+ },
1608
+ },
1609
+ },
1610
+ };
1611
+ };
1612
+
1613
+
1614
+ // Get OpenSearch connection
1615
+
1616
+ const baseStoreQuery = JSON.parse( JSON.stringify( storeQuery ) );
1617
+
1618
+ // Total Tickets (all tickets with mappingInfo.type == tangoreview)
1619
+ let totalTickets = 0;
1620
+
1621
+ let allQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1622
+
1623
+ allQuery.size = 0;
1624
+ const totalResp = await getOpenSearchData( openSearch.footfallDirectory, allQuery );
1625
+ totalTickets = totalResp?.body?.hits?.total?.value || 0;
1626
+
1627
+ // openTickets: mappingInfo.status: 'Open'
1628
+ let openTickets = 0;
1629
+
1630
+ let otQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open' );
1631
+ otQ.size = 0;
1632
+ const openResp = await getOpenSearchData( openSearch.footfallDirectory, otQ );
1633
+ openTickets = openResp?.body?.hits?.total?.value || 0;
1634
+ // logger.info( { msd: '..............2', openResp } );
1635
+
1636
+
1637
+ // inprogress: mappingInfo.status: 'in-Progress'
1638
+ let inprogress = 0;
1639
+
1640
+ let ipQ = buildStoreQueryWithStatus( baseStoreQuery, 'In-Progress' );
1641
+ ipQ.size = 0;
1642
+ const ipResp = await getOpenSearchData( openSearch.footfallDirectory, ipQ );
1643
+ inprogress = ipResp?.body?.hits?.total?.value || 0;
1644
+
1645
+ // closedTickets: mappingInfo.status: 'closed'
1646
+ let closedTickets = 0;
1647
+
1648
+ let clQ = buildStoreQueryWithStatus( baseStoreQuery, 'Closed' );
1649
+ clQ.size = 0;
1650
+ const clResp = await getOpenSearchData( openSearch.footfallDirectory, clQ );
1651
+ closedTickets = clResp?.body?.hits?.total?.value || 0;
1652
+ // Average revisedPerc (for all tangoreview)
1653
+
1654
+ // dueToday: Tickets whose dueDate is today (format 'yyyy-MM-dd')
1655
+ let dueToday = 0;
1656
+ let todayDateString = new Date().toISOString().slice( 0, 10 );
1657
+
1658
+ // Build a query for tickets with mappingInfo.dueDate == todayDateString
1659
+ let dueTodayQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1660
+ // Locate nested mappingInfo query
1661
+ let nestedDue = dueTodayQuery.query.bool.must.find( ( m ) => m.nested );
1662
+ if ( nestedDue ) {
1663
+ // Remove any previous mappingInfo.dueDate term
1664
+ nestedDue.nested.query.bool.must = nestedDue.nested.query.bool.must.filter(
1665
+ ( mustItem ) => !( mustItem.term && mustItem.term['mappingInfo.dueDate'] ),
1666
+ );
1667
+ // Add new dueDate filter
1668
+ nestedDue.nested.query.bool.must.push( {
1669
+ term: { 'mappingInfo.dueDate': todayDateString },
1670
+ } );
1671
+ }
1672
+ dueTodayQuery.size = 0;
1673
+ const dueTodayResp = await getOpenSearchData( openSearch.footfallDirectory, dueTodayQuery );
1674
+ dueToday = dueTodayResp?.body?.hits?.total?.value || 0;
1675
+
1676
+
1677
+ // filter expired Tickets
1678
+ let expiredTickets = 0;
1679
+
1680
+ let eQ = buildStoreQueryWithStatus( baseStoreQuery, 'Under Tango Review' );
1681
+ eQ.size = 0;
1682
+ const eResp = await getOpenSearchData( openSearch.footfallDirectory, eQ );
1683
+ expiredTickets = eResp?.body?.hits?.total?.value || 0;
1684
+
1685
+ // ticketAccuracyAbove: avg of revicedPerc > 85%
1686
+ let ticketAccuracyAbove = 0;
1687
+
1688
+
1689
+ // For this, add a filter on revicedPerc >= 85
1690
+ let aboveQ = buildAggStoreQuery( baseStoreQuery, [
1691
+ {
1692
+ script: {
1693
+ script: {
1694
+ lang: 'painless',
1695
+ source: `
1696
+ doc['revicedPerc.keyword'].size()!=0 &&
1697
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) >= params.num
1698
+ `,
1699
+ params: { num: 85 },
1700
+ },
1701
+ },
1702
+ },
1703
+
1704
+
1705
+ ] );
1706
+ const aboveResp = await getOpenSearchData( openSearch.footfallDirectory, aboveQ );
1707
+ ticketAccuracyAbove = aboveResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1708
+
1709
+ // ticketAccuracyBelow: avg of revicedPerc < 85%
1710
+ let ticketAccuracyBelow = 0;
1711
+
1712
+ let belowQ = buildAggStoreQuery( baseStoreQuery, [
1713
+ {
1714
+ script: {
1715
+ script: {
1716
+ lang: 'painless',
1717
+ source: `
1718
+ doc['revicedPerc.keyword'].size()!=0 &&
1719
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) < params.num
1720
+ `,
1721
+ params: { num: 85 },
1722
+ },
1723
+ },
1724
+ },
1725
+
1726
+ ] );
1727
+ const belowResp = await getOpenSearchData( openSearch.footfallDirectory, belowQ );
1728
+ ticketAccuracyBelow = belowResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1729
+
1730
+ // Final result object
1731
+ result = {
1732
+ totalTickets,
1733
+ openTickets,
1734
+ inprogress,
1735
+ closedTickets,
1736
+ dueToday: dueToday,
1737
+ Expired: expiredTickets,
1738
+ avgTicket: ticketAccuracyAbove+'%',
1739
+ avgAccuracy: ticketAccuracyBelow+'%',
1740
+ };
1741
+ } else {
1742
+ const storeQuery = {
1743
+ size: 0,
1744
+ query: {
1745
+ bool: {
1746
+ must: [
1747
+ {
1748
+ 'range': {
1749
+ 'dateString': {
1750
+ 'gte': inputData?.fromDate,
1751
+ 'lte': inputData?.toDate,
1752
+ 'format': 'yyyy-MM-dd',
1753
+ },
1754
+ },
1755
+ },
1756
+ {
1757
+ nested: {
1758
+ path: 'mappingInfo',
1759
+ query: {
1760
+ bool: {
1761
+ must: [
1762
+ {
1763
+ term: {
1764
+ 'mappingInfo.type': inputData.permissionType === 'review'? 'review':'approve',
1765
+ },
1766
+ },
1767
+ ],
1768
+ },
1769
+ },
1770
+ },
1771
+ },
1772
+
1773
+ ],
1774
+ },
1775
+ },
1776
+ };
1777
+
1778
+ // Helper function to clone deep and replace mappingInfo.status for openTickets/closed/etc
1779
+ function buildStoreQueryWithStatus( baseQuery, statusValue ) {
1780
+ let q = JSON.parse( JSON.stringify( baseQuery ) );
1781
+ // Remove any previous mappingInfo.status term
1782
+ let nested = q.query.bool.must.find( ( m ) => m.nested );
1783
+ if ( nested ) {
1784
+ // filter out all mappingInfo.status
1785
+ nested.nested.query.bool.must = nested?.nested?.query?.bool?.must.filter( ( mustItem ) => {
1786
+ return !( mustItem.term && mustItem.term['mappingInfo.status'] );
1787
+ } );
1788
+ // add desired status
1789
+ nested.nested.query.bool.must.push( {
1790
+ term: {
1791
+ 'mappingInfo.status': statusValue,
1792
+ },
1793
+ } );
1794
+ }
1795
+ return q;
1796
+ }
1797
+
1798
+ const buildAggStoreQuery = ( baseQuery, filters = [] ) => {
1799
+ const q = JSON.parse( JSON.stringify( baseQuery ) );
1800
+
1801
+ // locate nested section
1802
+ const nested = q.query.bool.must.find( ( m ) => m.nested );
1803
+
1804
+ if ( nested ) {
1805
+ // remove old status filters
1806
+ nested.nested.query.bool.must =
1807
+ nested.nested.query.bool.must.filter( ( m ) => !( m.term && m.term['mappingInfo.status'] ) );
1808
+
1809
+ // add new filters
1810
+ nested.nested.query.bool.must.push( ...filters );
1811
+ }
1812
+
1813
+ return {
1814
+ ...q,
1815
+ size: 0,
1816
+ aggs: {
1817
+ avg_value: {
1818
+ avg: {
1819
+ script: {
1820
+ lang: 'painless',
1821
+ source: `
1822
+ if (doc['revicedPerc.keyword'].size() == 0) return null;
1823
+ String v = doc['revicedPerc.keyword'].value.replace('%','');
1824
+ try {
1825
+ return Double.parseDouble(v);
1826
+ } catch (Exception e) {
1827
+ return null;
1828
+ }
1829
+ `,
1830
+ },
1831
+ },
1832
+ },
1833
+ },
1834
+ };
1835
+ };
1836
+
1837
+
1838
+ // Get OpenSearch connection
1839
+
1840
+ const baseStoreQuery = JSON.parse( JSON.stringify( storeQuery ) );
1841
+
1842
+ // Total Tickets (all tickets with mappingInfo.type == tangoreview)
1843
+ let totalTickets = 0;
1844
+
1845
+ let allQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1846
+
1847
+ allQuery.size = 0;
1848
+ const totalResp = await getOpenSearchData( openSearch.footfallDirectory, allQuery );
1849
+ totalTickets = totalResp?.body?.hits?.total?.value || 0;
1850
+
1851
+ // openTickets: mappingInfo.status: 'Open'
1852
+ let openTickets = 0;
1853
+
1854
+ let otQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open' );
1855
+ otQ.size = 0;
1856
+ const openResp = await getOpenSearchData( openSearch.footfallDirectory, otQ );
1857
+ openTickets = openResp?.body?.hits?.total?.value || 0;
1858
+ // logger.info( { msd: '..............2', openResp } );
1859
+
1860
+
1861
+ // inprogress: mappingInfo.status: 'in-Progress'
1862
+ let inprogress = 0;
1863
+
1864
+ let ipQ = buildStoreQueryWithStatus( baseStoreQuery, 'In-Progress' );
1865
+ ipQ.size = 0;
1866
+ const ipResp = await getOpenSearchData( openSearch.footfallDirectory, ipQ );
1867
+ inprogress = ipResp?.body?.hits?.total?.value || 0;
1868
+
1869
+ // closedTickets: mappingInfo.status: 'closed'
1870
+ let closedTickets = 0;
1871
+
1872
+ let clQ = buildStoreQueryWithStatus( baseStoreQuery, 'Closed' );
1873
+ clQ.size = 0;
1874
+ const clResp = await getOpenSearchData( openSearch.footfallDirectory, clQ );
1875
+ closedTickets = clResp?.body?.hits?.total?.value || 0;
1876
+ // dueToday: Tickets whose dueDate is today (format 'yyyy-MM-dd')
1877
+ let dueToday = 0;
1878
+ let todayDateString = new Date().toISOString().slice( 0, 10 );
1879
+
1880
+ // Build a query for tickets with mappingInfo.dueDate == todayDateString
1881
+ let dueTodayQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1882
+ // Locate nested mappingInfo query
1883
+ let nestedDue = dueTodayQuery.query.bool.must.find( ( m ) => m.nested );
1884
+ if ( nestedDue ) {
1885
+ // Remove any previous mappingInfo.dueDate term
1886
+ nestedDue.nested.query.bool.must = nestedDue.nested.query.bool.must.filter(
1887
+ ( mustItem ) => !( mustItem.term && mustItem.term['mappingInfo.dueDate'] ),
1888
+ );
1889
+ // Add new dueDate filter
1890
+ nestedDue.nested.query.bool.must.push( {
1891
+ term: { 'mappingInfo.dueDate': todayDateString },
1892
+ } );
1893
+ }
1894
+ dueTodayQuery.size = 0;
1895
+ const dueTodayResp = await getOpenSearchData( openSearch.footfallDirectory, dueTodayQuery );
1896
+ dueToday = dueTodayResp?.body?.hits?.total?.value || 0;
1897
+
646
1898
 
647
- export async function ticketSummary1( req, res ) {
648
- try {
649
- const openSearch = JSON.parse( process.env.OPENSEARCH );
650
- const inputData = req.query;
651
- inputData.clientId = inputData.clientId.split( ',' ); // convert strig to array
652
- const getQuery = {
653
- size: 0,
654
- query: {
655
- bool: {
656
- filter: [
657
- { terms: { 'clientId.keyword': inputData.clientId } },
658
- {
659
- range: {
660
- dateString: {
661
- gte: inputData.fromDate,
662
- lte: inputData.toDate,
663
- },
1899
+ // filter expired Tickets
1900
+ let expiredTickets = 0;
1901
+
1902
+ let eQ = buildStoreQueryWithStatus( baseStoreQuery, 'Under Tango Review' );
1903
+ eQ.size = 0;
1904
+ const eResp = await getOpenSearchData( openSearch.footfallDirectory, eQ );
1905
+ expiredTickets = eResp?.body?.hits?.total?.value || 0;
1906
+
1907
+ // filter under tango review
1908
+
1909
+ let undertangoTickets = 0;
1910
+
1911
+ let utrQ = buildStoreQueryWithStatus( baseStoreQuery, 'Under Tango Review' );
1912
+ utrQ.size = 0;
1913
+ const utrResp = await getOpenSearchData( openSearch.footfallDirectory, utrQ );
1914
+ undertangoTickets = utrResp?.body?.hits?.total?.value || 0;
1915
+
1916
+ // For this, add a filter on revicedPerc >= 85
1917
+ let ticketAccuracyAbove = 0;
1918
+ let aboveQ = buildAggStoreQuery( baseStoreQuery, [
1919
+ {
1920
+ script: {
1921
+ script: {
1922
+ lang: 'painless',
1923
+ source: `
1924
+ doc['revicedPerc.keyword'].size()!=0 &&
1925
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) >= params.num
1926
+ `,
1927
+ params: { num: 85 },
664
1928
  },
665
1929
  },
666
- ],
667
- },
668
- },
669
- aggs: {
670
- totalTicketCount: {
671
- value_count: { field: '_id' },
672
- },
673
- openStatusCount: {
674
- filter: {
675
- term: { 'status.keyword': 'open' },
676
- },
677
- },
678
- closeTicketCount: {
679
- filter: {
680
- term: { 'status.keyword': 'closed' },
681
- },
682
- },
683
- duplicateCount: {
684
- sum: {
685
- field: 'duplicateCount',
686
- },
687
- },
688
- employeeCount: {
689
- sum: {
690
- field: 'employeeCount',
691
- },
692
- },
693
- houseKeepingCount: {
694
- sum: {
695
- field: 'houseKeepingCount',
696
- },
697
- },
698
- junkCount: {
699
- sum: {
700
- field: 'junkCount',
701
1930
  },
702
- },
703
- },
704
- };
705
1931
 
706
- const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
707
- const aggs = getData?.body?.aggregations;
708
1932
 
709
- const result = {
710
- totalTickets: aggs.totalTicketCount.value,
711
- openTickets: aggs.openStatusCount.doc_count,
712
- closedTickets: aggs.closeTicketCount.doc_count,
713
- duplicateCount: aggs.duplicateCount.value,
714
- employeeCount: aggs.employeeCount.value,
715
- houseKeepingCount: aggs.houseKeepingCount.value,
716
- junkCount: aggs.junkCount.value,
717
- noShopper: aggs.duplicateCount.value + aggs.employeeCount.value + aggs.houseKeepingCount.value + aggs.junkCount.value,
718
- };
1933
+ ] );
719
1934
 
720
- return res.sendSuccess( { result: result } );
721
- } catch ( error ) {
722
- const err = error.message || 'Internal Server Error';
723
- logger.error( { error: error, messgage: req.query } );
724
- return res.sendSuccess( err, 500 );
725
- }
726
- }
1935
+ const aboveResp = await getOpenSearchData( openSearch.footfallDirectory, aboveQ );
1936
+ ticketAccuracyAbove = aboveResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
727
1937
 
728
- export async function ticketSummary( req, res ) {
729
- try {
730
- let result = '';
731
- const userInfo = req.user;
732
- const ticketsFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'reviewer' && ( m.isAdd == true || m.isEdit == true ) ) ) );
733
- // const ticketsApproveFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'approver' && ( m.isAdd == true || m.isEdit == true ) ) ) );
734
- if ( req.user.userType == 'tango' ) {
735
- result = {
736
- totalTickets: 0,
737
- averageAccuracyOverAll: 0,
738
- openTickets: 0,
739
- openInfraIssues: 0,
740
- inprogress: 0,
741
- closedTickets: 0,
742
- ticketAccuracyAbove: '0%',
743
- ticketAccuracyBelow: '0%',
744
- };
745
- } else {
746
- result = req.user.role === 'superadmin' ?
747
- {
748
- totalTickets: 0,
749
- openTickets: 0,
750
- inprogress: 0,
751
- closedTickets: 0,
752
- dueToday: 0,
753
- Expired: 0,
754
- underTangoReview: 0,
755
- avgTicket: '0%',
756
- avgAccuracy: '0%',
757
- } :
758
- req.user.role === 'user' ? 'NA' :
759
- ticketsFeature ?
760
- {
761
- totalTickets: 0,
762
- openTickets: 0,
763
- inprogress: 0,
764
- closedTickets: 0,
765
- dueToday: 0,
766
- Expired: 0,
767
- avgTicket: '0%',
768
- avgAccuracy: '0%',
769
- } : 'NA';
1938
+ // ticketAccuracyBelow: avg of revicedPerc < 85%
1939
+ let ticketAccuracyBelow = 0;
1940
+
1941
+ let belowQ = buildAggStoreQuery( baseStoreQuery, [
1942
+ {
1943
+ script: {
1944
+ script: {
1945
+ lang: 'painless',
1946
+ source: `
1947
+ doc['revicedPerc.keyword'].size()!=0 &&
1948
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) < params.num
1949
+ `,
1950
+ params: { num: 85 },
1951
+ },
1952
+ },
1953
+ },
1954
+
1955
+ ] );
1956
+ const belowResp = await getOpenSearchData( openSearch.footfallDirectory, belowQ );
1957
+ ticketAccuracyBelow = belowResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1958
+
1959
+ // Final result object
1960
+ result = {
1961
+ totalTickets,
1962
+ openTickets,
1963
+ inprogress,
1964
+ closedTickets,
1965
+ dueToday: dueToday,
1966
+ Expired: expiredTickets,
1967
+ underTangoReview: undertangoTickets,
1968
+ avgTicket: ticketAccuracyAbove+'%',
1969
+ avgAccuracy: ticketAccuracyBelow+'%',
1970
+ };
1971
+ }
770
1972
  }
771
1973
 
772
1974
  return res.sendSuccess( { result: result } );
@@ -781,7 +1983,7 @@ export async function ticketList1( req, res ) {
781
1983
  try {
782
1984
  const openSearch = JSON.parse( process.env.OPENSEARCH );
783
1985
  const inputData = req.query;
784
- const limit = inputData.limit || 10;
1986
+ const limit =inputData?.isExport ? 10000: inputData.limit || 10;
785
1987
  const offset = inputData.offset == 0 ? 0 : ( inputData.offset - 1 ) * limit || 0;
786
1988
  const order = inputData?.sortOrder || -1;
787
1989
 
@@ -1003,7 +2205,7 @@ export async function ticketList( req, res ) {
1003
2205
  const openSearch = JSON.parse( process.env.OPENSEARCH );
1004
2206
  const inputData = req.query;
1005
2207
  const userInfo = req.user;
1006
- const limit = inputData?.limit || 10;
2208
+ const limit =inputData?.isExport? 10000: inputData?.limit || 10;
1007
2209
  const offset = inputData.offset == 0 ? 0 : ( inputData.offset - 1 ) * limit || 0;
1008
2210
  inputData.clientId = inputData?.clientId?.split( ',' ); // convert strig to array
1009
2211
 
@@ -1326,6 +2528,87 @@ export async function ticketList( req, res ) {
1326
2528
  }
1327
2529
  }
1328
2530
 
2531
+ if ( inputData?.filterByStore && inputData?.filterByStore !== '' ) {
2532
+ let percQuery = null;
2533
+ const value = inputData.filterByStore;
2534
+
2535
+ // Helper function: remove trailing '%' and convert to number
2536
+ const percValue = ( val ) => {
2537
+ if ( typeof val === 'string' ) {
2538
+ return parseFloat( val.replace( '%', '' ).trim() );
2539
+ }
2540
+ return parseFloat( val );
2541
+ };
2542
+
2543
+ // Example filter values: "<90", "<=90", ">=90", "50 to 90"
2544
+ if ( /^<=?\d+$/.test( value ) ) {
2545
+ // "<90" or "<=90"
2546
+ const num = percValue( value.replace( /[^\d]/g, '' ) );
2547
+ const op = value.includes( '=' ) ? 'lte' : 'lt';
2548
+ percQuery = {
2549
+ script: {
2550
+ script: {
2551
+ source: `doc['mappingInfo.revicedPerc.keyword'].size()!=0 && Integer.parseInt(doc['mappingInfo.revicedPerc.keyword'].value.replace('%','')) ${op === 'lt' ? '<' : '<='} params.num`,
2552
+ params: { num },
2553
+ },
2554
+ },
2555
+ };
2556
+ } else if ( /^>=?\d+$/.test( value ) ) {
2557
+ // ">=90"
2558
+ const num = percValue( value.replace( /[^\d]/g, '' ) );
2559
+ const op = value.includes( '=' ) ? 'gte' : 'gt';
2560
+ percQuery = {
2561
+ script: {
2562
+ script: {
2563
+ source: `doc['mappingInfo.revicedPerc.keyword'].size()!=0 && Integer.parseInt(doc['mappingInfo.revicedPerc.keyword'].value.replace('%','')) ${op === 'gt' ? '>' : '>='} params.num`,
2564
+ params: { num },
2565
+ },
2566
+ },
2567
+ };
2568
+ } else if ( /^(\d+)\s*to\s*(\d+)$/.test( value ) ) {
2569
+ // "50 to 90"
2570
+ const match = value.match( /^(\d+)\s*to\s*(\d+)$/ );
2571
+ const from = percValue( match[1] );
2572
+ const to = percValue( match[2] );
2573
+ percQuery = {
2574
+ script: {
2575
+ script: {
2576
+ source:
2577
+ `doc['mappingInfo.revicedPerc.keyword'].size()!=0 && Integer.parseInt(doc['mappingInfo.revicedPerc.keyword'].value.replace('%','')) >= params.from && Integer.parseInt(doc['mappingInfo.revicedPerc.keyword'].value.replace('%','')) <= params.to`,
2578
+ params: { from, to },
2579
+ },
2580
+ },
2581
+ };
2582
+ }
2583
+ // fallback: treat as exact match (e.g., "90")
2584
+ if ( !percQuery && /^\d+$/.test( value ) ) {
2585
+ percQuery = {
2586
+ script: {
2587
+ script: {
2588
+ source: `doc['mappingInfo.revicedPerc.keyword'].size()!=0 && Integer.parseInt(doc['mappingInfo.revicedPerc.keyword'].value.replace('%','')) == params.num`,
2589
+ params: { num: percValue( value ) },
2590
+ },
2591
+ },
2592
+ };
2593
+ }
2594
+
2595
+ if ( percQuery ) {
2596
+ searchQuery.query.bool.must.push( {
2597
+ nested: {
2598
+ path: 'mappingInfo',
2599
+ query: {
2600
+ bool: {
2601
+ must: [
2602
+ { term: { 'mappingInfo.type': 'tagging' } },
2603
+ percQuery,
2604
+ ],
2605
+ },
2606
+ },
2607
+ },
2608
+ } );
2609
+ }
2610
+ }
2611
+
1329
2612
  if ( inputData?.filterByReviewer && inputData?.filterByReviewer !== '' ) {
1330
2613
  let percQuery = null;
1331
2614
  const value = inputData.filterByReviewer;
@@ -1633,7 +2916,7 @@ export async function ticketList( req, res ) {
1633
2916
 
1634
2917
 
1635
2918
  );
1636
- } else if ( req?.user?.userType === 'client' && inputData.tangoType !== 'internal' ) {
2919
+ } else if ( req?.user?.userType === 'client' ) {
1637
2920
  searchQuery.query.bool.must.push(
1638
2921
  {
1639
2922
  term: {
@@ -1727,137 +3010,285 @@ export async function ticketList( req, res ) {
1727
3010
  let temp = [];
1728
3011
  if ( req.user.userType === 'tango' ) {
1729
3012
  if ( inputData.tangoType === 'store' ) {
1730
- for ( let item of ticketListData ) {
1731
- temp.push( {
1732
-
1733
- ticketId: item?.ticketId,
1734
- storeId: item?.storeId,
1735
- storeName: item?.storeName,
1736
- ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1737
- issueDate: item?.dateString,
1738
- dueDate: '',
1739
- footfall: item?.footfallCount,
1740
- storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1741
- reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1742
- approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
1743
- tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1744
- status: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
3013
+ if ( inputData?.isExport ) {
3014
+ const exportData = [];
3015
+ for ( let item of ticketListData ) {
3016
+ exportData.push( {
3017
+
3018
+ 'Ticket ID': item?.ticketId,
3019
+ 'store Name': item?.storeName,
3020
+ 'store ID': item?.storeId,
3021
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.createdAt,
3022
+ 'Issue Date': item?.dateString,
3023
+ 'Due Date': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.dueDate,
3024
+ 'Actual FF': item?.footfallCount,
3025
+ 'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3026
+ 'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3027
+ 'Approver (%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
3028
+ 'Tango (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3029
+ 'Status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
1745
3030
 
1746
- } );
3031
+ } );
3032
+ }
3033
+ return await download( exportData, res );
3034
+ } else {
3035
+ for ( let item of ticketListData ) {
3036
+ temp.push( {
3037
+
3038
+ ticketId: item?.ticketId,
3039
+ storeId: item?.storeId,
3040
+ storeName: item?.storeName,
3041
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.createdAt,
3042
+ issueDate: item?.dateString,
3043
+ dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.dueDate,
3044
+ footfall: item?.footfallCount,
3045
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3046
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3047
+ approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
3048
+ tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3049
+ status: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
3050
+
3051
+ } );
3052
+ }
1747
3053
  }
1748
3054
  } else {
1749
- for ( let item of ticketListData ) {
1750
- temp.push( {
1751
-
1752
- ticketId: item?.ticketId,
1753
- storeId: item?.storeId,
1754
- storeName: item?.storeName,
1755
-
1756
- ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1757
- issueDate: item?.dateString,
1758
- footfall: item?.footfallCount,
1759
- dueDate: '',
1760
- type: item?.type || 'store',
1761
- storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1762
- reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1763
- approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
1764
- tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1765
- status: item?.status,
1766
- tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
3055
+ if ( inputData?.isExport ) {
3056
+ const exportData = [];
3057
+ for ( let item of ticketListData ) {
3058
+ exportData.push( {
3059
+
3060
+ 'Ticket ID': item?.ticketId,
3061
+ 'Store Name': item?.storeName,
3062
+ 'Store ID': item?.storeId,
3063
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
3064
+ 'Issue Date': item?.dateString,
3065
+ 'Ticket Type': item?.type,
3066
+ 'Actual FF': item?.footfallCount,
3067
+ // Use the dueDate from the last mappingInfo item whose type is NOT 'finalRevisoon'
3068
+ 'Due Date': ( () => {
3069
+ if ( Array.isArray( item?.mappingInfo ) ) {
3070
+ const filtered = item.mappingInfo.filter( ( f ) => f.dueDate && f.type !== 'finalRevisoon' );
3071
+ return filtered.length > 0 ? filtered[filtered.length - 1].dueDate : '';
3072
+ }
3073
+ return '';
3074
+ } )(),
3075
+ 'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3076
+ 'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3077
+ 'Approver (%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
3078
+ 'Tango (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3079
+ 'Ticket Status': item?.status,
3080
+ 'Tango Status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
1767
3081
 
1768
- } );
3082
+ } );
3083
+ }
3084
+ return await download( exportData, res );
3085
+ } else {
3086
+ for ( let item of ticketListData ) {
3087
+ temp.push( {
3088
+
3089
+ ticketId: item?.ticketId,
3090
+ storeId: item?.storeId,
3091
+ storeName: item?.storeName,
3092
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
3093
+ issueDate: item?.dateString,
3094
+ footfall: item?.footfallCount,
3095
+ dueDate: ( () => {
3096
+ if ( Array.isArray( item?.mappingInfo ) ) {
3097
+ const filtered = item.mappingInfo.filter( ( f ) => f.dueDate && f.type !== 'finalRevisoon' );
3098
+ return filtered.length > 0 ? filtered[filtered.length - 1].dueDate : '';
3099
+ }
3100
+ return '';
3101
+ } )(),
3102
+ type: item?.type || 'store',
3103
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3104
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3105
+ approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
3106
+ tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3107
+ status: item?.status,
3108
+ tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
3109
+
3110
+ } );
3111
+ }
1769
3112
  }
1770
3113
  }
1771
3114
  } else {
1772
3115
  if ( inputData?.permissionType === 'approve' ) {
1773
- for ( let item of ticketListData ) {
1774
- temp.push( {
1775
-
1776
- ticketId: item?.ticketId,
1777
- storeId: item?.storeId,
1778
- storeName: item?.storeName,
1779
-
1780
- ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1781
- issueDate: item?.dateString,
1782
- dueDate: '',
1783
- footfall: item?.footfallCount,
1784
-
1785
- type: item.type || 'store',
1786
- storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1787
- reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1788
- approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
1789
- tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1790
- status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
1791
- tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1792
- approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
3116
+ if ( inputData.exportData ) {
3117
+ const exportData = [];
3118
+ for ( let item of ticketListData ) {
3119
+ exportData.push( {
3120
+
3121
+ 'Ticket ID': item?.ticketId,
3122
+ 'Store Name': item?.storeName,
3123
+ 'Store ID': item?.storeId,
3124
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
3125
+ 'Issue Date': item?.dateString,
3126
+ 'Due Date': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
3127
+ 'Actual FF': item?.footfallCount,
3128
+ 'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3129
+ 'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3130
+ 'Approver (%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
3131
+ 'Tango (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3132
+ 'Status': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
3133
+ 'Tango Status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3134
+ 'Approved by': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
1793
3135
 
1794
- } );
3136
+ } );
3137
+ }
3138
+ return await download( exportData, res );
3139
+ } else {
3140
+ for ( let item of ticketListData ) {
3141
+ temp.push( {
3142
+
3143
+ ticketId: item?.ticketId,
3144
+ storeId: item?.storeId,
3145
+ storeName: item?.storeName,
3146
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
3147
+ issueDate: item?.dateString,
3148
+ dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
3149
+ footfall: item?.footfallCount,
3150
+ type: item.type || 'store',
3151
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3152
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3153
+ approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
3154
+ tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3155
+ status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
3156
+ tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3157
+ approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
3158
+
3159
+ } );
3160
+ }
1795
3161
  }
1796
3162
  } else if ( inputData?.permissionType === 'review' ) {
1797
- for ( let item of ticketListData ) {
1798
- temp.push( {
1799
-
1800
- ticketId: item?.ticketId,
1801
- storeId: item?.storeId,
1802
- storeName: item?.storeName,
1803
- ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1804
- issueDate: item?.dateString,
1805
- footfall: item?.footfallCount,
1806
- dueDate: '',
1807
- type: item.type || 'store',
1808
- storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1809
- reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1810
-
1811
- status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
1812
- ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
3163
+ if ( inputData?.isExport ) {
3164
+ const exportData = [];
3165
+ for ( let item of ticketListData ) {
3166
+ exportData.push( {
3167
+
3168
+ 'Ticket ID': item?.ticketId,
3169
+ 'Store Name': item?.storeName,
3170
+ 'Store ID': item?.storeId,
3171
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
3172
+ 'Issue Date': item?.dateString,
3173
+ 'Actual FF': item?.footfallCount,
3174
+ 'Due Date': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
3175
+ 'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3176
+ 'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3177
+ 'Status': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
3178
+ 'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
1813
3179
 
1814
- } );
3180
+ } );
3181
+ }
3182
+ return await download( exportData, res );
3183
+ } else {
3184
+ for ( let item of ticketListData ) {
3185
+ temp.push( {
3186
+
3187
+ ticketId: item?.ticketId,
3188
+ storeId: item?.storeId,
3189
+ storeName: item?.storeName,
3190
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
3191
+ issueDate: item?.dateString,
3192
+ footfall: item?.footfallCount,
3193
+ dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
3194
+ type: item.type || 'store',
3195
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3196
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3197
+ status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
3198
+ ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
3199
+
3200
+ } );
3201
+ }
1815
3202
  }
1816
3203
  } else if ( req.user.role === 'user' ) {
1817
3204
  temp = [];
1818
3205
  } else if ( ticketsFeature ) {
1819
- for ( let item of ticketListData ) {
1820
- temp.push( {
1821
-
1822
- ticketId: item?.ticketId,
1823
- storeId: item?.storeId,
1824
- storeName: item?.storeName,
1825
- ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1826
- issueDate: item?.dateString,
1827
- footfall: item?.footfallCount,
1828
- dueDate: '',
1829
- type: item.type || 'store',
1830
- storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1831
- reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1832
-
1833
- status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
1834
- ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
3206
+ if ( inputData?.isExport ) {
3207
+ const exportData = [];
3208
+ for ( let item of ticketListData ) {
3209
+ exportData.push( {
3210
+
3211
+ 'Ticket ID': item?.ticketId,
3212
+ 'Store ID': item?.storeId,
3213
+ 'Store Name': item?.storeName,
3214
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
3215
+ 'Issue Date': item?.dateString,
3216
+ 'Actual FF': item?.footfallCount,
3217
+ 'Due Date': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
3218
+ 'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3219
+ 'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3220
+ 'Status': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
3221
+ 'Reviewed by': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
1835
3222
 
1836
- } );
3223
+ } );
3224
+ }
3225
+ return await download( exportData, res );
3226
+ } else {
3227
+ for ( let item of ticketListData ) {
3228
+ temp.push( {
3229
+
3230
+ ticketId: item?.ticketId,
3231
+ storeId: item?.storeId,
3232
+ storeName: item?.storeName,
3233
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdAt,
3234
+ issueDate: item?.dateString,
3235
+ footfall: item?.footfallCount,
3236
+ dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.dueDate,
3237
+ type: item.type || 'store',
3238
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3239
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3240
+ status: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.status || '--',
3241
+ ReviewedBy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.createdByEmail || '--',
3242
+
3243
+ } );
3244
+ }
1837
3245
  }
1838
3246
  } else if ( ticketsApproveFeature ) {
1839
- for ( let item of ticketListData ) {
1840
- temp.push( {
1841
-
1842
- ticketId: item?.ticketId,
1843
- storeId: item?.storeId,
1844
- storeName: item?.storeName,
1845
-
1846
- ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.createdAt,
1847
- issueDate: item?.dateString,
1848
- dueDate: '',
1849
- footfall: item?.footfallCount,
1850
-
1851
- type: item.type || 'store',
1852
- storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
1853
- reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1854
- approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
1855
- tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1856
- status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
1857
- tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1858
- approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
3247
+ if ( inputData.isExport ) {
3248
+ const exportData = [];
3249
+ for ( let item of ticketListData ) {
3250
+ exportData.push( {
3251
+
3252
+ 'Ticket ID': item?.ticketId,
3253
+ 'Store Name': item?.storeName,
3254
+ 'Store ID': item?.storeId,
3255
+ 'Ticket Raised': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
3256
+ 'Issue Date': item?.dateString,
3257
+ 'Due Date': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
3258
+ 'Actual FF': item?.footfallCount,
3259
+ 'Store (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3260
+ 'Reviewer (%)': item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3261
+ 'Approver (%)': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
3262
+ 'Tango (%)': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3263
+ 'Status': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
3264
+ 'Tango Status': item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3265
+ 'Approved by': item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
1859
3266
 
1860
- } );
3267
+ } );
3268
+ }
3269
+ return await download( exportData, res );
3270
+ } else {
3271
+ for ( let item of ticketListData ) {
3272
+ temp.push( {
3273
+
3274
+ ticketId: item?.ticketId,
3275
+ storeId: item?.storeId,
3276
+ storeName: item?.storeName,
3277
+ ticketRaised: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdAt,
3278
+ issueDate: item?.dateString,
3279
+ dueDate: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.dueDate,
3280
+ footfall: item?.footfallCount,
3281
+ type: item.type || 'store',
3282
+ storeRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tagging' )?.revicedPerc || '--',
3283
+ reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
3284
+ approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
3285
+ tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3286
+ status: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.status || '--',
3287
+ tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
3288
+ approvedBy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.createdByEmail || '--',
3289
+
3290
+ } );
3291
+ }
1861
3292
  }
1862
3293
  } else {
1863
3294
  temp = [];
@@ -2666,7 +4097,7 @@ export async function getTaggedStores( req, res ) {
2666
4097
  + (doc.containsKey('employeeCount') && !doc['employeeCount'].empty ? doc['employeeCount'].value : 0)
2667
4098
  + (doc.containsKey('junkCount') && !doc['junkCount'].empty ? doc['junkCount'].value : 0);
2668
4099
  `,
2669
- lang: 'painless',
4100
+ lang: 'scripting',
2670
4101
  },
2671
4102
  },
2672
4103
  },
@@ -3076,6 +4507,7 @@ export async function reviewerList( req, res ) {
3076
4507
  export async function openTicketList( req, res ) {
3077
4508
  try {
3078
4509
  const inputData = req.body;
4510
+ logger.info( { inputData } );
3079
4511
  const openSearch = JSON.parse( process.env.OPENSEARCH );
3080
4512
 
3081
4513
  // INSERT_YOUR_CODE
@@ -3122,6 +4554,7 @@ export async function openTicketList( req, res ) {
3122
4554
  },
3123
4555
  ];
3124
4556
 
4557
+
3125
4558
  const openSearchQuery = {
3126
4559
  size: 10000,
3127
4560
  query: {
@@ -3131,6 +4564,31 @@ export async function openTicketList( req, res ) {
3131
4564
  },
3132
4565
  _source: [ 'ticketId', 'storeName', 'storeId', 'dateString', 'revicedFootfall', 'footfallCount', 'revicedPerc', 'type' ],
3133
4566
  };
4567
+
4568
+ if ( inputData.searchValue && inputData.searchValue !== '' ) {
4569
+ openSearchQuery.query.bool['should'] = [];
4570
+ openSearchQuery.query.bool.should = [
4571
+
4572
+ {
4573
+ 'wildcard': {
4574
+ 'storeName.keyword': {
4575
+ 'value': `*${inputData.searchValue}*`,
4576
+ },
4577
+ },
4578
+ },
4579
+ {
4580
+ 'wildcard': {
4581
+ 'ticketId.keyword': {
4582
+ 'value': `*${inputData.searchValue}*`,
4583
+ },
4584
+ },
4585
+ },
4586
+
4587
+
4588
+ ];
4589
+ openSearchQuery.query.bool['minimum_should_match'] = 1;
4590
+ }
4591
+
3134
4592
  // INSERT_YOUR_CODE
3135
4593
  // Add sorting by revicedPerc descending (highest revised accuracy first)
3136
4594
  openSearchQuery.sort = [