tango-app-api-analysis-traffic 3.8.7-vms.29 → 3.8.7-vms.30

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-analysis-traffic",
3
- "version": "3.8.7-vms.29",
3
+ "version": "3.8.7-vms.30",
4
4
  "description": "Traffic Analysis",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -137,6 +137,109 @@ export async function getrevoptagging( req, res ) {
137
137
  return res.sendError( { error: error }, 500 );
138
138
  }
139
139
  }
140
+
141
+ export async function migrateRevopIndex( req, res ) {
142
+ try {
143
+ const { storeId, dateString, size = 500 } = req.body;
144
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
145
+
146
+ const query = {
147
+ size: size,
148
+ query: {
149
+ bool: {
150
+ must: [],
151
+ },
152
+ },
153
+ };
154
+
155
+ if ( storeId ) {
156
+ query.query.bool.must.push( {
157
+ term: {
158
+ 'storeId.keyword': storeId,
159
+ },
160
+ } );
161
+ }
162
+
163
+ if ( dateString ) {
164
+ query.query.bool.must.push( {
165
+ terms: {
166
+ dateString: Array.isArray( dateString ) ? dateString : `${dateString}`.split( ',' ),
167
+ },
168
+ } );
169
+ }
170
+
171
+ const response = await getOpenSearchData( openSearch.revop, query );
172
+ const hits = response?.body?.hits?.hits || [];
173
+
174
+ if ( hits.length === 0 ) {
175
+ return res.sendSuccess( { message: 'No records found for migration', updated: 0 } );
176
+ }
177
+
178
+ const bulkBody = [];
179
+
180
+ for ( const hit of hits ) {
181
+ const src = hit._source || {};
182
+ const statusValue = ( src.status || '' ).toLowerCase();
183
+ const parentValue = src.parent;
184
+ const idValue = `${src.storeId || ''}_${src.dateString || src.dteString || ''}_${src.tempId || ''}`;
185
+
186
+ let actions = [
187
+ {
188
+ actionType: 'tagging',
189
+ action: 'submitted',
190
+ },
191
+ ];
192
+
193
+ if ( statusValue === 'approved' ) {
194
+ actions = [
195
+ {
196
+ actionType: 'tagging',
197
+ action: 'submitted',
198
+ },
199
+ {
200
+ actionType: 'review',
201
+ action: 'approved',
202
+ },
203
+ ];
204
+ } else if ( statusValue === 'rejected' ) {
205
+ actions = [
206
+ {
207
+ actionType: 'tagging',
208
+ action: 'submitted',
209
+ },
210
+ {
211
+ actionType: 'review',
212
+ action: 'rejected',
213
+ },
214
+ ];
215
+ }
216
+
217
+ const doc = {
218
+ id: idValue,
219
+ isParent: parentValue === null || parentValue === undefined ? false : true,
220
+ actions,
221
+ ticketStatus: src.status,
222
+ // updatedAt: new Date(),
223
+ };
224
+
225
+ bulkBody.push(
226
+ { update: { _index: openSearch.newRevop, _id: hit._id } },
227
+ { doc: doc, doc_as_upsert: true },
228
+ );
229
+ }
230
+
231
+ const bulkRes = await bulkUpdate( bulkBody );
232
+ if ( bulkRes?.errors ) {
233
+ logger.error( 'Bulk migration errors:', bulkRes.items );
234
+ return res.sendError( 'Failed to migrate some records', 500 );
235
+ }
236
+
237
+ return res.sendSuccess( { message: 'Migration completed', updated: hits.length } );
238
+ } catch ( error ) {
239
+ logger.error( { error: error, message: req.body, function: 'migrateRevopIndex' } );
240
+ return res.sendError( { error: error }, 500 );
241
+ }
242
+ }
140
243
  export async function revoptaggingcount( req, res ) {
141
244
  try {
142
245
  const openSearch = JSON.parse( process.env.OPENSEARCH );
@@ -425,7 +528,7 @@ export async function footFallImages( req, res ) {
425
528
  ],
426
529
  },
427
530
  },
428
- '_source': [ 'dateString', 'storeId', 'mappingInfo', 'revicedFootfall', 'revicedPerc', 'createdAt', 'updatedAt', 'footfallCount' ],
531
+ '_source': [ 'dateString', 'storeId', 'mappingInfo', 'revicedFootfall', 'revicedPerc', 'reviced', 'createdAt', 'updatedAt', 'footfallCount' ],
429
532
 
430
533
  };
431
534
 
@@ -548,7 +651,7 @@ export async function footFallImages( req, res ) {
548
651
  actionType: type,
549
652
  footfall: footfallValue,
550
653
  revicedFootfall: mapping.revicedFootfall ?? 0,
551
- revicedPerc: mapping.revicedPerc ?? '--',
654
+ revicedPerc: mapping.reviced ?? '--',
552
655
  count: countObj,
553
656
  createdAt: mapping.createdAt ?? '',
554
657
  createdByEmail: mapping.createdByEmail ?? '',
@@ -563,7 +666,7 @@ export async function footFallImages( req, res ) {
563
666
  actionType: type,
564
667
  footfall: footfallValue,
565
668
  revicedFootfall: mapping.revicedFootfall ?? 0,
566
- revicedPerc: mapping.revicedPerc ?? '--',
669
+ revicedPerc: mapping.reviced ?? '--',
567
670
  count: countObj,
568
671
  createdAt: mapping.createdAt ?? '',
569
672
  createdByEmail: mapping.createdByEmail ?? '',
@@ -786,3 +889,343 @@ export async function getCategorizedImages( req, res ) {
786
889
  return res.sendError( err, 500 );
787
890
  }
788
891
  }
892
+
893
+ export async function vmsDataMigration( req, res ) {
894
+ try {
895
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
896
+ const inputData = req.body;
897
+ const { storeId, dateString, limit = 100 } = inputData;
898
+
899
+ // Build query to fetch old structure documents
900
+ const query = {
901
+ query: {
902
+ bool: {
903
+ must: [
904
+ {
905
+ term: {
906
+ 'ticketName.keyword': 'footfall-directory',
907
+ },
908
+ },
909
+ ],
910
+ },
911
+ },
912
+ size: parseInt( limit ),
913
+ };
914
+
915
+ // Add storeId filter if provided
916
+ if ( storeId ) {
917
+ query.query.bool.must.push( {
918
+ term: {
919
+ 'storeId.keyword': storeId,
920
+ },
921
+ } );
922
+ }
923
+
924
+ // Add dateString filter if provided
925
+ if ( dateString ) {
926
+ query.query.bool.must.push( {
927
+ terms: {
928
+ dateString: dateString?.split( ',' ),
929
+ },
930
+ } );
931
+ }
932
+
933
+ // Exclude documents that already have the new structure (have mappingInfo or type: 'store')
934
+ query.query.bool.must_not = [
935
+ {
936
+ exists: {
937
+ field: 'mappingInfo',
938
+ },
939
+ },
940
+ {
941
+ term: {
942
+ 'type.keyword': 'store',
943
+ },
944
+ },
945
+ ];
946
+
947
+ const getData = await getOpenSearchData( openSearch.oldFootfallDirectory, query );
948
+ logger.info( { getData } );
949
+ const hits = getData?.body?.hits?.hits || [];
950
+
951
+ if ( hits.length === 0 ) {
952
+ return res.sendSuccess( { message: 'No documents found to migrate', migrated: 0 } );
953
+ }
954
+
955
+ let migratedCount = 0;
956
+ const errors = [];
957
+
958
+ for ( const hit of hits ) {
959
+ try {
960
+ const oldSource = hit._source;
961
+ const documentId = hit._id;
962
+
963
+ // Calculate revicedFootfall (sum of AC counts)
964
+ const revicedFootfall = ( oldSource.duplicateACCount || 0 ) +
965
+ ( oldSource.employeeACCount || 0 ) +
966
+ ( oldSource.houseKeepingACCount || 0 ) +
967
+ ( oldSource.junkACCount || 0 );
968
+
969
+ // Calculate revicedPerc
970
+ const footfallCount = oldSource.footfallCount || 0;
971
+ const revicedPerc = footfallCount > 0 ?
972
+ Math.round( ( revicedFootfall / footfallCount ) * 100 ) :
973
+ 0;
974
+
975
+ // Calculate reviced
976
+ const reviced = parseInt( revicedPerc );
977
+
978
+ // Transform arrays to revisedDetail format
979
+ const revisedDetail = [];
980
+
981
+ // Transform duplicateImages
982
+ if ( Array.isArray( oldSource.duplicateImages ) ) {
983
+ for ( const duplicate of oldSource.duplicateImages ) {
984
+ const parentId = `${oldSource.storeId}_${oldSource.dateString}_${duplicate.tempId}`;
985
+ const parentDetail = {
986
+ id: parentId,
987
+ clientId: oldSource.clientId,
988
+ storeId: oldSource.storeId,
989
+ tempId: duplicate.tempId,
990
+ dateString: oldSource.dateString,
991
+ timeRange: duplicate.timeRange,
992
+ processType: 'footfall',
993
+ revopsType: 'duplicate',
994
+ entryTime: duplicate.entryTime,
995
+ exitTime: duplicate.exitTime,
996
+ filePath: duplicate.filePath,
997
+ status: oldSource.duplicateStatus || 'submitted',
998
+ description: '',
999
+ isChecked: duplicate.isChecked !== undefined ? duplicate.isChecked : false,
1000
+ type: 'tagging-reflect',
1001
+ parent: null,
1002
+ isParent: true,
1003
+ createdAt: oldSource.createdAt || new Date(),
1004
+ updatedAt: oldSource.updatedAt || new Date(),
1005
+ duplicateImage: [],
1006
+ };
1007
+
1008
+ // Add child duplicate images
1009
+ if ( Array.isArray( duplicate.data ) ) {
1010
+ parentDetail.duplicateImage = duplicate.data.map( ( child ) => ( {
1011
+ id: `${oldSource.storeId}_${oldSource.dateString}_${child.tempId}`,
1012
+ tempId: child.tempId,
1013
+ timeRange: child.timeRange,
1014
+ entryTime: child.entryTime,
1015
+ exitTime: child.exitTime,
1016
+ filePath: child.filePath,
1017
+ isChecked: child.isChecked !== undefined ? child.isChecked : true,
1018
+ } ) );
1019
+
1020
+ // Add child details to revisedDetail
1021
+ for ( const child of duplicate.data ) {
1022
+ revisedDetail.push( {
1023
+ id: `${oldSource.storeId}_${oldSource.dateString}_${child.tempId}`,
1024
+ clientId: oldSource.clientId,
1025
+ storeId: oldSource.storeId,
1026
+ tempId: child.tempId,
1027
+ dateString: oldSource.dateString,
1028
+ timeRange: child.timeRange,
1029
+ processType: 'footfall',
1030
+ revopsType: 'duplicate',
1031
+ entryTime: child.entryTime,
1032
+ exitTime: child.exitTime,
1033
+ filePath: child.filePath,
1034
+ status: oldSource.duplicateStatus || 'submitted',
1035
+ description: '',
1036
+ isChecked: child.isChecked !== undefined ? child.isChecked : false,
1037
+ type: 'tagging-reflect',
1038
+ parent: duplicate.tempId,
1039
+ isParent: false,
1040
+ createdAt: oldSource.createdAt || new Date(),
1041
+ updatedAt: oldSource.updatedAt || new Date(),
1042
+ duplicateImage: [],
1043
+ } );
1044
+ }
1045
+ }
1046
+
1047
+ revisedDetail.push( parentDetail );
1048
+ }
1049
+ }
1050
+
1051
+ // Transform houseKeeping
1052
+ if ( Array.isArray( oldSource.houseKeeping ) ) {
1053
+ for ( const item of oldSource.houseKeeping ) {
1054
+ revisedDetail.push( {
1055
+ id: `${oldSource.storeId}_${oldSource.dateString}_${item.tempId}`,
1056
+ clientId: oldSource.clientId,
1057
+ storeId: oldSource.storeId,
1058
+ tempId: item.tempId,
1059
+ dateString: oldSource.dateString,
1060
+ timeRange: item.timeRange,
1061
+ processType: 'footfall',
1062
+ revopsType: 'houseKeeping',
1063
+ entryTime: item.entryTime,
1064
+ exitTime: item.exitTime,
1065
+ filePath: item.filePath,
1066
+ status: oldSource.houseKeepingStatus || 'submitted',
1067
+ description: '',
1068
+ isChecked: item.isChecked !== undefined ? item.isChecked : false,
1069
+ type: 'tagging-reflect',
1070
+ parent: null,
1071
+ isParent: false,
1072
+ createdAt: oldSource.createdAt || new Date(),
1073
+ updatedAt: oldSource.updatedAt || new Date(),
1074
+ duplicateImage: [],
1075
+ } );
1076
+ }
1077
+ }
1078
+
1079
+ // Transform employee
1080
+ if ( Array.isArray( oldSource.employee ) ) {
1081
+ for ( const item of oldSource.employee ) {
1082
+ revisedDetail.push( {
1083
+ id: `${oldSource.storeId}_${oldSource.dateString}_${item.tempId}`,
1084
+ clientId: oldSource.clientId,
1085
+ storeId: oldSource.storeId,
1086
+ tempId: item.tempId,
1087
+ dateString: oldSource.dateString,
1088
+ timeRange: item.timeRange,
1089
+ processType: 'footfall',
1090
+ revopsType: 'employee',
1091
+ entryTime: item.entryTime,
1092
+ exitTime: item.exitTime,
1093
+ filePath: item.filePath,
1094
+ status: oldSource.employeeStatus || 'submitted',
1095
+ description: '',
1096
+ isChecked: item.isChecked !== undefined ? item.isChecked : false,
1097
+ type: 'tagging-reflect',
1098
+ parent: null,
1099
+ isParent: false,
1100
+ createdAt: oldSource.createdAt || new Date(),
1101
+ updatedAt: oldSource.updatedAt || new Date(),
1102
+ duplicateImage: [],
1103
+ } );
1104
+ }
1105
+ }
1106
+
1107
+ // Transform junk
1108
+ if ( Array.isArray( oldSource.junk ) ) {
1109
+ for ( const item of oldSource.junk ) {
1110
+ revisedDetail.push( {
1111
+ id: `${oldSource.storeId}_${oldSource.dateString}_${item.tempId}`,
1112
+ clientId: oldSource.clientId,
1113
+ storeId: oldSource.storeId,
1114
+ tempId: item.tempId,
1115
+ dateString: oldSource.dateString,
1116
+ timeRange: item.timeRange,
1117
+ processType: 'footfall',
1118
+ revopsType: 'junk',
1119
+ entryTime: item.entryTime,
1120
+ exitTime: item.exitTime,
1121
+ filePath: item.filePath,
1122
+ status: 'submitted',
1123
+ description: '',
1124
+ isChecked: item.isChecked !== undefined ? item.isChecked : false,
1125
+ type: 'tagging-reflect',
1126
+ parent: null,
1127
+ isParent: false,
1128
+ createdAt: oldSource.createdAt || new Date(),
1129
+ updatedAt: oldSource.updatedAt || new Date(),
1130
+ duplicateImage: [],
1131
+ } );
1132
+ }
1133
+ }
1134
+
1135
+ // Create count array
1136
+ const count = [
1137
+ {
1138
+ name: 'Duplicate',
1139
+ value: oldSource.duplicateCount || 0,
1140
+ key: 'duplicateCount',
1141
+ type: 'duplicate',
1142
+ },
1143
+ {
1144
+ name: 'Employee',
1145
+ value: oldSource.employeeCount || 0,
1146
+ key: 'employeeCount',
1147
+ type: 'employee',
1148
+ },
1149
+ {
1150
+ name: 'House keeping',
1151
+ value: oldSource.houseKeepingCount || 0,
1152
+ key: 'houseKeepingCount',
1153
+ type: 'houseKeeping',
1154
+ },
1155
+ {
1156
+ name: 'Junk',
1157
+ value: oldSource.junkCount || 0,
1158
+ key: 'junkCount',
1159
+ type: 'junk',
1160
+ },
1161
+ ];
1162
+
1163
+ // Create mappingInfo array
1164
+ const mappingInfo = [
1165
+ {
1166
+ type: 'tagging',
1167
+ mode: 'mobile',
1168
+ revicedFootfall,
1169
+ revicedPerc: `${revicedPerc}%`,
1170
+ reviced,
1171
+ count,
1172
+ revisedDetail,
1173
+ status: oldSource.status === 'open' ? 'Raised' : oldSource.status || 'Raised',
1174
+ createdByEmail: oldSource.email || '',
1175
+ createdByUserName: oldSource.userName || '',
1176
+ createdByRole: oldSource.role || 'user',
1177
+ createdAt: oldSource.createdAt || new Date(),
1178
+ },
1179
+ {
1180
+ type: 'review',
1181
+ count,
1182
+ revisedDetail,
1183
+ status: oldSource.status === 'open' ? 'Open' : oldSource.status || 'Open',
1184
+ dueDate: oldSource.updatedAt ? new Date( new Date( oldSource.updatedAt ).getTime() + 3 * 24 * 60 * 60 * 1000 ) : new Date( Date.now() + 3 * 24 * 60 * 60 * 1000 ),
1185
+ },
1186
+ ];
1187
+
1188
+ // Create new structure
1189
+ const newSource = {
1190
+ storeId: oldSource.storeId,
1191
+ type: 'store',
1192
+ dateString: oldSource.dateString,
1193
+ storeName: oldSource.storeName,
1194
+ ticketName: oldSource.ticketName,
1195
+ footfallCount: oldSource.footfallCount,
1196
+ clientId: oldSource.clientId,
1197
+ ticketId: oldSource.ticketId,
1198
+ createdAt: oldSource.createdAt,
1199
+ updatedAt: oldSource.updatedAt,
1200
+ status: oldSource.status === 'open' ? 'Raised' : oldSource.status || 'Raised',
1201
+ comments: oldSource.comments || '',
1202
+ revicedFootfall,
1203
+ revicedPerc: `${revicedPerc}%`,
1204
+ reviced,
1205
+ mappingInfo,
1206
+ };
1207
+
1208
+ // Update document in OpenSearch
1209
+ // await updateOpenSearchData( openSearch.footfallDirectory, documentId, { doc: newSource } );
1210
+ // migratedCount++;
1211
+
1212
+ logger.info( { message: 'Document migrated successfully', newSource, documentId, storeId: oldSource.storeId, dateString: oldSource.dateString } );
1213
+ } catch ( error ) {
1214
+ const errorMsg = `Error migrating document ${hit._id}: ${error.message}`;
1215
+ errors.push( errorMsg );
1216
+ logger.error( { error: error, documentId: hit._id, function: 'vmsDataMigration' } );
1217
+ }
1218
+ }
1219
+
1220
+ return res.sendSuccess( {
1221
+ message: `Migration completed. ${migratedCount} document(s) migrated.`,
1222
+ migrated: migratedCount,
1223
+ total: hits.length,
1224
+ errors: errors.length > 0 ? errors : undefined,
1225
+ } );
1226
+ } catch ( error ) {
1227
+ logger.error( { error: error, message: req.query, function: 'vmsDataMigration' } );
1228
+ const err = error.message || 'Internal Server Error';
1229
+ return res.sendError( err, 500 );
1230
+ }
1231
+ }
@@ -105,3 +105,13 @@ export const getCategorizedImagesValid = {
105
105
  body: getCategorizedImagesSchema,
106
106
  };
107
107
 
108
+ export const vmsDataMigrationSchema = joi.object( {
109
+ storeId: joi.string().optional(),
110
+ dateString: joi.string().optional(),
111
+ limit: joi.number().optional().default( 100 ),
112
+ } );
113
+
114
+ export const vmsDataMigrationValid = {
115
+ query: vmsDataMigrationSchema,
116
+ };
117
+
@@ -1,8 +1,8 @@
1
1
 
2
2
  import express from 'express';
3
- import { storeProcessedData, getconfig, revoptagging, getrevoptagging, revoptaggingcount, footFallImages, tagTempId, getCategorizedImages } from '../controllers/revop.controller.js';
3
+ import { storeProcessedData, getconfig, revoptagging, getrevoptagging, revoptaggingcount, footFallImages, tagTempId, getCategorizedImages, vmsDataMigration, migrateRevopIndex } from '../controllers/revop.controller.js';
4
4
  import { isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
5
- import { footfallImagesValid, getCategorizedImagesValid, storeProcessedDataValid, tagTempIdValid } from '../dtos/revop.dtos.js';
5
+ import { footfallImagesValid, getCategorizedImagesValid, storeProcessedDataValid, tagTempIdValid, vmsDataMigrationValid } from '../dtos/revop.dtos.js';
6
6
  import { deleteTaggedDuplicate, getTaggingConfig, mappingConfig } from '../validations/revop.validation.js';
7
7
 
8
8
  export const revopRouter = express.Router();
@@ -11,13 +11,15 @@ revopRouter
11
11
  .get( '/getconfig', isAllowedSessionHandler, getconfig )
12
12
  .post( '/tagging', isAllowedSessionHandler, revoptagging )
13
13
  .post( '/getrevoptagging', isAllowedSessionHandler, getrevoptagging )
14
+ .post( '/migrate-revop', migrateRevopIndex )
14
15
  .post( '/revoptaggingcount', isAllowedSessionHandler, revoptaggingcount )
15
16
 
16
17
  // new enhnacemnet (for footfall directory)
17
18
  .get( '/store-processed-data', isAllowedSessionHandler, validate( storeProcessedDataValid ), storeProcessedData )
18
19
  .get( '/footfall-images', isAllowedSessionHandler, validate( footfallImagesValid ), getTaggingConfig, footFallImages )
19
20
  .post( '/tag-tempId', isAllowedSessionHandler, validate( tagTempIdValid ), deleteTaggedDuplicate, mappingConfig, tagTempId )
20
- .post( '/get-categorized-images', isAllowedSessionHandler, validate( getCategorizedImagesValid ), getCategorizedImages );
21
+ .post( '/get-categorized-images', isAllowedSessionHandler, validate( getCategorizedImagesValid ), getCategorizedImages )
22
+ .post( '/vms-data-migration', validate( vmsDataMigrationValid ), vmsDataMigration );
21
23
 
22
24
 
23
25
  export default revopRouter;