tango-app-api-analysis-traffic 3.8.7-vms.4 → 3.8.7-vms.6

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.4",
3
+ "version": "3.8.7-vms.6",
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.23",
26
+ "tango-api-schema": "^2.4.29",
27
27
  "tango-app-api-middleware": "^3.6.5",
28
28
  "winston": "^3.13.1",
29
29
  "winston-daily-rotate-file": "^5.0.0"
@@ -415,7 +415,6 @@ export async function footFallImages( req, res ) {
415
415
  let temp = [];
416
416
  const footfallValue = ticketDetails?._source?.footfallCount ?? 0;
417
417
  const mappingInfoArray = ticketDetails?._source?.mappingInfo ?? [];
418
-
419
418
  // Helper to get mappingInfo for an actionType
420
419
  function getMappingForType( type ) {
421
420
  return mappingInfoArray.find( ( m ) => m.type === type ) || {};
@@ -429,7 +428,6 @@ export async function footFallImages( req, res ) {
429
428
  const actionTypesUser = [ 'tagging', 'finalreview' ];
430
429
  temp = actionTypesUser.map( ( type ) => {
431
430
  const mapping = getMappingForType( type );
432
-
433
431
  const revisedFootfall = mapping.revisedFootfall ?? 0;
434
432
  // Do not divide by 0
435
433
  const revisedPerc =
@@ -438,7 +436,7 @@ export async function footFallImages( req, res ) {
438
436
  '0';
439
437
 
440
438
  // Since keys in count can be dynamic, just deep copy the count object or empty object
441
- const countObj = mapping.count ? { ...mapping.count } : {};
439
+ const countObj = mapping.count ? [ ...mapping.count ] : [];
442
440
 
443
441
  return {
444
442
  actionType: type,
@@ -446,6 +444,10 @@ export async function footFallImages( req, res ) {
446
444
  revicedFootfall: revisedFootfall,
447
445
  revicedPerc: revisedPerc,
448
446
  count: countObj,
447
+ createdAt: mapping.createdAt ?? '',
448
+ createdByEmail: mapping.createdByEmail ??'',
449
+ createdByUserName: mapping.createdByUserName ??'',
450
+ createdByRole: mapping.createdByRole ??'',
449
451
  };
450
452
  } );
451
453
  break;
@@ -462,7 +464,7 @@ export async function footFallImages( req, res ) {
462
464
  '0';
463
465
 
464
466
  // Since keys in count can be dynamic, just deep copy the count object or empty object
465
- const countObj = mapping.count ? { ...mapping.count } : {};
467
+ const countObj = mapping.count ? [ ...mapping.count ] : [];
466
468
 
467
469
  return {
468
470
  actionType: type,
@@ -470,6 +472,10 @@ export async function footFallImages( req, res ) {
470
472
  revicedFootfall: revisedFootfall,
471
473
  revicedPerc: revisedPerc,
472
474
  count: countObj,
475
+ createdAt: mapping.createdAt ?? '',
476
+ createdByEmail: mapping.createdByEmail ??'',
477
+ createdByUserName: mapping.createdByUserName ??'',
478
+ createdByRole: mapping.createdByRole ??'',
473
479
  };
474
480
  } );
475
481
  break;
@@ -478,8 +484,7 @@ export async function footFallImages( req, res ) {
478
484
  const actionTypes = [ 'tagging', 'review', 'approve', 'finalreview' ];
479
485
  temp = actionTypes.map( ( type ) => {
480
486
  const mapping = getMappingForType( type );
481
-
482
- const revisedFootfall = mapping.revisedFootfall ?? 0;
487
+ const revisedFootfall = mapping.revicedFootfall ?? 0;
483
488
  // Do not divide by 0
484
489
  const revisedPerc =
485
490
  footfallValue > 0 ?
@@ -487,7 +492,7 @@ export async function footFallImages( req, res ) {
487
492
  '0';
488
493
 
489
494
  // Since keys in count can be dynamic, just deep copy the count object or empty object
490
- const countObj = mapping.count ? { ...mapping.count } : {};
495
+ const countObj = mapping.count ? [ ...mapping.count ] : [];
491
496
 
492
497
  return {
493
498
  actionType: type,
@@ -495,6 +500,10 @@ export async function footFallImages( req, res ) {
495
500
  revicedFootfall: revisedFootfall,
496
501
  revicedPerc: revisedPerc,
497
502
  count: countObj,
503
+ createdAt: mapping.createdAt ?? '',
504
+ createdByEmail: mapping.createdByEmail ??'',
505
+ createdByUserName: mapping.createdByUserName ??'',
506
+ createdByRole: mapping.createdByRole ??'',
498
507
  };
499
508
  } );
500
509
  }
@@ -548,6 +557,7 @@ export async function tagTempId( req, res ) {
548
557
  action: 'submitted',
549
558
  },
550
559
  ],
560
+ comments: inputData.comments || '',
551
561
  createdAt: new Date(),
552
562
  updatedAt: new Date(),
553
563
 
@@ -601,6 +611,7 @@ export async function tagTempId( req, res ) {
601
611
  isParent: false,
602
612
  type: 'tagging-reflect',
603
613
  ticketStatus: 'submitted',
614
+ comments: inputData.comments || '',
604
615
  actions: [
605
616
  {
606
617
  actionType: 'tagging',
@@ -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
 
@@ -1,6 +1,5 @@
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';
2
+ import { deleteByQuery, getOpenSearchData } from 'tango-app-api-middleware/src/utils/openSearch.js';
4
3
  import { findOne } from '../services/clients.services.js';
5
4
 
6
5
  export async function getTaggingConfig( req, res, next ) {
@@ -65,41 +64,54 @@ export async function getFootfallCount( req, res, next ) {
65
64
  export async function mappingConfig( req, res, next ) {
66
65
  try {
67
66
  const inputData = req.body;
67
+
68
68
  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
- },
79
- },
80
- {
81
- term: {
82
- 'dateString': inputData.dateString,
83
- },
69
+ const footfallQuery ={
70
+ query: {
71
+ bool: {
72
+ must: [
73
+ {
74
+ term: {
75
+ 'store_id.keyword': inputData.storeId,
84
76
  },
85
- {
86
- term: {
87
- 'revopsType.keyword': inputData.revopsType,
88
- },
77
+ },
78
+ {
79
+ term: {
80
+ 'date_string': inputData.dateString,
89
81
  },
90
- ],
91
- },
82
+ },
83
+
84
+ ],
92
85
  },
93
- };
86
+ },
87
+ };
94
88
 
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 = {
89
+
90
+ const footfallOutput = await getOpenSearchData( openSearch.footfall, footfallQuery );
91
+ if ( footfallOutput?.body?.hits?.hits?.length === 0 ) {
92
+ return res.sendError( 'No updated footfall for this date', 400 );
93
+ }
94
+ const getFootfallCount = footfallOutput?.body?.hits?.hits;
95
+ const footfall = getFootfallCount?.[0]?._source?.footfall_count;
96
+
97
+ const getConfig = await findOne( { clientId: inputData?.storeId?.split( '-' )[0] }, { footfallDirectoryConfigs: 1 } );
98
+ const taggingLimitation = getConfig?.footfallDirectoryConfigs?.taggingLimitation;
99
+ // Find the tagging limitation for the given revopsType
100
+ let matchedLimitation = null;
101
+ if ( Array.isArray( taggingLimitation ) ) {
102
+ matchedLimitation = taggingLimitation.find(
103
+ ( l ) => l.type === inputData.revopsType,
104
+ );
105
+ }
106
+
107
+ if ( matchedLimitation ) {
108
+ // Determine the limit value
109
+ let limitValue = Number( matchedLimitation.value ) || 0;
110
+ let unit = matchedLimitation.unit;
111
+
112
+ // Assuming getData and/or getOpenSearchCount provides the actual tagged count for revopsType
113
+ // Query OpenSearch for current tagged count for this revopsType
114
+ const taggedCountQuery = {
103
115
  query: {
104
116
  bool: {
105
117
  must: [
@@ -118,92 +130,51 @@ export async function mappingConfig( req, res, next ) {
118
130
  'revopsType.keyword': inputData.revopsType,
119
131
  },
120
132
  },
133
+ {
134
+ term: {
135
+ 'isParent': false,
136
+ },
137
+ },
121
138
  ],
122
139
  },
123
140
  },
124
141
  };
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 );
142
+ const taggedData = await getOpenSearchCount( openSearch.revop, taggedCountQuery );
143
+ const taggedValue = ( taggedData?.body?.count || 0 )+ ( inputData.revopsType == 'duplicate'? inputData?.duplicateImage?.length : 1 );
144
+ // If the unit is %, compare percentage of taggedValue/footfall, otherwise compare taggedValue to limitValue directly
145
+ let isLimitExceeded = false;
146
+ if ( unit === '%' ) {
147
+ // footfall may be undefined, treat as 0 (avoid division by zero)
148
+ const totalFootfall = Number( footfall ) || 0;
149
+ const taggedPercent = totalFootfall > 0 ? ( taggedValue / totalFootfall ) * 100 : 0;
150
+ isLimitExceeded = taggedPercent > limitValue;
151
+ logger.info( {
152
+ limitType: 'PERCENT',
153
+ taggedValue,
154
+ totalFootfall,
155
+ taggedPercent,
156
+ limitValue,
157
+ isLimitExceeded,
158
+ forRevopsType: inputData.revopsType,
159
+ } );
128
160
  } else {
129
- next();
161
+ // Non-percent, treat limitValue as an absolute number
162
+ isLimitExceeded = taggedValue > limitValue;
163
+ logger.info( {
164
+ limitType: 'ABSOLUTE',
165
+ taggedValue,
166
+ limitValue,
167
+ isLimitExceeded,
168
+ forRevopsType: inputData.revopsType,
169
+ } );
130
170
  }
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 );
171
+ if ( isLimitExceeded ) {
172
+ return res.sendError( `Limit exceed: Only ${limitValue}${unit || ''} items allowed for ${inputData.revopsType}`, 400 );
137
173
  }
174
+ return next();
138
175
  } else {
139
- next();
176
+ return next();
140
177
  }
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
178
  } catch ( error ) {
208
179
  logger.error( { error: error, message: req.body, function: 'traffic-revop-getTaggingConfig' } );
209
180
  next();