tango-app-api-trax 3.9.53 → 3.9.55
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.
|
|
3
|
+
"version": "3.9.55",
|
|
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.
|
|
33
|
+
"tango-api-schema": "^2.6.30",
|
|
34
34
|
"tango-app-api-middleware": "^3.5.2",
|
|
35
35
|
"url": "^0.11.4",
|
|
36
36
|
"winston": "^3.13.1",
|
|
@@ -2141,6 +2141,7 @@ export async function sopMobilechecklistMultiSectionFormatterv2( req, res, next
|
|
|
2141
2141
|
|
|
2142
2142
|
if ( requestData.submittype == 'submit' ) {
|
|
2143
2143
|
requestData.userComplianceCount = 0;
|
|
2144
|
+
requestData.complianceCount = 0;
|
|
2144
2145
|
for ( let section of sectionFormat ) {
|
|
2145
2146
|
for ( let question of section.questions ) {
|
|
2146
2147
|
let mustValidate = !question.linkType || ( question.linkType && question.linkquestionenabled );
|
|
@@ -2148,9 +2149,27 @@ export async function sopMobilechecklistMultiSectionFormatterv2( req, res, next
|
|
|
2148
2149
|
return res.sendError( 'Please Fill All Fields', 400 );
|
|
2149
2150
|
}
|
|
2150
2151
|
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).
|
|
2151
2154
|
const scores = ( question.userAnswer || [] ).map( ( ua ) => ua?.complianceScore ?? 0 );
|
|
2152
2155
|
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.
|
|
2163
|
+
if ( question.answerType === 'multiplechoicesingle' ) {
|
|
2164
|
+
const naAnswer = ( question.userAnswer?.[0]?.answer || '' ).toString().trim().toLowerCase();
|
|
2165
|
+
if ( naAnswer === 'na' || naAnswer === 'n/a' ) {
|
|
2166
|
+
score = 0;
|
|
2167
|
+
max = 0;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2153
2171
|
requestData.userComplianceCount += score;
|
|
2172
|
+
requestData.complianceCount += max;
|
|
2154
2173
|
}
|
|
2155
2174
|
}
|
|
2156
2175
|
}
|
|
@@ -2796,6 +2815,8 @@ export async function submitChecklist( req, res ) {
|
|
|
2796
2815
|
updateData.updatedAt = dayjs.utc( currentDateTime.format( 'hh:mm:ss A, DD MMM YYYY' ), 'hh:mm:ss A, DD MMM YYYY' ).format();
|
|
2797
2816
|
updateData.submitMobileTime = requestData?.currentTime;
|
|
2798
2817
|
updateData.userComplianceCount = requestData?.userComplianceCount;
|
|
2818
|
+
// NA-aware max (denominator) recomputed at submit, alongside the score above.
|
|
2819
|
+
updateData.complianceCount = requestData?.complianceCount;
|
|
2799
2820
|
updateData.userSignature = requestData?.userSignature;
|
|
2800
2821
|
if ( requestData?.userImage ) {
|
|
2801
2822
|
let splitImgUrl = requestData?.userImage.split( '/' );
|
|
@@ -297,15 +297,21 @@ 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;
|
|
300
304
|
if ( requestData.searchValue && requestData.searchValue != '' ) {
|
|
301
305
|
let checkListSearch = requestData.searchValue.split( ',' ).map( ( item ) => item.trim().toLowerCase() );
|
|
302
306
|
let query;
|
|
303
307
|
if ( checkListSearch.length > 1 ) {
|
|
304
308
|
findQuery.push( { $addFields: { cheklistlowercase: { $toLower: '$checkListName' } } } );
|
|
305
309
|
query = { cheklistlowercase: { $in: checkListSearch } };
|
|
310
|
+
complianceSearch = { $expr: { $in: [ { $toLower: '$checkListName' }, checkListSearch ] } };
|
|
306
311
|
} else {
|
|
307
312
|
const safeSearch = escapeRegex( req.body?.searchValue );
|
|
308
313
|
query = { checkListName: { $regex: safeSearch, $options: 'i' } };
|
|
314
|
+
complianceSearch = { checkListName: { $regex: safeSearch, $options: 'i' } };
|
|
309
315
|
}
|
|
310
316
|
findQuery.push( { $match: { $or: [ query ] } } );
|
|
311
317
|
}
|
|
@@ -344,16 +350,6 @@ export const checklistPerformance = async ( req, res ) => {
|
|
|
344
350
|
timeFlag: { $sum: '$timeFlag' },
|
|
345
351
|
questionFlagCount: { $sum: '$questionFlag' },
|
|
346
352
|
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' },
|
|
357
353
|
checkListType: { $last: '$checkListType' },
|
|
358
354
|
redo: { $sum: { $cond: [ { $eq: [ '$redoStatus', true ] }, 1, 0 ] } },
|
|
359
355
|
task: {
|
|
@@ -413,84 +409,104 @@ export const checklistPerformance = async ( req, res ) => {
|
|
|
413
409
|
checkListType: 1,
|
|
414
410
|
redo: 1,
|
|
415
411
|
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
|
-
$max: [
|
|
432
|
-
0,
|
|
433
|
-
{
|
|
434
|
-
$round: [
|
|
435
|
-
{
|
|
436
|
-
$multiply: [
|
|
437
|
-
{ $divide: [ '$userComplianceCountTotal', '$$divisor' ] },
|
|
438
|
-
100,
|
|
439
|
-
],
|
|
440
|
-
},
|
|
441
|
-
0,
|
|
442
|
-
],
|
|
443
|
-
},
|
|
444
|
-
],
|
|
445
|
-
},
|
|
446
|
-
0,
|
|
447
|
-
],
|
|
448
|
-
},
|
|
449
|
-
},
|
|
450
|
-
},
|
|
451
412
|
},
|
|
452
413
|
} );
|
|
453
414
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
+
};
|
|
459
428
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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 ) {
|
|
472
446
|
return res.sendError( 'no data found', 204 );
|
|
473
447
|
}
|
|
474
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
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
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;
|
|
490
|
+
} );
|
|
491
|
+
|
|
492
|
+
const pageRows = getChecklistPerformanceData.slice( skip, skip + limit );
|
|
493
|
+
|
|
475
494
|
if ( requestData.export ) {
|
|
476
|
-
const exportdata =
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
'ReDo': element.redo || '--',
|
|
487
|
-
} );
|
|
488
|
-
} );
|
|
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
|
+
} ) );
|
|
489
505
|
return await download( exportdata, res );
|
|
490
506
|
}
|
|
491
507
|
|
|
492
|
-
result.totalCount = getChecklistPerformanceData
|
|
493
|
-
result.checklistPerformance =
|
|
508
|
+
result.totalCount = getChecklistPerformanceData.length;
|
|
509
|
+
result.checklistPerformance = pageRows;
|
|
494
510
|
return res.sendSuccess( result );
|
|
495
511
|
} catch ( error ) {
|
|
496
512
|
console.log( 'error =>', error );
|