tango-app-api-analysis-traffic 3.8.7-vms.5 → 3.8.7-vms.7

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.5",
3
+ "version": "3.8.7-vms.7",
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 ) || {};
@@ -427,75 +426,138 @@ export async function footFallImages( req, res ) {
427
426
  switch ( req.user.role ) {
428
427
  case 'user':
429
428
  const actionTypesUser = [ 'tagging', 'finalreview' ];
430
- temp = actionTypesUser.map( ( type ) => {
431
- const mapping = getMappingForType( type );
432
429
 
433
- const revisedFootfall = mapping.revisedFootfall ?? 0;
434
- // Do not divide by 0
435
- const revisedPerc =
436
- footfallValue > 0 ?
437
- `${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
438
- '0';
439
-
440
- // Since keys in count can be dynamic, just deep copy the count object or empty object
441
- const countObj = mapping.count ? { ...mapping.count } : {};
442
-
443
- return {
444
- actionType: type,
445
- footfall: footfallValue,
446
- revicedFootfall: revisedFootfall,
447
- revicedPerc: revisedPerc,
448
- count: countObj,
449
- };
430
+ temp = [];
431
+ actionTypesUser.forEach( ( type ) => {
432
+ const mapping = getMappingForType( type );
433
+ if ( type === 'tagging' ) {
434
+ const revisedFootfall = mapping.revicedFootfall ?? 0;
435
+ const revisedPerc =
436
+ footfallValue > 0 ?
437
+ `${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
438
+ '0';
439
+ const countObj = mapping.count ? [ ...mapping.count ] : [];
440
+ temp.push( {
441
+ actionType: type,
442
+ footfall: footfallValue,
443
+ revicedFootfall: revisedFootfall,
444
+ revicedPerc: revisedPerc,
445
+ count: countObj,
446
+ createdAt: mapping.createdAt ?? '',
447
+ createdByEmail: mapping.createdByEmail ?? '',
448
+ createdByUserName: mapping.createdByUserName ?? '',
449
+ createdByRole: mapping.createdByRole ?? '',
450
+ } );
451
+ } else if ( type !== 'tagging' && mapping.status === 'closed' ) {
452
+ const revisedFootfall = mapping.revicedFootfall ?? 0;
453
+ const revisedPerc =
454
+ footfallValue > 0 ?
455
+ `${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
456
+ '0';
457
+ const countObj = mapping.count ? [ ...mapping.count ] : [];
458
+ temp.push( {
459
+ actionType: type,
460
+ footfall: footfallValue,
461
+ revicedFootfall: revisedFootfall,
462
+ revicedPerc: revisedPerc,
463
+ count: countObj,
464
+ createdAt: mapping.createdAt ?? '',
465
+ createdByEmail: mapping.createdByEmail ?? '',
466
+ createdByUserName: mapping.createdByUserName ?? '',
467
+ createdByRole: mapping.createdByRole ?? '',
468
+ } );
469
+ }
450
470
  } );
451
471
  break;
452
472
  case 'admin':
453
473
  const actionTypesAdmin = [ 'tagging', 'review', 'finalreview' ];
454
- temp = actionTypesAdmin.map( ( type ) => {
474
+ temp = [];
475
+ actionTypesAdmin.forEach( ( type ) => {
455
476
  const mapping = getMappingForType( type );
456
-
457
- const revisedFootfall = mapping.revisedFootfall ?? 0;
458
- // Do not divide by 0
459
- const revisedPerc =
460
- footfallValue > 0 ?
461
- `${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
462
- '0';
463
-
464
- // Since keys in count can be dynamic, just deep copy the count object or empty object
465
- const countObj = mapping.count ? { ...mapping.count } : {};
466
-
467
- return {
468
- actionType: type,
469
- footfall: footfallValue,
470
- revicedFootfall: revisedFootfall,
471
- revicedPerc: revisedPerc,
472
- count: countObj,
473
- };
477
+ if ( type === 'tagging' ) {
478
+ const revisedFootfall = mapping.revicedFootfall ?? 0;
479
+ const revisedPerc =
480
+ footfallValue > 0 ?
481
+ `${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
482
+ '0';
483
+ const countObj = mapping.count ? [ ...mapping.count ] : [];
484
+ temp.push( {
485
+ actionType: type,
486
+ footfall: footfallValue,
487
+ revicedFootfall: revisedFootfall,
488
+ revicedPerc: revisedPerc,
489
+ count: countObj,
490
+ createdAt: mapping.createdAt ?? '',
491
+ createdByEmail: mapping.createdByEmail ?? '',
492
+ createdByUserName: mapping.createdByUserName ?? '',
493
+ createdByRole: mapping.createdByRole ?? '',
494
+ } );
495
+ } else if ( type !== 'tagging' && mapping.status === 'closed' ) {
496
+ const revisedFootfall = mapping.revicedFootfall ?? 0;
497
+ const revisedPerc =
498
+ footfallValue > 0 ?
499
+ `${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
500
+ '0';
501
+ const countObj = mapping.count ? [ ...mapping.count ] : [];
502
+ temp.push( {
503
+ actionType: type,
504
+ footfall: footfallValue,
505
+ revicedFootfall: revisedFootfall,
506
+ revicedPerc: revisedPerc,
507
+ count: countObj,
508
+ createdAt: mapping.createdAt ?? '',
509
+ createdByEmail: mapping.createdByEmail ?? '',
510
+ createdByUserName: mapping.createdByUserName ?? '',
511
+ createdByRole: mapping.createdByRole ?? '',
512
+ } );
513
+ }
474
514
  } );
475
- break;
476
515
  }
477
516
  } else {
478
517
  const actionTypes = [ 'tagging', 'review', 'approve', 'finalreview' ];
479
- temp = actionTypes.map( ( type ) => {
480
- const mapping = getMappingForType( type );
481
518
 
482
- const revisedFootfall = mapping.revisedFootfall ?? 0;
483
- // Do not divide by 0
484
- const revisedPerc =
485
- footfallValue > 0 ?
486
- `${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
487
- '0';
488
-
489
- // Since keys in count can be dynamic, just deep copy the count object or empty object
490
- const countObj = mapping.count ? { ...mapping.count } : {};
491
-
492
- return {
493
- actionType: type,
494
- footfall: footfallValue,
495
- revicedFootfall: revisedFootfall,
496
- revicedPerc: revisedPerc,
497
- count: countObj,
498
- };
519
+ // Dynamically add to temp only if actionType matches and status is 'closed',
520
+ // except for 'tagging' where status must be 'raised'
521
+ temp = [];
522
+ actionTypes.forEach( ( type ) => {
523
+ const mapping = getMappingForType( type );
524
+ if ( type === 'tagging' ) {
525
+ const revisedFootfall = mapping.revicedFootfall ?? 0;
526
+ const revisedPerc =
527
+ footfallValue > 0 ?
528
+ `${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
529
+ '0';
530
+ const countObj = mapping.count ? [ ...mapping.count ] : [];
531
+ temp.push( {
532
+ actionType: type,
533
+ footfall: footfallValue,
534
+ revicedFootfall: revisedFootfall,
535
+ revicedPerc: revisedPerc,
536
+ count: countObj,
537
+ createdAt: mapping.createdAt ?? '',
538
+ createdByEmail: mapping.createdByEmail ?? '',
539
+ createdByUserName: mapping.createdByUserName ?? '',
540
+ createdByRole: mapping.createdByRole ?? '',
541
+ } );
542
+ } else if ( type !== 'tagging' && mapping.status === 'closed' ) {
543
+ const revisedFootfall = mapping.revicedFootfall ?? 0;
544
+ const revisedPerc =
545
+ footfallValue > 0 ?
546
+ `${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
547
+ '0';
548
+ const countObj = mapping.count ? [ ...mapping.count ] : [];
549
+ temp.push( {
550
+ actionType: type,
551
+ footfall: footfallValue,
552
+ revicedFootfall: revisedFootfall,
553
+ revicedPerc: revisedPerc,
554
+ count: countObj,
555
+ createdAt: mapping.createdAt ?? '',
556
+ createdByEmail: mapping.createdByEmail ?? '',
557
+ createdByUserName: mapping.createdByUserName ?? '',
558
+ createdByRole: mapping.createdByRole ?? '',
559
+ } );
560
+ }
499
561
  } );
500
562
  }
501
563
 
@@ -505,7 +567,7 @@ export async function footFallImages( req, res ) {
505
567
  if ( resultData ) {
506
568
  // temp.length? temp[0].status = 'open': null;
507
569
  if ( resultData.status_code == '200' ) {
508
- return res.sendSuccess( { ...resultData, ticketStatus: temp?.length > 0? temp : null, config: req?.store?.footfallDirectoryConfigs } );
570
+ return res.sendSuccess( { ...resultData, ticketStatus: temp?.length > 0 && ticketDetails? temp : null, config: req?.store?.footfallDirectoryConfigs } );
509
571
  } else {
510
572
  return res.sendError( 'No Content', 204 );
511
573
  }
@@ -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();