tango-app-api-analysis-traffic 3.8.7-vms.3 → 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.
@@ -3129,29 +3129,30 @@ export const managerTrafficDensityExport = async ( req, res ) => {
3129
3129
  }
3130
3130
  };
3131
3131
 
3132
- // async function getGeocodedAddress( lat, lng ) {
3133
- // try {
3134
- // const apiKey = 'AIzaSyDlOezgwQO0JviD0aizrCuN1FY9tcWfR3o'; // Use this if you're using dotenv
3135
- // const url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${apiKey}`;
3136
-
3137
- // const requestOptions = {
3138
- // method: 'GET',
3139
- // headers: {
3140
- // 'Content-Type': 'application/json',
3141
- // },
3142
- // };
3143
- // const response = await fetch( url, requestOptions );
3144
- // console.log( 'response =>', response );
3145
- // if ( !response.ok ) {
3146
- // throw new Error( `Response status: ${response.status}` );
3147
- // return false;
3148
- // }
3149
- // const json = await response.json();
3150
- // return json;
3151
- // } catch ( error ) {
3152
- // console.log( 'getGeocodedAddress error =>', error );
3153
- // logger.error( { error: error, message: data, function: 'getGeocodedAddress' } );
3154
- // }
3155
- // }
3156
- // let getGEO = await getGeocodedAddress( 12.900260100404893, 80.23384232089138 );
3157
- // console.log( 'getGEO =>', getGEO );
3132
+
3133
+ export const getStoreListv2 = async ( req, res ) => {
3134
+ try {
3135
+ let reqestData = req.body;
3136
+ let getClientData = await getClientConfig( reqestData.clientId );
3137
+ if ( !getClientData ) {
3138
+ return res.sendError( 'Invalid Client Id', 400 );
3139
+ }
3140
+ reqestData.featureConfigs = getClientData.featureConfigs;
3141
+ let LamdaURL = 'https://nmnnlq3ie65bljcvxauhxmsrpa0edhfa.lambda-url.ap-south-1.on.aws/';
3142
+ let resultData = await LamdaServiceCall( LamdaURL, reqestData );
3143
+ if ( resultData ) {
3144
+ if ( resultData.status_code == '200' ) {
3145
+ return res.sendSuccess( resultData );
3146
+ } else {
3147
+ return res.sendError( 'No Content', 204 );
3148
+ }
3149
+ } else {
3150
+ return res.sendError( 'No Content', 204 );
3151
+ }
3152
+ } catch ( error ) {
3153
+ const err = error.message || 'Internal Server Error';
3154
+ logger.error( { error: error, message: req.body, function: 'getStoreListv2' } );
3155
+ return res.sendError( err, 500 );
3156
+ }
3157
+ };
3158
+
@@ -56,8 +56,8 @@ export const tagTempIdSchema = joi.object( {
56
56
 
57
57
  const diff = today.diff( inputDate, 'day' );
58
58
 
59
- if ( diff > 7 ) {
60
- return helpers.message( 'Tagging is not allowed for a period exceeding 7 days' );
59
+ if ( diff > 3 ) {
60
+ return helpers.message( 'Tagging is not allowed for a period exceeding 3 days' );
61
61
  }
62
62
 
63
63
  return value;
@@ -84,6 +84,7 @@ export const tagTempIdSchema = joi.object( {
84
84
  entryTime: joi.string().required(),
85
85
  exitTime: joi.string().required(),
86
86
  filePath: joi.string().required(),
87
+ comments: joi.string().optional().allow( '' ),
87
88
 
88
89
  } );
89
90
 
@@ -104,3 +105,13 @@ export const getCategorizedImagesValid = {
104
105
  body: getCategorizedImagesSchema,
105
106
  };
106
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
+
@@ -239,6 +239,17 @@ export const validateCountryHeaderSchemav2 = joi.object( {
239
239
  export const validateCountryHeaderParamsv2 = {
240
240
  body: validateCountryHeaderSchemav2,
241
241
  };
242
+
243
+ export const validateGetStoreListSchemav2 = joi.object( {
244
+ clientId: joi.string().required(),
245
+ storeId: joi.array().items( joi.string().required() ).required(),
246
+ downtime: joi.array().items( joi.string().required() ).optional(),
247
+ fromDate: joi.string().required(),
248
+ toDate: joi.string().required(),
249
+ } );
250
+ export const validateGetStoreList = {
251
+ body: validateGetStoreListSchemav2,
252
+ };
242
253
  export const getMyProductSchema = joi.object( {
243
254
  clientId: joi.string().required(),
244
255
  storeId: joi.array().optional().empty(),
@@ -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,15 +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
-
21
-
22
- .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 );
23
23
 
24
24
 
25
25
  export default revopRouter;
@@ -92,6 +92,7 @@ import {
92
92
  funnelV3,
93
93
  getStoreMapDataV3,
94
94
  managerTrafficDensityExport,
95
+ getStoreListv2,
95
96
  } from '../controllers/tangoTrafficV3.controllers.js';
96
97
  analysisTrafficRouter
97
98
  .get( '/welcome', welcome )
@@ -175,5 +176,6 @@ analysisTrafficRouter
175
176
  .post( '/headerCountry_v2', isAllowedSessionHandler, isAllowedClient, validate( validationDtos.validateCountryHeaderParamsv2 ), getAssinedStore, headerCountryV2 )
176
177
  .post( '/checkTodayReportStatus', isAllowedSessionHandler, checkTodayReportStatus )
177
178
  .post( '/headerZoneV2', isAllowedSessionHandler, headerZoneV2 )
178
- .post( '/trafficDensityExport_v2', managerTrafficDensityExport );
179
+ .post( '/trafficDensityExport_v2', managerTrafficDensityExport )
180
+ .post( '/get-store-list-v2', isAllowedSessionHandler, isAllowedClient, validate( validationDtos.validateGetStoreList ), getStoreListv2 );
179
181
  export default analysisTrafficRouter;
@@ -1,26 +1,100 @@
1
1
  import { getOpenSearchCount, logger } from 'tango-app-api-middleware';
2
- import { findOneStore } from '../services/stores.service.js';
3
- import { deleteByQuery } from 'tango-app-api-middleware/src/utils/openSearch.js';
4
- import { findOne } from '../services/clients.services.js';
2
+ import { deleteByQuery, getOpenSearchData } from 'tango-app-api-middleware/src/utils/openSearch.js';
3
+ import { aggregate } from '../services/clients.services.js';
5
4
 
6
5
  export async function getTaggingConfig( req, res, next ) {
7
6
  try {
8
7
  const inputData= req.query;
9
8
  const clientId = inputData.storeId.split( '-' )[0];
10
- const getData = await findOne( { clientId: clientId }, { footfallDirectoryConfigs: 1 } );
9
+ const configQuery = [
10
+ {
11
+ $match: {
12
+ clientId,
13
+ },
14
+ },
15
+
16
+ // Convert all effectiveFrom to proper Date
17
+ {
18
+ $addFields: {
19
+ taggingLimitationWithDate: {
20
+ $map: {
21
+ input: '$footfallDirectoryConfigs.taggingLimitation',
22
+ as: 'item',
23
+ in: {
24
+ effectiveFrom: { $toDate: '$$item.effectiveFrom' },
25
+ values: '$$item.values',
26
+ },
27
+ },
28
+ },
29
+ },
30
+ },
31
+
32
+ // Filter items <= input date
33
+ {
34
+ $addFields: {
35
+ matchedLimitation: {
36
+ $filter: {
37
+ input: '$taggingLimitationWithDate',
38
+ as: 'item',
39
+ cond: {
40
+ $lte: [
41
+ '$$item.effectiveFrom',
42
+ { $toDate: inputData.dateString },
43
+ ],
44
+ },
45
+ },
46
+ },
47
+ },
48
+ },
49
+
50
+ // Sort DESC and pick ONLY top 1 -> latest effective record
51
+ {
52
+ $addFields: {
53
+ effectiveLimitation: {
54
+ $arrayElemAt: [
55
+ {
56
+ $slice: [
57
+ {
58
+ $sortArray: {
59
+ input: '$matchedLimitation',
60
+ sortBy: { effectiveFrom: -1 },
61
+ },
62
+ },
63
+ 1,
64
+ ],
65
+ },
66
+ 0,
67
+ ],
68
+ },
69
+ },
70
+ },
71
+
72
+ {
73
+ $project: {
74
+ config: 1,
75
+ effectiveLimitation: 1,
76
+ footfallDirectoryConfigs: 1,
77
+ },
78
+ },
79
+ ];
80
+
81
+
82
+ const getData = await aggregate( configQuery );
11
83
 
12
84
  // Convert "taggingLimitation" array (if present) to "config" object with expected key-value pairs
13
85
  let config = {};
14
- if ( getData && Array.isArray( getData.taggingLimitation ) ) {
15
- for ( const item of getData.taggingLimitation ) {
86
+ if ( getData && getData?.length > 0 && Array.isArray( getData?.[0]?.effectiveLimitation?.values ) ) {
87
+ for ( const item of getData?.[0]?.footfallDirectoryConfigs?.taggingLimitation?.[0]?.values ) {
16
88
  if ( item && item.type && typeof item.value !== 'undefined' && item.unit ) {
17
89
  config[item.type] = `${item.value}${item.unit}`;
18
90
  }
19
91
  }
20
92
  }
21
- getData.config = config;
93
+ // getData[0].footfallDirectoryConfigs.taggingLimitation = [];
94
+ getData[0].footfallDirectoryConfigs.config = config;
22
95
 
23
- req.store = getData;
96
+ getData[0].footfallDirectoryConfigs.taggingLimitation = getData?.[0]?.effectiveLimitation?.values;
97
+ req.store = getData[0];
24
98
  next();
25
99
  } catch ( error ) {
26
100
  logger.error( { error: error, message: req.body, function: 'traffic-revop-getTaggingConfig' } );
@@ -65,41 +139,126 @@ export async function getFootfallCount( req, res, next ) {
65
139
  export async function mappingConfig( req, res, next ) {
66
140
  try {
67
141
  const inputData = req.body;
142
+
68
143
  const openSearch = JSON.parse( process.env.OPENSEARCH );
69
- const config =await findOneStore( { storeId: inputData.storeId }, { revopTagging: 1 } );
70
- if ( inputData.revopsType == 'employee' ) {
71
- const getQuery = {
72
- query: {
73
- bool: {
74
- must: [
75
- {
76
- term: {
77
- 'storeId.keyword': inputData.storeId,
78
- },
144
+ const footfallQuery ={
145
+ query: {
146
+ bool: {
147
+ must: [
148
+ {
149
+ term: {
150
+ 'store_id.keyword': inputData.storeId,
79
151
  },
80
- {
81
- term: {
82
- 'dateString': inputData.dateString,
83
- },
152
+ },
153
+ {
154
+ term: {
155
+ 'date_string': inputData.dateString,
84
156
  },
157
+ },
158
+
159
+ ],
160
+ },
161
+ },
162
+ };
163
+
164
+
165
+ const footfallOutput = await getOpenSearchData( openSearch.footfall, footfallQuery );
166
+ if ( footfallOutput?.body?.hits?.hits?.length === 0 ) {
167
+ return res.sendError( 'No updated footfall for this date', 400 );
168
+ }
169
+ const getFootfallCount = footfallOutput?.body?.hits?.hits;
170
+ const footfall = getFootfallCount?.[0]?._source?.footfall_count;
171
+ const clientId = inputData?.storeId?.split( '-' )[0];
172
+ const configQuery = [
173
+ {
174
+ $match: {
175
+ clientId: clientId,
176
+ },
177
+ },
178
+
179
+ // Convert all effectiveFrom to proper Date
180
+ {
181
+ $addFields: {
182
+ taggingLimitationWithDate: {
183
+ $map: {
184
+ input: '$footfallDirectoryConfigs.taggingLimitation',
185
+ as: 'item',
186
+ in: {
187
+ effectiveFrom: { $toDate: '$$item.effectiveFrom' },
188
+ values: '$$item.values',
189
+ },
190
+ },
191
+ },
192
+ },
193
+ },
194
+
195
+ // Filter items <= input date
196
+ {
197
+ $addFields: {
198
+ matchedLimitation: {
199
+ $filter: {
200
+ input: '$taggingLimitationWithDate',
201
+ as: 'item',
202
+ cond: {
203
+ $lte: [
204
+ '$$item.effectiveFrom',
205
+ { $toDate: inputData.dateString },
206
+ ],
207
+ },
208
+ },
209
+ },
210
+ },
211
+ },
212
+
213
+ // Sort DESC and pick ONLY top 1 -> latest effective record
214
+ {
215
+ $addFields: {
216
+ effectiveLimitation: {
217
+ $arrayElemAt: [
85
218
  {
86
- term: {
87
- 'revopsType.keyword': inputData.revopsType,
88
- },
219
+ $slice: [
220
+ {
221
+ $sortArray: {
222
+ input: '$matchedLimitation',
223
+ sortBy: { effectiveFrom: -1 },
224
+ },
225
+ },
226
+ 1,
227
+ ],
89
228
  },
229
+ 0,
90
230
  ],
91
231
  },
92
232
  },
93
- };
233
+ },
94
234
 
95
- const getData = await getOpenSearchCount( openSearch.revop, getQuery );
96
- if ( getData && getData?.body?.count >= config?.revopTagging?.employee ) {
97
- return res.sendError( `Select up to ${config?.revopTagging?.employee} items only`, 400 );
98
- } else {
99
- next();
100
- }
101
- } else if ( inputData.revopsType == 'house-keeping' ) {
102
- const getQuery = {
235
+ {
236
+ $project: {
237
+ config: 1,
238
+ effectiveLimitation: 1,
239
+ footfallDirectoryConfigs: 1,
240
+ },
241
+ },
242
+ ];
243
+ const getConfig = await aggregate( configQuery );
244
+ const taggingLimitation = getConfig?.[0]?.effectiveLimitation?.values;
245
+
246
+ // Find the tagging limitation for the given revopsType
247
+ let matchedLimitation = null;
248
+ if ( Array.isArray( taggingLimitation ) ) {
249
+ matchedLimitation = taggingLimitation.find(
250
+ ( l ) => l.type === inputData.revopsType,
251
+ );
252
+ }
253
+
254
+ if ( matchedLimitation ) {
255
+ // Determine the limit value
256
+ let limitValue = Number( matchedLimitation.value ) || 0;
257
+ let unit = matchedLimitation.unit;
258
+
259
+ // Assuming getData and/or getOpenSearchCount provides the actual tagged count for revopsType
260
+ // Query OpenSearch for current tagged count for this revopsType
261
+ const taggedCountQuery = {
103
262
  query: {
104
263
  bool: {
105
264
  must: [
@@ -118,92 +277,51 @@ export async function mappingConfig( req, res, next ) {
118
277
  'revopsType.keyword': inputData.revopsType,
119
278
  },
120
279
  },
280
+ {
281
+ term: {
282
+ 'isParent': false,
283
+ },
284
+ },
121
285
  ],
122
286
  },
123
287
  },
124
288
  };
125
- const getData = await getOpenSearchCount( openSearch.revop, getQuery );
126
- if ( getData && getData?.body?.count >= config?.revopTagging?.houseKeeping ) {
127
- return res.sendError( `Select up to ${config?.revopTagging?.houseKeeping} items only`, 400 );
289
+ const taggedData = await getOpenSearchCount( openSearch.revop, taggedCountQuery );
290
+ const taggedValue = ( taggedData?.body?.count || 0 )+ ( inputData.revopsType == 'duplicate'? inputData?.duplicateImage?.length : 1 );
291
+ // If the unit is %, compare percentage of taggedValue/footfall, otherwise compare taggedValue to limitValue directly
292
+ let isLimitExceeded = false;
293
+ if ( unit === '%' ) {
294
+ // footfall may be undefined, treat as 0 (avoid division by zero)
295
+ const totalFootfall = Number( footfall ) || 0;
296
+ const taggedPercent = totalFootfall > 0 ? ( taggedValue / totalFootfall ) * 100 : 0;
297
+ isLimitExceeded = taggedPercent > limitValue;
298
+ logger.info( {
299
+ limitType: 'PERCENT',
300
+ taggedValue,
301
+ totalFootfall,
302
+ taggedPercent,
303
+ limitValue,
304
+ isLimitExceeded,
305
+ forRevopsType: inputData.revopsType,
306
+ } );
128
307
  } else {
129
- next();
308
+ // Non-percent, treat limitValue as an absolute number
309
+ isLimitExceeded = taggedValue > limitValue;
310
+ logger.info( {
311
+ limitType: 'ABSOLUTE',
312
+ taggedValue,
313
+ limitValue,
314
+ isLimitExceeded,
315
+ forRevopsType: inputData.revopsType,
316
+ } );
130
317
  }
131
- } else if ( inputData.revopsType === 'junk' ) {
132
- if ( ( req?.user?.userType == 'client' && req?.user?.userType !== 'user ' ) || req?.user?.userType === 'tango' ) {
133
- next();
134
- return;
135
- } else {
136
- return res.sendError( 'Forbidden to junk mapping', 500 );
318
+ if ( isLimitExceeded ) {
319
+ return res.sendError( `Limit exceed: Only ${limitValue}${unit || ''} items allowed for ${inputData.revopsType}`, 400 );
137
320
  }
321
+ return next();
138
322
  } else {
139
- next();
323
+ return next();
140
324
  }
141
- // else if ( inputData.revopsType == 'duplicate' ) {
142
- // const getFootfallQuery = {
143
- // query: {
144
- // terms: {
145
- // _id: [ inputData?.dateString ],
146
- // },
147
- // },
148
- // _source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
149
- // sort: [
150
- // {
151
- // date_iso: {
152
- // order: 'desc',
153
- // },
154
- // },
155
- // ],
156
- // };
157
-
158
- // const getFootfall = await getOpenSearchData( openSearch.footfall, getFootfallQuery );
159
- // const footfall = getFootfall?.body?.hites?.hits?.[0]?._source?.footfall_count;
160
- // const getQuery = {
161
- // query: {
162
- // bool: {
163
- // must: [
164
- // {
165
- // term: {
166
- // 'storeId.keyword': inputData.storeId,
167
- // },
168
- // },
169
- // {
170
- // term: {
171
- // 'dateString': inputData.dateString,
172
- // },
173
- // },
174
- // {
175
- // term: {
176
- // 'revopsType.keyword': inputData.revopsType,
177
- // },
178
- // },
179
- // {
180
- // term: {
181
- // 'parent.keyword': null,
182
- // },
183
- // },
184
- // ],
185
- // },
186
- // },
187
- // };
188
- // const getData = await getOpenSearchCount( openSearch.revop, getQuery );
189
- // logger.info( { getData: getData, footfall: footfall, duplicate: config?.revopTagging?.duplicate } );
190
- // if ( getData && footfall && config?.revopTagging?.duplicate ) {
191
- // const data = config?.revopTagging?.duplicate;
192
- // // Convert "20%" → 0.2 (handle both "20%" and 20)
193
- // const percentStr = typeof data === 'string' ? data.replace( '%', '' ) : data;
194
- // logger.info( { percentStr: percentStr } );
195
- // const percentValue =percentStr / 100;
196
-
197
- // const result = percentValue * footfall;
198
-
199
- // logger.info( { result: result, footfall: footfall } );
200
- // }
201
- // if ( getData && getData?.body?.count >= Math.round( result ) ) {
202
- // return res.sendError( `Select up to ${config?.revopTagging?.duplicate} items only`, 400 );
203
- // } else {
204
- // next();
205
- // }
206
- // }
207
325
  } catch ( error ) {
208
326
  logger.error( { error: error, message: req.body, function: 'traffic-revop-getTaggingConfig' } );
209
327
  next();