tango-app-api-analysis-traffic 3.8.7-vms.1 → 3.8.7-vms.10
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.
|
|
3
|
+
"version": "3.8.7-vms.10",
|
|
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.
|
|
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"
|
|
@@ -221,6 +221,9 @@ export async function storeProcessedData( req, res ) {
|
|
|
221
221
|
dayjs.extend( isSameOrBefore );
|
|
222
222
|
|
|
223
223
|
let start = dayjs( fromDate );
|
|
224
|
+
// get start value from the before day one
|
|
225
|
+
// Move start one day back so we can access "day before" when looping
|
|
226
|
+
start = start.subtract( 1, 'day' );
|
|
224
227
|
let end = dayjs( toDate );
|
|
225
228
|
|
|
226
229
|
if ( !start.isValid() || !end.isValid() ) {
|
|
@@ -267,10 +270,12 @@ export async function storeProcessedData( req, res ) {
|
|
|
267
270
|
|
|
268
271
|
const responseArray = [];
|
|
269
272
|
|
|
270
|
-
for ( let i =
|
|
273
|
+
for ( let i = 1; i < orderedDates.length; i++ ) {
|
|
271
274
|
const currentDate = orderedDates[i];
|
|
272
275
|
const currentId = `${storeId}_${currentDate}`;
|
|
276
|
+
logger.info( { currentId, currentDate } );
|
|
273
277
|
const processedData = hitsMap.get( currentId );
|
|
278
|
+
logger.info( { processedData } );
|
|
274
279
|
if ( !processedData ) {
|
|
275
280
|
responseArray.push( {
|
|
276
281
|
date: currentDate,
|
|
@@ -283,13 +288,17 @@ export async function storeProcessedData( req, res ) {
|
|
|
283
288
|
|
|
284
289
|
const prevDate = dayjs( currentDate ).subtract( 1, 'day' ).format( 'YYYY-MM-DD' );
|
|
285
290
|
const prevId = `${storeId}_${prevDate}`;
|
|
291
|
+
logger.info( { prevId, prevDate } );
|
|
286
292
|
const previousData = hitsMap.get( prevId );
|
|
287
293
|
|
|
288
294
|
let footfallCountTrend = 0;
|
|
295
|
+
logger.info( { previousData, previoucubr1: previousData?.footfall_count } );
|
|
289
296
|
if ( previousData && previousData.footfall_count ) {
|
|
297
|
+
logger.info( { previousData, previoucubr: previousData?.footfall_count } );
|
|
290
298
|
footfallCountTrend = Math.round(
|
|
291
|
-
( ( processedData.footfall_count - previousData
|
|
299
|
+
( ( processedData.footfall_count - previousData?.footfall_count ) / previousData.footfall_count ) * 100,
|
|
292
300
|
);
|
|
301
|
+
logger.info( { footfallCountTrend } );
|
|
293
302
|
}
|
|
294
303
|
// Add ticket status from openSearch.footfallDirectory (_source.status)
|
|
295
304
|
let ticketStatus = null;
|
|
@@ -306,12 +315,19 @@ export async function storeProcessedData( req, res ) {
|
|
|
306
315
|
},
|
|
307
316
|
},
|
|
308
317
|
size: 1,
|
|
309
|
-
_source: [ 'status' ],
|
|
318
|
+
_source: [ 'status', 'mappingInfo' ],
|
|
310
319
|
};
|
|
311
320
|
try {
|
|
312
321
|
const footfallDirRes = await getOpenSearchData( openSearch.footfallDirectory, footfallDirQuery );
|
|
313
322
|
const hit = footfallDirRes?.body?.hits?.hits?.[0];
|
|
314
|
-
|
|
323
|
+
if ( hit?._source?.mappingInfo && Array.isArray( hit._source.mappingInfo ) ) {
|
|
324
|
+
for ( let i = 0; i < hit._source.mappingInfo.length; i++ ) {
|
|
325
|
+
if ( hit._source.mappingInfo[i].type === 'tagging' ) {
|
|
326
|
+
ticketStatus = hit._source.mappingInfo[i].status;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
315
331
|
} catch ( err ) {
|
|
316
332
|
logger.warn( { message: 'Could not get ticket status from footfallDirectory', error: err } );
|
|
317
333
|
}
|
|
@@ -335,7 +351,27 @@ export async function storeProcessedData( req, res ) {
|
|
|
335
351
|
downtime: processedData?.down_time || 0,
|
|
336
352
|
ticketStatus,
|
|
337
353
|
raisedStatusEnabled,
|
|
354
|
+
|
|
338
355
|
} );
|
|
356
|
+
|
|
357
|
+
if ( raisedStatusEnabled === 'block' ) {
|
|
358
|
+
// Calculate the number of days from currentDate + 1 to the end of the month
|
|
359
|
+
// Assume currentDate is in format 'YYYY-MM-DD'
|
|
360
|
+
const currentDateObj = new Date( currentDate );
|
|
361
|
+
// Move to next day
|
|
362
|
+
const nextDay = new Date( currentDateObj );
|
|
363
|
+
nextDay.setDate( currentDateObj.getDate() + 1 );
|
|
364
|
+
|
|
365
|
+
// Get the last date of the current month
|
|
366
|
+
const endOfMonth = new Date( currentDateObj.getFullYear(), currentDateObj.getMonth() + 1, 0 );
|
|
367
|
+
|
|
368
|
+
// Calculate number of days (inclusive of end date, exclusive of nextDay)
|
|
369
|
+
let noOfBlockedDays = Math.floor( ( endOfMonth - nextDay ) / ( 1000 * 60 * 60 * 24 ) ) + 1;
|
|
370
|
+
if ( noOfBlockedDays < 0 ) noOfBlockedDays = 0;
|
|
371
|
+
|
|
372
|
+
// Add to response
|
|
373
|
+
responseArray[responseArray.length - 1].noOfBlockedDays = noOfBlockedDays;
|
|
374
|
+
}
|
|
339
375
|
}
|
|
340
376
|
|
|
341
377
|
return res.sendSuccess( responseArray );
|
|
@@ -379,40 +415,177 @@ export async function footFallImages( req, res ) {
|
|
|
379
415
|
],
|
|
380
416
|
},
|
|
381
417
|
},
|
|
382
|
-
'_source': [ 'dateString', 'storeId', '
|
|
418
|
+
'_source': [ 'dateString', 'storeId', 'mappingInfo', 'revicedFootfall', 'revicedPerc', 'createdAt', 'updatedAt', 'footfallCount' ],
|
|
383
419
|
|
|
384
420
|
};
|
|
385
421
|
|
|
386
422
|
const getData = await getOpenSearchData( opensearch.footfallDirectory, query );
|
|
387
423
|
const ticketDetails = getData?.body?.hits?.hits[0];
|
|
388
424
|
let temp = [];
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
425
|
+
const footfallValue = ticketDetails?._source?.footfallCount ?? 0;
|
|
426
|
+
const mappingInfoArray = ticketDetails?._source?.mappingInfo ?? [];
|
|
427
|
+
// Helper to get mappingInfo for an actionType
|
|
428
|
+
function getMappingForType( type ) {
|
|
429
|
+
return mappingInfoArray.find( ( m ) => m.type === type ) || {};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// List of actionTypes to process in sequence
|
|
433
|
+
|
|
434
|
+
if ( req.user.userType !== 'tango' && req.user.role !== 'superadmin' ) {
|
|
435
|
+
switch ( req.user.role ) {
|
|
436
|
+
case 'user':
|
|
437
|
+
const actionTypesUser = [ 'tagging', 'finalreview' ];
|
|
438
|
+
|
|
439
|
+
temp = [];
|
|
440
|
+
actionTypesUser.forEach( ( type ) => {
|
|
441
|
+
const mapping = getMappingForType( type );
|
|
442
|
+
if ( type === 'tagging' ) {
|
|
443
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
444
|
+
const revisedPerc =
|
|
445
|
+
footfallValue > 0 ?
|
|
446
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
447
|
+
'0';
|
|
448
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
449
|
+
temp.push( {
|
|
450
|
+
actionType: type,
|
|
451
|
+
footfall: footfallValue,
|
|
452
|
+
revicedFootfall: revisedFootfall,
|
|
453
|
+
revicedPerc: revisedPerc,
|
|
454
|
+
count: countObj,
|
|
455
|
+
createdAt: mapping.createdAt ?? '',
|
|
456
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
457
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
458
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
459
|
+
isUp: false,
|
|
460
|
+
} );
|
|
461
|
+
} else if ( type !== 'tagging' && mapping.status === 'closed' ) {
|
|
462
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
463
|
+
const revisedPerc =
|
|
464
|
+
footfallValue > 0 ?
|
|
465
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
466
|
+
'0';
|
|
467
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
468
|
+
temp.push( {
|
|
469
|
+
actionType: type,
|
|
470
|
+
footfall: footfallValue,
|
|
471
|
+
revicedFootfall: revisedFootfall,
|
|
472
|
+
revicedPerc: revisedPerc,
|
|
473
|
+
count: countObj,
|
|
474
|
+
createdAt: mapping.createdAt ?? '',
|
|
475
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
476
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
477
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
478
|
+
} );
|
|
479
|
+
}
|
|
480
|
+
} );
|
|
481
|
+
break;
|
|
482
|
+
case 'admin':
|
|
483
|
+
const actionTypesAdmin = [ 'tagging', 'review', 'finalreview' ];
|
|
484
|
+
temp = [];
|
|
485
|
+
actionTypesAdmin.forEach( ( type ) => {
|
|
486
|
+
const mapping = getMappingForType( type );
|
|
487
|
+
if ( type === 'tagging' ) {
|
|
488
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
489
|
+
const revisedPerc =
|
|
490
|
+
footfallValue > 0 ?
|
|
491
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
492
|
+
'0';
|
|
493
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
494
|
+
temp.push( {
|
|
495
|
+
actionType: type,
|
|
496
|
+
footfall: footfallValue,
|
|
497
|
+
revicedFootfall: revisedFootfall,
|
|
498
|
+
revicedPerc: revisedPerc,
|
|
499
|
+
count: countObj,
|
|
500
|
+
createdAt: mapping.createdAt ?? '',
|
|
501
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
502
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
503
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
504
|
+
isUp: false,
|
|
505
|
+
} );
|
|
506
|
+
} else if ( type !== 'tagging' && mapping.status === 'closed' ) {
|
|
507
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
508
|
+
const revisedPerc =
|
|
509
|
+
footfallValue > 0 ?
|
|
510
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
511
|
+
'0';
|
|
512
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
513
|
+
temp.push( {
|
|
514
|
+
actionType: type,
|
|
515
|
+
footfall: footfallValue,
|
|
516
|
+
revicedFootfall: revisedFootfall,
|
|
517
|
+
revicedPerc: revisedPerc,
|
|
518
|
+
count: countObj,
|
|
519
|
+
createdAt: mapping.createdAt ?? '',
|
|
520
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
521
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
522
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
523
|
+
} );
|
|
524
|
+
}
|
|
525
|
+
} );
|
|
526
|
+
}
|
|
527
|
+
} else {
|
|
528
|
+
const actionTypes = [ 'tagging', 'review', 'approve', 'finalreview' ];
|
|
529
|
+
|
|
530
|
+
// Dynamically add to temp only if actionType matches and status is 'closed',
|
|
531
|
+
// except for 'tagging' where status must be 'raised'
|
|
532
|
+
temp = [];
|
|
533
|
+
actionTypes.forEach( ( type ) => {
|
|
534
|
+
const mapping = getMappingForType( type );
|
|
535
|
+
if ( type === 'tagging' ) {
|
|
536
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
537
|
+
const revisedPerc =
|
|
538
|
+
footfallValue > 0 ?
|
|
539
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
540
|
+
'0';
|
|
541
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
542
|
+
temp.push( {
|
|
543
|
+
actionType: type,
|
|
544
|
+
footfall: footfallValue,
|
|
545
|
+
revicedFootfall: revisedFootfall,
|
|
546
|
+
revicedPerc: revisedPerc,
|
|
547
|
+
count: countObj,
|
|
548
|
+
createdAt: mapping.createdAt ?? '',
|
|
549
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
550
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
551
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
552
|
+
isUp: false,
|
|
553
|
+
} );
|
|
554
|
+
} else if ( type !== 'tagging' && mapping.status === 'closed' ) {
|
|
555
|
+
const revisedFootfall = mapping.revicedFootfall ?? 0;
|
|
556
|
+
const revisedPerc =
|
|
557
|
+
footfallValue > 0 ?
|
|
558
|
+
`${Math.round( ( revisedFootfall / footfallValue ) * 100 )}` :
|
|
559
|
+
'0';
|
|
560
|
+
const countObj = mapping.count ? [ ...mapping.count ] : [];
|
|
561
|
+
temp.push( {
|
|
562
|
+
actionType: type,
|
|
563
|
+
footfall: footfallValue,
|
|
564
|
+
revicedFootfall: revisedFootfall,
|
|
565
|
+
revicedPerc: revisedPerc,
|
|
566
|
+
count: countObj,
|
|
567
|
+
createdAt: mapping.createdAt ?? '',
|
|
568
|
+
createdByEmail: mapping.createdByEmail ?? '',
|
|
569
|
+
createdByUserName: mapping.createdByUserName ?? '',
|
|
570
|
+
createdByRole: mapping.createdByRole ?? '',
|
|
571
|
+
} );
|
|
572
|
+
}
|
|
573
|
+
} );
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
const LamdaURL = revop.getImages;
|
|
578
|
+
let resultData = await LamdaServiceCall( LamdaURL, inputData );
|
|
579
|
+
if ( resultData ) {
|
|
580
|
+
// temp.length? temp[0].status = 'open': null;
|
|
581
|
+
if ( resultData.status_code == '200' ) {
|
|
582
|
+
return res.sendSuccess( { ...resultData, ticketStatus: temp?.length > 0 && ticketDetails? temp : null, config: req?.store?.footfallDirectoryConfigs } );
|
|
583
|
+
} else {
|
|
584
|
+
return res.sendError( 'No Content', 204 );
|
|
585
|
+
}
|
|
586
|
+
} else {
|
|
587
|
+
return res.sendError( 'No Content', 204 );
|
|
588
|
+
}
|
|
416
589
|
} catch ( error ) {
|
|
417
590
|
logger.error( { message: error, data: req.query, function: 'storeProcessedData' } );
|
|
418
591
|
const err = error.message || 'Internal Server Error';
|
|
@@ -440,6 +613,13 @@ export async function tagTempId( req, res ) {
|
|
|
440
613
|
description: '',
|
|
441
614
|
isChecked: inputData.isChecked,
|
|
442
615
|
duplicateImage: inputData?.duplicateImage?.length>0? inputData?.duplicateImage :[],
|
|
616
|
+
// Add id to each object in duplicateImage if it exists and is an array
|
|
617
|
+
duplicateImage: Array.isArray( inputData?.duplicateImage ) ?
|
|
618
|
+
inputData.duplicateImage.map( ( img ) => ( {
|
|
619
|
+
...img,
|
|
620
|
+
id: `${inputData?.storeId}_${inputData?.dateString}_${img?.tempId}`,
|
|
621
|
+
} ) ) :
|
|
622
|
+
[],
|
|
443
623
|
type: 'tagging-reflect',
|
|
444
624
|
ticketStatus: 'submitted',
|
|
445
625
|
isParent: inputData?.revopsType === 'duplicate'? true : false,
|
|
@@ -449,6 +629,7 @@ export async function tagTempId( req, res ) {
|
|
|
449
629
|
action: 'submitted',
|
|
450
630
|
},
|
|
451
631
|
],
|
|
632
|
+
comments: inputData.comments || '',
|
|
452
633
|
createdAt: new Date(),
|
|
453
634
|
updatedAt: new Date(),
|
|
454
635
|
|
|
@@ -502,6 +683,7 @@ export async function tagTempId( req, res ) {
|
|
|
502
683
|
isParent: false,
|
|
503
684
|
type: 'tagging-reflect',
|
|
504
685
|
ticketStatus: 'submitted',
|
|
686
|
+
comments: inputData.comments || '',
|
|
505
687
|
actions: [
|
|
506
688
|
{
|
|
507
689
|
actionType: 'tagging',
|
package/src/dtos/revop.dtos.js
CHANGED
|
@@ -66,6 +66,9 @@ export const tagTempIdSchema = joi.object( {
|
|
|
66
66
|
revopsType: joi.string().required(),
|
|
67
67
|
timeRange: joi.string().required(),
|
|
68
68
|
isChecked: joi.boolean().required().allow( null ),
|
|
69
|
+
mode: joi.string().valid( 'web', 'mobile' ).required().messages( {
|
|
70
|
+
'any.only': 'type must be one of [mobile,web]',
|
|
71
|
+
} ),
|
|
69
72
|
duplicateImage: joi.array().items( joi.object(
|
|
70
73
|
{
|
|
71
74
|
tempId: joi.number().required(),
|
|
@@ -81,6 +84,7 @@ export const tagTempIdSchema = joi.object( {
|
|
|
81
84
|
entryTime: joi.string().required(),
|
|
82
85
|
exitTime: joi.string().required(),
|
|
83
86
|
filePath: joi.string().required(),
|
|
87
|
+
comments: joi.string().optional().allow( '' ),
|
|
84
88
|
|
|
85
89
|
} );
|
|
86
90
|
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { getOpenSearchCount, logger } from 'tango-app-api-middleware';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { deleteByQuery, getOpenSearchData } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
3
|
+
import { findOne } from '../services/clients.services.js';
|
|
4
4
|
|
|
5
5
|
export async function getTaggingConfig( req, res, next ) {
|
|
6
6
|
try {
|
|
7
7
|
const inputData= req.query;
|
|
8
|
-
const
|
|
8
|
+
const clientId = inputData.storeId.split( '-' )[0];
|
|
9
|
+
const getData = await findOne( { clientId: clientId }, { footfallDirectoryConfigs: 1 } );
|
|
10
|
+
|
|
11
|
+
// Convert "taggingLimitation" array (if present) to "config" object with expected key-value pairs
|
|
12
|
+
let config = {};
|
|
13
|
+
if ( getData && Array.isArray( getData.taggingLimitation ) ) {
|
|
14
|
+
for ( const item of getData.taggingLimitation ) {
|
|
15
|
+
if ( item && item.type && typeof item.value !== 'undefined' && item.unit ) {
|
|
16
|
+
config[item.type] = `${item.value}${item.unit}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
getData.config = config;
|
|
21
|
+
|
|
9
22
|
req.store = getData;
|
|
10
23
|
next();
|
|
11
24
|
} catch ( error ) {
|
|
@@ -51,41 +64,54 @@ export async function getFootfallCount( req, res, next ) {
|
|
|
51
64
|
export async function mappingConfig( req, res, next ) {
|
|
52
65
|
try {
|
|
53
66
|
const inputData = req.body;
|
|
67
|
+
|
|
54
68
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
term: {
|
|
63
|
-
'storeId.keyword': inputData.storeId,
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
term: {
|
|
68
|
-
'dateString': inputData.dateString,
|
|
69
|
-
},
|
|
69
|
+
const footfallQuery ={
|
|
70
|
+
query: {
|
|
71
|
+
bool: {
|
|
72
|
+
must: [
|
|
73
|
+
{
|
|
74
|
+
term: {
|
|
75
|
+
'store_id.keyword': inputData.storeId,
|
|
70
76
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
term: {
|
|
80
|
+
'date_string': inputData.dateString,
|
|
75
81
|
},
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
],
|
|
78
85
|
},
|
|
79
|
-
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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 = {
|
|
89
115
|
query: {
|
|
90
116
|
bool: {
|
|
91
117
|
must: [
|
|
@@ -104,92 +130,51 @@ export async function mappingConfig( req, res, next ) {
|
|
|
104
130
|
'revopsType.keyword': inputData.revopsType,
|
|
105
131
|
},
|
|
106
132
|
},
|
|
133
|
+
{
|
|
134
|
+
term: {
|
|
135
|
+
'isParent': false,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
107
138
|
],
|
|
108
139
|
},
|
|
109
140
|
},
|
|
110
141
|
};
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
} );
|
|
114
160
|
} else {
|
|
115
|
-
|
|
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
|
+
} );
|
|
116
170
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
next();
|
|
120
|
-
return;
|
|
121
|
-
} else {
|
|
122
|
-
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 );
|
|
123
173
|
}
|
|
174
|
+
return next();
|
|
124
175
|
} else {
|
|
125
|
-
next();
|
|
176
|
+
return next();
|
|
126
177
|
}
|
|
127
|
-
// else if ( inputData.revopsType == 'duplicate' ) {
|
|
128
|
-
// const getFootfallQuery = {
|
|
129
|
-
// query: {
|
|
130
|
-
// terms: {
|
|
131
|
-
// _id: [ inputData?.dateString ],
|
|
132
|
-
// },
|
|
133
|
-
// },
|
|
134
|
-
// _source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
|
|
135
|
-
// sort: [
|
|
136
|
-
// {
|
|
137
|
-
// date_iso: {
|
|
138
|
-
// order: 'desc',
|
|
139
|
-
// },
|
|
140
|
-
// },
|
|
141
|
-
// ],
|
|
142
|
-
// };
|
|
143
|
-
|
|
144
|
-
// const getFootfall = await getOpenSearchData( openSearch.footfall, getFootfallQuery );
|
|
145
|
-
// const footfall = getFootfall?.body?.hites?.hits?.[0]?._source?.footfall_count;
|
|
146
|
-
// const getQuery = {
|
|
147
|
-
// query: {
|
|
148
|
-
// bool: {
|
|
149
|
-
// must: [
|
|
150
|
-
// {
|
|
151
|
-
// term: {
|
|
152
|
-
// 'storeId.keyword': inputData.storeId,
|
|
153
|
-
// },
|
|
154
|
-
// },
|
|
155
|
-
// {
|
|
156
|
-
// term: {
|
|
157
|
-
// 'dateString': inputData.dateString,
|
|
158
|
-
// },
|
|
159
|
-
// },
|
|
160
|
-
// {
|
|
161
|
-
// term: {
|
|
162
|
-
// 'revopsType.keyword': inputData.revopsType,
|
|
163
|
-
// },
|
|
164
|
-
// },
|
|
165
|
-
// {
|
|
166
|
-
// term: {
|
|
167
|
-
// 'parent.keyword': null,
|
|
168
|
-
// },
|
|
169
|
-
// },
|
|
170
|
-
// ],
|
|
171
|
-
// },
|
|
172
|
-
// },
|
|
173
|
-
// };
|
|
174
|
-
// const getData = await getOpenSearchCount( openSearch.revop, getQuery );
|
|
175
|
-
// logger.info( { getData: getData, footfall: footfall, duplicate: config?.revopTagging?.duplicate } );
|
|
176
|
-
// if ( getData && footfall && config?.revopTagging?.duplicate ) {
|
|
177
|
-
// const data = config?.revopTagging?.duplicate;
|
|
178
|
-
// // Convert "20%" → 0.2 (handle both "20%" and 20)
|
|
179
|
-
// const percentStr = typeof data === 'string' ? data.replace( '%', '' ) : data;
|
|
180
|
-
// logger.info( { percentStr: percentStr } );
|
|
181
|
-
// const percentValue =percentStr / 100;
|
|
182
|
-
|
|
183
|
-
// const result = percentValue * footfall;
|
|
184
|
-
|
|
185
|
-
// logger.info( { result: result, footfall: footfall } );
|
|
186
|
-
// }
|
|
187
|
-
// if ( getData && getData?.body?.count >= Math.round( result ) ) {
|
|
188
|
-
// return res.sendError( `Select up to ${config?.revopTagging?.duplicate} items only`, 400 );
|
|
189
|
-
// } else {
|
|
190
|
-
// next();
|
|
191
|
-
// }
|
|
192
|
-
// }
|
|
193
178
|
} catch ( error ) {
|
|
194
179
|
logger.error( { error: error, message: req.body, function: 'traffic-revop-getTaggingConfig' } );
|
|
195
180
|
next();
|