tango-app-api-analysis-traffic 3.8.1-alpha.8 → 3.8.2-alpha.0
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/.eslintrc.cjs +1 -1
- package/README.md +28 -28
- package/package.json +3 -3
- package/src/controllers/revop.controller.js +102 -24
- package/src/controllers/tangoTrafficV1.controllers.js +18 -18
- package/src/controllers/tangoTrafficV3.controllers.js +44 -0
- package/src/dtos/revop.dtos.js +36 -1
- package/src/dtos/validation.dtos.js +1 -1
- package/src/routes/revop.routes.js +2 -2
- package/src/routes/traffic.routes.js +3 -2
- package/src/validations/revop.validation.js +199 -1
package/.eslintrc.cjs
CHANGED
package/README.md
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
# README #
|
|
2
|
-
|
|
3
|
-
This README would normally document whatever steps are necessary to get your application up and running.
|
|
4
|
-
|
|
5
|
-
### What is this repository for? ###
|
|
6
|
-
|
|
7
|
-
* Quick summary
|
|
8
|
-
* Version
|
|
9
|
-
* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo)
|
|
10
|
-
|
|
11
|
-
### How do I get set up? ###
|
|
12
|
-
|
|
13
|
-
* Summary of set up
|
|
14
|
-
* Configuration
|
|
15
|
-
* Dependencies
|
|
16
|
-
* Database configuration
|
|
17
|
-
* How to run tests
|
|
18
|
-
* Deployment instructions
|
|
19
|
-
|
|
20
|
-
### Contribution guidelines ###
|
|
21
|
-
|
|
22
|
-
* Writing tests
|
|
23
|
-
* Code review
|
|
24
|
-
* Other guidelines
|
|
25
|
-
|
|
26
|
-
### Who do I talk to? ###
|
|
27
|
-
|
|
28
|
-
* Repo owner or admin
|
|
1
|
+
# README #
|
|
2
|
+
|
|
3
|
+
This README would normally document whatever steps are necessary to get your application up and running.
|
|
4
|
+
|
|
5
|
+
### What is this repository for? ###
|
|
6
|
+
|
|
7
|
+
* Quick summary
|
|
8
|
+
* Version
|
|
9
|
+
* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo)
|
|
10
|
+
|
|
11
|
+
### How do I get set up? ###
|
|
12
|
+
|
|
13
|
+
* Summary of set up
|
|
14
|
+
* Configuration
|
|
15
|
+
* Dependencies
|
|
16
|
+
* Database configuration
|
|
17
|
+
* How to run tests
|
|
18
|
+
* Deployment instructions
|
|
19
|
+
|
|
20
|
+
### Contribution guidelines ###
|
|
21
|
+
|
|
22
|
+
* Writing tests
|
|
23
|
+
* Code review
|
|
24
|
+
* Other guidelines
|
|
25
|
+
|
|
26
|
+
### Who do I talk to? ###
|
|
27
|
+
|
|
28
|
+
* Repo owner or admin
|
|
29
29
|
* Other community or team contact
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tango-app-api-analysis-traffic",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.2-alpha.0",
|
|
4
4
|
"description": "Traffic Analysis",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"start": "nodemon --exec \"eslint --fix . && node
|
|
8
|
+
"start": "nodemon --exec \"eslint --fix . && node app.js\""
|
|
9
9
|
},
|
|
10
10
|
"engines": {
|
|
11
11
|
"node": ">=18.10.0"
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"nodemon": "^3.1.4",
|
|
25
25
|
"swagger-ui-express": "^5.0.1",
|
|
26
26
|
"tango-api-schema": "^2.2.203",
|
|
27
|
-
"tango-app-api-middleware": "^3.1.
|
|
27
|
+
"tango-app-api-middleware": "^3.1.92",
|
|
28
28
|
"winston": "^3.13.1",
|
|
29
29
|
"winston-daily-rotate-file": "^5.0.0"
|
|
30
30
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logger, insertOpenSearchData, getOpenSearchData, updateOpenSearchData } from 'tango-app-api-middleware';
|
|
2
2
|
import { findOnerevopConfig } from '../services/revopConfig.service.js';
|
|
3
3
|
import * as clientService from '../services/clients.services.js';
|
|
4
|
-
import { upsertWithScript } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
4
|
+
import { bulkUpdate, upsertWithScript } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
5
5
|
import dayjs from 'dayjs';
|
|
6
6
|
// Lamda Service Call //
|
|
7
7
|
async function LamdaServiceCall( url, data ) {
|
|
@@ -74,6 +74,7 @@ export async function revoptagging( req, res ) {
|
|
|
74
74
|
} else {
|
|
75
75
|
item.createdAt = new Date();
|
|
76
76
|
item.updatedAt = new Date();
|
|
77
|
+
item.type='tagging';
|
|
77
78
|
await insertOpenSearchData( openSearch.revops, item );
|
|
78
79
|
}
|
|
79
80
|
}
|
|
@@ -229,12 +230,25 @@ export async function storeProcessedData( req, res ) {
|
|
|
229
230
|
],
|
|
230
231
|
};
|
|
231
232
|
|
|
232
|
-
|
|
233
233
|
const getData = await getOpenSearchData( openSearch.footfall, getQuery );
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
234
|
+
const hits = getData?.body?.hits?.hits || [];
|
|
235
|
+
|
|
236
|
+
const processedData = hits.find( ( d ) => d._id === dateString )?._source || null;
|
|
237
|
+
const previousData = hits.find( ( d ) => d._id === dateStringPrevious )?._source || null;
|
|
238
|
+
|
|
239
|
+
let footfallCountTrend = 0;
|
|
240
|
+
|
|
241
|
+
if ( processedData && previousData && previousData.footfall_count ) {
|
|
242
|
+
footfallCountTrend = Math.round(
|
|
243
|
+
( ( processedData.footfall_count - previousData.footfall_count ) / previousData.footfall_count ) * 100,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return res.sendSuccess( {
|
|
248
|
+
footfallCount: processedData?.footfall_count || 0,
|
|
249
|
+
footfallCountTrend,
|
|
250
|
+
downtime: processedData?.down_time || 0,
|
|
251
|
+
} );
|
|
238
252
|
} catch ( error ) {
|
|
239
253
|
logger.error( { message: error, data: req.query, function: 'storeProcessedData' } );
|
|
240
254
|
const err = error.message || 'Internal Server Error';
|
|
@@ -244,6 +258,7 @@ export async function storeProcessedData( req, res ) {
|
|
|
244
258
|
|
|
245
259
|
export async function footFallImages( req, res ) {
|
|
246
260
|
try {
|
|
261
|
+
const revop = JSON.parse( process.env.URL );
|
|
247
262
|
const inputData = req.query;
|
|
248
263
|
inputData.clientId = inputData.storeId.split( '-' )[0];
|
|
249
264
|
inputData.storeId=[ inputData.storeId ];
|
|
@@ -272,28 +287,40 @@ export async function footFallImages( req, res ) {
|
|
|
272
287
|
],
|
|
273
288
|
},
|
|
274
289
|
},
|
|
275
|
-
'_source': [ 'dateString', 'storeId', 'duplicateCount', 'footfallCount', 'employeeCount', 'houseKeepingCount', 'status', 'ticketId', 'comments', 'userName', 'role' ],
|
|
290
|
+
'_source': [ 'dateString', 'storeId', 'duplicateCount', 'footfallCount', 'employeeCount', 'houseKeepingCount', 'junkCount', 'status', 'ticketId', 'comments', 'userName', 'role', 'createdAt', 'email', 'houseKeepingACCount', 'duplicateACCount', 'employeeACCount','junkACCount', 'approverEmail', 'approverRole', 'approverUserName' ],
|
|
276
291
|
|
|
277
292
|
};
|
|
278
293
|
|
|
279
294
|
const getData = await getOpenSearchData( opensearch.footfallDirectory, query );
|
|
280
295
|
const ticketDetails = getData?.body?.hits?.hits[0];
|
|
281
296
|
let temp = [];
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
+
ticketDetails?._source? temp.push( ticketDetails?._source ) :null;
|
|
298
|
+
// temp[0].status = 'open';
|
|
299
|
+
if ( ticketDetails?._source?.status == 'closed' ) {
|
|
300
|
+
delete temp[0].status;
|
|
301
|
+
temp.push( { ...ticketDetails?._source, status: 'closed' } );
|
|
302
|
+
|
|
303
|
+
temp[1].userName = getData?.body?.hits?.hits?.[0]?._source?.approverUserName;
|
|
304
|
+
temp[1].email = getData?.body?.hits?.hits?.[0]?._source?.approverEmail;
|
|
305
|
+
temp[1].role = getData?.body?.hits?.hits?.[0]?._source?.approverRole;
|
|
306
|
+
temp[1].employeeCount = getData?.body?.hits?.hits?.[0]?._source?.employeeACCount;
|
|
307
|
+
temp[1].houseKeepingCount = getData?.body?.hits?.hits?.[0]?._source?.houseKeepingACCount;
|
|
308
|
+
temp[1].duplicateCount = getData?.body?.hits?.hits?.[0]?._source?.duplicateACCount;
|
|
309
|
+
temp[1].junkCount = getData?.body?.hits?.hits?.[0]?._source?.junkACCount;
|
|
310
|
+
}
|
|
311
|
+
const LamdaURL = revop.getImages;
|
|
312
|
+
let resultData = await LamdaServiceCall( LamdaURL, inputData );
|
|
313
|
+
logger.info( { resultData: resultData } );
|
|
314
|
+
if ( resultData ) {
|
|
315
|
+
temp.length? temp[0].status = 'open': null;
|
|
316
|
+
if ( resultData.status_code == '200' ) {
|
|
317
|
+
return res.sendSuccess( { ...resultData, ticketStatus: temp?.length > 0? temp : null, config: req?.store?.revopTagging } );
|
|
318
|
+
} else {
|
|
319
|
+
return res.sendError( 'No Content', 204 );
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
return res.sendError( 'No Content', 204 );
|
|
323
|
+
}
|
|
297
324
|
} catch ( error ) {
|
|
298
325
|
logger.error( { message: error, data: req.query, function: 'storeProcessedData' } );
|
|
299
326
|
const err = error.message || 'Internal Server Error';
|
|
@@ -316,9 +343,11 @@ export async function tagTempId( req, res ) {
|
|
|
316
343
|
entryTime: inputData.entryTime,
|
|
317
344
|
exitTime: inputData.exitTime,
|
|
318
345
|
filePath: inputData.filePath,
|
|
319
|
-
status: 'submitted',
|
|
346
|
+
status: inputData?.revopsType == 'non-tagging' ?'':'submitted',
|
|
320
347
|
description: '',
|
|
348
|
+
isChecked: inputData.isChecked,
|
|
321
349
|
duplicateImage: inputData?.duplicateImage?.length>0? inputData?.duplicateImage :[],
|
|
350
|
+
type: 'tagging-reflect',
|
|
322
351
|
createdAt: new Date(),
|
|
323
352
|
updatedAt: new Date(),
|
|
324
353
|
|
|
@@ -332,6 +361,7 @@ export async function tagTempId( req, res ) {
|
|
|
332
361
|
ctx._source.filePath = params.filePath;
|
|
333
362
|
ctx._source.status = params.status;
|
|
334
363
|
ctx._source.description = params.description;
|
|
364
|
+
ctx._source.isChecked = params.isChecked;
|
|
335
365
|
ctx._source.duplicateImage = params.duplicateImage;
|
|
336
366
|
ctx._source.updatedAt = params.updatedAt;
|
|
337
367
|
ctx._source.timeRange = params.timeRange;
|
|
@@ -341,6 +371,7 @@ export async function tagTempId( req, res ) {
|
|
|
341
371
|
ctx._source.storeId = params.storeId;
|
|
342
372
|
ctx._source.tempId = params.tempId;
|
|
343
373
|
ctx._source.dateString = params.dateString;
|
|
374
|
+
ctx._source.type = "tagging-reflect";
|
|
344
375
|
ctx._source.processType = params.processType;
|
|
345
376
|
}
|
|
346
377
|
`,
|
|
@@ -348,7 +379,54 @@ export async function tagTempId( req, res ) {
|
|
|
348
379
|
};
|
|
349
380
|
const id = `${inputData.storeId}_${inputData.dateString}_${inputData.timeRange}_${inputData.tempId}`;
|
|
350
381
|
await upsertWithScript( openSearch.revop, id, { script, upsert: upsertRecord } );
|
|
351
|
-
|
|
382
|
+
if ( inputData?.duplicateImage?.length> 0 ) {
|
|
383
|
+
let bulkBody = [];
|
|
384
|
+
for ( let item of inputData?.duplicateImage ) {
|
|
385
|
+
const insertRecord = {
|
|
386
|
+
clientId: inputData.storeId.split( '-' )[0],
|
|
387
|
+
storeId: inputData.storeId,
|
|
388
|
+
tempId: item.tempId,
|
|
389
|
+
dateString: inputData.dateString,
|
|
390
|
+
timeRange: item.timeRange,
|
|
391
|
+
isChecked: item.isChecked,
|
|
392
|
+
processType: inputData.processType,
|
|
393
|
+
revopsType: item.revopsType,
|
|
394
|
+
entryTime: item.entryTime,
|
|
395
|
+
exitTime: inputData.exitTime,
|
|
396
|
+
filePath: item.filePath,
|
|
397
|
+
status: item?.revopsType == 'non-tagging' ?'':'submitted',
|
|
398
|
+
description: '',
|
|
399
|
+
duplicateImage: [],
|
|
400
|
+
type: 'tagging-reflect',
|
|
401
|
+
createdAt: new Date(),
|
|
402
|
+
updatedAt: new Date(),
|
|
403
|
+
|
|
404
|
+
};
|
|
405
|
+
const id =`${inputData.storeId}_${inputData.dateString}_${item.timeRange}_${item.tempId}`;
|
|
406
|
+
const updatedDuplicateImages = {
|
|
407
|
+
...insertRecord,
|
|
408
|
+
parent: inputData.tempId,
|
|
409
|
+
};
|
|
410
|
+
bulkBody.push(
|
|
411
|
+
{ update: { _index: openSearch.revop, _id: id } },
|
|
412
|
+
{ doc: updatedDuplicateImages, doc_as_upsert: true },
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
if ( bulkBody.length > 0 ) {
|
|
416
|
+
const res1 = await bulkUpdate( bulkBody );
|
|
417
|
+
if ( res1?.errors ) {
|
|
418
|
+
logger.error( 'Bulk update errors:', res1.items );
|
|
419
|
+
return { success: false, errors: res1.items };
|
|
420
|
+
} else {
|
|
421
|
+
logger.info( { msg: 'res1' } );
|
|
422
|
+
return res.sendSuccess( `ID tagged as duplicates` );
|
|
423
|
+
// return { success: true };
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
const message = inputData?.revopsType == 'non-tagging' ? 'ID removed from tagging' :inputData?.revopsType == 'employee' ?'ID tagged as an employee/staff':inputData?.revopsType == 'junk'? 'ID tagged as a junk':'ID tagged as an house keeping';
|
|
428
|
+
return res.sendSuccess( message );
|
|
429
|
+
}
|
|
352
430
|
} catch ( error ) {
|
|
353
431
|
logger.error( { message: error, data: req.query, function: 'tagTempId' } );
|
|
354
432
|
const err = error.message || 'Internal Server Error';
|
|
@@ -847,6 +847,7 @@ export async function headerClustersV2( req, res ) {
|
|
|
847
847
|
try {
|
|
848
848
|
let requestData = req.body;
|
|
849
849
|
let getUserEmail = req.user.email;
|
|
850
|
+
let clientId = req?.user?.clientId;
|
|
850
851
|
let getUserType = req.user.userType;
|
|
851
852
|
let getRole = req.user.role;
|
|
852
853
|
let clusterNames=[];
|
|
@@ -862,9 +863,9 @@ export async function headerClustersV2( req, res ) {
|
|
|
862
863
|
clusterNames = await getclusterList( requestData.clientId, getUserType, getRole, req );
|
|
863
864
|
} else if ( getUserType == 'client' ) {
|
|
864
865
|
if ( getRole == 'superadmin' ) {
|
|
865
|
-
clusterNames = await getclusterList(
|
|
866
|
+
clusterNames = await getclusterList( clientId, getUserType, getRole, req );
|
|
866
867
|
} else {
|
|
867
|
-
clusterNames = await getclusterList(
|
|
868
|
+
clusterNames = await getclusterList( clientId, getUserType, getRole, req );
|
|
868
869
|
}
|
|
869
870
|
}
|
|
870
871
|
}
|
|
@@ -1577,31 +1578,30 @@ async function getLocationStores( userClientId, cityList, req ) {
|
|
|
1577
1578
|
}
|
|
1578
1579
|
async function getclusterList( userClientId, getUserType, getRole, req ) {
|
|
1579
1580
|
try {
|
|
1580
|
-
if ( userClientId && userClientId !='' ) {
|
|
1581
|
-
let filter = [
|
|
1582
|
-
|
|
1583
|
-
|
|
1581
|
+
if ( userClientId && userClientId !='' || getUserType === 'tango' ) {
|
|
1582
|
+
let filter = [];
|
|
1583
|
+
if ( userClientId && userClientId !='' ) {
|
|
1584
|
+
filter.push( { clientId: { $eq: userClientId } },
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1584
1587
|
if ( req.body.assignedStores&&req.body.assignedStores.length>0 ) {
|
|
1585
1588
|
filter.push( { 'stores.storeId': { $in: req.body.assignedStores } } );
|
|
1586
1589
|
}
|
|
1587
1590
|
if ( getUserType == 'client'&&getRole!='superadmin' ) {
|
|
1588
1591
|
filter.push( { 'Teamlead.email': req.user.email } );
|
|
1589
1592
|
}
|
|
1593
|
+
let clusterQuery = [];
|
|
1590
1594
|
|
|
1595
|
+
if ( filter.length > 0 ) {
|
|
1596
|
+
clusterQuery.push( { $match: { $and: filter } } );
|
|
1597
|
+
}
|
|
1591
1598
|
|
|
1592
|
-
|
|
1593
|
-
{
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
},
|
|
1597
|
-
},
|
|
1598
|
-
{
|
|
1599
|
-
$group: {
|
|
1600
|
-
_id: null,
|
|
1601
|
-
clusterName: { $push: '$clusterName' },
|
|
1602
|
-
},
|
|
1599
|
+
clusterQuery.push( {
|
|
1600
|
+
$group: {
|
|
1601
|
+
_id: null,
|
|
1602
|
+
clusterName: { $push: '$clusterName' },
|
|
1603
1603
|
},
|
|
1604
|
-
|
|
1604
|
+
} );
|
|
1605
1605
|
const clusterIds = await aggregateCluster( clusterQuery );
|
|
1606
1606
|
if ( clusterIds && clusterIds.length>0 && clusterIds[0]?.clusterName.length > 0 ) {
|
|
1607
1607
|
let uniqueclusterIds = [ ...new Set( clusterIds[0].clusterName ) ];
|
|
@@ -3069,6 +3069,50 @@ export const getStoreMapDataV3 = async ( req, res ) => {
|
|
|
3069
3069
|
}
|
|
3070
3070
|
};
|
|
3071
3071
|
|
|
3072
|
+
export const managerTrafficDensityExport = async ( req, res ) => {
|
|
3073
|
+
try {
|
|
3074
|
+
let reqestData = req.body;
|
|
3075
|
+
let getClientData = await getClientConfig( reqestData.clientId );
|
|
3076
|
+
if ( !getClientData ) {
|
|
3077
|
+
return res.sendError( 'Invalid Client Id', 400 );
|
|
3078
|
+
}
|
|
3079
|
+
reqestData.featureConfigs = getClientData.featureConfigs;
|
|
3080
|
+
let LamdaURL = 'https://bkmqop3brhzloib2bik3hn7xey0uadas.lambda-url.ap-south-1.on.aws/';
|
|
3081
|
+
let resultData = await LamdaServiceCall( LamdaURL, reqestData );
|
|
3082
|
+
let resultDataValue = resultData.data;
|
|
3083
|
+
if ( resultData ) {
|
|
3084
|
+
if ( resultData.status_code == '200' ) {
|
|
3085
|
+
if ( reqestData.export ) {
|
|
3086
|
+
if ( resultDataValue && resultDataValue.length > 0 ) {
|
|
3087
|
+
let exportdata = [];
|
|
3088
|
+
resultDataValue.forEach( ( element ) => {
|
|
3089
|
+
let row = {};
|
|
3090
|
+
row['Date'] = element.date || '--';
|
|
3091
|
+
row['Time'] = element.time ?? '--';
|
|
3092
|
+
row['Day'] = element.day ?? '--';
|
|
3093
|
+
row['Count'] = ( element.count === 0 ) ? '0' : element.count ? element.count:'--';
|
|
3094
|
+
exportdata.push( row );
|
|
3095
|
+
} );
|
|
3096
|
+
|
|
3097
|
+
return await download( exportdata, res );
|
|
3098
|
+
} else {
|
|
3099
|
+
return res.sendError( 'No Content', 204 );
|
|
3100
|
+
}
|
|
3101
|
+
} else {
|
|
3102
|
+
return res.sendSuccess( resultData );
|
|
3103
|
+
}
|
|
3104
|
+
} else {
|
|
3105
|
+
return res.sendError( 'No Content', 204 );
|
|
3106
|
+
}
|
|
3107
|
+
} else {
|
|
3108
|
+
return res.sendError( 'No Content', 204 );
|
|
3109
|
+
}
|
|
3110
|
+
} catch ( error ) {
|
|
3111
|
+
logger.error( { error: error, message: req.query, function: 'managerTrafficDensityExport' } );
|
|
3112
|
+
return res.sendError( { error: error }, 500 );
|
|
3113
|
+
}
|
|
3114
|
+
};
|
|
3115
|
+
|
|
3072
3116
|
// async function getGeocodedAddress( lat, lng ) {
|
|
3073
3117
|
// try {
|
|
3074
3118
|
// const apiKey = 'AIzaSyDlOezgwQO0JviD0aizrCuN1FY9tcWfR3o'; // Use this if you're using dotenv
|
package/src/dtos/revop.dtos.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import joi from 'joi';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
2
3
|
|
|
3
4
|
export const storeProcessedDataSchema = joi.object( {
|
|
4
5
|
|
|
@@ -15,6 +16,22 @@ export const footfallImagesSchema = joi.object( {
|
|
|
15
16
|
|
|
16
17
|
storeId: joi.string().required(),
|
|
17
18
|
dateString: joi.string().required(),
|
|
19
|
+
// .custom( ( value, helpers ) => {
|
|
20
|
+
// const inputDate = dayjs( value, 'YYYY-MM-DD', true );
|
|
21
|
+
// const today = dayjs();
|
|
22
|
+
|
|
23
|
+
// if ( !inputDate.isValid() ) {
|
|
24
|
+
// return helpers.error( 'any.invalid' );
|
|
25
|
+
// }
|
|
26
|
+
|
|
27
|
+
// const diff = today.diff( inputDate, 'day' );
|
|
28
|
+
|
|
29
|
+
// if ( diff > 7 ) {
|
|
30
|
+
// return helpers.message( 'Viewing is not allowed for a period exceeding 7 days' );
|
|
31
|
+
// }
|
|
32
|
+
|
|
33
|
+
// return value;
|
|
34
|
+
// } ),
|
|
18
35
|
groupByKey: joi.string().required(),
|
|
19
36
|
processType: joi.string().required(),
|
|
20
37
|
type: joi.string().optional(),
|
|
@@ -28,10 +45,26 @@ export const footfallImagesValid = {
|
|
|
28
45
|
export const tagTempIdSchema = joi.object( {
|
|
29
46
|
|
|
30
47
|
storeId: joi.string().required(),
|
|
31
|
-
dateString: joi.string().required(),
|
|
48
|
+
dateString: joi.string().required().custom( ( value, helpers ) => {
|
|
49
|
+
const inputDate = dayjs( value, 'YYYY-MM-DD', true );
|
|
50
|
+
const today = dayjs();
|
|
51
|
+
|
|
52
|
+
if ( !inputDate.isValid() ) {
|
|
53
|
+
return helpers.error( 'any.invalid' );
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const diff = today.diff( inputDate, 'day' );
|
|
57
|
+
|
|
58
|
+
if ( diff > 7 ) {
|
|
59
|
+
return helpers.message( 'Tagging is not allowed for a period exceeding 7 days' );
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return value;
|
|
63
|
+
} ), // yyyy-mm-dd
|
|
32
64
|
tempId: joi.number().required(),
|
|
33
65
|
revopsType: joi.string().required(),
|
|
34
66
|
timeRange: joi.string().required(),
|
|
67
|
+
isChecked: joi.boolean().required().allow( null ),
|
|
35
68
|
duplicateImage: joi.array().items( joi.object(
|
|
36
69
|
{
|
|
37
70
|
tempId: joi.number().required(),
|
|
@@ -39,6 +72,8 @@ export const tagTempIdSchema = joi.object( {
|
|
|
39
72
|
entryTime: joi.string().required(),
|
|
40
73
|
exitTime: joi.string().required(),
|
|
41
74
|
filePath: joi.string().required(),
|
|
75
|
+
revopsType: joi.string().required(),
|
|
76
|
+
isChecked: joi.boolean().required().allow( null ),
|
|
42
77
|
},
|
|
43
78
|
) ).optional(),
|
|
44
79
|
processType: joi.string().required(),
|
|
@@ -208,7 +208,7 @@ export const validateperformanceMatrixParams = {
|
|
|
208
208
|
};
|
|
209
209
|
|
|
210
210
|
export const validateHeaderSchema = joi.object( {
|
|
211
|
-
clientId: joi.string().required(),
|
|
211
|
+
clientId: joi.string().required().allow( '' ),
|
|
212
212
|
city: joi.array().required(),
|
|
213
213
|
group: joi.array().required(),
|
|
214
214
|
country: joi.array().optional().empty(),
|
|
@@ -3,7 +3,7 @@ import express from 'express';
|
|
|
3
3
|
import { storeProcessedData, getconfig, revoptagging, getrevoptagging, revoptaggingcount, footFallImages, tagTempId, getCategorizedImages } from '../controllers/revop.controller.js';
|
|
4
4
|
import { isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
|
|
5
5
|
import { footfallImagesValid, getCategorizedImagesValid, storeProcessedDataValid, tagTempIdValid } from '../dtos/revop.dtos.js';
|
|
6
|
-
import { getTaggingConfig } from '../validations/revop.validation.js';
|
|
6
|
+
import { deletetagedDuplicate, getTaggingConfig, mappingConfig } from '../validations/revop.validation.js';
|
|
7
7
|
|
|
8
8
|
export const revopRouter = express.Router();
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ revopRouter
|
|
|
16
16
|
// new enhnacemnet (for footfall directory)
|
|
17
17
|
.get( '/store-processed-data', isAllowedSessionHandler, validate( storeProcessedDataValid ), storeProcessedData )
|
|
18
18
|
.get( '/footfall-images', isAllowedSessionHandler, validate( footfallImagesValid ), getTaggingConfig, footFallImages )
|
|
19
|
-
.post( '/tag-tempId', isAllowedSessionHandler, validate( tagTempIdValid ), tagTempId )
|
|
19
|
+
.post( '/tag-tempId', isAllowedSessionHandler, validate( tagTempIdValid ), deletetagedDuplicate, mappingConfig, tagTempId )
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
.post( '/get-categorized-images', isAllowedSessionHandler, validate( getCategorizedImagesValid ), getCategorizedImages );
|
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
zoneDwellTimeSplitV3,
|
|
92
92
|
funnelV3,
|
|
93
93
|
getStoreMapDataV3,
|
|
94
|
-
|
|
94
|
+
managerTrafficDensityExport,
|
|
95
95
|
} from '../controllers/tangoTrafficV3.controllers.js';
|
|
96
96
|
analysisTrafficRouter
|
|
97
97
|
.get( '/welcome', welcome )
|
|
@@ -174,5 +174,6 @@ analysisTrafficRouter
|
|
|
174
174
|
.post( '/headerUserEmails_v2', isAllowedSessionHandler, getUserEmails )
|
|
175
175
|
.post( '/headerCountry_v2', isAllowedSessionHandler, isAllowedClient, validate( validationDtos.validateCountryHeaderParamsv2 ), getAssinedStore, headerCountryV2 )
|
|
176
176
|
.post( '/checkTodayReportStatus', isAllowedSessionHandler, checkTodayReportStatus )
|
|
177
|
-
.post( '/headerZoneV2', isAllowedSessionHandler, headerZoneV2 )
|
|
177
|
+
.post( '/headerZoneV2', isAllowedSessionHandler, headerZoneV2 )
|
|
178
|
+
.post( '/trafficDensityExport_v2', managerTrafficDensityExport );
|
|
178
179
|
export default analysisTrafficRouter;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { getOpenSearchCount, logger } from 'tango-app-api-middleware';
|
|
1
2
|
import { findOneStore } from '../services/stores.service.js';
|
|
3
|
+
import { deleteByQuery } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
2
4
|
|
|
3
5
|
export async function getTaggingConfig( req, res, next ) {
|
|
4
6
|
try {
|
|
5
|
-
const inputData= req.
|
|
7
|
+
const inputData= req.query;
|
|
6
8
|
const getData = await findOneStore( { storeId: inputData.storeId }, { revopTagging: 1 } );
|
|
7
9
|
req.store = getData;
|
|
8
10
|
next();
|
|
@@ -11,3 +13,199 @@ export async function getTaggingConfig( req, res, next ) {
|
|
|
11
13
|
next();
|
|
12
14
|
}
|
|
13
15
|
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export async function getFootfallCount( req, res, next ) {
|
|
19
|
+
try {
|
|
20
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
21
|
+
const inputData = req.query;
|
|
22
|
+
const dateString = `${inputData.storeId}_${inputData.dateString}`;
|
|
23
|
+
const getQuery = {
|
|
24
|
+
query: {
|
|
25
|
+
terms: {
|
|
26
|
+
_id: [ dateString ],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
_source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
|
|
30
|
+
sort: [
|
|
31
|
+
{
|
|
32
|
+
date_iso: {
|
|
33
|
+
order: 'desc',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getData = await getOpenSearchData( openSearch.footfall, getQuery );
|
|
40
|
+
const hits = getData?.body?.hits?.hits || [];
|
|
41
|
+
|
|
42
|
+
const processedData = hits.find( ( d ) => d._id === dateString )?._source || null;
|
|
43
|
+
req.Footfall =processedData;
|
|
44
|
+
next();
|
|
45
|
+
} catch ( error ) {
|
|
46
|
+
logger.error( { error: error, message: req.body, function: 'traffic-revop-getFootfallCount' } );
|
|
47
|
+
next();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function mappingConfig( req, res, next ) {
|
|
52
|
+
try {
|
|
53
|
+
const inputData = req.body;
|
|
54
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
55
|
+
const config =await findOneStore( { storeId: inputData.storeId }, { revopTagging: 1 } );
|
|
56
|
+
if ( inputData.revopsType == 'employee' ) {
|
|
57
|
+
const getQuery = {
|
|
58
|
+
query: {
|
|
59
|
+
bool: {
|
|
60
|
+
must: [
|
|
61
|
+
{
|
|
62
|
+
term: {
|
|
63
|
+
'storeId.keyword': inputData.storeId,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
term: {
|
|
68
|
+
'dateString': inputData.dateString,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
term: {
|
|
73
|
+
'revopsType.keyword': inputData.revopsType,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const getData = await getOpenSearchCount( openSearch.revop, getQuery );
|
|
82
|
+
if ( getData && getData?.body?.count >= config?.revopTagging?.employee ) {
|
|
83
|
+
return res.sendError( `Select up to ${config?.revopTagging?.employee} items only`, 400 );
|
|
84
|
+
} else {
|
|
85
|
+
next();
|
|
86
|
+
}
|
|
87
|
+
} else if ( inputData.revopsType == 'house-keeping' ) {
|
|
88
|
+
const getQuery = {
|
|
89
|
+
query: {
|
|
90
|
+
bool: {
|
|
91
|
+
must: [
|
|
92
|
+
{
|
|
93
|
+
term: {
|
|
94
|
+
'storeId.keyword': inputData.storeId,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
term: {
|
|
99
|
+
'dateString': inputData.dateString,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
term: {
|
|
104
|
+
'revopsType.keyword': inputData.revopsType,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const getData = await getOpenSearchCount( openSearch.revop, getQuery );
|
|
112
|
+
if ( getData && getData?.body?.count >= config?.revopTagging?.houseKeeping ) {
|
|
113
|
+
return res.sendError( `Select up to ${config?.revopTagging?.houseKeeping} items only`, 400 );
|
|
114
|
+
} else {
|
|
115
|
+
next();
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
next();
|
|
119
|
+
}
|
|
120
|
+
// else if ( inputData.revopsType == 'duplicate' ) {
|
|
121
|
+
// const getFootfallQuery = {
|
|
122
|
+
// query: {
|
|
123
|
+
// terms: {
|
|
124
|
+
// _id: [ inputData?.dateString ],
|
|
125
|
+
// },
|
|
126
|
+
// },
|
|
127
|
+
// _source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
|
|
128
|
+
// sort: [
|
|
129
|
+
// {
|
|
130
|
+
// date_iso: {
|
|
131
|
+
// order: 'desc',
|
|
132
|
+
// },
|
|
133
|
+
// },
|
|
134
|
+
// ],
|
|
135
|
+
// };
|
|
136
|
+
|
|
137
|
+
// const getFootfall = await getOpenSearchData( openSearch.footfall, getFootfallQuery );
|
|
138
|
+
// const footfall = getFootfall?.body?.hites?.hits?.[0]?._source?.footfall_count;
|
|
139
|
+
// const getQuery = {
|
|
140
|
+
// query: {
|
|
141
|
+
// bool: {
|
|
142
|
+
// must: [
|
|
143
|
+
// {
|
|
144
|
+
// term: {
|
|
145
|
+
// 'storeId.keyword': inputData.storeId,
|
|
146
|
+
// },
|
|
147
|
+
// },
|
|
148
|
+
// {
|
|
149
|
+
// term: {
|
|
150
|
+
// 'dateString': inputData.dateString,
|
|
151
|
+
// },
|
|
152
|
+
// },
|
|
153
|
+
// {
|
|
154
|
+
// term: {
|
|
155
|
+
// 'revopsType.keyword': inputData.revopsType,
|
|
156
|
+
// },
|
|
157
|
+
// },
|
|
158
|
+
// {
|
|
159
|
+
// term: {
|
|
160
|
+
// 'parent.keyword': null,
|
|
161
|
+
// },
|
|
162
|
+
// },
|
|
163
|
+
// ],
|
|
164
|
+
// },
|
|
165
|
+
// },
|
|
166
|
+
// };
|
|
167
|
+
// const getData = await getOpenSearchCount( openSearch.revop, getQuery );
|
|
168
|
+
// logger.info( { getData: getData, footfall: footfall, duplicate: config?.revopTagging?.duplicate } );
|
|
169
|
+
// if ( getData && footfall && config?.revopTagging?.duplicate ) {
|
|
170
|
+
// const data = config?.revopTagging?.duplicate;
|
|
171
|
+
// // Convert "20%" → 0.2 (handle both "20%" and 20)
|
|
172
|
+
// const percentStr = typeof data === 'string' ? data.replace( '%', '' ) : data;
|
|
173
|
+
// logger.info( { percentStr: percentStr } );
|
|
174
|
+
// const percentValue =percentStr / 100;
|
|
175
|
+
|
|
176
|
+
// const result = percentValue * footfall;
|
|
177
|
+
|
|
178
|
+
// logger.info( { result: result, footfall: footfall } );
|
|
179
|
+
// }
|
|
180
|
+
// if ( getData && getData?.body?.count >= Math.round( result ) ) {
|
|
181
|
+
// return res.sendError( `Select up to ${config?.revopTagging?.duplicate} items only`, 400 );
|
|
182
|
+
// } else {
|
|
183
|
+
// next();
|
|
184
|
+
// }
|
|
185
|
+
// }
|
|
186
|
+
} catch ( error ) {
|
|
187
|
+
logger.error( { error: error, message: req.body, function: 'traffic-revop-getTaggingConfig' } );
|
|
188
|
+
next();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function deletetagedDuplicate( req, res, next ) {
|
|
193
|
+
try {
|
|
194
|
+
const inputData = req.body;
|
|
195
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
196
|
+
const getQuery = {
|
|
197
|
+
query: {
|
|
198
|
+
term: {
|
|
199
|
+
parent: inputData?.tempId,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
};
|
|
204
|
+
await deleteByQuery( openSearch.revop, getQuery );
|
|
205
|
+
next();
|
|
206
|
+
} catch ( error ) {
|
|
207
|
+
logger.error( { error: error, message: req.body, function: 'traffic-revop-deletetagedDuplicate' } );
|
|
208
|
+
next();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|