tango-app-api-trax 3.9.55 → 3.9.57

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-trax",
3
- "version": "3.9.55",
3
+ "version": "3.9.57",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -2834,6 +2834,14 @@ export async function countUpdateRunAI( req, res ) {
2834
2834
  const scores = ( question.userAnswer || [] ).map( ( ua ) => ua.complianceScore ?? 0 );
2835
2835
  score = scores.length ? Math.max( ...scores ) : 0;
2836
2836
  }
2837
+ // Single-choice questions answered 'NA' / 'N/A' (case-insensitive) contribute 0;
2838
+ // don't count the selected answer's compliance score for that question.
2839
+ if ( question.answerType === 'multiplechoicesingle' ) {
2840
+ const naAnswer = ( question.userAnswer?.[0]?.answer || '' ).toString().trim().toLowerCase();
2841
+ if ( naAnswer === 'na' || naAnswer === 'n/a' ) {
2842
+ score = 0;
2843
+ }
2844
+ }
2837
2845
  userComplianceTotal += score;
2838
2846
  }
2839
2847
  } );
@@ -2141,7 +2141,6 @@ export async function sopMobilechecklistMultiSectionFormatterv2( req, res, next
2141
2141
 
2142
2142
  if ( requestData.submittype == 'submit' ) {
2143
2143
  requestData.userComplianceCount = 0;
2144
- requestData.complianceCount = 0;
2145
2144
  for ( let section of sectionFormat ) {
2146
2145
  for ( let question of section.questions ) {
2147
2146
  let mustValidate = !question.linkType || ( question.linkType && question.linkquestionenabled );
@@ -2149,27 +2148,17 @@ export async function sopMobilechecklistMultiSectionFormatterv2( req, res, next
2149
2148
  return res.sendError( 'Please Fill All Fields', 400 );
2150
2149
  }
2151
2150
  if ( question.compliance ) {
2152
- // score = user's achieved value; max = best possible for the question
2153
- // (same max formula as config-time complianceCount in trax.controller).
2154
2151
  const scores = ( question.userAnswer || [] ).map( ( ua ) => ua?.complianceScore ?? 0 );
2155
2152
  let score = scores.length ? Math.max( ...scores ) : 0;
2156
-
2157
- const answerScores = ( question.answers || [] ).map( ( o ) => o?.complianceScore ?? Math.max( o?.matchedCount ?? 0, o?.notMatchedCount ?? 0 ) );
2158
- let max = answerScores.length ? Math.max( ...answerScores ) : 0;
2159
-
2160
- // Single-choice questions answered 'NA' / 'N/A' (case-insensitive) are
2161
- // excluded from compliance entirely (score AND max = 0), matching the
2162
- // visit-checklist PDF and dashboard compliance calculation.
2153
+ // Single-choice questions answered 'NA' / 'N/A' (case-insensitive) contribute 0;
2154
+ // don't count the selected answer's compliance score for that question.
2163
2155
  if ( question.answerType === 'multiplechoicesingle' ) {
2164
2156
  const naAnswer = ( question.userAnswer?.[0]?.answer || '' ).toString().trim().toLowerCase();
2165
2157
  if ( naAnswer === 'na' || naAnswer === 'n/a' ) {
2166
2158
  score = 0;
2167
- max = 0;
2168
2159
  }
2169
2160
  }
2170
-
2171
2161
  requestData.userComplianceCount += score;
2172
- requestData.complianceCount += max;
2173
2162
  }
2174
2163
  }
2175
2164
  }
@@ -2815,8 +2804,6 @@ export async function submitChecklist( req, res ) {
2815
2804
  updateData.updatedAt = dayjs.utc( currentDateTime.format( 'hh:mm:ss A, DD MMM YYYY' ), 'hh:mm:ss A, DD MMM YYYY' ).format();
2816
2805
  updateData.submitMobileTime = requestData?.currentTime;
2817
2806
  updateData.userComplianceCount = requestData?.userComplianceCount;
2818
- // NA-aware max (denominator) recomputed at submit, alongside the score above.
2819
- updateData.complianceCount = requestData?.complianceCount;
2820
2807
  updateData.userSignature = requestData?.userSignature;
2821
2808
  if ( requestData?.userImage ) {
2822
2809
  let splitImgUrl = requestData?.userImage.split( '/' );
@@ -297,21 +297,15 @@ export const checklistPerformance = async ( req, res ) => {
297
297
 
298
298
  findQuery.push( { $match: { $and: findAndQuery } } );
299
299
 
300
- // Same search expressed as a plain find condition, so the parallel compliance
301
- // query (below) stays scoped to the same checklists without depending on the
302
- // aggregation's result.
303
- let complianceSearch = null;
304
300
  if ( requestData.searchValue && requestData.searchValue != '' ) {
305
301
  let checkListSearch = requestData.searchValue.split( ',' ).map( ( item ) => item.trim().toLowerCase() );
306
302
  let query;
307
303
  if ( checkListSearch.length > 1 ) {
308
304
  findQuery.push( { $addFields: { cheklistlowercase: { $toLower: '$checkListName' } } } );
309
305
  query = { cheklistlowercase: { $in: checkListSearch } };
310
- complianceSearch = { $expr: { $in: [ { $toLower: '$checkListName' }, checkListSearch ] } };
311
306
  } else {
312
307
  const safeSearch = escapeRegex( req.body?.searchValue );
313
308
  query = { checkListName: { $regex: safeSearch, $options: 'i' } };
314
- complianceSearch = { checkListName: { $regex: safeSearch, $options: 'i' } };
315
309
  }
316
310
  findQuery.push( { $match: { $or: [ query ] } } );
317
311
  }
@@ -350,6 +344,16 @@ export const checklistPerformance = async ( req, res ) => {
350
344
  timeFlag: { $sum: '$timeFlag' },
351
345
  questionFlagCount: { $sum: '$questionFlag' },
352
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' },
353
357
  checkListType: { $last: '$checkListType' },
354
358
  redo: { $sum: { $cond: [ { $eq: [ '$redoStatus', true ] }, 1, 0 ] } },
355
359
  task: {
@@ -409,104 +413,79 @@ export const checklistPerformance = async ( req, res ) => {
409
413
  checkListType: 1,
410
414
  redo: 1,
411
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
+ },
412
446
  },
413
447
  } );
414
448
 
415
- // Compliance % is computed in JS (not in the pipeline). The grouped aggregation
416
- // and the compliance fetch are independent (the compliance query is scoped by the
417
- // same base filter + search, NOT by the aggregation's result), so we run them in
418
- // parallel. We then reduce the compliance docs per checklist in JS (NA-aware,
419
- // matching the visit-checklist PDF) and sort + paginate in JS — because the table
420
- // can sort by the compliance column, which no longer exists in the pipeline.
421
- const complianceFilter = {
422
- $and: [
423
- ...findAndQuery,
424
- { checklistStatus: 'submit' },
425
- ...( complianceSearch ? [ complianceSearch ] : [] ),
426
- ],
427
- };
428
-
429
- const [ getChecklistPerformanceData, complianceDocs ] = await Promise.all( [
430
- processedchecklistService.aggregate( findQuery ),
431
- // Only the compliance-relevant subfields, so we transfer far less than the
432
- // full questionAnswers blob.
433
- processedchecklistService.find( complianceFilter, {
434
- 'sourceCheckList_id': 1,
435
- 'questionAnswers.questions.compliance': 1,
436
- 'questionAnswers.questions.answerType': 1,
437
- 'questionAnswers.questions.answers.complianceScore': 1,
438
- 'questionAnswers.questions.answers.matchedCount': 1,
439
- 'questionAnswers.questions.answers.notMatchedCount': 1,
440
- 'questionAnswers.questions.userAnswer.complianceScore': 1,
441
- 'questionAnswers.questions.userAnswer.answer': 1,
442
- } ).lean(),
443
- ] );
444
-
445
- if ( !getChecklistPerformanceData.length ) {
446
- return res.sendError( 'no data found', 204 );
447
- }
448
-
449
- // Sum NA-aware { score, max } per checklist.
450
- const complianceByChecklist = {};
451
- for ( const doc of complianceDocs ) {
452
- const id = doc.sourceCheckList_id?.toString();
453
- if ( !id ) continue;
454
- if ( !complianceByChecklist[id] ) complianceByChecklist[id] = { score: 0, max: 0 };
455
-
456
- for ( const section of ( doc.questionAnswers || [] ) ) {
457
- for ( const q of ( section.questions || [] ) ) {
458
- if ( !q?.compliance ) continue;
459
-
460
- // Single-choice answered NA/N/A (case-insensitive) is excluded from both.
461
- if ( q.answerType === 'multiplechoicesingle' ) {
462
- const ans = ( q.userAnswer?.[0]?.answer || '' ).toString().trim().toLowerCase();
463
- if ( ans === 'na' || ans === 'n/a' ) continue;
464
- }
465
-
466
- const max = Math.max( 0, ...( q.answers || [] ).map( ( o ) => o?.complianceScore ?? Math.max( o?.matchedCount ?? 0, o?.notMatchedCount ?? 0 ) ) );
467
- const score = Math.max( 0, ...( q.userAnswer || [] ).map( ( o ) => o?.complianceScore ?? 0 ) );
468
-
469
- complianceByChecklist[id].score += score;
470
- complianceByChecklist[id].max += max;
471
- }
472
- }
449
+ if ( requestData.sortColumnName && requestData.sortColumnName != '' && requestData.sortBy && requestData.sortBy !='' ) {
450
+ findQuery.push( { $sort: { [requestData.sortColumnName]: requestData.sortBy } } );
451
+ } else {
452
+ findQuery.push( { $sort: { ['submittedChecklist']: -1 } } );
473
453
  }
474
454
 
475
- getChecklistPerformanceData.forEach( ( row ) => {
476
- const c = complianceByChecklist[row.sourceCheckList_id?.toString()] || { score: 0, max: 0 };
477
- row.questionCompliance = c.max > 0 ? Math.max( 0, Math.round( ( c.score / c.max ) * 100 ) ) : 0;
478
- } );
479
-
480
- // Sort in JS (column may be the JS-computed questionCompliance), then paginate.
481
- const sortColumn = ( requestData.sortColumnName && requestData.sortColumnName != '' ) ? requestData.sortColumnName : 'submittedChecklist';
482
- const sortDir = Number( requestData.sortBy ) === 1 ? 1 : -1;
483
- getChecklistPerformanceData.sort( ( a, b ) => {
484
- const av = a[sortColumn];
485
- const bv = b[sortColumn];
486
- if ( typeof av === 'string' || typeof bv === 'string' ) {
487
- return ( av ?? '' ).toString().localeCompare( ( bv ?? '' ).toString() ) * sortDir;
488
- }
489
- return ( ( av ?? 0 ) - ( bv ?? 0 ) ) * sortDir;
455
+ findQuery.push( {
456
+ $facet: {
457
+ data: [
458
+ { $skip: skip }, { $limit: limit },
459
+ ],
460
+ count: [
461
+ { $count: 'total' },
462
+ ],
463
+ },
490
464
  } );
491
-
492
- const pageRows = getChecklistPerformanceData.slice( skip, skip + limit );
465
+ let getChecklistPerformanceData = await processedchecklistService.aggregate( findQuery );
466
+ if ( !getChecklistPerformanceData[0].data.length ) {
467
+ return res.sendError( 'no data found', 204 );
468
+ }
493
469
 
494
470
  if ( requestData.export ) {
495
- const exportdata = pageRows.map( ( element ) => ( {
496
- 'Checklist Name': element.checkListName || '--',
497
- 'Coverage': element.coverage || '--',
498
- 'Scheduled': element.scheduleRepeatedType || '--',
499
- 'Assigned To': element.storeCount || '--',
500
- 'Submitted': element.submittedChecklist || '--',
501
- 'Flags': element.flaggedChecklist || '--',
502
- 'Tasks': element.task || '--',
503
- 'ReDo': element.redo || '--',
504
- } ) );
471
+ const exportdata = [];
472
+ getChecklistPerformanceData[0].data.forEach( ( element ) => {
473
+ exportdata.push( {
474
+ 'Checklist Name': element.checkListName || '--',
475
+ 'Coverage': element.coverage || '--',
476
+ 'Scheduled': element.scheduleRepeatedType || '--',
477
+ 'Assigned To': element.storeCount || '--',
478
+ 'Submitted': element.submittedChecklist || '--',
479
+ 'Flags': element.flaggedChecklist || '--',
480
+ 'Tasks': element.task || '--',
481
+ 'ReDo': element.redo || '--',
482
+ } );
483
+ } );
505
484
  return await download( exportdata, res );
506
485
  }
507
486
 
508
- result.totalCount = getChecklistPerformanceData.length;
509
- result.checklistPerformance = pageRows;
487
+ result.totalCount = getChecklistPerformanceData[0].count[0].total;
488
+ result.checklistPerformance = getChecklistPerformanceData[0].data;
510
489
  return res.sendSuccess( result );
511
490
  } catch ( error ) {
512
491
  console.log( 'error =>', error );