tango-app-api-trax 3.9.23 → 3.9.25

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.
Files changed (46) hide show
  1. package/package.json +2 -2
  2. package/src/controllers/internalTrax.controller.js +114 -73
  3. package/src/controllers/mobileTrax.controller.js +47 -28
  4. package/src/controllers/trax.controller.js +2 -1
  5. package/src/controllers/traxDashboard.controllers.js +63 -54
  6. package/src/hbs/flag.hbs +1 -1
  7. package/src/logging/activityLogFlusher.js +59 -0
  8. package/src/logging/activityLogMiddleware.js +45 -0
  9. package/src/logging/activityLogStore.js +91 -0
  10. package/src/logging/compressBatches.js +83 -0
  11. package/src/logging/config.js +24 -0
  12. package/src/logging/createLoggableService.js +46 -0
  13. package/src/logging/logExternalCall.js +37 -0
  14. package/src/services/app.service.js +20 -14
  15. package/src/services/approver.service.js +28 -20
  16. package/src/services/authentication.service.js +12 -6
  17. package/src/services/camera.service.js +24 -18
  18. package/src/services/checklist.service.js +36 -31
  19. package/src/services/checklistAssign.service.js +44 -39
  20. package/src/services/checklistQuestion.service.js +40 -35
  21. package/src/services/checklistlog.service.js +40 -35
  22. package/src/services/clientRequest.service.js +12 -5
  23. package/src/services/clients.services.js +23 -18
  24. package/src/services/cluster.service.js +36 -28
  25. package/src/services/domain.service.js +33 -28
  26. package/src/services/download.services.js +48 -38
  27. package/src/services/group.service.js +28 -22
  28. package/src/services/lenskartEmployeeMapping.service.js +20 -15
  29. package/src/services/locus.service.js +41 -34
  30. package/src/services/notification.service.js +40 -31
  31. package/src/services/otp.service.js +24 -17
  32. package/src/services/planogram.service.js +12 -5
  33. package/src/services/processedTaskConfig.service.js +40 -32
  34. package/src/services/processedTaskList.service.js +36 -30
  35. package/src/services/processedchecklist.services.js +56 -48
  36. package/src/services/processedchecklistconfig.services.js +40 -35
  37. package/src/services/recurringFlagTracker.service.js +40 -33
  38. package/src/services/runAIFeatures.services.js +36 -31
  39. package/src/services/runAIRequest.services.js +44 -39
  40. package/src/services/store.service.js +36 -31
  41. package/src/services/tagging.service.js +12 -5
  42. package/src/services/taskConfig.service.js +40 -32
  43. package/src/services/teams.service.js +41 -30
  44. package/src/services/ticket.service.js +20 -15
  45. package/src/services/user.service.js +32 -25
  46. package/src/services/userAssignedstores.service.js +17 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-trax",
3
- "version": "3.9.23",
3
+ "version": "3.9.25",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "path": "^0.12.7",
31
31
  "puppeteer": "^24.39.1",
32
32
  "swagger-ui-express": "^5.0.1",
33
- "tango-api-schema": "^2.6.6",
33
+ "tango-api-schema": "^2.6.12",
34
34
  "tango-app-api-middleware": "^3.5.2",
35
35
  "url": "^0.11.4",
36
36
  "winston": "^3.13.1",
@@ -624,6 +624,7 @@ export async function PCLconfigCreation( req, res ) {
624
624
  element4.rawImageUpload = getCLconfig?.rawImageUpload || false;
625
625
  element4.rawVideoUpload = getCLconfig?.rawVideoUpload || false;
626
626
  element4.videoUploadTimeLimit = getCLconfig?.videoUploadTimeLimit || 0;
627
+ element4.userVerification = getCLconfig?.userVerification;
627
628
  }
628
629
  if ( userIdList.length ) {
629
630
  allQuestion = allQuestion.filter( ( item ) => typeof item._id == 'undefined' );
@@ -1383,40 +1384,40 @@ export async function insertTimeDelayFlags( req, res ) {
1383
1384
  let updatedDetails = await processedchecklist.updateMany( { _id: { $in: checklistId } }, { timeFlag: 1 } );
1384
1385
 
1385
1386
 
1386
- let notifyStores = await processedchecklist.find( { _id: { $in: checklistId }, notify: { $exists: false } } );
1387
-
1388
- if ( notifyStores.length ) {
1389
- Promise.all( notifyStores.map( async ( store ) => {
1390
- let checklistDetails = await CLconfig.findOne( { _id: store.sourceCheckList_id }, { notifyFlags: 1, approver: 1 } );
1391
- if ( checklistDetails?.notifyFlags?.notifyType?.length ) {
1392
- let emailList = checklistDetails?.notifyFlags?.notifyType.includes( 'approver' ) ? checklistDetails.approver.map( ( ele ) => ele?.value ): [];
1393
- emailList = [ ...emailList, ...checklistDetails?.notifyFlags?.users?.map( ( ele ) => ele?.value ) ];
1394
- let data = {
1395
- storeName: store.storeName,
1396
- flagCount: 1,
1397
- checklistName: store.checkListName?.trim(),
1398
- submittedBy: '--',
1399
- time: '--',
1400
- domain: `${JSON.parse( process.env.URL ).domain}/manage/trax/flags?date=${dayjs().format( 'YYYY-MM-DD' )}`,
1401
- type: 'delay',
1402
- };
1403
- const fileContent = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/flag.hbs', 'utf8' );
1404
- const htmlContent = handlebars.compile( fileContent );
1405
- const html = htmlContent( { data: data } );
1406
- emailList.forEach( ( email ) => {
1407
- let params = {
1408
- toEmail: email,
1409
- mailSubject: 'TangoEye | Checklist Flag',
1410
- htmlBody: html,
1411
- attachment: '',
1412
- sourceEmail: JSON.parse( process.env.SES ).adminEmail,
1413
- };
1414
- sendEmailWithSES( params.toEmail, params.mailSubject, params.htmlBody, params.attachment, params.sourceEmail );
1415
- } );
1416
- }
1417
- await processedchecklist.updateOne( { _id: store._id }, { notify: true } );
1418
- } ) );
1419
- }
1387
+ // let notifyStores = await processedchecklist.find( { _id: { $in: checklistId }, notify: { $exists: false } } );
1388
+
1389
+ // if ( notifyStores.length ) {
1390
+ // Promise.all( notifyStores.map( async ( store ) => {
1391
+ // let checklistDetails = await CLconfig.findOne( { _id: store.sourceCheckList_id }, { notifyFlags: 1, approver: 1 } );
1392
+ // if ( checklistDetails?.notifyFlags?.notifyType?.length ) {
1393
+ // let emailList = checklistDetails?.notifyFlags?.notifyType.includes( 'approver' ) ? checklistDetails.approver.map( ( ele ) => ele?.value ): [];
1394
+ // emailList = [ ...emailList, ...checklistDetails?.notifyFlags?.users?.map( ( ele ) => ele?.value ) ];
1395
+ // let data = {
1396
+ // storeName: store.storeName,
1397
+ // flagCount: 1,
1398
+ // checklistName: store.checkListName?.trim(),
1399
+ // submittedBy: '--',
1400
+ // time: '--',
1401
+ // domain: `${JSON.parse( process.env.URL ).domain}/manage/trax/flags?date=${dayjs().format( 'YYYY-MM-DD' )}`,
1402
+ // type: 'delay',
1403
+ // };
1404
+ // const fileContent = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/flag.hbs', 'utf8' );
1405
+ // const htmlContent = handlebars.compile( fileContent );
1406
+ // const html = htmlContent( { data: data } );
1407
+ // emailList.forEach( ( email ) => {
1408
+ // let params = {
1409
+ // toEmail: email,
1410
+ // mailSubject: 'TangoEye | Checklist Flag',
1411
+ // htmlBody: html,
1412
+ // attachment: '',
1413
+ // sourceEmail: JSON.parse( process.env.SES ).adminEmail,
1414
+ // };
1415
+ // sendEmailWithSES( params.toEmail, params.mailSubject, params.htmlBody, params.attachment, params.sourceEmail );
1416
+ // } );
1417
+ // }
1418
+ // await processedchecklist.updateOne( { _id: store._id }, { notify: true } );
1419
+ // } ) );
1420
+ // }
1420
1421
 
1421
1422
  if ( updatedDetails ) {
1422
1423
  return res.sendSuccess( { message: 'Time Delay Updated Successfully' } );
@@ -2700,10 +2701,57 @@ export async function countUpdateRunAI( req, res ) {
2700
2701
  runAIFlag: req.body.runAICount,
2701
2702
  };
2702
2703
 
2704
+ // complianceCount is already populated for the other answer types; add the image/video scores on top.
2705
+ let complianceCount = getDetails.userComplianceCount || 0;
2706
+
2707
+ // Pull every "Matched/Not Matched" verdict out of a runAIData array, tolerating both shapes we store:
2708
+ // 1) [ { answerImage, results: [ { featureName, value } ] } ] (verdict nested under results)
2709
+ // 2) [ { featureName, value } ] (flat verdict)
2710
+ const extractMatchValues = ( runAIData ) => {
2711
+ if ( !Array.isArray( runAIData ) ) return [];
2712
+ const values = [];
2713
+ runAIData.forEach( ( entry ) => {
2714
+ const results = Array.isArray( entry?.results ) ? entry.results : [ entry ];
2715
+ results.forEach( ( r ) => {
2716
+ if ( r?.featureName === 'Matched/Not Matched' ) values.push( r?.value );
2717
+ } );
2718
+ } );
2719
+ return values;
2720
+ };
2721
+
2722
+ getDetails.questionAnswers.forEach( ( section ) => {
2723
+ section.questions.forEach( ( question ) => {
2724
+ if ( question.compliance && [ 'image', 'image/video' ].includes( question.answerType ) ) {
2725
+ // For 'image/video' each userAnswer carries its own answerType and runAIData only lives on
2726
+ // the image entries; a pure 'image' question keeps runAIData directly on every userAnswer.
2727
+ const aiAnswers = question.answerType === 'image/video' ?
2728
+ ( question.userAnswer || [] ).filter( ( ua ) => ua?.answerType === 'image' ) :
2729
+ ( question.userAnswer || [] );
2730
+
2731
+ // Compliance comes from the first configured answer: every "Matched/Not Matched" verdict
2732
+ // across the image answers must be True to count as matched, otherwise it's not matched.
2733
+ const matchValues = aiAnswers.flatMap( ( ua ) => extractMatchValues( ua?.runAIData ) );
2734
+ const allMatched = matchValues.length > 0 && matchValues.every( ( v ) => v === 'True' || v === true );
2735
+ const score = allMatched ?
2736
+ ( question.answers?.[0]?.matchedCount ?? 0 ) :
2737
+ ( question.answers?.[0]?.notMatchedCount ?? 0 );
2738
+
2739
+ // Persist the derived score back onto each userAnswer so PDF/dashboard reads stay consistent.
2740
+ ( question.userAnswer || [] ).forEach( ( ua ) => {
2741
+ ua.complianceScore = score;
2742
+ } );
2743
+
2744
+ complianceCount += score;
2745
+ }
2746
+ } );
2747
+ } );
2748
+
2749
+ updateData.complianceCount = complianceCount;
2750
+
2703
2751
  await processedchecklist.updateOne( { _id: req.body.id }, updateData );
2704
2752
  return res.sendSuccess( 'RunAI Count updated successfully' );
2705
2753
  } catch ( e ) {
2706
- logger.error( { functionName: 'updateRunAI', error: e } );
2754
+ logger.error( { functionName: 'countUpdateRunAI', error: e } );
2707
2755
  return res.sendError( e, 500 );
2708
2756
  }
2709
2757
  }
@@ -3540,47 +3588,40 @@ export async function runAIFlag( req, res ) {
3540
3588
  ],
3541
3589
  },
3542
3590
  }, { _id: 1, notifyFlags: 1, approver: 1 } );
3543
- let date = dayjs().subtract( 1, 'day' ).format( 'YYYY-MM-DD' );
3591
+ console.log( checklistDetails );
3592
+ let date = dayjs().format( 'YYYY-MM-DD' );
3544
3593
  if ( checklistDetails.length ) {
3545
3594
  await Promise.all( checklistDetails.map( async ( ele ) => {
3546
- let submitDetails = await processedchecklist.find( { sourceCheckList_id: ele._id, checklistStatus: 'submit', date_string: date }, { storeName: 1, checkListName: 1, questionAnswers: 1 } );
3595
+ let submitDetails = await processedchecklist.find( { sourceCheckList_id: ele._id, date_string: date, $or: [ { timeFlag: { $gt: 0 } }, { questionFlag: { $gt: 0 } }, { runAIFlag: { $gt: 0 } } ] }, { storeName: 1, checkListName: 1, userName: 1, submitTime_string: 1, runAIFlag: 1, questionFlag: 1, checklistStatus: 1 } );
3596
+ console.log( submitDetails );
3597
+ let emailList = ele?.notifyFlags?.notifyType.includes( 'approver' ) ? ele.approver.map( ( approver ) => approver?.value ): [];
3598
+ emailList = [ ...emailList, ...ele?.notifyFlags?.users?.map( ( user ) => user?.value ) ];
3547
3599
  await Promise.all( submitDetails.map( ( store ) => {
3548
- if ( store.questionAnswers.length ) {
3549
- let runAIFlag = 0;
3550
- store.questionAnswers.forEach( ( section ) => {
3551
- section.questions.forEach( ( question ) => {
3552
- if ( question.answerType == 'image' && ( question?.userAnswer?.[0]?.runAIData?.value == 'False' || !question?.userAnswer?.[0]?.runAIData?.value ) ) {
3553
- runAIFlag++;
3554
- }
3555
- } );
3556
- } );
3557
- if ( runAIFlag ) {
3558
- let emailList = ele?.notifyFlags?.notifyType.includes( 'approver' ) ? ele.approver.map( ( approver ) => approver?.value ): [];
3559
- emailList = [ ...emailList, ...ele?.notifyFlags?.users?.map( ( user ) => user?.value ) ];
3560
- let data = {
3561
- storeName: store.storeName,
3562
- flagCount: 1,
3563
- checklistName: store.checkListName?.trim(),
3564
- submittedBy: '--',
3565
- time: '--',
3566
- domain: `${JSON.parse( process.env.URL ).domain}/manage/trax/flags?date=${dayjs().format( 'YYYY-MM-DD' )}`,
3567
- type: 'delay',
3568
- };
3569
- const fileContent = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/flag.hbs', 'utf8' );
3570
- const htmlContent = handlebars.compile( fileContent );
3571
- const html = htmlContent( { data: data } );
3572
- emailList.forEach( ( email ) => {
3573
- let params = {
3574
- toEmail: email,
3575
- mailSubject: 'TangoEye | Checklist Flag',
3576
- htmlBody: html,
3577
- attachment: '',
3578
- sourceEmail: JSON.parse( process.env.SES ).adminEmail,
3579
- };
3580
- sendEmailWithSES( params.toEmail, params.mailSubject, params.htmlBody, params.attachment, params.sourceEmail );
3581
- } );
3582
- }
3583
- }
3600
+ let data = {
3601
+ storeName: store.storeName,
3602
+ flagCount: store.timeFlag ? 1 : store?.runAIFlag + store?.questionFlag,
3603
+ runAIFlag: store?.runAIFlag,
3604
+ questionFlag: store?.questionFlag,
3605
+ checklistName: store.checkListName?.trim(),
3606
+ submittedBy: store?.userName,
3607
+ time: store?.submitTime_string ?? '--',
3608
+ domain: `${JSON.parse( process.env.URL ).domain}/manage/trax/flags?date=${dayjs().format( 'YYYY-MM-DD' )}`,
3609
+ status: store.checklistStatus,
3610
+ };
3611
+ const fileContent = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/flag.hbs', 'utf8' );
3612
+ const htmlContent = handlebars.compile( fileContent );
3613
+ const html = htmlContent( { data: data } );
3614
+ emailList.forEach( ( email ) => {
3615
+ console.log( email );
3616
+ let params = {
3617
+ toEmail: email,
3618
+ mailSubject: 'TangoEye | Checklist Flag',
3619
+ htmlBody: html,
3620
+ attachment: '',
3621
+ sourceEmail: JSON.parse( process.env.SES ).adminEmail,
3622
+ };
3623
+ sendEmailWithSES( params.toEmail, params.mailSubject, params.htmlBody, params.attachment, params.sourceEmail );
3624
+ } );
3584
3625
  } ) );
3585
3626
  } ) );
3586
3627
  }
@@ -1777,6 +1777,18 @@ export async function sopMobilechecklistMultiSectionFormatterv2( req, res, next
1777
1777
  };
1778
1778
  }
1779
1779
  }
1780
+ if ( requestSection[i]?.validationVideo?.length ) {
1781
+ for ( let video of requestSection[i].validationVideo ) {
1782
+ let validationAnswer = decodeURIComponent( video.split( '?' )[0] );
1783
+ if ( validationAnswer.length ) {
1784
+ let splitImgUrl = validationAnswer.split( '/' );
1785
+ if ( splitImgUrl.length > 1 ) {
1786
+ splitImgUrl.splice( 0, 3 );
1787
+ qaans[k].validationVideo.push( splitImgUrl.join( '/' ) || '' );
1788
+ }
1789
+ };
1790
+ }
1791
+ }
1780
1792
  }
1781
1793
  // qaans[k].descriptivetype = qaAnswers[j].descriptivetype || '';
1782
1794
  qaans[k].validationAnswer = requestSection[i].validationAnswer || '';
@@ -2127,12 +2139,18 @@ export async function sopMobilechecklistMultiSectionFormatterv2( req, res, next
2127
2139
  logger.error( { message: requestData.questionAnswers, error: 'QuestionanswersPayload' } );
2128
2140
 
2129
2141
  if ( requestData.submittype == 'submit' ) {
2142
+ requestData.userComplianceCount = 0;
2130
2143
  for ( let section of sectionFormat ) {
2131
2144
  for ( let question of section.questions ) {
2132
2145
  let mustValidate = !question.linkType || ( question.linkType && question.linkquestionenabled );
2133
2146
  if ( mustValidate && ( !question.userAnswer || !question.userAnswer.length ) ) {
2134
2147
  return res.sendError( 'Please Fill All Fields', 400 );
2135
2148
  }
2149
+ if ( question.compliance ) {
2150
+ const scores = ( question.userAnswer || [] ).map( ( ua ) => ua?.complianceScore ?? 0 );
2151
+ let score = scores.length ? Math.max( ...scores ) : 0;
2152
+ requestData.userComplianceCount += score;
2153
+ }
2136
2154
  }
2137
2155
  }
2138
2156
  }
@@ -2776,6 +2794,7 @@ export async function submitChecklist( req, res ) {
2776
2794
  updateData.checklistStatus = 'submit';
2777
2795
  updateData.updatedAt = dayjs.utc( currentDateTime.format( 'hh:mm:ss A, DD MMM YYYY' ), 'hh:mm:ss A, DD MMM YYYY' ).format();
2778
2796
  updateData.submitMobileTime = requestData?.currentTime;
2797
+ updateData.userComplianceCount = requestData?.userComplianceCount;
2779
2798
  }
2780
2799
  // updateData.questionAnswers.forEach( ( section ) => {
2781
2800
  // section.questions.forEach( ( question ) => {
@@ -2876,34 +2895,34 @@ export async function submitChecklist( req, res ) {
2876
2895
  userType: req.user.userType,
2877
2896
  };
2878
2897
  insertOpenSearchData( openSearch.traxActivityLog, inserttraxlogs );
2879
- if ( updateData.questionFlag ) {
2880
- let checklistDetails = await checklistService.findOne( { _id: getchecklist[0].sourceCheckList_id }, { notifyFlags: 1, approver: 1 } );
2881
- if ( checklistDetails?.notifyFlags?.notifyType?.length ) {
2882
- let emailList = checklistDetails?.notifyFlags?.notifyType.includes( 'approver' ) ? checklistDetails.approver.map( ( ele ) => ele?.value ): [];
2883
- emailList = [ ...emailList, ...checklistDetails?.notifyFlags?.users?.map( ( ele ) => ele?.value ) ];
2884
- let data = {
2885
- storeName: getchecklist[0].storeName,
2886
- flagCount: updateData.questionFlag,
2887
- checklistName: getchecklist[0].checkListName,
2888
- submittedBy: getchecklist[0].userName,
2889
- time: updateData.submitTime_string,
2890
- domain: `${JSON.parse( process.env.URL ).domain}/manage/trax/flags?date=${dayjs().format( 'YYYY-MM-DD' )}`,
2891
- };
2892
- const fileContent = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/flag.hbs', 'utf8' );
2893
- const htmlContent = handlebars.compile( fileContent );
2894
- const html = htmlContent( { data: data } );
2895
- emailList.forEach( ( email ) => {
2896
- let params = {
2897
- toEmail: email,
2898
- mailSubject: 'TangoEye | Checklist Flag',
2899
- htmlBody: html,
2900
- attachment: '',
2901
- sourceEmail: JSON.parse( process.env.SES ).adminEmail,
2902
- };
2903
- sendEmailWithSES( params.toEmail, params.mailSubject, params.htmlBody, params.attachment, params.sourceEmail );
2904
- } );
2905
- }
2906
- }
2898
+ // if ( updateData.questionFlag ) {
2899
+ // let checklistDetails = await checklistService.findOne( { _id: getchecklist[0].sourceCheckList_id }, { notifyFlags: 1, approver: 1 } );
2900
+ // if ( checklistDetails?.notifyFlags?.notifyType?.length ) {
2901
+ // let emailList = checklistDetails?.notifyFlags?.notifyType.includes( 'approver' ) ? checklistDetails.approver.map( ( ele ) => ele?.value ): [];
2902
+ // emailList = [ ...emailList, ...checklistDetails?.notifyFlags?.users?.map( ( ele ) => ele?.value ) ];
2903
+ // let data = {
2904
+ // storeName: getchecklist[0].storeName,
2905
+ // flagCount: updateData.questionFlag,
2906
+ // checklistName: getchecklist[0].checkListName,
2907
+ // submittedBy: getchecklist[0].userName,
2908
+ // time: updateData.submitTime_string,
2909
+ // domain: `${JSON.parse( process.env.URL ).domain}/manage/trax/flags?date=${dayjs().format( 'YYYY-MM-DD' )}`,
2910
+ // };
2911
+ // const fileContent = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/flag.hbs', 'utf8' );
2912
+ // const htmlContent = handlebars.compile( fileContent );
2913
+ // const html = htmlContent( { data: data } );
2914
+ // emailList.forEach( ( email ) => {
2915
+ // let params = {
2916
+ // toEmail: email,
2917
+ // mailSubject: 'TangoEye | Checklist Flag',
2918
+ // htmlBody: html,
2919
+ // attachment: '',
2920
+ // sourceEmail: JSON.parse( process.env.SES ).adminEmail,
2921
+ // };
2922
+ // sendEmailWithSES( params.toEmail, params.mailSubject, params.htmlBody, params.attachment, params.sourceEmail );
2923
+ // } );
2924
+ // }
2925
+ // }
2907
2926
  }
2908
2927
  if ( getchecklist?.[0]?.redoEdit?.toString() != requestData?.redoEdit ) {
2909
2928
  return res.sendSuccess( 'New Questions Added So,Checklist moved back to In Progress. Please complete the updated checklist.' );
@@ -620,6 +620,7 @@ export const zoneList = async ( req, res ) => {
620
620
  const allowedChecklistsStreams = [
621
621
  'Camera Angle Change Compliance',
622
622
  ];
623
+ console.log( allowedChecklists.includes( inputBody.checkListName ) );
623
624
 
624
625
  if ( inputBody.checkListName && allowedChecklists.includes( inputBody.checkListName ) ) {
625
626
  console.log( '==================' );
@@ -1914,7 +1915,6 @@ export const updateConfigure = async ( req, res ) => {
1914
1915
 
1915
1916
  export const updateConfigurev1 = async ( req, res ) => {
1916
1917
  try {
1917
- console.log( '123' );
1918
1918
  let inputBody = req.body;
1919
1919
  let id;
1920
1920
  let checklistDetails;
@@ -3972,6 +3972,7 @@ async function insertPCBulkV4( getCLconfig, checklistId, currentdate, updatedche
3972
3972
  element4.rawVideoUpload = getCLconfig?.rawVideoUpload || false;
3973
3973
  element4.videoUploadTimeLimit = getCLconfig?.videoUploadTimeLimit || 0;
3974
3974
  element4.complianceCount = getCLconfig?.complianceCount || 0;
3975
+ element4.userVerification = getCLconfig?.userVerification;
3975
3976
  assignUserList.push( { ...element4 } );
3976
3977
  }
3977
3978
  } ) );
@@ -276,29 +276,10 @@ export const checklistPerformance = async ( req, res ) => {
276
276
  toDate = new Date( toDate.getTime() - userTimezoneOffset );
277
277
  toDate.setUTCHours( 23, 59, 59, 59 );
278
278
  let result = {};
279
- let checklistIdList = [];
280
279
 
281
280
  let limit = parseInt( requestData?.limit ) || 10;
282
281
  let skip = limit * ( requestData?.offset ) || 0;
283
282
 
284
- const detectionPayload = {
285
- 'fromDate': requestData.fromDate,
286
- 'toDate': requestData.toDate,
287
- 'clientId': requestData.clientId,
288
- 'sortColumnName': requestData.sortColumnName,
289
- 'sortBy': requestData.sortBy,
290
- 'storeId': requestData.storeId,
291
- };
292
-
293
-
294
- let complianceURL = JSON.parse( process.env.LAMBDAURL ).complianceURL;
295
- const complianceData = await LamdaServiceCall( complianceURL, detectionPayload );
296
- if ( complianceData?.data?.length && requestData?.sortColumnName == 'questionCompliance' ) {
297
- const end = skip + requestData?.limit;
298
- checklistIdList = complianceData.data.slice( skip, end )?.map( ( ele ) => ele?.sourceCheckList_id );
299
- }
300
-
301
-
302
283
  // Get User Based Checklist //
303
284
  // let loginUser = { clientId: requestData.clientId, role: req.user.role, userType: req.user.userType, userEmail: req.user.email };
304
285
  // let getUserEmails = await getChecklistUsers( loginUser );
@@ -314,10 +295,6 @@ export const checklistPerformance = async ( req, res ) => {
314
295
  { $or: [ { store_id: { $in: requestData.storeId } }, { store_id: { $eq: '' }, userEmail: { $in: requestData.userEmailes } } ] },
315
296
  );
316
297
 
317
- if ( requestData?.sortColumnName == 'questionCompliance' ) {
318
- findAndQuery.push( { sourceCheckList_id: { $in: checklistIdList } } );
319
- }
320
-
321
298
  findQuery.push( { $match: { $and: findAndQuery } } );
322
299
 
323
300
  if ( requestData.searchValue && requestData.searchValue != '' ) {
@@ -367,6 +344,16 @@ export const checklistPerformance = async ( req, res ) => {
367
344
  timeFlag: { $sum: '$timeFlag' },
368
345
  questionFlagCount: { $sum: '$questionFlag' },
369
346
  runAIFlagCount: { $sum: '$runAIFlag' },
347
+ userComplianceCountTotal: {
348
+ $sum: {
349
+ $cond: [
350
+ { $eq: [ '$checklistStatus', 'submit' ] },
351
+ { $ifNull: [ '$userComplianceCount', 0 ] },
352
+ 0,
353
+ ],
354
+ },
355
+ },
356
+ complianceCount: { $first: '$complianceCount' },
370
357
  checkListType: { $last: '$checkListType' },
371
358
  redo: { $sum: { $cond: [ { $eq: [ '$redoStatus', true ] }, 1, 0 ] } },
372
359
  task: {
@@ -426,6 +413,36 @@ export const checklistPerformance = async ( req, res ) => {
426
413
  checkListType: 1,
427
414
  redo: 1,
428
415
  task: 1,
416
+ questionCompliance: {
417
+ $let: {
418
+ vars: {
419
+ divisor: {
420
+ $cond: [
421
+ { $gt: [ '$submittedChecklist', 1 ] },
422
+ { $multiply: [ { $ifNull: [ '$complianceCount', 0 ] }, '$submittedChecklist' ] },
423
+ { $ifNull: [ '$complianceCount', 0 ] },
424
+ ],
425
+ },
426
+ },
427
+ in: {
428
+ $cond: [
429
+ { $gt: [ '$$divisor', 0 ] },
430
+ {
431
+ $round: [
432
+ {
433
+ $multiply: [
434
+ { $divide: [ '$userComplianceCountTotal', '$$divisor' ] },
435
+ 100,
436
+ ],
437
+ },
438
+ 0,
439
+ ],
440
+ },
441
+ 0,
442
+ ],
443
+ },
444
+ },
445
+ },
429
446
  },
430
447
  } );
431
448
 
@@ -450,14 +467,6 @@ export const checklistPerformance = async ( req, res ) => {
450
467
  return res.sendError( 'no data found', 204 );
451
468
  }
452
469
 
453
- getChecklistPerformanceData?.[0]?.data.forEach( ( ele ) => {
454
- let findCompliance;
455
- if ( complianceData?.data?.length ) {
456
- findCompliance = complianceData?.data?.find( ( data ) => data.sourceCheckList_id == ele?.sourceCheckList_id );
457
- }
458
- ele['questionComplianceRate'] = findCompliance?.compliancePercentage ?? 0;
459
- } );
460
-
461
470
  if ( requestData.export ) {
462
471
  const exportdata = [];
463
472
  getChecklistPerformanceData[0].data.forEach( ( element ) => {
@@ -4335,26 +4344,26 @@ function escapeRegex( text ) {
4335
4344
  // }
4336
4345
  // }
4337
4346
 
4338
- async function LamdaServiceCall( url, data ) {
4339
- try {
4340
- const requestOptions = {
4341
- method: 'POST',
4342
- headers: {
4343
- 'Content-Type': 'application/json',
4344
- },
4345
- body: JSON.stringify( data ),
4346
- };
4347
- console.log( data );
4348
- const response = await fetch( url, requestOptions );
4349
- if ( !response.ok ) {
4350
- throw new Error( `Response status: ${response.status}` );
4351
- return false;
4352
- }
4353
- const json = await response.json();
4354
- return json;
4355
- } catch ( error ) {
4356
- console.log( error );
4357
- logger.error( { error: error, message: data, function: 'LamdaServiceCall' } );
4358
- return false;
4359
- }
4360
- }
4347
+ // async function LamdaServiceCall( url, data ) {
4348
+ // try {
4349
+ // const requestOptions = {
4350
+ // method: 'POST',
4351
+ // headers: {
4352
+ // 'Content-Type': 'application/json',
4353
+ // },
4354
+ // body: JSON.stringify( data ),
4355
+ // };
4356
+ // console.log( data );
4357
+ // const response = await fetch( url, requestOptions );
4358
+ // if ( !response.ok ) {
4359
+ // throw new Error( `Response status: ${response.status}` );
4360
+ // return false;
4361
+ // }
4362
+ // const json = await response.json();
4363
+ // return json;
4364
+ // } catch ( error ) {
4365
+ // console.log( error );
4366
+ // logger.error( { error: error, message: data, function: 'LamdaServiceCall' } );
4367
+ // return false;
4368
+ // }
4369
+ // }
package/src/hbs/flag.hbs CHANGED
@@ -176,7 +176,7 @@
176
176
  <td class="flagText" style="padding-left:30px; line-height: 24px;">No of Flags :</td>
177
177
  <td></td>
178
178
  <td></td>
179
- <td class="flagText">{{data.flagCount}}</td>
179
+ <td class="flagText">{{data.flagCount}}{{#eq data.status 'submit'}}(RunAIFlag:{{data.runAIFlag}},QuestionFlag:{{data.questionFlag}}){{/eq}}</td>
180
180
  </tr>
181
181
  <tr bgcolor="#ffffff" style="border:none;margin-top:3px;">
182
182
  <td class="flagText" style="padding-left:30px; line-height: 24px;">Submitted By :</td>
@@ -0,0 +1,59 @@
1
+ import { insertOpenSearchData, logger } from 'tango-app-api-middleware';
2
+ import { loggingConfig } from './config.js';
3
+ import { compressBatches } from './compressBatches.js';
4
+
5
+ export async function flushActivityLog( ctx, status ) {
6
+ if ( !loggingConfig.enabled ) return;
7
+ if ( !ctx ) return;
8
+
9
+ if ( ctx.steps.length === 0 && !ctx.error ) return;
10
+
11
+ const compressedSteps = safeCompress( ctx.steps );
12
+
13
+ const doc = {
14
+ version: loggingConfig.version,
15
+ requestId: ctx.requestId,
16
+ timestamp: new Date( ctx.startTime ).toISOString(),
17
+ duration: Date.now() - ctx.startTime,
18
+ user: ctx.user,
19
+ api: {
20
+ method: ctx.api.method,
21
+ path: ctx.api.path,
22
+ action: ctx.api.action,
23
+ body: stringifyBody( ctx.api.body ),
24
+ },
25
+ response: {
26
+ code: ctx.response?.code ?? null,
27
+ body: stringifyBody( ctx.response?.body ),
28
+ },
29
+ status,
30
+ steps: compressedSteps,
31
+ error: ctx.error,
32
+ };
33
+
34
+ try {
35
+ await insertOpenSearchData( loggingConfig.index, doc );
36
+ } catch ( e ) {
37
+ logger.error( { functionName: 'flushActivityLog', error: e } );
38
+ }
39
+ }
40
+
41
+ function safeCompress( steps ) {
42
+ try {
43
+ return compressBatches( steps );
44
+ } catch ( _e ) {
45
+ // Never let compression failure block log delivery — fall back to raw steps.
46
+ return steps;
47
+ }
48
+ }
49
+
50
+ function stringifyBody( body ) {
51
+ if ( body === null || body === undefined ) return null;
52
+ if ( typeof body === 'string' ) return body;
53
+ if ( body instanceof Error ) return body.message || String( body );
54
+ try {
55
+ return JSON.stringify( body );
56
+ } catch ( _e ) {
57
+ return '[unserializable]';
58
+ }
59
+ }
@@ -0,0 +1,45 @@
1
+ import { activityLogStore, createLogContext } from './activityLogStore.js';
2
+ import { flushActivityLog } from './activityLogFlusher.js';
3
+ import { loggingConfig } from './config.js';
4
+
5
+ function refreshUser( ctx, req ) {
6
+ if ( req?.user ) {
7
+ ctx.user = {
8
+ _id: req.user._id,
9
+ userName: req.user.userName,
10
+ email: req.user.email,
11
+ };
12
+ }
13
+ }
14
+
15
+ export default function activityLogMiddleware( req, res, next ) {
16
+ if ( !loggingConfig.enabled ) return next();
17
+
18
+ const WRITE_METHODS = [ 'POST', 'PUT', 'PATCH', 'DELETE' ];
19
+ if ( !WRITE_METHODS.includes( req.method ) ) return next();
20
+
21
+ const ctx = createLogContext( req );
22
+
23
+ activityLogStore.run( ctx, () => {
24
+ const originalSendSuccess = res.sendSuccess;
25
+ const originalSendError = res.sendError;
26
+
27
+ res.sendSuccess = ( data ) => {
28
+ refreshUser( ctx, req );
29
+ ctx.response.code = 200;
30
+ ctx.response.body = data;
31
+ originalSendSuccess( data );
32
+ setImmediate( () => flushActivityLog( ctx, 'success' ).catch( () => {} ) );
33
+ };
34
+
35
+ res.sendError = ( message, code ) => {
36
+ refreshUser( ctx, req );
37
+ ctx.response.code = code ?? 500;
38
+ ctx.response.body = message;
39
+ originalSendError( message, code );
40
+ setImmediate( () => flushActivityLog( ctx, 'failed' ).catch( () => {} ) );
41
+ };
42
+
43
+ next();
44
+ } );
45
+ }