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.
|
|
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.
|
|
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
|
|
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
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
234
|
-
|
|
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
|
-
|
|
237
|
-
const previousData = hits.find( ( d ) => d._id === dateStringPrevious )?._source || null;
|
|
268
|
+
const responseArray = [];
|
|
238
269
|
|
|
239
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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?.
|
|
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
|
}
|
package/src/dtos/revop.dtos.js
CHANGED
|
@@ -4,7 +4,8 @@ import dayjs from 'dayjs';
|
|
|
4
4
|
export const storeProcessedDataSchema = joi.object( {
|
|
5
5
|
|
|
6
6
|
storeId: joi.string().required(),
|
|
7
|
-
|
|
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(),
|
|
@@ -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
|
|
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 ) {
|