tango-app-api-analysis-traffic 3.8.7-vms.0 → 3.8.7-vms.2

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.0",
3
+ "version": "3.8.7-vms.2",
4
4
  "description": "Traffic Analysis",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -23,7 +23,7 @@
23
23
  "mongodb": "^6.8.0",
24
24
  "nodemon": "^3.1.4",
25
25
  "swagger-ui-express": "^5.0.1",
26
- "tango-api-schema": "^2.4.9",
26
+ "tango-api-schema": "^2.4.23",
27
27
  "tango-app-api-middleware": "^3.6.5",
28
28
  "winston": "^3.13.1",
29
29
  "winston-daily-rotate-file": "^5.0.0"
@@ -2,7 +2,8 @@ import { logger, insertOpenSearchData, getOpenSearchData, updateOpenSearchData }
2
2
  import { findOnerevopConfig } from '../services/revopConfig.service.js';
3
3
  import * as clientService from '../services/clients.services.js';
4
4
  import { bulkUpdate, upsertWithScript } from 'tango-app-api-middleware/src/utils/openSearch.js';
5
- import dayjs from 'dayjs';
5
+ import { findOneVmsStoreRequest } from '../services/vmsStoreRequest.service.js';
6
+ // import dayjs from 'dayjs';
6
7
  // Lamda Service Call //
7
8
  async function LamdaServiceCall( url, data ) {
8
9
  try {
@@ -211,44 +212,135 @@ export async function storeProcessedData( req, res ) {
211
212
  try {
212
213
  const openSearch = JSON.parse( process.env.OPENSEARCH );
213
214
  const inputData = req.query;
214
- const previousDate = dayjs( inputData.dateString ).subtract( 1, 'day' ).format( 'YYYY-MM-DD' );
215
- const dateString = `${inputData.storeId}_${inputData.dateString}`;
216
- const dateStringPrevious = `${inputData.storeId}_${previousDate}`;
217
- const getQuery = {
218
- query: {
219
- terms: {
220
- _id: [ dateString, dateStringPrevious ],
221
- },
222
- },
223
- _source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
224
- sort: [
225
- {
226
- date_iso: {
227
- order: 'desc',
215
+ const { fromDate, toDate, storeId } = inputData;
216
+
217
+ // Multi-date range handling for a single store
218
+ if ( fromDate && toDate && storeId ) {
219
+ const dayjs = ( await import( 'dayjs' ) ).default;
220
+ const isSameOrBefore = ( await import( 'dayjs/plugin/isSameOrBefore.js' ) ).default;
221
+ dayjs.extend( isSameOrBefore );
222
+
223
+ let start = dayjs( fromDate );
224
+ let end = dayjs( toDate );
225
+
226
+ if ( !start.isValid() || !end.isValid() ) {
227
+ return res.sendError( 'Invalid date range supplied', 400 );
228
+ }
229
+
230
+ if ( end.isBefore( start ) ) {
231
+ [ start, end ] = [ end, start ];
232
+ }
233
+
234
+ const allDateStrings = [];
235
+ const orderedDates = [];
236
+
237
+ while ( start.isSameOrBefore( end ) ) {
238
+ const formatted = start.format( 'YYYY-MM-DD' );
239
+ orderedDates.push( formatted );
240
+ allDateStrings.push( `${storeId}_${formatted}` );
241
+ start = start.add( 1, 'day' );
242
+ }
243
+
244
+ if ( allDateStrings.length === 0 ) {
245
+ return res.sendSuccess( [] );
246
+ }
247
+
248
+ const footfallQuery = {
249
+ query: {
250
+ terms: {
251
+ _id: allDateStrings,
228
252
  },
229
253
  },
230
- ],
231
- };
254
+ _source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
255
+ sort: [
256
+ { date_iso: { order: 'asc' } },
257
+ ],
258
+ size: allDateStrings.length,
259
+ };
232
260
 
233
- const getData = await getOpenSearchData( openSearch.footfall, getQuery );
234
- const hits = getData?.body?.hits?.hits || [];
261
+ const multiGet = await getOpenSearchData( openSearch.footfall, footfallQuery );
262
+ const multiHits = multiGet?.body?.hits?.hits || [];
263
+ const hitsMap = new Map();
264
+ multiHits.forEach( ( hit ) => {
265
+ hitsMap.set( hit?._id, hit?._source || null );
266
+ } );
235
267
 
236
- const processedData = hits.find( ( d ) => d._id === dateString )?._source || null;
237
- const previousData = hits.find( ( d ) => d._id === dateStringPrevious )?._source || null;
268
+ const responseArray = [];
238
269
 
239
- let footfallCountTrend = 0;
270
+ for ( let i = 0; i < orderedDates.length; i++ ) {
271
+ const currentDate = orderedDates[i];
272
+ const currentId = `${storeId}_${currentDate}`;
273
+ const processedData = hitsMap.get( currentId );
274
+ if ( !processedData ) {
275
+ responseArray.push( {
276
+ date: currentDate,
277
+ footfallCount: 0,
278
+ footfallCountTrend: 0,
279
+ downtime: 0,
280
+ } );
281
+ continue;
282
+ }
240
283
 
241
- if ( processedData && previousData && previousData.footfall_count ) {
242
- footfallCountTrend = Math.round(
243
- ( ( processedData.footfall_count - previousData.footfall_count ) / previousData.footfall_count ) * 100,
244
- );
245
- }
284
+ const prevDate = dayjs( currentDate ).subtract( 1, 'day' ).format( 'YYYY-MM-DD' );
285
+ const prevId = `${storeId}_${prevDate}`;
286
+ const previousData = hitsMap.get( prevId );
246
287
 
247
- return res.sendSuccess( {
248
- footfallCount: processedData?.footfall_count || 0,
249
- footfallCountTrend,
250
- downtime: processedData?.down_time || 0,
251
- } );
288
+ let footfallCountTrend = 0;
289
+ if ( previousData && previousData.footfall_count ) {
290
+ footfallCountTrend = Math.round(
291
+ ( ( processedData.footfall_count - previousData.footfall_count ) / previousData.footfall_count ) * 100,
292
+ );
293
+ }
294
+ // Add ticket status from openSearch.footfallDirectory (_source.status)
295
+ let ticketStatus = null;
296
+ // Try to find a matching footfallDirectory record for this date+storeId
297
+ // const ticketKey = `${storeId}_${currentDate}`;
298
+ const footfallDirQuery = {
299
+ query: {
300
+ bool: {
301
+ must: [
302
+ { term: { 'storeId.keyword': storeId } },
303
+ { term: { 'dateString': currentDate } },
304
+ { term: { 'ticketName.keyword': 'footfall-directory' } },
305
+ ],
306
+ },
307
+ },
308
+ size: 1,
309
+ _source: [ 'status' ],
310
+ };
311
+ try {
312
+ const footfallDirRes = await getOpenSearchData( openSearch.footfallDirectory, footfallDirQuery );
313
+ const hit = footfallDirRes?.body?.hits?.hits?.[0];
314
+ ticketStatus = hit?._source?.status || null;
315
+ } catch ( err ) {
316
+ logger.warn( { message: 'Could not get ticket status from footfallDirectory', error: err } );
317
+ }
318
+ // Check if request status ("raised") should be enabled or disabled by querying MongoDB config
319
+ // We'll assume findOnerevopConfig can fetch the record with status for this storeId and dateString
320
+ let raisedStatusEnabled = 'reset'; // default: enabled
321
+ try {
322
+ const mongoConfig = await findOneVmsStoreRequest( { storeId: storeId, dateString: currentDate } );
323
+ if ( mongoConfig && mongoConfig.status ) {
324
+ raisedStatusEnabled = mongoConfig.status;
325
+ }
326
+ } catch ( err ) {
327
+ logger.warn( { message: 'Could not get request status from MongoDB', error: err } );
328
+ // Leave raisedStatusEnabled as default
329
+ }
330
+
331
+ responseArray.push( {
332
+ date: processedData?.date_string || currentDate,
333
+ footfallCount: processedData?.footfall_count || 0,
334
+ footfallCountTrend,
335
+ downtime: processedData?.down_time || 0,
336
+ ticketStatus,
337
+ raisedStatusEnabled,
338
+ } );
339
+ }
340
+
341
+ return res.sendSuccess( responseArray );
342
+ }
343
+ return res.sendError( 'Required parameters missing', 400 );
252
344
  } catch ( error ) {
253
345
  logger.error( { message: error, data: req.query, function: 'storeProcessedData' } );
254
346
  const err = error.message || 'Internal Server Error';
@@ -287,7 +379,7 @@ export async function footFallImages( req, res ) {
287
379
  ],
288
380
  },
289
381
  },
290
- '_source': [ 'dateString', 'storeId', 'duplicateCount', 'footfallCount', 'employeeCount', 'houseKeepingCount', 'junkCount', 'status', 'ticketId', 'comments', 'userName', 'role', 'createdAt', 'email', 'houseKeepingACCount', 'duplicateACCount', 'employeeACCount', 'junkACCount', 'approverEmail', 'approverRole', 'approverUserName' ],
382
+ // '_source': [ 'dateString', 'storeId', 'duplicateCount', 'footfallCount', 'employeeCount', 'houseKeepingCount', 'junkCount', 'status', 'ticketId', 'comments', 'userName', 'role', 'createdAt', 'email', 'houseKeepingACCount', 'duplicateACCount', 'employeeACCount', 'junkACCount', 'approverEmail', 'approverRole', 'approverUserName' ],
291
383
 
292
384
  };
293
385
 
@@ -314,7 +406,7 @@ export async function footFallImages( req, res ) {
314
406
  if ( resultData ) {
315
407
  temp.length? temp[0].status = 'open': null;
316
408
  if ( resultData.status_code == '200' ) {
317
- return res.sendSuccess( { ...resultData, ticketStatus: temp?.length > 0? temp : null, config: req?.store?.revopTagging } );
409
+ return res.sendSuccess( { ...resultData, ticketStatus: temp?.length > 0? temp : null, config: req?.store?.footfallDirectoryConfigs } );
318
410
  } else {
319
411
  return res.sendError( 'No Content', 204 );
320
412
  }
@@ -4,7 +4,8 @@ import dayjs from 'dayjs';
4
4
  export const storeProcessedDataSchema = joi.object( {
5
5
 
6
6
  storeId: joi.string().required(),
7
- dateString: joi.string().required(),
7
+ fromDate: joi.string().required(),
8
+ toDate: joi.string().required(),
8
9
 
9
10
  } );
10
11
 
@@ -65,6 +66,9 @@ export const tagTempIdSchema = joi.object( {
65
66
  revopsType: joi.string().required(),
66
67
  timeRange: joi.string().required(),
67
68
  isChecked: joi.boolean().required().allow( null ),
69
+ mode: joi.string().valid( 'web', 'mobile' ).required().messages( {
70
+ 'any.only': 'type must be one of [mobile,web]',
71
+ } ),
68
72
  duplicateImage: joi.array().items( joi.object(
69
73
  {
70
74
  tempId: joi.number().required(),
@@ -0,0 +1,5 @@
1
+ import vmsStoreRequestModel from 'tango-api-schema/schema/vmsStoreRequest.model.js';
2
+
3
+ export async function findOneVmsStoreRequest( query = {} ) {
4
+ return await vmsStoreRequestModel.findOne( query );
5
+ };
@@ -1,11 +1,25 @@
1
1
  import { getOpenSearchCount, logger } from 'tango-app-api-middleware';
2
2
  import { findOneStore } from '../services/stores.service.js';
3
3
  import { deleteByQuery } from 'tango-app-api-middleware/src/utils/openSearch.js';
4
+ import { findOne } from '../services/clients.services.js';
4
5
 
5
6
  export async function getTaggingConfig( req, res, next ) {
6
7
  try {
7
8
  const inputData= req.query;
8
- const getData = await findOneStore( { storeId: inputData.storeId }, { revopTagging: 1 } );
9
+ const clientId = inputData.storeId.split( '-' )[0];
10
+ const getData = await findOne( { clientId: clientId }, { footfallDirectoryConfigs: 1 } );
11
+
12
+ // Convert "taggingLimitation" array (if present) to "config" object with expected key-value pairs
13
+ let config = {};
14
+ if ( getData && Array.isArray( getData.taggingLimitation ) ) {
15
+ for ( const item of getData.taggingLimitation ) {
16
+ if ( item && item.type && typeof item.value !== 'undefined' && item.unit ) {
17
+ config[item.type] = `${item.value}${item.unit}`;
18
+ }
19
+ }
20
+ }
21
+ getData.config = config;
22
+
9
23
  req.store = getData;
10
24
  next();
11
25
  } catch ( error ) {