tango-app-api-audit 3.4.0-alpha.1 → 3.4.0-alpha.2

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-audit",
3
- "version": "3.4.0-alpha.1",
3
+ "version": "3.4.0-alpha.2",
4
4
  "description": "audit & audit metrics apis",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  "mongodb": "^6.7.0",
25
25
  "nodemon": "^3.1.3",
26
26
  "swagger-ui-express": "^5.0.1",
27
- "tango-api-schema": "^2.1.5",
27
+ "tango-api-schema": "^2.1.6",
28
28
  "tango-app-api-middleware": "^3.1.33",
29
29
  "winston": "^3.13.0",
30
30
  "winston-daily-rotate-file": "^5.0.0"
@@ -1,7 +1,15 @@
1
- import { checkFileExist, getDate, insertOpenSearchData, listFileByPath, logger, signedUrl, sqsReceive } from 'tango-app-api-middleware';
2
- import { aggregateBinaryAuditModel, createBinaryAudit } from '../service/binaryAudit.service.js';
1
+ import { checkFileExist, download, getDate, getDateWithCustomizeTime, getUTC, insertOpenSearchData, listFileByPath, logger, signedUrl, sqsReceive } from 'tango-app-api-middleware';
2
+ import { aggregateBinaryAudit, createBinaryAudit, updateOneBinaryAuditModel } from '../service/binaryAudit.service.js';
3
3
  import { findOneStore } from '../service/store.service.js';
4
4
  import { findOneUser } from '../service/user.service.js';
5
+ import { aggregateUserEmpDetection, createUserEmpDetection } from '../service/userEmpDetection.service.js';
6
+ import { aggregateTraxAuditData, findOneTraxAuditData } from '../service/traxAuditData.service.js';
7
+ import { aggregateClient } from '../service/client.service.js';
8
+ import dayjs from 'dayjs';
9
+ import { aggregateAssignAudit } from '../service/assignAudit.service.js';
10
+ import { aggregateStoreEmpDetection } from '../service/storeEmpDetection.service.js';
11
+ import { listQueue } from 'tango-app-api-middleware/src/utils/aws/sqs.js';
12
+ import _ from 'lodash';
5
13
 
6
14
 
7
15
  export async function getDetectionAuditFile( req, res ) {
@@ -37,7 +45,7 @@ export async function getDetectionAuditFile( req, res ) {
37
45
  },
38
46
  ];
39
47
 
40
- const userDetails = await aggregateUserAudit( userQuery );
48
+ const userDetails = await aggregateUserEmpDetection( userQuery );
41
49
  logger.info( { userDetails: userDetails, function: 'userDetails' } );
42
50
  const auditStatus =
43
51
  userDetails && userDetails.length > 0 ? userDetails[0].auditStatus : null;
@@ -380,7 +388,7 @@ export async function getDetectionAuditFile( req, res ) {
380
388
  moduleType: inputData.moduleType,
381
389
  };
382
390
 
383
- insertData = await createUserAudit( record );
391
+ insertData = await createUserEmpDetection( record );
384
392
  await updateManyStoreAudit( query, storeRecord );
385
393
  } else {
386
394
  insertData = userDetails[0];
@@ -451,6 +459,403 @@ export async function getDetectionAuditFile( req, res ) {
451
459
  }
452
460
  }
453
461
 
462
+ export async function workSpace( req, res ) {
463
+ try {
464
+ const inputData = req.body;
465
+ const limit = inputData.limit || 10;
466
+ const offset = inputData.offset ?
467
+ ( inputData.offset - 1 ) * limit :
468
+ 0;
469
+ const dateRange = await getDate( new Date(), new Date() );
470
+ const filter =[
471
+ { status: { $in: [ 'active', 'hold' ] } },
472
+ { 'auditConfigs.audit': { $eq: true } },
473
+ ];
474
+ if ( inputData?.clientId?.length > 0 ) {
475
+ filter.push( { clientId: { $in: inputData.clientId } } );
476
+ }
477
+ const temp = [];
478
+ const clientQuery = [
479
+ {
480
+ $match: {
481
+ $and: filter,
482
+ },
483
+ },
484
+ {
485
+ $project: {
486
+ clientName: 1,
487
+ clientId: 1,
488
+ queueName: {
489
+ $cond: [
490
+ { $eq: [ inputData.moduleType, 'unattended-customer' ] }, '$auditConfigs.traxQueueName.unattendedCustomer',
491
+ { $cond: [
492
+ { $eq: [ inputData.moduleType, 'left-in-middle' ] }, '$auditConfigs.traxQueueName.leftInMiddle',
493
+ {
494
+ $cond: [
495
+ { $eq: [ inputData.moduleType, 'uniform-detection' ] }, '$auditConfigs.traxQueueName.uniformDetection',
496
+ {
497
+ $cond: [
498
+ { $eq: [ inputData.moduleType, 'mobile-detection' ] }, '$auditConfigs.traxQueueName.mobileDetection', '$auditConfigs.traxQueueName.cameraAngleChange',
499
+ ],
500
+ },
501
+ ],
502
+ },
503
+ ],
504
+ },
505
+ ],
506
+ },
507
+ },
508
+ },
509
+ ];
510
+
511
+
512
+ if ( inputData.searchValue && inputData.searchValue !== '' ) {
513
+ clientQuery.push( {
514
+ $match: {
515
+ $or: [
516
+ { clientName: { $regex: inputData.searchValue, $options: 'i' } },
517
+ { clientId: { $regex: inputData.searchValue, $options: 'i' } },
518
+ ],
519
+ },
520
+ } );
521
+ }
522
+ if ( inputData.sortColumnName ) {
523
+ const sortBy = inputData.sortBy || -1;
524
+ const sortColumn = inputData.sortColumnName;
525
+ clientQuery.push( {
526
+ $sort: { [sortColumn]: sortBy },
527
+ },
528
+ );
529
+ }
530
+
531
+ const count = await aggregateClient( clientQuery );
532
+ if ( count?.length == 0 ) {
533
+ return res.sendError( 'No Data Found', 204 );
534
+ }
535
+
536
+ if ( !inputData.isExport ) {
537
+ clientQuery.push( { $skip: offset }, { $limit: limit } );
538
+ }
539
+
540
+ const clientDetails = await aggregateClient( clientQuery );
541
+ const clientList = clientDetails.map( ( i ) => i.clientId );
542
+ const fileDate = dayjs( dateRange.start ).subtract( 1, 'days' );
543
+ const getStoreData = await findOneTraxAuditData(
544
+ {},
545
+ { fileDate: 1 },
546
+ { createdAt: -1 },
547
+ );
548
+ const latestDate = getStoreData?.fileDate || dayjs( fileDate ).format( 'DD-MM-YYYY' );
549
+ const auditStoreDataQuery = [
550
+ {
551
+ $match: {
552
+ clientId: { $in: clientList },
553
+ moduleType: { $eq: inputData.moduleType },
554
+ fileDate: { $eq: latestDate },
555
+ },
556
+ },
557
+ {
558
+ $group: {
559
+ _id: '$clientId',
560
+ clientId: { $last: '$clientId' },
561
+ clientName: { $last: '$clientName' },
562
+ installedStore: { $last: '$installedStore' },
563
+ // queueName: { $last: '$queueName' },
564
+ totalCount: { $sum: 1 },
565
+ },
566
+ },
567
+ {
568
+ $project: {
569
+ _id: 0,
570
+ clientId: 1,
571
+ clientName: 1,
572
+ installedStore: 1,
573
+ queueName: 1,
574
+ totalCount: 1,
575
+ },
576
+ },
577
+ ];
578
+
579
+ const completedStores = [
580
+ {
581
+ $match: {
582
+ $and: [
583
+ { clientId: { $in: clientList } },
584
+ { fileDate: { $eq: latestDate } },
585
+ { moduleType: { $eq: inputData.moduleType } },
586
+ ],
587
+ },
588
+ },
589
+ {
590
+ $project: {
591
+ clientId: 1,
592
+ completed: {
593
+ $cond: [ { $eq: [ '$status', 'completed' ] }, 1, 0 ],
594
+ },
595
+ },
596
+ },
597
+ // {
598
+ // $group: {
599
+ // _id: { clientId: '$clientId', storeId: '$storeId' },
600
+ // clientId: { $first: '$clientId' },
601
+ // completedStoresCount: { $sum: '$completed' },
602
+ // },
603
+ // },
604
+ {
605
+ $group: {
606
+ _id: '$clientId',
607
+ clientId: { $first: '$clientId' },
608
+ // completedStoresCount: { $first: '$completedStoresCount' },
609
+ completedStoresCount: { $sum: '$completed' },
610
+ },
611
+ },
612
+ {
613
+ $project: {
614
+ _id: 0,
615
+ clientId: 1,
616
+ completedStoresCount: 1,
617
+ },
618
+ },
619
+ ];
620
+
621
+ const auditFiles = [
622
+ {
623
+ $match: {
624
+ $and: [
625
+ { clientId: { $in: clientList } },
626
+ { createdAt: { $gte: dateRange.start } },
627
+ { createdAt: { $lte: dateRange.end } },
628
+ { moduleType: { $eq: inputData.moduleType } },
629
+ ],
630
+ },
631
+ },
632
+ {
633
+ $project: {
634
+ clientId: 1,
635
+ pending: {
636
+ $cond: [
637
+ { $or: [ { $in: [ '$auditStatus', [ 'inprogress', 'drafted' ] ] } ] },
638
+ 1,
639
+ 0,
640
+ ],
641
+ },
642
+ },
643
+ },
644
+ {
645
+ $group: {
646
+ _id: { clientId: '$clientId', storeId: '$storeId' },
647
+ clientId: { $first: '$clientId' },
648
+ pendingCount: { $sum: '$pending' },
649
+ totalFilesCount: { $sum: 1 },
650
+ },
651
+ },
652
+ {
653
+ $group: {
654
+ _id: '$clientId',
655
+ clientId: { $first: '$clientId' },
656
+ pendingCount: { $first: { $ifNull: [ '$pendingCount', 0 ] } },
657
+ totalFilesCount: { $first: '$totalFilesCount' },
658
+ },
659
+ },
660
+ ];
661
+ const userIncompleteFiles = [
662
+ {
663
+ $match: {
664
+ $and: [
665
+ { clientId: { $in: clientList } },
666
+ { userId: { $eq: req.user._id } },
667
+ { moduleType: { $eq: inputData.moduleType } },
668
+ { createdAt: { $gte: dateRange.start } },
669
+ { createdAt: { $lte: dateRange.end } },
670
+ ],
671
+ },
672
+ },
673
+ {
674
+ $sort: {
675
+ updatedAt: -1,
676
+ },
677
+ },
678
+ {
679
+ $project: {
680
+ clientId: 1,
681
+ isDraft: 1,
682
+ draft: {
683
+ $cond: [ { $eq: [ '$auditStatus', 'drafted' ] }, 1, 0 ],
684
+ },
685
+ inprogress: {
686
+ $cond: [ { $eq: [ '$auditStatus', 'inprogress' ] }, 1, 0 ],
687
+ },
688
+ },
689
+ },
690
+ {
691
+ $group: {
692
+ _id: '$clientId',
693
+ clientId: { $first: '$clientId' },
694
+ isDrafted: { $first: '$isDraft' },
695
+ userDraftCount: { $sum: '$draft' },
696
+ userInprogressCount: { $sum: '$inprogress' },
697
+ },
698
+ },
699
+ ];
700
+
701
+ const userAsignAudit = [
702
+ {
703
+ $match: {
704
+ $and: [
705
+ { userId: { $eq: req.user._id } },
706
+ { isCompleted: false },
707
+ { clientId: { $in: clientList } },
708
+ { moduleType: { $eq: inputData.moduleType } },
709
+ ],
710
+ },
711
+ },
712
+ {
713
+ $group: {
714
+ _id: '$clientId',
715
+ clientId: { $first: '$clientId' },
716
+ userAsignedCount: { $sum: 1 },
717
+ },
718
+ },
719
+ {
720
+ $project: {
721
+ _id: 0,
722
+ clientId: 1,
723
+ userAsignedCount: 1,
724
+ },
725
+ },
726
+ ];
727
+
728
+ const clientAssign = [
729
+ {
730
+ $match: {
731
+ $and: [ { isCompleted: false },
732
+ { clientId: { $in: clientList } },
733
+ { moduleType: { $eq: inputData.moduleType } },
734
+ ],
735
+ },
736
+ },
737
+ {
738
+ $group: {
739
+ _id: '$clientId',
740
+ clientId: { $first: '$clientId' },
741
+ clientAsignedCount: { $sum: 1 },
742
+ },
743
+ },
744
+ {
745
+ $project: {
746
+ _id: 0,
747
+ clientId: 1,
748
+ clientAsignedCount: { $ifNull: [ '$clientAsignedCount', 0 ] },
749
+ },
750
+ },
751
+ ];
752
+
753
+ const auditUserCount = inputData.moduleType.includes( [ 'uniform-detection', 'mobile-detection' ] )?await aggregateUserEmpDetection( auditFiles ) : await aggregateBinaryAudit( auditFiles );
754
+ const userIncompleteFilesCount = inputData.moduleType.includes( [ 'uniform-detection', 'mobile-detection' ] )?await aggregateUserEmpDetection( userIncompleteFiles ) : await aggregateBinaryAudit( userIncompleteFiles );
755
+ const userAsignAuditCount = await aggregateAssignAudit( userAsignAudit );
756
+ const completedStoresCount = inputData.moduleType.includes( [ 'uniform-detection', 'mobile-detection' ] )? await aggregateStoreEmpDetection( completedStores ) : await aggregateBinaryAudit( completedStores );
757
+ const clientAssignedCount = await aggregateAssignAudit( clientAssign );
758
+ const auditStoreData = await aggregateTraxAuditData(
759
+ auditStoreDataQuery,
760
+ );
761
+
762
+ const mergeAll = _.merge(
763
+ _.keyBy( clientDetails, 'clientId' ),
764
+ _.keyBy( auditUserCount, 'clientId' ),
765
+ _.keyBy( userIncompleteFilesCount, 'clientId' ),
766
+ _.keyBy( clientAssignedCount, 'clientId' ),
767
+ );
768
+ const finalResult = _.values( mergeAll );
769
+ const merge = _.values(
770
+ _.merge(
771
+ _.keyBy( finalResult, 'clientId' ),
772
+ _.keyBy( auditStoreData, 'clientId' ),
773
+ _.keyBy( userAsignAuditCount, 'clientId' ),
774
+ _.keyBy( completedStoresCount, 'clientId' ),
775
+ ),
776
+ );
777
+ let totalStores = 0;
778
+ for ( let i = 0; i < merge.length; i++ ) {
779
+ totalStores = merge[i]?.totalCount || 0;
780
+
781
+ const pending = await listQueue( merge[i].queueName );
782
+
783
+ if ( !pending.statusCode ) {
784
+ temp.push( {
785
+ clientId: merge[i].clientId,
786
+ clientName: merge[i].clientName,
787
+ completedStoresCount: merge[i].completedStoresCount ?
788
+ Number( merge[i].completedStoresCount ) :
789
+ 0,
790
+ pendingByQueue: Number( pending ),
791
+ pendingByUser:
792
+ merge[i].pendingCount >= 0 ?
793
+ Number( merge[i].pendingCount ) +
794
+ ( merge[i].clientAsignedCount ?
795
+ Number( merge[i].clientAsignedCount ) :
796
+ 0 ) :
797
+ 0,
798
+ totalCount: merge[i].totalCount,
799
+ queueName: merge[i].queueName,
800
+ isDraft: merge[i]?.isDrafted || false,
801
+ isEnable:
802
+ Number( pending ) > 0 ||
803
+ merge[i].isDrafted ||
804
+ ( merge[i].userAsignedCount && merge[i].userAsignedCount > 0 ) ||
805
+ ( merge[i].userInprogressCount && merge[i].userInprogressCount > 0 ) ?
806
+ 1 :
807
+ 0,
808
+ completedRatio: merge[i].totalCount ?
809
+ merge[i].completedStoresCount ?
810
+ Math.round(
811
+ ( Number( merge[i].completedStoresCount ) /
812
+ Number( merge[i].totalCount ) ) *
813
+ 100,
814
+ ) :
815
+ 0 :
816
+ 0,
817
+ isAssigned:
818
+ merge[i].userAsignedCount && merge[i].userAsignedCount > 0 ?
819
+ true :
820
+ false,
821
+ Assignedcount:
822
+ merge[i].userAsignedCount && merge[i].userAsignedCount > 0 ?
823
+ merge[i].userAsignedCount :
824
+ 0,
825
+ } );
826
+ }
827
+ }
828
+
829
+ if ( inputData.isExport ) {
830
+ const exportdata = [];
831
+ temp.forEach( ( element ) => {
832
+ exportdata.push( {
833
+ 'Client Name': element.clientName,
834
+ 'Client Id': element.clientId,
835
+ 'Completed stores Count': element.completedStoresCount,
836
+ 'Completed Percentage': element.completedRatio + ' %',
837
+ 'Pending Queue': element.pendingByQueue,
838
+ 'Pending User': element.pendingByUser,
839
+ 'Draft': element.isDraft ? 1 : 0,
840
+ 'Assigned': element.Assignedcount,
841
+ } );
842
+ } );
843
+ await download( exportdata, res );
844
+ return;
845
+ } else {
846
+ return res.sendSuccess( {
847
+ result: temp,
848
+ count: count.length,
849
+ totalStores: totalStores,
850
+ } );
851
+ }
852
+ } catch ( error ) {
853
+ const err = error.message || 'Internal Server Error';
854
+ logger.info( { error: error, message: req.query, function: 'workSpace' } );
855
+ return res.sendError( err, 500 );
856
+ }
857
+ }
858
+
454
859
  export async function getAuditFile( req, res ) {
455
860
  try {
456
861
  const inputData = req.query;
@@ -480,7 +885,7 @@ export async function getAuditFile( req, res ) {
480
885
  },
481
886
  ];
482
887
 
483
- const userDetails = await aggregateBinaryAuditModel( userQuery );
888
+ const userDetails = await aggregateBinaryAudit( userQuery );
484
889
  logger.info( { userDetails: userDetails, function: 'userDetails' } );
485
890
  const auditStatus = userDetails && userDetails.length > 0 ? userDetails[0].auditStatus : null;
486
891
 
@@ -728,6 +1133,7 @@ export async function getAuditFile( req, res ) {
728
1133
  question: msg.question,
729
1134
  questionType: msg.questionType,
730
1135
  streamName: msg.streamName,
1136
+ auditId: insertData._id,
731
1137
  } );
732
1138
  } catch ( error ) {
733
1139
  const err = error.message || 'Internal Server Error';
@@ -735,3 +1141,342 @@ export async function getAuditFile( req, res ) {
735
1141
  return res.sendError( err, 500 );
736
1142
  }
737
1143
  }
1144
+
1145
+ export async function saveBinary( req, res ) {
1146
+ try {
1147
+ const inputData = req.body;
1148
+
1149
+ switch ( inputData.moduleType ) {
1150
+ case 'unattended-customer':
1151
+ let unattendedquery ={
1152
+ _id: inputData.auditId,
1153
+ storeId: inputData.storeId,
1154
+ fileDate: inputData.fileDate,
1155
+ tempId: inputData.tempId,
1156
+ moduleType: inputData.moduleType,
1157
+ };
1158
+
1159
+ let update={
1160
+ auditStatus: 'completed',
1161
+ answer: inputData.answer,
1162
+ };
1163
+ let updatDataunattended= await updateOneBinaryAuditModel( unattendedquery, update );
1164
+ if ( updatDataunattended.modifiedCount==0 ) {
1165
+ return res.sendError( updatDataunattended, 204 );
1166
+ }
1167
+ logger.info( 'update in binary data', { query: unattendedquery, update: update } );
1168
+ res.sendSuccess( `Successfully update in ${inputData.moduleType} ` );
1169
+
1170
+ break;
1171
+
1172
+ case 'left-in-middle':
1173
+ let leftquery ={
1174
+ _id: inputData.auditId,
1175
+ storeId: inputData.storeId,
1176
+ fileDate: inputData.fileDate,
1177
+ question: inputData.question,
1178
+ moduleType: inputData.moduleType,
1179
+ };
1180
+ let leftupdate={
1181
+ auditStatus: 'completed',
1182
+ answer: inputData.answer,
1183
+ };
1184
+ let updatDataleft= await updateOneBinaryAuditModel( leftquery, leftupdate );
1185
+ if ( updatDataleft.modifiedCount==0 ) {
1186
+ return res.sendError( updatDataleft, 204 );
1187
+ }
1188
+ logger.info( 'update in binary data', { query: leftquery, update: leftupdate } );
1189
+ res.sendSuccess( `Successfully update in ${inputData.moduleType} ` );
1190
+
1191
+ break;
1192
+
1193
+ case 'camera-angle-change':
1194
+ let cameraquery ={
1195
+ _id: inputData.auditId,
1196
+ storeId: inputData.storeId,
1197
+ fileDate: inputData.fileDate,
1198
+ streamName: inputData.streamName,
1199
+ moduleType: inputData.moduleType,
1200
+ };
1201
+ let cameraupdate={
1202
+ auditStatus: 'completed',
1203
+ answer: inputData.answer,
1204
+ };
1205
+ let updatDatacamera= await updateOneBinaryAuditModel( cameraquery, cameraupdate );
1206
+ if ( updatDatacamera.modifiedCount==0 ) {
1207
+ return res.sendError( updatDatacamera, 204 );
1208
+ }
1209
+ logger.info( 'update in binary data', { query: cameraquery, update: cameraupdate } );
1210
+ res.sendSuccess( `Successfully update in ${inputData.moduleType} ` );
1211
+
1212
+ break;
1213
+
1214
+ default:
1215
+ res.sendError( 'no data', 204 );
1216
+ break
1217
+ ;
1218
+ }
1219
+ } catch ( error ) {
1220
+ const err = error.mesage || 'Internal Server Error';
1221
+ logger.error( { error: error, data: req.body, function: 'saveBinary' } );
1222
+ return res.sendError( err, 500 );
1223
+ }
1224
+ }
1225
+
1226
+ export async function userAuditHistory( req, res ) {
1227
+ try {
1228
+ const inputData = req.body;
1229
+ const userId = inputData.userId? new mongoose.Types.ObjectId( inputData.userId ) : req.user._id;
1230
+ const limit = inputData.limit || 10;
1231
+ const offset = inputData.offset ? ( inputData.offset - 1 ) * limit : 0;
1232
+ const dateRange = await getUTC( new Date( inputData.fromDate ), new Date( inputData.toDate ) );
1233
+ const dateRangeCreatedAt = await getDateWithCustomizeTime( new Date( inputData.fromDate ), new Date( inputData.toDate ) );
1234
+ let filter = [
1235
+ { userId: { $eq: userId } },
1236
+ { moduleType: { $eq: inputData.moduleType } },
1237
+ ];
1238
+
1239
+ if ( inputData.dateType == 'fileDate' ) {
1240
+ filter.push(
1241
+ { fileDateISO: { $gte: dateRange.start } },
1242
+ { fileDateISO: { $lte: dateRange.end } },
1243
+ );
1244
+ } else {
1245
+ filter.push(
1246
+ { createdAt: { $gte: dateRangeCreatedAt.start } },
1247
+ { createdAt: { $lte: dateRangeCreatedAt.end } },
1248
+ );
1249
+ }
1250
+
1251
+
1252
+ if ( inputData.filterByAuditType?.length > 0 ) {
1253
+ filter.push( {
1254
+ auditType: { $in: inputData.filterByAuditType },
1255
+ } );
1256
+ }
1257
+
1258
+ if ( inputData.filterByStoreId?.length > 0 ) {
1259
+ filter.push( {
1260
+ storeId: { $in: inputData.filterByStoreId },
1261
+ } );
1262
+ }
1263
+
1264
+ if ( inputData.filterByStatus?.length > 0 ) {
1265
+ filter.push( {
1266
+ auditStatus: { $in: inputData.filterByStatus },
1267
+ } );
1268
+ }
1269
+ if ( inputData?.clientId?.length > 0 ) {
1270
+ filter.push( { clientId: { $in: inputData.clientId } } );
1271
+ }
1272
+ const query = [
1273
+ {
1274
+ $match: {
1275
+ $and: filter,
1276
+ },
1277
+ },
1278
+ {
1279
+ $lookup: {
1280
+ from: 'stores',
1281
+ let: { storeId: '$storeId' },
1282
+ pipeline: [
1283
+ {
1284
+ $match: {
1285
+ $expr: {
1286
+ $eq: [ '$storeId', '$$storeId' ],
1287
+ },
1288
+ },
1289
+ },
1290
+ {
1291
+ $project: {
1292
+ storeName: 1,
1293
+ },
1294
+ },
1295
+ ], as: 'store',
1296
+ },
1297
+ },
1298
+ {
1299
+ $unwind: {
1300
+ path: '$store', preserveNullAndEmptyArrays: true,
1301
+ },
1302
+ },
1303
+ {
1304
+ $lookup: {
1305
+ from: 'clients',
1306
+ let: { clientId: '$clientId' },
1307
+ pipeline: [
1308
+ {
1309
+ $match: {
1310
+ $expr: {
1311
+ $eq: [ '$clientId', '$$clientId' ],
1312
+ },
1313
+ },
1314
+ },
1315
+ {
1316
+ $project: {
1317
+ clientName: 1,
1318
+ },
1319
+ },
1320
+ ], as: 'client',
1321
+ },
1322
+
1323
+ },
1324
+ {
1325
+ $unwind: {
1326
+ path: '$client', preserveNullAndEmptyArrays: true,
1327
+ },
1328
+ },
1329
+ {
1330
+ $project: {
1331
+ storeId: 1,
1332
+ userId: 1,
1333
+ tempId: 1,
1334
+ streamName: 1,
1335
+ question: 1,
1336
+ answer: 1,
1337
+ storeName: '$store.storeName',
1338
+ clientId: 1,
1339
+ clientName: '$client.clientName',
1340
+ fileDate: 1,
1341
+ auditType: 1,
1342
+ zoneName: 1,
1343
+ moduleType: 1,
1344
+ beforeCount: 1,
1345
+ afterCount: 1,
1346
+ accuracy: { $cond: [ { $eq: [ '$auditStatus', 'completed' ] }, { $round: [
1347
+ {
1348
+ $multiply: [ { $divide: [ '$afterCount', '$beforeCount' ] }, 100 ],
1349
+ }, 1,
1350
+ ],
1351
+ }, null ] },
1352
+
1353
+ startTime: {
1354
+ $dateToString: {
1355
+ format: '%Y-%m-%d %H:%M:%S',
1356
+ date: '$startTime',
1357
+ timezone: 'Asia/Kolkata',
1358
+ },
1359
+ },
1360
+ endTime: {
1361
+ $dateToString: {
1362
+ format: '%Y-%m-%d %H:%M:%S',
1363
+ date: '$endTime',
1364
+ timezone: 'Asia/Kolkata',
1365
+ },
1366
+ },
1367
+ // timeSpent: { $round: [
1368
+ // { $divide: [ '$timeSpent', 60 ] }, 2,
1369
+ // ] },
1370
+ timeSpent: {
1371
+
1372
+ $cond: [
1373
+ { $lt: [ '$timeSpent', 60 ] }, // Case 1: If less than 60 seconds
1374
+ { $concat: [ { $toString: '$timeSpent' }, ' sec' ] },
1375
+ {
1376
+ $cond: [
1377
+ { $lt: [ '$timeSpent', 3600 ] }, // Case 2: If less than 60 minutes (3600 seconds)
1378
+ {
1379
+ $concat: [
1380
+ { $toString: { $round: [ { $divide: [ '$timeSpent', 60 ] }, 1 ] } }, // Convert to minutes
1381
+ ' min',
1382
+ ],
1383
+ },
1384
+ {
1385
+ $concat: [
1386
+ { $toString: { $round: [ { $divide: [ '$timeSpent', 3600 ] }, 1 ] } }, // Convert to hours
1387
+ ' hr',
1388
+ ],
1389
+ },
1390
+ ],
1391
+ },
1392
+ ],
1393
+
1394
+ },
1395
+ auditStatus: 1,
1396
+ createdAt: 1,
1397
+ },
1398
+ },
1399
+ ];
1400
+
1401
+ if ( inputData.searchValue && inputData.searchValue!== '' ) {
1402
+ query.push( {
1403
+ $match: {
1404
+ $or: [
1405
+ { clientName: { $regex: inputData.searchValue, $options: 'i' } },
1406
+ { clientId: { $regex: inputData.searchValue, $options: 'i' } },
1407
+ { storeId: { $regex: inputData.searchValue, $options: 'i' } },
1408
+ { storeName: { $regex: inputData.searchValue, $options: 'i' } },
1409
+ { streamName: { $regex: inputData.searchValue, $options: 'i' } },
1410
+ { moduleType: { $regex: inputData.searchValue, $options: 'i' } },
1411
+ { auditType: { $regex: inputData.searchValue, $options: 'i' } },
1412
+ { auditStatus: { $regex: inputData.searchValue, $options: 'i' } },
1413
+ { tempId: { $regex: inputData.searchValue, $options: 'i' } },
1414
+ { question: { $regex: inputData.searchValue, $options: 'i' } },
1415
+ ],
1416
+
1417
+ },
1418
+ } );
1419
+ }
1420
+
1421
+ if ( inputData.sortColumnName ) {
1422
+ const sortBy = inputData.sortBy || -1;
1423
+ const sortColumn = inputData.sortColumnName;
1424
+ query.push( {
1425
+ $sort: { [sortColumn]: sortBy },
1426
+ },
1427
+ );
1428
+ }
1429
+ const count = inputData.moduleType.includes( [ 'uniform-detection', 'mobile-detection' ] )? await aggregateUserEmpDetection( query ) : await aggregateBinaryAudit( query );
1430
+ if ( count.length == 0 ) {
1431
+ return res.sendError( 'No Data Found', 204 );
1432
+ }
1433
+
1434
+ if ( inputData.isExport ) {
1435
+ const chunkedMappingData = await chunkArray( count, 10 );
1436
+ const promises = chunkedMappingData.map( async ( chunk ) => {
1437
+ const exportData = [];
1438
+ chunk.forEach( ( element ) => {
1439
+ exportData.push( {
1440
+ 'File Date': element.fileDate,
1441
+ 'Brand ID': element.clientId,
1442
+ 'Brand Name': element.clientName,
1443
+ 'Store ID': element.storeId,
1444
+ 'Store Name': element.storeName,
1445
+ 'Stream Name': element.moduleType == 'camera-angle-change'? element.zoneName : '',
1446
+ 'Audit Type': element.auditType,
1447
+ 'Product Type': element.moduleType,
1448
+ 'Before Count': element.beforeCount,
1449
+ 'After Count': element.afterCount || '',
1450
+ 'Accuracy': element.accuracy || '',
1451
+ 'Start Time': element.startTime,
1452
+ 'End Time': element.endTime || '',
1453
+ 'Time Spent': element.timeSpent,
1454
+ 'Audit Status': element.auditStatus,
1455
+ 'Audited Date': dayjs( element.createdAt ).format( 'YYYY,MMM D' ),
1456
+
1457
+ } );
1458
+ } );
1459
+ return exportData;
1460
+ } );
1461
+ const mappedArrays = await Promise.all( promises );
1462
+ mappedArrays.flat();
1463
+ logger.info( { mappedArrays: mappedArrays[0] } );
1464
+ await download( mappedArrays[0], res );
1465
+ return;
1466
+ }
1467
+
1468
+ query.push( {
1469
+ $skip: offset,
1470
+ },
1471
+ {
1472
+ $limit: limit,
1473
+ } );
1474
+ const result = inputData.moduleType.includes( [ 'uniform-detection', 'mobile-detection' ] )? await aggregateUserEmpDetection( query ) : await aggregateBinaryAudit( query );
1475
+
1476
+ return res.sendSuccess( { result: result, count: count.length } );
1477
+ } catch ( error ) {
1478
+ const err = error.message || 'Internal Server Error';
1479
+ logger.info( { error: error, message: req.body, function: 'userAuditHistory' } );
1480
+ return res.sendError( err, 500 );
1481
+ }
1482
+ }
@@ -1,5 +1,5 @@
1
1
  import j2s from 'joi-to-swagger';
2
- import { getFileSchema } from '../dtos/traxAudit.dtos.js';
2
+ import { getFileSchema, userAuditHistorySchema, workSpaceSchema, saveSchema } from '../dtos/traxAudit.dtos.js';
3
3
 
4
4
  export const traxAuditDocs = {
5
5
 
@@ -43,4 +43,72 @@ export const traxAuditDocs = {
43
43
  },
44
44
  },
45
45
  },
46
+
47
+ '/v3/trax-audit/work-space': {
48
+ post: {
49
+ tags: [ 'Trax Audit' ],
50
+ description: 'queue wise info of audit files ',
51
+ operationId: 'work-space',
52
+ requestBody: {
53
+ content: {
54
+ 'application/json': {
55
+ schema: j2s( workSpaceSchema ).swagger,
56
+ },
57
+ },
58
+ },
59
+ responses: {
60
+ 200: { description: 'Successful' },
61
+ 401: { description: 'Unauthorized User' },
62
+ 422: { description: 'Field Error' },
63
+ 500: { description: 'Server Error' },
64
+ 204: { description: 'Not Found' },
65
+ },
66
+ },
67
+ },
68
+
69
+ '/v3/trax-audit/save-binary-audit': {
70
+ post: {
71
+ tags: [ 'Trax Audit' ],
72
+ description: 'Save binay data type',
73
+ operationId: 'save-binary-audit',
74
+ parameters: {},
75
+ requestBody: {
76
+ content: {
77
+ 'application/json': {
78
+ schema: j2s( saveSchema ).swagger,
79
+ },
80
+ },
81
+ },
82
+ responses: {
83
+ 200: { description: 'Successful' },
84
+ 401: { description: 'Unauthorized User' },
85
+ 422: { description: 'Field Error' },
86
+ 500: { description: 'Server Error' },
87
+ 204: { description: 'Not Found' },
88
+ },
89
+ },
90
+ },
91
+
92
+ '/v3/trax-audit/metrics/user-audit-history': {
93
+ post: {
94
+ tags: [ 'Trax Audit' ],
95
+ description: `Get list of user wise audit user details with date range`,
96
+ operationId: 'metrics/user-audit-history',
97
+ parameters: {},
98
+ requestBody: {
99
+ content: {
100
+ 'application/json': {
101
+ schema: j2s( userAuditHistorySchema ).swagger,
102
+ },
103
+ },
104
+ },
105
+ responses: {
106
+ 200: { description: 'Successful' },
107
+ 401: { description: 'Unauthorized User' },
108
+ 422: { description: 'Field Error' },
109
+ 500: { description: 'Server Error' },
110
+ 204: { description: 'Not Found' },
111
+ },
112
+ },
113
+ },
46
114
  };
@@ -68,18 +68,11 @@ export const saveSchema = joi.object(
68
68
  auditId: joi.string().required(),
69
69
  storeId: joi.string().required(),
70
70
  fileDate: joi.string().required(),
71
- auditType: joi.string().required(),
72
- moduleType: joi.string().required(),
73
- zoneName: joi.string().required(),
74
- beforeCount: joi.number().required(),
75
- junkCount: joi.number().required(),
76
- junk: joi.array().required(),
77
- employeeCount: joi.number().required(),
78
- employee: joi.array().required(),
79
- customerCount: joi.number().required(),
80
- customer: joi.array().required(),
71
+ tempId: joi.number().optional(),
81
72
  moduleType: joi.string().required(),
82
- zoneName: joi.string().required(),
73
+ question: joi.string().optional(),
74
+ streamName: joi.string().optional(),
75
+ answer: joi.string().required(),
83
76
  },
84
77
  );
85
78
 
@@ -103,3 +96,28 @@ export const workSpaceSchema = joi.object(
103
96
  export const workSpaceValid = {
104
97
  body: workSpaceSchema,
105
98
  };
99
+
100
+ export const userAuditHistorySchema = joi.object(
101
+ {
102
+ userId: joi.string().optional(),
103
+ fromDate: joi.string().required(),
104
+ toDate: joi.string().required(),
105
+ dateType: joi.string().required(),
106
+ clientId: joi.array().optional(),
107
+ filterByStoreId: joi.array().required(),
108
+ moduleType: joi.string().required(),
109
+ filterByAuditType: joi.array().optional(),
110
+ filterByStatus: joi.array().optional(),
111
+ searchValue: joi.string().optional().allow( '' ),
112
+ limit: joi.number().optional(),
113
+ offset: joi.number().optional(),
114
+ sortColumnName: joi.string().optional(),
115
+ sortBy: joi.number().optional(),
116
+ isExport: joi.boolean().optional(),
117
+
118
+ },
119
+ );
120
+
121
+ export const userAuditHistoryValid = {
122
+ body: userAuditHistorySchema,
123
+ };
@@ -1,17 +1,26 @@
1
1
  import { Router } from 'express';
2
2
  import { isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
3
- import { getDetectionFileValid, getFileValid } from '../dtos/traxAudit.dtos.js';
3
+ import { getDetectionFileValid, getFileValid, userAuditHistoryValid, workSpaceValid, saveValid } from '../dtos/traxAudit.dtos.js';
4
4
  import { isExistsQueue } from '../validation/audit.validation.js';
5
- import { getAuditFile, getDetectionAuditFile } from '../controllers/traxAudit.controllers.js';
5
+ import { getAuditFile, getDetectionAuditFile, saveBinary, userAuditHistory, workSpace } from '../controllers/traxAudit.controllers.js';
6
+ import { validateBinaryAudit } from '../validation/traxAuditValidation.js';
6
7
 
7
8
  export const traxAuditRouter = Router();
8
9
 
9
10
  // Audit Mapping
10
- traxAuditRouter.get( '/get-file', isAllowedSessionHandler, validate( getFileValid ), isExistsQueue, getAuditFile );
11
- traxAuditRouter.get( '/get-file', isAllowedSessionHandler, validate( getDetectionFileValid ), isExistsQueue, getDetectionAuditFile );
11
+
12
+ traxAuditRouter.get( '/get-file-detection', isAllowedSessionHandler, validate( getDetectionFileValid ), isExistsQueue, getDetectionAuditFile );
12
13
  // traxAuditRouter.post( '/save-draft', isAllowedSessionHandler, validate( saveDraftValid ), saveDraft );
13
14
  // traxAuditRouter.get( '/get-drafted-data', isAllowedSessionHandler, validate( getDraftedDataValid ), getDraftedData );
14
- // traxAuditRouter.post( '/save', isAllowedSessionHandler, validate( saveValid ), validateUserAudit, save );
15
- // traxAuditRouter.post( '/work-space', isAllowedSessionHandler, validate( workSpaceValid ), workSpace );
15
+ // traxAuditRouter.post( '/save', isAllowedSessionHandler, validate( saveValid ), validateUserEmpDetection, save ); use this validateuserEMpDetection
16
+ traxAuditRouter.post( '/work-space', isAllowedSessionHandler, validate( workSpaceValid ), workSpace );
17
+
18
+
19
+ // Y/n
20
+ traxAuditRouter.get( '/get-file', isAllowedSessionHandler, validate( getFileValid ), isExistsQueue, getAuditFile ); // remove the reaudit concept
21
+ traxAuditRouter.post( '/save-binary-audit', isAllowedSessionHandler, validate( saveValid ), validateBinaryAudit, saveBinary ); // need to add valid joi function, saveBinary function
16
22
 
17
23
  export default traxAuditRouter;
24
+
25
+ // metrcis
26
+ traxAuditRouter.post( '/metrics/user-audit-history', isAllowedSessionHandler, validate( userAuditHistoryValid ), userAuditHistory );
@@ -5,6 +5,14 @@ export function createBinaryAudit( record ) {
5
5
  return binaryAuditModel.create( record );
6
6
  }
7
7
 
8
- export function aggregateBinaryAuditModel( query ) {
8
+ export function aggregateBinaryAudit( query ) {
9
9
  return binaryAuditModel.aggregate( query, { collation: { locale: 'en', strength: 2 } } );
10
10
  }
11
+
12
+ export function findOneBinaryAudit( query ) {
13
+ return binaryAuditModel.findOne( query );
14
+ }
15
+
16
+ export function updateOneBinaryAuditModel( query, record ) {
17
+ return binaryAuditModel.updateOne( query, { $set: record } );
18
+ }
@@ -0,0 +1,6 @@
1
+ import storeEmpDetectionModel from 'tango-api-schema/schema/storeEmpDetection.model.js';
2
+
3
+
4
+ export function aggregateStoreEmpDetection( query ) {
5
+ return storeEmpDetectionModel.aggregate( query, { collation: { locale: 'en', strength: 2 } } );
6
+ }
@@ -0,0 +1,10 @@
1
+
2
+ import traxAuditDataModel from 'tango-api-schema/schema/traxAuditData.model.js';
3
+
4
+ export function findOneTraxAuditData( query, fields, sort ) {
5
+ return traxAuditDataModel.findOne( query, fields ).sort( sort );
6
+ }
7
+
8
+ export function aggregateTraxAuditData( query ) {
9
+ return traxAuditDataModel.aggregate( query, { collation: { locale: 'en', strength: 2 } } );
10
+ }
@@ -1,6 +1,14 @@
1
1
  import userEmpDetectionModel from 'tango-api-schema/schema/userEmpDetection.model.js';
2
2
 
3
3
 
4
- export function aggregateUserAudit( query ) {
4
+ export function aggregateUserEmpDetection( query ) {
5
5
  return userEmpDetectionModel.aggregate( query, { collation: { locale: 'en', strength: 2 } } );
6
6
  }
7
+
8
+ export function createUserEmpDetection( record ) {
9
+ return userEmpDetectionModel.create( record );
10
+ }
11
+
12
+ export function findOneUserEmpDetection( query, fields ) {
13
+ return userEmpDetectionModel.findOne( query, fields );
14
+ }
@@ -0,0 +1,39 @@
1
+ import { findOneBinaryAudit } from '../service/binaryAudit.service.js';
2
+ import { findOneUserEmpDetection } from '../service/userEmpDetection.service.js';
3
+
4
+
5
+ export async function validateUserEmpDetection( req, res, next ) {
6
+ try {
7
+ const inputData = req.body;
8
+ const userAuditDetails =await findOneUserEmpDetection( { _id: inputData.auditId } );
9
+ if ( !userAuditDetails ) {
10
+ return res.sendError( 'No Data Available' );
11
+ } else if ( userAuditDetails && userAuditDetails.auditStatus == 'skipped' ) {
12
+ return res.sendError( 'The Audit file Assigned to some one else', 203 );
13
+ }
14
+ req.userAudit = userAuditDetails;
15
+ next();
16
+ } catch ( error ) {
17
+ const err = error.message || 'Internal Server Error';
18
+ logger.error( { error: error, message: req.query, function: 'validateUserEmpDetection' } );
19
+ return res.sendError( err, 500 );
20
+ }
21
+ }
22
+
23
+ export async function validateBinaryAudit( req, res, next ) {
24
+ try {
25
+ const inputData = req.body;
26
+ const userAuditDetails =await findOneBinaryAudit( { _id: inputData.auditId } );
27
+ if ( !userAuditDetails ) {
28
+ return res.sendError( 'No Data Available' );
29
+ } else if ( userAuditDetails && userAuditDetails.auditStatus == 'completed' ) {
30
+ return res.sendError( 'Forbidden to this action', 203 );
31
+ }
32
+ req.userAudit = userAuditDetails;
33
+ next();
34
+ } catch ( error ) {
35
+ const err = error.message || 'Internal Server Error';
36
+ logger.error( { error: error, message: req.query, function: 'validateBinaryAudit' } );
37
+ return res.sendError( err, 500 );
38
+ }
39
+ }