tango-app-api-analysis-traffic 3.8.13 → 3.8.15-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-analysis-traffic",
3
- "version": "3.8.13",
3
+ "version": "3.8.15-alpha.0",
4
4
  "description": "Traffic Analysis",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -63,7 +63,6 @@ export async function addBills( req, res ) {
63
63
  let resData = [];
64
64
  const openSearch = JSON.parse( process.env.OPENSEARCH );
65
65
  const inputData = req.tempInserData;
66
- console.log( '🚀 ~ addBills ~ inputData:', inputData.length );
67
66
  // return;
68
67
  for ( let i = 0; i < inputData?.length; i++ ) {
69
68
  await updateOneNobBilling( inputData[i]?.query, inputData[i]?.data );
@@ -166,7 +165,6 @@ export async function getNobData( req, res ) {
166
165
  };
167
166
 
168
167
  const getNobData = await getOpenSearchData( openSearch.nob, nobQuery );
169
- console.log( '🚀 ~ getNobData ~ openSearch.nob:', openSearch.nob );
170
168
  const nobData = getNobData?.body?.hits?.hits;
171
169
  if ( !nobData || nobData?.length == 0 ) {
172
170
  if ( inputData.searchValue && inputData.searchValue !== '' || inputData.offset > 1 ) {
@@ -593,7 +593,6 @@ export async function expireReviewStatus( req, res ) {
593
593
 
594
594
  cutoffDate.setUTCHours( 23, 59, 59, 0 );
595
595
 
596
- console.log( cutoffDate );
597
596
  if ( Number.isNaN( cutoffDate.getTime() ) ) {
598
597
  return res.sendError( 'Invalid thresholdDate', 400 );
599
598
  }
@@ -703,12 +702,16 @@ export async function expireReviewStatus( req, res ) {
703
702
  export async function expireApproveStatus( req, res ) {
704
703
  try {
705
704
  const {
706
- thresholdDate = '2026-01-21',
705
+ thresholdDate,
707
706
  batchSize = 500,
708
707
  storeId,
709
708
  dateString,
710
709
  } = req.body;
711
710
  const cutoffDate = new Date( thresholdDate );
711
+ // Convert cutoffDate to "2026-01-12T23:59:59.000Z"
712
+
713
+ cutoffDate.setUTCHours( 23, 59, 59, 0 );
714
+
712
715
  if ( Number.isNaN( cutoffDate.getTime() ) ) {
713
716
  return res.sendError( 'Invalid thresholdDate', 400 );
714
717
  }
@@ -762,7 +765,6 @@ export async function expireApproveStatus( req, res ) {
762
765
  let updatedMapping = mappingInfo.map( ( item ) => {
763
766
  if ( item?.type === 'approve' && item?.status !== 'Closed' && item?.dueDate ) {
764
767
  const due = new Date( item.dueDate );
765
- logger.info( { item, due, msg: '..........1', cutoffDate } );
766
768
  if ( !Number.isNaN( due.getTime() ) && due < cutoffDate ) {
767
769
  changed = true;
768
770
 
@@ -772,24 +774,19 @@ export async function expireApproveStatus( req, res ) {
772
774
 
773
775
  return item;
774
776
  } );
775
- logger.info( { updatedMapping, msh: '.......12' } );
776
777
  if ( changed ) {
777
- logger.info( { changed, msg: '.......2' } );
778
778
  updatedMapping = updatedMapping.map( ( item ) => {
779
- logger.info( { item, msg: '.......3' } );
780
779
  if ( item?.type === 'tagging' ) {
781
- logger.info( { item: item?.type, msg: '.......4' } );
782
780
  return { ...item, status: 'Expired' };
783
781
  }
784
782
  return item;
785
783
  } );
786
784
  }
787
785
 
788
- logger.info( { updatedMapping, msh: '.......13' } );
789
786
  if ( changed ) {
790
787
  const doc = {
791
788
  mappingInfo: updatedMapping,
792
- status: 'Expired',
789
+ status: 'Approver-Expired',
793
790
  };
794
791
  logger.info( { updatedMapping } );
795
792
  bulkBody.push(
@@ -815,7 +812,130 @@ export async function expireApproveStatus( req, res ) {
815
812
 
816
813
  return res.sendSuccess( { message: 'Expired approve status updated', updated: totalUpdated } );
817
814
  } catch ( error ) {
818
- logger.error( { error: error, message: req.body, function: 'expireApproveStatus' } );
815
+ logger.error( { error: error, message: req.body, function: 'expireReviewStatus' } );
816
+ return res.sendError( { error: error }, 500 );
817
+ }
818
+ }
819
+
820
+
821
+ export async function expireTangoReviewStatus( req, res ) {
822
+ try {
823
+ const {
824
+ thresholdDate,
825
+ batchSize = 500,
826
+ storeId,
827
+ dateString,
828
+ } = req.body;
829
+ const cutoffDate = new Date( thresholdDate );
830
+ // Convert cutoffDate to "2026-01-12T23:59:59.000Z"
831
+
832
+ cutoffDate.setUTCHours( 23, 59, 59, 0 );
833
+
834
+ if ( Number.isNaN( cutoffDate.getTime() ) ) {
835
+ return res.sendError( 'Invalid thresholdDate', 400 );
836
+ }
837
+
838
+
839
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
840
+ const query = {
841
+ size: batchSize,
842
+ query: {
843
+ bool: {
844
+ must: [
845
+ { term: { 'ticketName.keyword': 'footfall-directory' } },
846
+ { term: { 'type.keyword': 'store' } },
847
+ ],
848
+ must_not: [
849
+ { terms: { 'status.keyword': [ 'Closed' ] } },
850
+ ],
851
+ },
852
+ },
853
+ };
854
+
855
+
856
+ if ( storeId ) {
857
+ query.query.bool.must.push( { term: { 'storeId.keyword': storeId } } );
858
+ }
859
+
860
+
861
+ if ( dateString ) {
862
+ query.query.bool.must.push( {
863
+ terms: {
864
+ dateString: Array.isArray( dateString ) ? dateString : `${dateString}`.split( ',' ),
865
+ },
866
+ } );
867
+ }
868
+
869
+
870
+ let totalUpdated = 0;
871
+ let scrollId = null;
872
+
873
+ let firstResponse = await searchOpenSearchData( openSearch.footfallDirectory, query );
874
+ let hitsBatch = firstResponse?.body?.hits?.hits || [];
875
+ scrollId = firstResponse?.body?._scroll_id;
876
+
877
+ while ( hitsBatch.length > 0 ) {
878
+ const bulkBody = [];
879
+
880
+ for ( const hit of hitsBatch ) {
881
+ const src = hit._source || {};
882
+ const mappingInfo = Array.isArray( src.mappingInfo ) ? src.mappingInfo : [];
883
+ let changed = false;
884
+ let updatedMapping = mappingInfo.map( ( item ) => {
885
+ if ( item?.type === 'tangoreview' && item?.status !== 'Closed' && item?.dueDate ) {
886
+ const due = new Date( item.dueDate );
887
+ if ( !Number.isNaN( due.getTime() ) && due < cutoffDate ) {
888
+ changed = true;
889
+
890
+ return { ...item, status: 'Expired' };
891
+ }
892
+ }
893
+
894
+ return item;
895
+ } );
896
+ if ( changed ) {
897
+ const getConfig = await clientService.findOne( src.clientId, { 'footfallDirectoryConfigs.tangoApproved': 1 } );
898
+ if ( getConfig?.footfallDirectoryConfigs?.tangoApproved && getConfig?.footfallDirectoryConfigs?.tangoApproved === true ) {
899
+ ;
900
+ updatedMapping = updatedMapping.map( ( item ) => {
901
+ if ( item?.type === 'tagging' ) {
902
+ return { ...item, status: 'Expired' };
903
+ }
904
+ return item;
905
+ } );
906
+ }
907
+ }
908
+
909
+ if ( changed ) {
910
+ const doc = {
911
+ mappingInfo: updatedMapping,
912
+ status: 'Tango-Expired',
913
+ };
914
+ logger.info( { updatedMapping } );
915
+ bulkBody.push(
916
+ { update: { _index: openSearch.footfallDirectory, _id: hit._id } },
917
+ { doc: doc, doc_as_upsert: true },
918
+ );
919
+ }
920
+ }
921
+
922
+ if ( bulkBody.length > 0 ) {
923
+ const bulkRes = await bulkUpdate( bulkBody );
924
+ if ( bulkRes?.errors ) {
925
+ logger.error( { message: 'Bulk expire errors', items: bulkRes.items } );
926
+ }
927
+ totalUpdated += bulkBody.length / 2;
928
+ }
929
+ logger.info( { totalUpdated, msg: '........9' } );
930
+ if ( !scrollId ) break;
931
+ const nextRes = await scrollResponse( scrollId );
932
+ hitsBatch = nextRes?.body?.hits?.hits || [];
933
+ scrollId = nextRes?.body?._scroll_id;
934
+ }
935
+
936
+ return res.sendSuccess( { message: 'Expired approve status updated', updated: totalUpdated } );
937
+ } catch ( error ) {
938
+ logger.error( { error: error, message: req.body, function: 'expireReviewStatus' } );
819
939
  return res.sendError( { error: error }, 500 );
820
940
  }
821
941
  }
@@ -1010,14 +1130,7 @@ export async function storeProcessedData( req, res ) {
1010
1130
  const footfallDirRes = await getOpenSearchData( openSearch.footfallDirectory, footfallDirQuery );
1011
1131
  const hit = footfallDirRes?.body?.hits?.hits?.[0];
1012
1132
  receivedfootfall = hit._source.footfallCount;
1013
- if ( hit?._source?.mappingInfo && Array.isArray( hit._source.mappingInfo ) ) {
1014
- for ( let i = 0; i < hit._source.mappingInfo.length; i++ ) {
1015
- if ( hit._source.mappingInfo[i].type === 'tagging' ) {
1016
- ticketStatus = hit._source.mappingInfo[i].status;
1017
- break;
1018
- }
1019
- }
1020
- }
1133
+ ticketStatus = hit?._source?.status;
1021
1134
  } catch ( err ) {
1022
1135
  logger.warn( { message: 'Could not get ticket status from footfallDirectory', error: err } );
1023
1136
  }
@@ -1044,7 +1157,6 @@ export async function storeProcessedData( req, res ) {
1044
1157
  raisedStatusEnabled,
1045
1158
  footfallticketCount: statusArray.includes( ticketStatus )?receivedfootfall:processedData?.footfall_count,
1046
1159
  } );
1047
- console.log( '🚀 ~ storeProcessedData ~ ticketStatus:', ticketStatus );
1048
1160
 
1049
1161
  if ( raisedStatusEnabled === 'block' ) {
1050
1162
  // Calculate the number of days from currentDate + 1 to the end of the month
@@ -1274,6 +1386,9 @@ export async function footFallImages( req, res ) {
1274
1386
  // temp.length? temp[0].status = 'open': null;
1275
1387
 
1276
1388
  if ( resultData.status_code == '200' ) {
1389
+ if ( !req?.store?.footfallDirectoryConfigs?.contactEmail ||( req?.store?.footfallDirectoryConfigs && req?.store?.footfallDirectoryConfigs?.contactEmail && req?.store?.footfallDirectoryConfigs?.contactEmail === '' ) ) {
1390
+ req.store.footfallDirectoryConfigs.contactEmail = getFinal?.contactEmail || '';
1391
+ }
1277
1392
  return res.sendSuccess( { ...resultData, ticketStatus: temp?.length > 0 && ticketDetails? temp : null, config: req?.store?.footfallDirectoryConfigs } );
1278
1393
  } else {
1279
1394
  return res.sendError( 'No Content', 204 );
@@ -1300,6 +1415,7 @@ export async function tagTempId( req, res ) {
1300
1415
  const openSearch = JSON.parse( process.env.OPENSEARCH );
1301
1416
 
1302
1417
  const upsertRecord = {
1418
+ mode: inputData.mode || '',
1303
1419
  clientId: inputData.storeId.split( '-' )[0],
1304
1420
  storeId: inputData.storeId,
1305
1421
  tempId: inputData.tempId,
@@ -1,9 +1,10 @@
1
1
 
2
2
  import express from 'express';
3
- import { storeProcessedData, getconfig, revoptagging, getrevoptagging, revoptaggingcount, footFallImages, tagTempId, getCategorizedImages, vmsDataMigration, migrateRevopIndex, expireReviewStatus } from '../controllers/revop.controller.js';
3
+ import { storeProcessedData, getconfig, revoptagging, getrevoptagging, revoptaggingcount, footFallImages, tagTempId, getCategorizedImages, vmsDataMigration, migrateRevopIndex, expireReviewStatus, expireApproveStatus } from '../controllers/revop.controller.js';
4
4
  import { isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
5
5
  import { footfallImagesValid, getCategorizedImagesValid, storeProcessedDataValid, tagTempIdValid, vmsDataMigrationValid } from '../dtos/revop.dtos.js';
6
- import { deleteTaggedDuplicate, getTaggingConfig, mappingConfig } from '../validations/revop.validation.js';
6
+ import { deleteTaggedDuplicate, getTaggingConfig, mappingConfig, validateDateAndFilePath } from '../validations/revop.validation.js';
7
+
7
8
 
8
9
  export const revopRouter = express.Router();
9
10
 
@@ -13,12 +14,13 @@ revopRouter
13
14
  .post( '/getrevoptagging', isAllowedSessionHandler, getrevoptagging )
14
15
  .post( '/migrate-revop', migrateRevopIndex )
15
16
  .post( '/expire-review-status', expireReviewStatus )
17
+ .post( '/expire-approve-status', expireApproveStatus )
16
18
  .post( '/revoptaggingcount', isAllowedSessionHandler, revoptaggingcount )
17
19
 
18
- // new enhnacemnet (for footfall directory)
20
+ // new enhancement (for footfall directory)
19
21
  .get( '/store-processed-data', isAllowedSessionHandler, validate( storeProcessedDataValid ), storeProcessedData )
20
22
  .get( '/footfall-images', isAllowedSessionHandler, validate( footfallImagesValid ), getTaggingConfig, footFallImages )
21
- .post( '/tag-tempId', isAllowedSessionHandler, validate( tagTempIdValid ), mappingConfig, deleteTaggedDuplicate, tagTempId )
23
+ .post( '/tag-tempId', isAllowedSessionHandler, validate( tagTempIdValid ), mappingConfig, deleteTaggedDuplicate, validateDateAndFilePath, tagTempId )
22
24
  .post( '/get-categorized-images', isAllowedSessionHandler, validate( getCategorizedImagesValid ), getCategorizedImages )
23
25
  .post( '/vms-data-migration', validate( vmsDataMigrationValid ), vmsDataMigration );
24
26
 
@@ -1,7 +1,6 @@
1
1
  import nobBillingModel from 'tango-api-schema/schema/nobBilling.model.js';
2
2
 
3
3
  export async function updateOneNobBilling( query, record ) {
4
- console.log( '🚀 ~ updateOneNobBilling ~ record:', record );
5
4
  return await nobBillingModel.updateOne( query, { $set: record }, { upsert: true } );
6
5
  }
7
6
 
@@ -284,6 +284,14 @@ export async function mappingConfig( req, res, next ) {
284
284
  },
285
285
  },
286
286
  ],
287
+ must_not: [
288
+ {
289
+ term: {
290
+ 'parent': inputData?.tempId || '', // Exclude current tempId if it's a duplicate tagging (to avoid counting the same image twice)
291
+ },
292
+ },
293
+ ],
294
+
287
295
  },
288
296
  },
289
297
  };
@@ -358,3 +366,38 @@ export async function deleteTaggedDuplicate( req, res, next ) {
358
366
  }
359
367
  }
360
368
 
369
+
370
+ export async function validateDateAndFilePath( req, res, next ) {
371
+ try {
372
+ const inputData = req.body;
373
+ const getDateString = inputData?.dateString;
374
+ const dateString = getDateString ?
375
+ getDateString.split( '-' ).reverse().join( '-' ) :
376
+ '';
377
+ const filePath = inputData?.filePath;
378
+
379
+ if ( !dateString ) {
380
+ return res.sendError( 'Missing payload field: dateString', 400 );
381
+ }
382
+ if ( !filePath ) {
383
+ return res.sendError( 'Missing payload field: filePath', 400 );
384
+ }
385
+
386
+ // Based on requirement: match payload dateString with filePath.split[1]
387
+ // Example: 11-1001/06-05-2026/5_customer_entry_frame.jpeg
388
+ // split('/') => [ '11-1001', '06-05-2026', '5_customer_entry_frame.jpeg' ] => index 1 is '06-05-2026'
389
+ const parts = String( filePath ).split( '/' );
390
+ const pathDateString = parts?.[1];
391
+
392
+ if ( dateString !== pathDateString ) {
393
+ return res.sendError( `Invalid payload: Image Date Mismatch `, 400 );
394
+ }
395
+
396
+ next();
397
+ } catch ( error ) {
398
+ logger.error( { error: error, message: req.body, function: 'traffic-revop-validateDateAndFilePath' } );
399
+ return res.sendError( error.message || 'Error validating date and filePath', 400 );
400
+ }
401
+ }
402
+
403
+