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 CHANGED
@@ -36,7 +36,7 @@ module.exports = {
36
36
  'no-unused-vars': 'error',
37
37
  'new-cap': [ 'error', { 'newIsCap': true, 'capIsNew': false } ],
38
38
  'prefer-const': 'off',
39
- 'no-console': 'error',
39
+ // 'no-console': 'error',
40
40
  },
41
41
  };
42
42
 
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.1-alpha.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 index.js\""
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.89",
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 processedData = getData?.body?.hits?.hits?.[0]?._source;
235
- const previousData = getData?.body?.hits?.hits?.[1]?._source;
236
- const result=( ( processedData?.footfall_count - previousData?.footfall_count )/previousData?.footfall_count )*100;
237
- return res.sendSuccess( { footfallCount: processedData?.footfall_count, footfallCountTrend: Math.round( result ), downtime: processedData?.down_time } );
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
- temp.push( ticketDetails?._source );
283
- if ( ticketDetails?._source?.status == 'close' ) {
284
- temp.push( ticketDetails?._source );
285
- }
286
- const LamdaURL = 'https://cncmzszloku7y3bewxbpiy6nkm0yunqe.lambda-url.ap-south-1.on.aws/';
287
- let resultData = await LamdaServiceCall( LamdaURL, inputData );
288
- if ( resultData ) {
289
- if ( resultData.status_code == '200' ) {
290
- return res.sendSuccess( { ...resultData, ticketStatus: temp, config: req?.store?.revopTagging } );
291
- } else {
292
- return res.sendError( 'No Content', 204 );
293
- }
294
- } else {
295
- return res.sendError( 'No Content', 204 );
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
- return res.sendSuccess( `tempId ${inputData.tempId} has been tagged successfully` );
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( requestData.clientId, getUserType, getRole, req );
866
+ clusterNames = await getclusterList( clientId, getUserType, getRole, req );
866
867
  } else {
867
- clusterNames = await getclusterList( requestData.clientId, getUserType, getRole, req );
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
- { clientId: { $eq: userClientId } },
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
- let clusterQuery = [
1593
- {
1594
- $match: {
1595
- $and: filter,
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
@@ -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(), // yyyy-mm-dd
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.body;
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
+