tango-app-api-infra 3.9.5-vms.70 → 3.9.5-vms.71

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-infra",
3
- "version": "3.9.5-vms.70",
3
+ "version": "3.9.5-vms.71",
4
4
  "description": "infra",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -450,6 +450,361 @@ export async function tangoReviewTicket( req, res ) {
450
450
  }
451
451
  }
452
452
 
453
+ export async function tangoReviewAccuracyClosedTicket( req, res ) {
454
+ try {
455
+ const inputData = req.body;
456
+
457
+ // get store info by the storeId into mongo db
458
+ const getstoreName = await findOneStore( { storeId: inputData.storeId, status: 'active' }, { storeId: 1, storeName: 1, clientId: 1 } );
459
+
460
+ if ( !getstoreName || getstoreName == null ) {
461
+ return res.sendError( 'The store ID is either inActive or not found', 400 );
462
+ }
463
+
464
+ // get the footfall count from opensearch
465
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
466
+ const dateString = `${inputData.storeId}_${inputData.dateString}`;
467
+ const getQuery = {
468
+ query: {
469
+ terms: {
470
+ _id: [ dateString ],
471
+ },
472
+ },
473
+ _source: [ 'footfall', 'date_string', 'store_id', 'down_time', 'footfall_count' ],
474
+ sort: [
475
+ {
476
+ date_iso: {
477
+ order: 'desc',
478
+ },
479
+ },
480
+ ],
481
+ };
482
+
483
+ const getFootfallCount = await getOpenSearchData( openSearch.footfall, getQuery );
484
+ const hits = getFootfallCount?.body?.hits?.hits || [];
485
+ if ( hits?.[0]?._source?.footfall_count <= 0 ) {
486
+ return res.sendError( 'You can’t create a ticket because this store has 0 footfall data' );
487
+ }
488
+
489
+ // get category details from the client level configuration
490
+ const getConfig = await findOneClient( { clientId: getstoreName.clientId }, { footfallDirectoryConfigs: 1 } );
491
+ if ( !getConfig || getConfig == null ) {
492
+ return res.sendError( 'The Client ID is either not configured or not found', 400 );
493
+ }
494
+ let findQuery = {
495
+ size: 10000,
496
+ query: {
497
+ bool: {
498
+ must: [
499
+ {
500
+ term: {
501
+ 'storeId.keyword': inputData.storeId,
502
+ },
503
+ },
504
+ {
505
+ term: {
506
+ 'dateString': inputData.dateString,
507
+ },
508
+ },
509
+ ],
510
+ },
511
+ },
512
+ };
513
+ let findTicket = await getOpenSearchData( openSearch.footfallDirectory, findQuery );
514
+ let Ticket = findTicket.body?.hits?.hits;
515
+
516
+ if ( Ticket.length === 0 ) {
517
+ return res.sendError( 'Ticket not found', 400 );
518
+ }
519
+ const getTicket = {
520
+ size: 10000,
521
+ query: {
522
+ bool: {
523
+ must: [
524
+ {
525
+ term: {
526
+ 'storeId.keyword': inputData.storeId,
527
+ },
528
+ },
529
+ {
530
+ term: {
531
+ 'dateString': inputData.dateString,
532
+ },
533
+ },
534
+
535
+ ],
536
+ },
537
+ },
538
+ };
539
+ if ( Ticket[0]?._source?.type != 'internal' ) {
540
+ getTicket.query.bool.must.push(
541
+ {
542
+ nested: {
543
+ path: 'mappingInfo',
544
+ query: {
545
+ bool: {
546
+ must: [
547
+ {
548
+ term: {
549
+ 'mappingInfo.type': 'tangoreview',
550
+ },
551
+ },
552
+
553
+ ],
554
+ },
555
+ },
556
+ },
557
+ },
558
+ );
559
+ }
560
+ const getFootfallticketData = await getOpenSearchData( openSearch.footfallDirectory, getTicket );
561
+ const ticketData = getFootfallticketData?.body?.hits?.hits;
562
+
563
+ if ( !ticketData || ticketData?.length == 0 ) {
564
+ return res.sendError( 'You don’t have any tagged images right now', 400 );
565
+ }
566
+
567
+ const record = {
568
+
569
+ status: parseInt( inputData?.mappingInfo?.[0]?.revicedPerc || 0 ) < parseInt( getConfig?.footfallDirectoryConfigs?.tangoReview|| 0 ) ? 'Open - Accuracy Issue' : 'Closed',
570
+ revicedFootfall: inputData.mappingInfo?.[0]?.revicedFootfall,
571
+ revicedPerc: inputData.mappingInfo?.[0]?.revicedPerc,
572
+ mappingInfo: ticketData?.[0]?._source?.mappingInfo,
573
+ createdByEmail: req?.user?.email,
574
+ createdByUserName: req?.user?.userName,
575
+ createdByRole: req?.user?.role,
576
+
577
+ };
578
+
579
+
580
+ // Retrieve client footfallDirectoryConfigs revision
581
+ let isAutoCloseEnable = getConfig.footfallDirectoryConfigs.isAutoCloseEnable;
582
+ let autoCloseAccuracy = getConfig?.footfallDirectoryConfigs?.autoCloseAccuracy;
583
+
584
+ const getNumber = autoCloseAccuracy.split( '%' )[0];
585
+
586
+ let autoCloseAccuracyValue = parseFloat( ( autoCloseAccuracy || getNumber ).replace( '%', '' ) );
587
+ let revisedPercentage = inputData.mappingInfo?.revicedPerc;
588
+ const revised = Number( revisedPercentage?.split( '%' )[0] );
589
+ const tangoReview = Number( getConfig?.footfallDirectoryConfigs?.tangoReview?.split( '%' )[0] );
590
+ // If autoclose enabled and revisedPercentage meets/exceeds threshold, close ticket and skip revision
591
+ if (
592
+ isAutoCloseEnable === true &&
593
+ revisedPercentage >= autoCloseAccuracyValue
594
+ ) {
595
+ record.status = 'Closed';
596
+ // Only keep or modify mappingInfo items with type "review"
597
+ if ( Array.isArray( record.mappingInfo ) ) {
598
+ const temp = record.mappingInfo
599
+ .filter( ( item ) => item.type === 'tangoreview' )
600
+ .map( ( item ) => ( {
601
+ ...item,
602
+
603
+ mode: inputData.mappingInfo?.mode,
604
+ revicedFootfall: inputData.mappingInfo?.revicedFootfall,
605
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
606
+ count: inputData.mappingInfo?.count,
607
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
608
+ status: 'Closed',
609
+ createdByEmail: req?.user?.email,
610
+ createdByUserName: req?.user?.userName,
611
+ createdByRole: req?.user?.role,
612
+ } ) );
613
+
614
+ record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
615
+ ...temp ];
616
+ // If updating the mapping config to mark [i].status as 'Closed'
617
+ // Make sure all relevant mappingInfo items of type 'approve' are set to status 'Closed'
618
+ if ( Array.isArray( record.mappingInfo ) ) {
619
+ record.mappingInfo = record.mappingInfo.map( ( item ) => {
620
+ return {
621
+ ...item,
622
+ status: 'Closed',
623
+ };
624
+ } );
625
+ }
626
+ // If no review mapping existed, push a new one
627
+ // if ( record.mappingInfo.length === 0 ) {
628
+ // record.mappingInfo.push( {
629
+ // type: 'tangoreview',
630
+ // mode: inputData.mappingInfo?.mode,
631
+ // revicedFootfall: inputData.mappingInfo?.revicedFootfall,
632
+ // revicedPerc: inputData.mappingInfo?.revicedPerc,
633
+ // count: inputData.mappingInfo?.count,
634
+ // revisedDetail: inputData.mappingInfo?.revisedDetail,
635
+ // status: 'Closed',
636
+ // createdByEmail: req?.user?.email,
637
+ // createdByUserName: req?.user?.userName,
638
+ // createdByRole: req?.user?.role,
639
+ // } );
640
+ // }
641
+ }
642
+ record.mappingInfo.push(
643
+ {
644
+ type: 'finalRevision',
645
+ mode: inputData.mappingInfo?.mode,
646
+ revicedFootfall: revisedFootfall,
647
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
648
+ count: inputData.mappingInfo?.count,
649
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
650
+ status: 'Closed',
651
+ createdByEmail: req?.user?.email,
652
+ createdByUserName: req?.user?.userName,
653
+ createdByRole: req?.user?.role,
654
+ createdAt: new Date(),
655
+ },
656
+ );
657
+ } else if ( revised < tangoReview ) {
658
+ // If ticket is closed, do not proceed with revision mapping
659
+
660
+ record.status = 'Closed - Accuracy Issue';
661
+ // Only keep or modify mappingInfo items with type "review"
662
+ if ( Array.isArray( record.mappingInfo ) ) {
663
+ const temp = record.mappingInfo
664
+ .filter( ( item ) => item.type === 'tangoreview' )
665
+ .map( ( item ) => ( {
666
+ ...item,
667
+ mode: inputData.mappingInfo?.mode,
668
+ revicedFootfall: inputData.mappingInfo?.revicedFootfall,
669
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
670
+ count: inputData.mappingInfo?.count,
671
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
672
+ status: 'Closed',
673
+ createdByEmail: req?.user?.email,
674
+ createdByUserName: req?.user?.userName,
675
+ createdByRole: req?.user?.role,
676
+ } ) );
677
+
678
+ record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
679
+ ...temp ];
680
+ if ( Array.isArray( record.mappingInfo ) ) {
681
+ record.mappingInfo = record.mappingInfo.map( ( item ) => {
682
+ return {
683
+ ...item,
684
+ status: 'Closed',
685
+ };
686
+ } );
687
+ }
688
+ // If no review mapping existed, push a new one
689
+ // if ( record.mappingInfo.length === 0 ) {
690
+ // record.mappingInfo.push( {
691
+ // type: 'tangoreview',
692
+ // mode: inputData.mappingInfo?.mode,
693
+ // revicedFootfall: inputData.mappingInfo?.revicedFootfall,
694
+ // revicedPerc: inputData.mappingInfo?.revicedPerc,
695
+ // count: inputData.mappingInfo?.count,
696
+ // revisedDetail: inputData.mappingInfo?.revisedDetail,
697
+ // status: 'Closed',
698
+ // createdByEmail: req?.user?.email,
699
+ // createdByUserName: req?.user?.userName,
700
+ // createdByRole: req?.user?.role,
701
+ // } );
702
+ // }
703
+ }
704
+ } else {
705
+ if ( Array.isArray( record.mappingInfo ) ) {
706
+ const temp = record.mappingInfo
707
+ .filter( ( item ) => item.type === 'tangoreview' )
708
+ .map( ( item ) => ( {
709
+ ...item,
710
+ mode: inputData.mappingInfo?.mode,
711
+ revicedFootfall: inputData.mappingInfo?.revicedFootfall,
712
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
713
+ count: inputData.mappingInfo?.count,
714
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
715
+ status: 'Closed',
716
+ createdByEmail: req?.user?.email,
717
+ createdByUserName: req?.user?.userName,
718
+ createdByRole: req?.user?.role,
719
+ } ) );
720
+
721
+ record.mappingInfo = [ ...ticketData?.[0]?._source?.mappingInfo.slice( 0, -1 ),
722
+ ...temp ];
723
+ if ( Array.isArray( record.mappingInfo ) ) {
724
+ record.mappingInfo = record.mappingInfo.map( ( item ) => {
725
+ return {
726
+ ...item,
727
+ status: 'Closed',
728
+ };
729
+ } );
730
+ }
731
+ // If no review mapping existed, push a new one
732
+ // if ( record.mappingInfo.length === 0 ) {
733
+ // record.mappingInfo.push( {
734
+ // type: 'tangoreview',
735
+ // mode: inputData.mappingInfo?.mode,
736
+ // revicedFootfall: inputData.mappingInfo?.revicedFootfall,
737
+ // revicedPerc: inputData.mappingInfo?.revicedPerc,
738
+ // count: inputData.mappingInfo?.count,
739
+ // revisedDetail: inputData.mappingInfo?.revisedDetail,
740
+ // status: 'Closed',
741
+ // createdByEmail: req?.user?.email,
742
+ // createdByUserName: req?.user?.userName,
743
+ // createdByRole: req?.user?.role,
744
+ // } );
745
+ // }
746
+ }
747
+ record.mappingInfo.push(
748
+ {
749
+ type: 'finalRevision',
750
+ mode: inputData.mappingInfo?.mode,
751
+ revicedFootfall: inputData.mappingInfo?.revicedFootfall,
752
+ revicedPerc: inputData.mappingInfo?.revicedPerc,
753
+ count: inputData.mappingInfo?.count,
754
+ revisedDetail: inputData.mappingInfo?.revisedDetail,
755
+ status: 'Closed',
756
+ createdByEmail: req?.user?.email,
757
+ createdByUserName: req?.user?.userName,
758
+ createdByRole: req?.user?.role,
759
+ createdAt: new Date(),
760
+ },
761
+ );
762
+ }
763
+
764
+ if ( Ticket[0]?._source?.type==='store' ) {
765
+ let findTagging = Ticket[0]?._source?.mappingInfo.filter( ( data ) => data.type==='tagging' );
766
+ if ( findTagging?.length>0&&findTagging[0].createdByEmail!='' ) {
767
+ let userData = await findOneUser( { email: findTagging[0]?.createdByEmail } );
768
+ let title = `Received response for the Footfall ticket raised.`;
769
+ let createdOn = dayjs( Ticket[0]?._source?.dateString ).format( 'DD MMM YYYY' );
770
+ let description = `Raised on ${createdOn}`;
771
+
772
+ let Data = {
773
+ 'title': title,
774
+ 'body': description,
775
+ 'type': 'closed',
776
+ 'date': Ticket[0]?._source?.dateString,
777
+ 'storeId': Ticket[0]?._source?.storeId,
778
+ 'clientId': Ticket[0]?._source?.clientId,
779
+ 'ticketId': Ticket[0]?._source?.ticketId,
780
+ };
781
+ if ( userData && userData.fcmToken ) {
782
+ const fcmToken = userData.fcmToken;
783
+ await sendPushNotification( title, description, fcmToken, Data );
784
+ }
785
+ }
786
+ }
787
+ // return;
788
+
789
+ let id = `${inputData.storeId}_${inputData.dateString}_footfall-directory-tagging`;
790
+ if ( inputData.ticketType === 'internal' ) {
791
+ id = `${inputData.storeId}_${inputData.dateString}_internal_footfall-directory-tagging`;
792
+ }
793
+
794
+ const insertResult = await updateOpenSearchData( openSearch.footfallDirectory, id, { doc: record } );
795
+
796
+ if ( insertResult && ( insertResult.statusCode === 201 || insertResult.statusCode === 200 ) ) {
797
+ return res.sendSuccess( 'Ticket closed successfully' );
798
+ } else {
799
+ return res.sendError( 'Internal Server Error', 500 );
800
+ }
801
+ } catch ( error ) {
802
+ const err = error.message || 'Internal Server Error';
803
+ logger.error( { error: err, funtion: 'tangoReviewTicket' } );
804
+ return res.sendError( err, 500 );
805
+ }
806
+ }
807
+
453
808
  async function bulkUpdateStatusToPending( indexName, inputData ) {
454
809
  const bulkBody = [];
455
810
 
@@ -1053,7 +1408,7 @@ export async function ticketList( req, res ) {
1053
1408
  }
1054
1409
  }
1055
1410
 
1056
- if ( req?.user?.userType == 'tango' && inputData.tangoType !== 'internal' ) {
1411
+ if ( req?.user?.userType === 'tango' && inputData.tangoType !== 'internal' ) {
1057
1412
  searchQuery.query.bool.must.push(
1058
1413
  {
1059
1414
  term: {
@@ -1081,7 +1436,7 @@ export async function ticketList( req, res ) {
1081
1436
 
1082
1437
 
1083
1438
  );
1084
- } else if ( req?.user?.userType == 'client' && inputData.tangoType !== 'internal' ) {
1439
+ } else if ( req?.user?.userType === 'client' && inputData.tangoType !== 'internal' ) {
1085
1440
  searchQuery.query.bool.must.push(
1086
1441
  {
1087
1442
  term: {
@@ -1173,8 +1528,8 @@ export async function ticketList( req, res ) {
1173
1528
  const ticketListData = searchResult?.body?.hits?.hits?.map( ( hit ) => hit._source ) || [];
1174
1529
 
1175
1530
  let temp = [];
1176
- if ( req.user.userType == 'tango' ) {
1177
- if ( inputData.tangotype == 'store' ) {
1531
+ if ( req.user.userType === 'tango' ) {
1532
+ if ( inputData.tangoType === 'store' ) {
1178
1533
  for ( let item of ticketListData ) {
1179
1534
  temp.push( {
1180
1535
 
@@ -1189,7 +1544,7 @@ export async function ticketList( req, res ) {
1189
1544
  reviewerRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'review' )?.revicedPerc || '--',
1190
1545
  approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
1191
1546
  tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1192
- status: item?.status,
1547
+ status: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
1193
1548
 
1194
1549
  } );
1195
1550
  }
@@ -1211,7 +1566,7 @@ export async function ticketList( req, res ) {
1211
1566
  approverRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'approve' )?.revicedPerc || '--',
1212
1567
  tangoRevisedAccuracy: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1213
1568
  status: item?.status,
1214
- tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.revicedPerc || '--',
1569
+ tangoStatus: item?.mappingInfo?.find( ( f ) => f.type === 'tangoreview' )?.status || '--',
1215
1570
 
1216
1571
  } );
1217
1572
  }
@@ -72,6 +72,17 @@ export const tangoReviewTicketSchema = Joi.object().keys( {
72
72
  export const tangoReviewTicketValid = {
73
73
  body: tangoReviewTicketSchema,
74
74
  };
75
+ export const tangoReviewAccuracyClosedTicketSchema = Joi.object().keys( {
76
+ storeId: Joi.string().required(),
77
+ dateString: Joi.string().required(),
78
+ comments: Joi.string().required(),
79
+ subComment: Joi.string().required(),
80
+
81
+ } );
82
+
83
+ export const tangoReviewAccuracyClosedTicketValid = {
84
+ body: tangoReviewAccuracyClosedTicketSchema,
85
+ };
75
86
 
76
87
  export const ticketSummarySchema = Joi.object().keys( {
77
88
  clientId: Joi.string().required(),
@@ -1,7 +1,7 @@
1
1
  import express from 'express';
2
2
  import { getClusters, getConfig, isGrantedUsers, isTicketExists, ticketApprove, ticketCreation, ticketReview } from '../validations/footfallDirectory.validation.js';
3
- import { assignTicket, createTicket, downloadTickets, getTaggedStores, getTickets, multiCloseTicket, openTicketList, reviewerList, tangoReviewTicket, ticketList, ticketSummary, updateStatus, updateTempStatus, updateUserTicketStatus, createinternalTicket, checkTicketExists } from '../controllers/footfallDirectory.controllers.js';
4
- import { createTicketValid, downloadTicketsValid, getTaggedStoresValid, getTicketsValid, openTicketListValid, reviewerListValid, ticketListValid, ticketSummaryValid, updateStatusValid, assignTicketValid, updateTempStatusValid, updateTicketStatusValid, tangoReviewTicketValid, multiCloseTicketValid } from '../dtos/footfallDirectory.dtos.js';
3
+ import { assignTicket, createTicket, downloadTickets, getTaggedStores, getTickets, multiCloseTicket, openTicketList, reviewerList, tangoReviewTicket, ticketList, ticketSummary, updateStatus, updateTempStatus, updateUserTicketStatus, createinternalTicket, checkTicketExists, tangoReviewAccuracyClosedTicket } from '../controllers/footfallDirectory.controllers.js';
4
+ import { createTicketValid, downloadTicketsValid, getTaggedStoresValid, getTicketsValid, openTicketListValid, reviewerListValid, ticketListValid, ticketSummaryValid, updateStatusValid, assignTicketValid, updateTempStatusValid, updateTicketStatusValid, tangoReviewTicketValid, multiCloseTicketValid, tangoReviewAccuracyClosedTicketValid } from '../dtos/footfallDirectory.dtos.js';
5
5
  import { bulkValidate, getAssinedStore, isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
6
6
 
7
7
  export const footfallDirectoryRouter = express.Router();
@@ -11,6 +11,7 @@ footfallDirectoryRouter.post( '/create-internalticket', isAllowedSessionHandler,
11
11
  footfallDirectoryRouter.post( '/checkTicketExists', isAllowedSessionHandler, checkTicketExists );
12
12
 
13
13
  footfallDirectoryRouter.post( '/tango-review-ticket', isAllowedSessionHandler, validate( tangoReviewTicketValid ), tangoReviewTicket );
14
+ footfallDirectoryRouter.post( '/tango-review-ticket', isAllowedSessionHandler, validate( tangoReviewAccuracyClosedTicketValid ), tangoReviewAccuracyClosedTicket );
14
15
 
15
16
  footfallDirectoryRouter.get( '/ticket-summary', isAllowedSessionHandler, bulkValidate( ticketSummaryValid ), ticketSummary );
16
17