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

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.77",
3
+ "version": "3.9.5-vms.78",
4
4
  "description": "infra",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -385,14 +385,11 @@ 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 ) ) {
398
395
  return res.sendSuccess( 'Ticket closed successfully' );
@@ -400,7 +397,6 @@ export async function tangoReviewTicket( req, res ) {
400
397
  return res.sendError( 'Internal Server Error', 500 );
401
398
  }
402
399
  } catch ( error ) {
403
- console.log( '🚀 ~ tangoReviewTicket ~ error:', error );
404
400
  const err = error.message || 'Internal Server Error';
405
401
  logger.error( { error: error, funtion: 'tangoReviewTicket' } );
406
402
  return res.sendError( err, 500 );
@@ -727,20 +723,858 @@ export async function ticketSummary1( req, res ) {
727
723
 
728
724
  export async function ticketSummary( req, res ) {
729
725
  try {
726
+ const inputData = req.query;
727
+
730
728
  let result = '';
729
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
731
730
  const userInfo = req.user;
732
731
  const ticketsFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'reviewer' && ( m.isAdd == true || m.isEdit == true ) ) ) );
732
+ const ticketsApproveFeature = userInfo?.rolespermission?.some( ( f ) => f.featureName === 'FootfallDirectory' && ( f.modules.find( ( m ) => m.name == 'approver' && ( m.isAdd == true || m.isEdit == true ) ) ) );
733
+
733
734
  // 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
+ if ( req?.user?.userType === 'tango' ) {
736
+ switch ( inputData?.tangoType ) {
737
+ case 'store':
738
+ const storeQuery = {
739
+ size: 0,
740
+ query: {
741
+ bool: {
742
+ must: [
743
+ {
744
+ 'range': {
745
+ 'dateString': {
746
+ 'gte': inputData?.fromDate,
747
+ 'lte': inputData?.toDate,
748
+ 'format': 'yyyy-MM-dd',
749
+ },
750
+ },
751
+ },
752
+ {
753
+ nested: {
754
+ path: 'mappingInfo',
755
+ query: {
756
+ bool: {
757
+ must: [
758
+ {
759
+ term: {
760
+ 'mappingInfo.type': 'tangoreview',
761
+ },
762
+ },
763
+ ],
764
+ },
765
+ },
766
+ },
767
+ },
768
+
769
+ ],
770
+ },
771
+ },
772
+ };
773
+
774
+ // Helper function to clone deep and replace mappingInfo.status for openTickets/closed/etc
775
+ function buildStoreQueryWithStatus( baseQuery, statusValue ) {
776
+ let q = JSON.parse( JSON.stringify( baseQuery ) );
777
+ // Remove any previous mappingInfo.status term
778
+ let nested = q.query.bool.must.find( ( m ) => m.nested );
779
+ if ( nested ) {
780
+ // filter out all mappingInfo.status
781
+ nested.nested.query.bool.must = nested?.nested?.query?.bool?.must.filter( ( mustItem ) => {
782
+ return !( mustItem.term && mustItem.term['mappingInfo.status'] );
783
+ } );
784
+ // add desired status
785
+ nested.nested.query.bool.must.push( {
786
+ term: {
787
+ 'mappingInfo.status': statusValue,
788
+ },
789
+ } );
790
+ }
791
+ return q;
792
+ }
793
+
794
+ const buildAggStoreQuery = ( baseQuery, filters = [] ) => {
795
+ const q = JSON.parse( JSON.stringify( baseQuery ) );
796
+
797
+ // locate nested section
798
+ const nested = q.query.bool.must.find( ( m ) => m.nested );
799
+
800
+ if ( nested ) {
801
+ // remove old status filters
802
+ nested.nested.query.bool.must =
803
+ nested.nested.query.bool.must.filter( ( m ) => !( m.term && m.term['mappingInfo.status'] ) );
804
+
805
+ // add new filters
806
+ nested.nested.query.bool.must.push( ...filters );
807
+ }
808
+
809
+ return {
810
+ ...q,
811
+ size: 0,
812
+ aggs: {
813
+ avg_value: {
814
+ avg: {
815
+ script: {
816
+ lang: 'painless',
817
+ source: `
818
+ if (doc['revicedPerc.keyword'].size() == 0) return null;
819
+ String v = doc['revicedPerc.keyword'].value.replace('%','');
820
+ try {
821
+ return Double.parseDouble(v);
822
+ } catch (Exception e) {
823
+ return null;
824
+ }
825
+ `,
826
+ },
827
+ },
828
+ },
829
+ },
830
+ };
831
+ };
832
+
833
+
834
+ // Get OpenSearch connection
835
+
836
+ const baseStoreQuery = JSON.parse( JSON.stringify( storeQuery ) );
837
+
838
+ // Total Tickets (all tickets with mappingInfo.type == tangoreview)
839
+ let totalTickets = 0;
840
+
841
+ let allQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
842
+
843
+ allQuery.size = 0;
844
+ const totalResp = await getOpenSearchData( openSearch.footfallDirectory, allQuery );
845
+ totalTickets = totalResp?.body?.hits?.total?.value || 0;
846
+
847
+ // openTickets: mappingInfo.status: 'Open'
848
+ let openTickets = 0;
849
+
850
+ let otQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open' );
851
+ otQ.size = 0;
852
+ const openResp = await getOpenSearchData( openSearch.footfallDirectory, otQ );
853
+ openTickets = openResp?.body?.hits?.total?.value || 0;
854
+ // logger.info( { msd: '..............2', openResp } );
855
+
856
+
857
+ // openInfraIssues: mappingInfo.status: 'Open Accuracy Issue'
858
+ let openInfraIssues = 0;
859
+
860
+ let oiQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open Accuracy Issue' );
861
+ oiQ.size = 0;
862
+ const infraResp = await getOpenSearchData( openSearch.footfallDirectory, oiQ );
863
+ openInfraIssues = infraResp?.body?.hits?.total?.value || 0;
864
+
865
+ // inprogress: mappingInfo.status: 'in-Progress'
866
+ let inprogress = 0;
867
+
868
+ let ipQ = buildStoreQueryWithStatus( baseStoreQuery, 'In-Progress' );
869
+ ipQ.size = 0;
870
+ const ipResp = await getOpenSearchData( openSearch.footfallDirectory, ipQ );
871
+ inprogress = ipResp?.body?.hits?.total?.value || 0;
872
+
873
+ // closedTickets: mappingInfo.status: 'closed'
874
+ let closedTickets = 0;
875
+
876
+ let clQ = buildStoreQueryWithStatus( baseStoreQuery, 'Closed' );
877
+ clQ.size = 0;
878
+ const clResp = await getOpenSearchData( openSearch.footfallDirectory, clQ );
879
+ closedTickets = clResp?.body?.hits?.total?.value || 0;
880
+ // Average revisedPerc (for all tangoreview)
881
+ let averageAccuracyOverAll = 0;
882
+
883
+ let avgQ = buildAggStoreQuery( baseStoreQuery );
884
+ const avgResp = await getOpenSearchData( openSearch.footfallDirectory, avgQ );
885
+ averageAccuracyOverAll = avgResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
886
+
887
+ // ticketAccuracyAbove: avg of revicedPerc > 85%
888
+ let ticketAccuracyAbove = 0;
889
+
890
+
891
+ // For this, add a filter on revicedPerc >= 85
892
+ let aboveQ = buildAggStoreQuery( baseStoreQuery, [
893
+ {
894
+ script: {
895
+ script: {
896
+ lang: 'painless',
897
+ source: `
898
+ doc['revicedPerc.keyword'].size()!=0 &&
899
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) >= params.num
900
+ `,
901
+ params: { num: 85 },
902
+ },
903
+ },
904
+ },
905
+
906
+
907
+ ] );
908
+ const aboveResp = await getOpenSearchData( openSearch.footfallDirectory, aboveQ );
909
+ ticketAccuracyAbove = aboveResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
910
+
911
+ // ticketAccuracyBelow: avg of revicedPerc < 85%
912
+ let ticketAccuracyBelow = 0;
913
+
914
+ let belowQ = buildAggStoreQuery( baseStoreQuery, [
915
+ {
916
+ script: {
917
+ script: {
918
+ lang: 'painless',
919
+ source: `
920
+ doc['revicedPerc.keyword'].size()!=0 &&
921
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) < params.num
922
+ `,
923
+ params: { num: 85 },
924
+ },
925
+ },
926
+ },
927
+
928
+ ] );
929
+ const belowResp = await getOpenSearchData( openSearch.footfallDirectory, belowQ );
930
+ ticketAccuracyBelow = belowResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
931
+
932
+ // Final result object
933
+ result = {
934
+ totalTickets,
935
+ averageAccuracyOverAll: averageAccuracyOverAll+'%',
936
+ openTickets,
937
+ openInfraIssues,
938
+ inprogress,
939
+ closedTickets,
940
+ ticketAccuracyAbove: ticketAccuracyAbove+'%',
941
+ ticketAccuracyBelow: ticketAccuracyBelow+'%',
942
+ };
943
+ break;
944
+ case 'internal':
945
+ const internalQuery = {
946
+ size: 0,
947
+ query: {
948
+ bool: {
949
+ must: [
950
+ {
951
+ 'range': {
952
+ 'dateString': {
953
+ 'gte': inputData?.fromDate,
954
+ 'lte': inputData?.toDate,
955
+ 'format': 'yyyy-MM-dd',
956
+ },
957
+ },
958
+ },
959
+
960
+ {
961
+ nested: {
962
+ path: 'mappingInfo',
963
+ query: {
964
+ bool: {
965
+ must: [
966
+ {
967
+ terms: {
968
+ 'mappingInfo.type': [ 'tagging', 'review', 'approve', 'tangoreview' ],
969
+ },
970
+ },
971
+ ],
972
+ },
973
+ },
974
+ },
975
+ },
976
+
977
+ ],
978
+ },
979
+ },
980
+ };
981
+
982
+ // Helper function to clone deep and replace mappingInfo.status for openTickets/closed/etc
983
+ function buildInternalQueryWithStatus( baseQuery, statusValue ) {
984
+ let q = JSON.parse( JSON.stringify( baseQuery ) );
985
+ // Remove any previous mappingInfo.status term
986
+ let nested = q.query.bool.must.find( ( m ) => m.nested );
987
+ if ( nested ) {
988
+ // filter out all mappingInfo.status
989
+ nested.nested.query.bool.must = nested?.nested?.query?.bool?.must.filter( ( mustItem ) => {
990
+ return !( mustItem.term && mustItem.term['mappingInfo.status'] );
991
+ } );
992
+ // add desired status
993
+ nested.nested.query.bool.must.push( {
994
+ term: {
995
+ 'mappingInfo.status': statusValue,
996
+ },
997
+ } );
998
+ }
999
+ return q;
1000
+ }
1001
+
1002
+ const buildAggInternalQuery = ( baseQuery, filters = [] ) => {
1003
+ const q = JSON.parse( JSON.stringify( baseQuery ) );
1004
+
1005
+ // locate nested section
1006
+ const nested = q.query.bool.must.find( ( m ) => m.nested );
1007
+
1008
+ if ( nested ) {
1009
+ // remove old status filters
1010
+ nested.nested.query.bool.must =
1011
+ nested.nested.query.bool.must.filter( ( m ) => !( m.term && m.term['mappingInfo.status'] ) );
1012
+
1013
+ // add new filters
1014
+ nested.nested.query.bool.must.push( ...filters );
1015
+ }
1016
+
1017
+ return {
1018
+ ...q,
1019
+ size: 0,
1020
+ aggs: {
1021
+ avg_value: {
1022
+ avg: {
1023
+ script: {
1024
+ lang: 'painless',
1025
+ source: `
1026
+ if (doc['revicedPerc.keyword'].size() == 0) return null;
1027
+ String v = doc['revicedPerc.keyword'].value.replace('%','');
1028
+ try {
1029
+ return Double.parseDouble(v);
1030
+ } catch (Exception e) {
1031
+ return null;
1032
+ }
1033
+ `,
1034
+ },
1035
+ },
1036
+ },
1037
+ },
1038
+ };
1039
+ };
1040
+
1041
+
1042
+ // Get OpenSearch connection
1043
+
1044
+
1045
+ const baseInternalQuery = JSON.parse( JSON.stringify( internalQuery ) );
1046
+
1047
+ // Total Tickets (all tickets with mappingInfo.type == tangoreview)
1048
+ let totalInternalTickets = 0;
1049
+
1050
+ let allInternalQuery = JSON.parse( JSON.stringify( baseInternalQuery ) );
1051
+
1052
+ allInternalQuery.size = 0;
1053
+ const totalInternalResp = await getOpenSearchData( openSearch.footfallDirectory, allInternalQuery );
1054
+ totalInternalTickets = totalInternalResp?.body?.hits?.total?.value || 0;
1055
+
1056
+ // openTickets: mappingInfo.status: 'Open'
1057
+ let openInternalTickets = 0;
1058
+
1059
+ let otQInternal = buildInternalQueryWithStatus( baseInternalQuery, 'Open' );
1060
+ otQInternal.size = 0;
1061
+ const openInternalResp = await getOpenSearchData( openSearch.footfallDirectory, otQInternal );
1062
+ openInternalTickets = openInternalResp?.body?.hits?.total?.value || 0;
1063
+ // logger.info( { msd: '..............2', openResp } );
1064
+
1065
+
1066
+ // openInfraIssues: mappingInfo.status: 'Open Accuracy Issue'
1067
+ let openInternalInfraIssues = 0;
1068
+
1069
+ let oiQinternal = buildInternalQueryWithStatus( baseInternalQuery, 'Open Accuracy Issue' );
1070
+ oiQinternal.size = 0;
1071
+ const infraInternalResp = await getOpenSearchData( openSearch.footfallDirectory, oiQinternal );
1072
+ openInternalInfraIssues = infraInternalResp?.body?.hits?.total?.value || 0;
1073
+
1074
+ // inprogress: mappingInfo.status: 'in-Progress'
1075
+ let inprogressIntrenal = 0;
1076
+
1077
+ let ipQInternal = buildInternalQueryWithStatus( baseInternalQuery, 'In-Progress' );
1078
+ ipQInternal.size = 0;
1079
+ const ipInternalResp = await getOpenSearchData( openSearch.footfallDirectory, ipQInternal );
1080
+ inprogressIntrenal = ipInternalResp?.body?.hits?.total?.value || 0;
1081
+
1082
+ // closedTickets: mappingInfo.status: 'closed'
1083
+ let closedInternalTickets = 0;
1084
+
1085
+ let clQInternal = buildInternalQueryWithStatus( baseInternalQuery, 'Closed' );
1086
+ clQInternal.size = 0;
1087
+ const clInternalResp = await getOpenSearchData( openSearch.footfallDirectory, clQInternal );
1088
+ closedInternalTickets = clInternalResp?.body?.hits?.total?.value || 0;
1089
+ // Average revisedPerc (for all tangoreview)
1090
+ let internalAverageAccuracyOverAll = 0;
1091
+
1092
+ let avgQInternal = buildAggInternalQuery( baseInternalQuery );
1093
+ const avgInternalResp = await getOpenSearchData( openSearch.footfallDirectory, avgQInternal );
1094
+ internalAverageAccuracyOverAll = avgInternalResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1095
+
1096
+ // ticketAccuracyAbove: avg of revicedPerc > 85%
1097
+ let internalTicketAccuracyAbove = 0;
1098
+
1099
+
1100
+ // For this, add a filter on revicedPerc >= 85
1101
+ let aboveQinternal = buildAggInternalQuery( baseInternalQuery, [
1102
+ {
1103
+ script: {
1104
+ script: {
1105
+ lang: 'painless',
1106
+ source: `
1107
+ doc['revicedPerc.keyword'].size()!=0 &&
1108
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) >= params.num
1109
+ `,
1110
+ params: { num: 85 },
1111
+ },
1112
+ },
1113
+ },
1114
+
1115
+
1116
+ ] );
1117
+ const aboveRespInternal = await getOpenSearchData( openSearch.footfallDirectory, aboveQinternal );
1118
+ internalTicketAccuracyAbove = aboveRespInternal?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1119
+
1120
+ // ticketAccuracyBelow: avg of revicedPerc < 85%
1121
+ let internalTicketAccuracyBelow = 0;
1122
+
1123
+ let belowQIneranl = buildAggInternalQuery( baseInternalQuery, [
1124
+ {
1125
+ script: {
1126
+ script: {
1127
+ lang: 'painless',
1128
+ source: `
1129
+ doc['revicedPerc.keyword'].size()!=0 &&
1130
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) < params.num
1131
+ `,
1132
+ params: { num: 85 },
1133
+ },
1134
+ },
1135
+ },
1136
+
1137
+ ] );
1138
+ const belowRespInternal = await getOpenSearchData( openSearch.footfallDirectory, belowQIneranl );
1139
+ internalTicketAccuracyBelow = belowRespInternal?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1140
+
1141
+ // Final result object
1142
+ result = {
1143
+ totalTickets: totalInternalTickets,
1144
+ averageAccuracyOverAll: internalAverageAccuracyOverAll+'%',
1145
+ openTickets: openInternalTickets,
1146
+ openInfraIssues: openInternalInfraIssues,
1147
+ inprogress: inprogressIntrenal,
1148
+ closedTickets: closedInternalTickets,
1149
+ ticketAccuracyAbove: internalTicketAccuracyAbove+'%',
1150
+ ticketAccuracyBelow: internalTicketAccuracyBelow+'%',
1151
+ };
1152
+ break;
1153
+ default: '';
1154
+ }
1155
+ // result = {
1156
+ // totalTickets: 0,
1157
+ // averageAccuracyOverAll: 0,
1158
+ // openTickets: 0,
1159
+ // openInfraIssues: 0,
1160
+ // inprogress: 0,
1161
+ // closedTickets: 0,
1162
+ // ticketAccuracyAbove: '0%',
1163
+ // ticketAccuracyBelow: '0%',
1164
+ // };
1165
+ } else if ( req?.user?.userType === 'client' ) {
1166
+ if ( ticketsFeature && !ticketsApproveFeature ) {
1167
+ const storeQuery = {
1168
+ size: 0,
1169
+ query: {
1170
+ bool: {
1171
+ must: [
1172
+ {
1173
+ 'range': {
1174
+ 'dateString': {
1175
+ 'gte': inputData?.fromDate,
1176
+ 'lte': inputData?.toDate,
1177
+ 'format': 'yyyy-MM-dd',
1178
+ },
1179
+ },
1180
+ },
1181
+ {
1182
+ nested: {
1183
+ path: 'mappingInfo',
1184
+ query: {
1185
+ bool: {
1186
+ must: [
1187
+ {
1188
+ term: {
1189
+ 'mappingInfo.type': 'review',
1190
+ },
1191
+ },
1192
+ ],
1193
+ },
1194
+ },
1195
+ },
1196
+ },
1197
+
1198
+ ],
1199
+ },
1200
+ },
1201
+ };
1202
+
1203
+ // Helper function to clone deep and replace mappingInfo.status for openTickets/closed/etc
1204
+ function buildStoreQueryWithStatus( baseQuery, statusValue ) {
1205
+ let q = JSON.parse( JSON.stringify( baseQuery ) );
1206
+ // Remove any previous mappingInfo.status term
1207
+ let nested = q.query.bool.must.find( ( m ) => m.nested );
1208
+ if ( nested ) {
1209
+ // filter out all mappingInfo.status
1210
+ nested.nested.query.bool.must = nested?.nested?.query?.bool?.must.filter( ( mustItem ) => {
1211
+ return !( mustItem.term && mustItem.term['mappingInfo.status'] );
1212
+ } );
1213
+ // add desired status
1214
+ nested.nested.query.bool.must.push( {
1215
+ term: {
1216
+ 'mappingInfo.status': statusValue,
1217
+ },
1218
+ } );
1219
+ }
1220
+ return q;
1221
+ }
1222
+
1223
+ const buildAggStoreQuery = ( baseQuery, filters = [] ) => {
1224
+ const q = JSON.parse( JSON.stringify( baseQuery ) );
1225
+
1226
+ // locate nested section
1227
+ const nested = q.query.bool.must.find( ( m ) => m.nested );
1228
+
1229
+ if ( nested ) {
1230
+ // remove old status filters
1231
+ nested.nested.query.bool.must =
1232
+ nested.nested.query.bool.must.filter( ( m ) => !( m.term && m.term['mappingInfo.status'] ) );
1233
+
1234
+ // add new filters
1235
+ nested.nested.query.bool.must.push( ...filters );
1236
+ }
1237
+
1238
+ return {
1239
+ ...q,
1240
+ size: 0,
1241
+ aggs: {
1242
+ avg_value: {
1243
+ avg: {
1244
+ script: {
1245
+ lang: 'painless',
1246
+ source: `
1247
+ if (doc['revicedPerc.keyword'].size() == 0) return null;
1248
+ String v = doc['revicedPerc.keyword'].value.replace('%','');
1249
+ try {
1250
+ return Double.parseDouble(v);
1251
+ } catch (Exception e) {
1252
+ return null;
1253
+ }
1254
+ `,
1255
+ },
1256
+ },
1257
+ },
1258
+ },
1259
+ };
1260
+ };
1261
+
1262
+
1263
+ // Get OpenSearch connection
1264
+
1265
+ const baseStoreQuery = JSON.parse( JSON.stringify( storeQuery ) );
1266
+
1267
+ // Total Tickets (all tickets with mappingInfo.type == tangoreview)
1268
+ let totalTickets = 0;
1269
+
1270
+ let allQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1271
+
1272
+ allQuery.size = 0;
1273
+ const totalResp = await getOpenSearchData( openSearch.footfallDirectory, allQuery );
1274
+ totalTickets = totalResp?.body?.hits?.total?.value || 0;
1275
+
1276
+ // openTickets: mappingInfo.status: 'Open'
1277
+ let openTickets = 0;
1278
+
1279
+ let otQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open' );
1280
+ otQ.size = 0;
1281
+ const openResp = await getOpenSearchData( openSearch.footfallDirectory, otQ );
1282
+ openTickets = openResp?.body?.hits?.total?.value || 0;
1283
+ // logger.info( { msd: '..............2', openResp } );
1284
+
1285
+
1286
+ // openInfraIssues: mappingInfo.status: 'Open Accuracy Issue'
1287
+ let openInfraIssues = 0;
1288
+
1289
+ let oiQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open Accuracy Issue' );
1290
+ oiQ.size = 0;
1291
+ const infraResp = await getOpenSearchData( openSearch.footfallDirectory, oiQ );
1292
+ openInfraIssues = infraResp?.body?.hits?.total?.value || 0;
1293
+
1294
+ // inprogress: mappingInfo.status: 'in-Progress'
1295
+ let inprogress = 0;
1296
+
1297
+ let ipQ = buildStoreQueryWithStatus( baseStoreQuery, 'In-Progress' );
1298
+ ipQ.size = 0;
1299
+ const ipResp = await getOpenSearchData( openSearch.footfallDirectory, ipQ );
1300
+ inprogress = ipResp?.body?.hits?.total?.value || 0;
1301
+
1302
+ // closedTickets: mappingInfo.status: 'closed'
1303
+ let closedTickets = 0;
1304
+
1305
+ let clQ = buildStoreQueryWithStatus( baseStoreQuery, 'Closed' );
1306
+ clQ.size = 0;
1307
+ const clResp = await getOpenSearchData( openSearch.footfallDirectory, clQ );
1308
+ closedTickets = clResp?.body?.hits?.total?.value || 0;
1309
+ // Average revisedPerc (for all tangoreview)
1310
+ let averageAccuracyOverAll = 0;
1311
+
1312
+ let avgQ = buildAggStoreQuery( baseStoreQuery );
1313
+ const avgResp = await getOpenSearchData( openSearch.footfallDirectory, avgQ );
1314
+ averageAccuracyOverAll = avgResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1315
+
1316
+ // ticketAccuracyAbove: avg of revicedPerc > 85%
1317
+ let ticketAccuracyAbove = 0;
1318
+
1319
+
1320
+ // For this, add a filter on revicedPerc >= 85
1321
+ let aboveQ = buildAggStoreQuery( baseStoreQuery, [
1322
+ {
1323
+ script: {
1324
+ script: {
1325
+ lang: 'painless',
1326
+ source: `
1327
+ doc['revicedPerc.keyword'].size()!=0 &&
1328
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) >= params.num
1329
+ `,
1330
+ params: { num: 85 },
1331
+ },
1332
+ },
1333
+ },
1334
+
1335
+
1336
+ ] );
1337
+ const aboveResp = await getOpenSearchData( openSearch.footfallDirectory, aboveQ );
1338
+ ticketAccuracyAbove = aboveResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1339
+
1340
+ // ticketAccuracyBelow: avg of revicedPerc < 85%
1341
+ let ticketAccuracyBelow = 0;
1342
+
1343
+ let belowQ = buildAggStoreQuery( baseStoreQuery, [
1344
+ {
1345
+ script: {
1346
+ script: {
1347
+ lang: 'painless',
1348
+ source: `
1349
+ doc['revicedPerc.keyword'].size()!=0 &&
1350
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) < params.num
1351
+ `,
1352
+ params: { num: 85 },
1353
+ },
1354
+ },
1355
+ },
1356
+
1357
+ ] );
1358
+ const belowResp = await getOpenSearchData( openSearch.footfallDirectory, belowQ );
1359
+ ticketAccuracyBelow = belowResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1360
+
1361
+ // Final result object
1362
+ result = {
1363
+ totalTickets,
1364
+ averageAccuracyOverAll: averageAccuracyOverAll+'%',
1365
+ openTickets,
1366
+ openInfraIssues,
1367
+ inprogress,
1368
+ closedTickets,
1369
+ ticketAccuracyAbove: ticketAccuracyAbove+'%',
1370
+ ticketAccuracyBelow: ticketAccuracyBelow+'%',
1371
+ };
1372
+ }
1373
+ } else if ( ticketsFeature && !ticketsApproveFeature ) {
1374
+ const storeQuery = {
1375
+ size: 0,
1376
+ query: {
1377
+ bool: {
1378
+ must: [
1379
+ {
1380
+ 'range': {
1381
+ 'dateString': {
1382
+ 'gte': inputData?.fromDate,
1383
+ 'lte': inputData?.toDate,
1384
+ 'format': 'yyyy-MM-dd',
1385
+ },
1386
+ },
1387
+ },
1388
+ {
1389
+ nested: {
1390
+ path: 'mappingInfo',
1391
+ query: {
1392
+ bool: {
1393
+ must: [
1394
+ {
1395
+ term: {
1396
+ 'mappingInfo.type': inputData.permissionType === 'review'? 'review':'approve',
1397
+ },
1398
+ },
1399
+ ],
1400
+ },
1401
+ },
1402
+ },
1403
+ },
1404
+
1405
+ ],
1406
+ },
1407
+ },
1408
+ };
1409
+
1410
+ // Helper function to clone deep and replace mappingInfo.status for openTickets/closed/etc
1411
+ function buildStoreQueryWithStatus( baseQuery, statusValue ) {
1412
+ let q = JSON.parse( JSON.stringify( baseQuery ) );
1413
+ // Remove any previous mappingInfo.status term
1414
+ let nested = q.query.bool.must.find( ( m ) => m.nested );
1415
+ if ( nested ) {
1416
+ // filter out all mappingInfo.status
1417
+ nested.nested.query.bool.must = nested?.nested?.query?.bool?.must.filter( ( mustItem ) => {
1418
+ return !( mustItem.term && mustItem.term['mappingInfo.status'] );
1419
+ } );
1420
+ // add desired status
1421
+ nested.nested.query.bool.must.push( {
1422
+ term: {
1423
+ 'mappingInfo.status': statusValue,
1424
+ },
1425
+ } );
1426
+ }
1427
+ return q;
1428
+ }
1429
+
1430
+ const buildAggStoreQuery = ( baseQuery, filters = [] ) => {
1431
+ const q = JSON.parse( JSON.stringify( baseQuery ) );
1432
+
1433
+ // locate nested section
1434
+ const nested = q.query.bool.must.find( ( m ) => m.nested );
1435
+
1436
+ if ( nested ) {
1437
+ // remove old status filters
1438
+ nested.nested.query.bool.must =
1439
+ nested.nested.query.bool.must.filter( ( m ) => !( m.term && m.term['mappingInfo.status'] ) );
1440
+
1441
+ // add new filters
1442
+ nested.nested.query.bool.must.push( ...filters );
1443
+ }
1444
+
1445
+ return {
1446
+ ...q,
1447
+ size: 0,
1448
+ aggs: {
1449
+ avg_value: {
1450
+ avg: {
1451
+ script: {
1452
+ lang: 'painless',
1453
+ source: `
1454
+ if (doc['revicedPerc.keyword'].size() == 0) return null;
1455
+ String v = doc['revicedPerc.keyword'].value.replace('%','');
1456
+ try {
1457
+ return Double.parseDouble(v);
1458
+ } catch (Exception e) {
1459
+ return null;
1460
+ }
1461
+ `,
1462
+ },
1463
+ },
1464
+ },
1465
+ },
1466
+ };
1467
+ };
1468
+
1469
+
1470
+ // Get OpenSearch connection
1471
+
1472
+ const baseStoreQuery = JSON.parse( JSON.stringify( storeQuery ) );
1473
+
1474
+ // Total Tickets (all tickets with mappingInfo.type == tangoreview)
1475
+ let totalTickets = 0;
1476
+
1477
+ let allQuery = JSON.parse( JSON.stringify( baseStoreQuery ) );
1478
+
1479
+ allQuery.size = 0;
1480
+ const totalResp = await getOpenSearchData( openSearch.footfallDirectory, allQuery );
1481
+ totalTickets = totalResp?.body?.hits?.total?.value || 0;
1482
+
1483
+ // openTickets: mappingInfo.status: 'Open'
1484
+ let openTickets = 0;
1485
+
1486
+ let otQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open' );
1487
+ otQ.size = 0;
1488
+ const openResp = await getOpenSearchData( openSearch.footfallDirectory, otQ );
1489
+ openTickets = openResp?.body?.hits?.total?.value || 0;
1490
+ // logger.info( { msd: '..............2', openResp } );
1491
+
1492
+
1493
+ // openInfraIssues: mappingInfo.status: 'Open Accuracy Issue'
1494
+ let openInfraIssues = 0;
1495
+
1496
+ let oiQ = buildStoreQueryWithStatus( baseStoreQuery, 'Open Accuracy Issue' );
1497
+ oiQ.size = 0;
1498
+ const infraResp = await getOpenSearchData( openSearch.footfallDirectory, oiQ );
1499
+ openInfraIssues = infraResp?.body?.hits?.total?.value || 0;
1500
+
1501
+ // inprogress: mappingInfo.status: 'in-Progress'
1502
+ let inprogress = 0;
1503
+
1504
+ let ipQ = buildStoreQueryWithStatus( baseStoreQuery, 'In-Progress' );
1505
+ ipQ.size = 0;
1506
+ const ipResp = await getOpenSearchData( openSearch.footfallDirectory, ipQ );
1507
+ inprogress = ipResp?.body?.hits?.total?.value || 0;
1508
+
1509
+ // closedTickets: mappingInfo.status: 'closed'
1510
+ let closedTickets = 0;
1511
+
1512
+ let clQ = buildStoreQueryWithStatus( baseStoreQuery, 'Closed' );
1513
+ clQ.size = 0;
1514
+ const clResp = await getOpenSearchData( openSearch.footfallDirectory, clQ );
1515
+ closedTickets = clResp?.body?.hits?.total?.value || 0;
1516
+ // Average revisedPerc (for all tangoreview)
1517
+ let averageAccuracyOverAll = 0;
1518
+
1519
+ let avgQ = buildAggStoreQuery( baseStoreQuery );
1520
+ const avgResp = await getOpenSearchData( openSearch.footfallDirectory, avgQ );
1521
+ averageAccuracyOverAll = avgResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1522
+
1523
+ // ticketAccuracyAbove: avg of revicedPerc > 85%
1524
+ let ticketAccuracyAbove = 0;
1525
+
1526
+
1527
+ // For this, add a filter on revicedPerc >= 85
1528
+ let aboveQ = buildAggStoreQuery( baseStoreQuery, [
1529
+ {
1530
+ script: {
1531
+ script: {
1532
+ lang: 'painless',
1533
+ source: `
1534
+ doc['revicedPerc.keyword'].size()!=0 &&
1535
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) >= params.num
1536
+ `,
1537
+ params: { num: 85 },
1538
+ },
1539
+ },
1540
+ },
1541
+
1542
+
1543
+ ] );
1544
+ const aboveResp = await getOpenSearchData( openSearch.footfallDirectory, aboveQ );
1545
+ ticketAccuracyAbove = aboveResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1546
+
1547
+ // ticketAccuracyBelow: avg of revicedPerc < 85%
1548
+ let ticketAccuracyBelow = 0;
1549
+
1550
+ let belowQ = buildAggStoreQuery( baseStoreQuery, [
1551
+ {
1552
+ script: {
1553
+ script: {
1554
+ lang: 'painless',
1555
+ source: `
1556
+ doc['revicedPerc.keyword'].size()!=0 &&
1557
+ Integer.parseInt(doc['revicedPerc.keyword'].value.replace('%','')) < params.num
1558
+ `,
1559
+ params: { num: 85 },
1560
+ },
1561
+ },
1562
+ },
1563
+
1564
+ ] );
1565
+ const belowResp = await getOpenSearchData( openSearch.footfallDirectory, belowQ );
1566
+ ticketAccuracyBelow = belowResp?.body?.aggregations?.avg_value?.value?.toFixed( 2 ) || '0';
1567
+
1568
+ // Final result object
735
1569
  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%',
1570
+ totalTickets,
1571
+ averageAccuracyOverAll: averageAccuracyOverAll+'%',
1572
+ openTickets,
1573
+ openInfraIssues,
1574
+ inprogress,
1575
+ closedTickets,
1576
+ ticketAccuracyAbove: ticketAccuracyAbove+'%',
1577
+ ticketAccuracyBelow: ticketAccuracyBelow+'%',
744
1578
  };
745
1579
  } else {
746
1580
  result = req.user.role === 'superadmin' ?
@@ -1326,6 +2160,87 @@ export async function ticketList( req, res ) {
1326
2160
  }
1327
2161
  }
1328
2162
 
2163
+ if ( inputData?.filterByStore && inputData?.filterByStore !== '' ) {
2164
+ let percQuery = null;
2165
+ const value = inputData.filterByStore;
2166
+
2167
+ // Helper function: remove trailing '%' and convert to number
2168
+ const percValue = ( val ) => {
2169
+ if ( typeof val === 'string' ) {
2170
+ return parseFloat( val.replace( '%', '' ).trim() );
2171
+ }
2172
+ return parseFloat( val );
2173
+ };
2174
+
2175
+ // Example filter values: "<90", "<=90", ">=90", "50 to 90"
2176
+ if ( /^<=?\d+$/.test( value ) ) {
2177
+ // "<90" or "<=90"
2178
+ const num = percValue( value.replace( /[^\d]/g, '' ) );
2179
+ const op = value.includes( '=' ) ? 'lte' : 'lt';
2180
+ percQuery = {
2181
+ script: {
2182
+ script: {
2183
+ source: `doc['mappingInfo.revicedPerc.keyword'].size()!=0 && Integer.parseInt(doc['mappingInfo.revicedPerc.keyword'].value.replace('%','')) ${op === 'lt' ? '<' : '<='} params.num`,
2184
+ params: { num },
2185
+ },
2186
+ },
2187
+ };
2188
+ } else if ( /^>=?\d+$/.test( value ) ) {
2189
+ // ">=90"
2190
+ const num = percValue( value.replace( /[^\d]/g, '' ) );
2191
+ const op = value.includes( '=' ) ? 'gte' : 'gt';
2192
+ percQuery = {
2193
+ script: {
2194
+ script: {
2195
+ source: `doc['mappingInfo.revicedPerc.keyword'].size()!=0 && Integer.parseInt(doc['mappingInfo.revicedPerc.keyword'].value.replace('%','')) ${op === 'gt' ? '>' : '>='} params.num`,
2196
+ params: { num },
2197
+ },
2198
+ },
2199
+ };
2200
+ } else if ( /^(\d+)\s*to\s*(\d+)$/.test( value ) ) {
2201
+ // "50 to 90"
2202
+ const match = value.match( /^(\d+)\s*to\s*(\d+)$/ );
2203
+ const from = percValue( match[1] );
2204
+ const to = percValue( match[2] );
2205
+ percQuery = {
2206
+ script: {
2207
+ script: {
2208
+ source:
2209
+ `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`,
2210
+ params: { from, to },
2211
+ },
2212
+ },
2213
+ };
2214
+ }
2215
+ // fallback: treat as exact match (e.g., "90")
2216
+ if ( !percQuery && /^\d+$/.test( value ) ) {
2217
+ percQuery = {
2218
+ script: {
2219
+ script: {
2220
+ source: `doc['mappingInfo.revicedPerc.keyword'].size()!=0 && Integer.parseInt(doc['mappingInfo.revicedPerc.keyword'].value.replace('%','')) == params.num`,
2221
+ params: { num: percValue( value ) },
2222
+ },
2223
+ },
2224
+ };
2225
+ }
2226
+
2227
+ if ( percQuery ) {
2228
+ searchQuery.query.bool.must.push( {
2229
+ nested: {
2230
+ path: 'mappingInfo',
2231
+ query: {
2232
+ bool: {
2233
+ must: [
2234
+ { term: { 'mappingInfo.type': 'tagging' } },
2235
+ percQuery,
2236
+ ],
2237
+ },
2238
+ },
2239
+ },
2240
+ } );
2241
+ }
2242
+ }
2243
+
1329
2244
  if ( inputData?.filterByReviewer && inputData?.filterByReviewer !== '' ) {
1330
2245
  let percQuery = null;
1331
2246
  const value = inputData.filterByReviewer;
@@ -1633,7 +2548,7 @@ export async function ticketList( req, res ) {
1633
2548
 
1634
2549
 
1635
2550
  );
1636
- } else if ( req?.user?.userType === 'client' && inputData.tangoType !== 'internal' ) {
2551
+ } else if ( req?.user?.userType === 'client' ) {
1637
2552
  searchQuery.query.bool.must.push(
1638
2553
  {
1639
2554
  term: {
@@ -2666,7 +3581,7 @@ export async function getTaggedStores( req, res ) {
2666
3581
  + (doc.containsKey('employeeCount') && !doc['employeeCount'].empty ? doc['employeeCount'].value : 0)
2667
3582
  + (doc.containsKey('junkCount') && !doc['junkCount'].empty ? doc['junkCount'].value : 0);
2668
3583
  `,
2669
- lang: 'painless',
3584
+ lang: 'scripting',
2670
3585
  },
2671
3586
  },
2672
3587
  },
@@ -3076,6 +3991,7 @@ export async function reviewerList( req, res ) {
3076
3991
  export async function openTicketList( req, res ) {
3077
3992
  try {
3078
3993
  const inputData = req.body;
3994
+ logger.info( { inputData } );
3079
3995
  const openSearch = JSON.parse( process.env.OPENSEARCH );
3080
3996
 
3081
3997
  // INSERT_YOUR_CODE
@@ -86,7 +86,8 @@ export const tangoReviewAccuracyClosedTicketValid = {
86
86
 
87
87
  export const ticketSummarySchema = Joi.object().keys( {
88
88
  clientId: Joi.string().required(),
89
-
89
+ tangoType: Joi.string().valid( 'store', 'internal' ).optional(),
90
+ permissionType: Joi.string().valid( 'approve', 'review' ).optional(),
90
91
  fromDate: Joi.string()
91
92
  .pattern( /^\d{4}-\d{2}-\d{2}$/, 'YYYY-MM-DD format' )
92
93
  .required()
@@ -159,6 +160,7 @@ export const ticketListSchema = Joi.object().keys( {
159
160
  tangoType: Joi.string().valid( 'store', 'internal', '' ).optional(),
160
161
  permissionType: Joi.string().valid( 'review', 'approve' ).optional(),
161
162
  filterByStatus: Joi.string().optional().allow( '' ),
163
+ filterByStore: Joi.string().optional().allow( '' ),
162
164
  filterByReviewer: Joi.string().optional().allow( '' ),
163
165
  filterByApprover: Joi.string().optional().allow( '' ),
164
166
  filterByTango: Joi.string().optional().allow( '' ),