tango-app-api-trax 3.8.24 → 3.8.26
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 +2 -2
- package/src/controllers/internalTrax.controller.js +252 -39
- package/src/controllers/mobileTrax.controller.js +18 -8
- package/src/hbs/recurringFlag.hbs +18 -16
- package/src/hbs/template.hbs +6 -3
- package/src/hbs/visit-checklist.hbs +30 -24
- package/src/routes/internalTraxApi.router.js +1 -0
- package/src/services/recurringFlagTracker.service.js +33 -33
- package/src/utils/visitChecklistPdf.utils.js +5 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tango-app-api-trax",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.26",
|
|
4
4
|
"description": "Trax",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"nodemon": "^3.1.4",
|
|
30
30
|
"path": "^0.12.7",
|
|
31
31
|
"puppeteer": "^24.39.1",
|
|
32
|
-
"tango-api-schema": "^2.5.
|
|
32
|
+
"tango-api-schema": "^2.5.88",
|
|
33
33
|
"tango-app-api-middleware": "^3.5.2",
|
|
34
34
|
"url": "^0.11.4",
|
|
35
35
|
"winston": "^3.13.1",
|
|
@@ -2697,6 +2697,113 @@ export async function countUpdateRunAI( req, res ) {
|
|
|
2697
2697
|
}
|
|
2698
2698
|
}
|
|
2699
2699
|
|
|
2700
|
+
// Called by the runAI processing team once per (subject, checklist, section, qno, date) when their cron
|
|
2701
|
+
// detects a runAI flag. Idempotent per day via lastRunAIFlaggedDate — replaying the same date is a no-op.
|
|
2702
|
+
export async function incrementRunAIRecurring( req, res ) {
|
|
2703
|
+
try {
|
|
2704
|
+
let body = { ...( req.body || {} ) };
|
|
2705
|
+
if ( !body.section_id ) return res.sendError( 'section_id is required', 400 );
|
|
2706
|
+
if ( body.qno === undefined || body.qno === null || body.qno === '' ) return res.sendError( 'qno is required', 400 );
|
|
2707
|
+
if ( !body.id ) return res.sendError( 'Id is required', 400 );
|
|
2708
|
+
if ( !body.qname ) return res.sendError( 'Question name is required', 400 );
|
|
2709
|
+
if ( !body.sectionName ) return res.sendError( 'Section name is required', 400 );
|
|
2710
|
+
|
|
2711
|
+
const checklistDetails = await processedchecklist.findOne( { _id: body.id } );
|
|
2712
|
+
if ( !checklistDetails ) {
|
|
2713
|
+
return res.sendError( 'Checklist not found', 204 );
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
// sectionName/qname live inside questionAnswers; surface them so the first-insert tracker doc is complete.
|
|
2717
|
+
// const section = ( checklistDetails.questionAnswers || [] ).find( ( s ) => String( s?.section_id ) === String( body.section_id ) );
|
|
2718
|
+
// const question = section?.questions?.find( ( q ) => String( q?.qno ) === String( body.qno ) );
|
|
2719
|
+
|
|
2720
|
+
body = {
|
|
2721
|
+
...body,
|
|
2722
|
+
date: checklistDetails.date_string,
|
|
2723
|
+
sourceCheckList_id: checklistDetails.sourceCheckList_id,
|
|
2724
|
+
client_id: checklistDetails.client_id,
|
|
2725
|
+
coverage: checklistDetails.coverage,
|
|
2726
|
+
store_id: checklistDetails.store_id,
|
|
2727
|
+
storeName: checklistDetails.storeName,
|
|
2728
|
+
user_id: checklistDetails.userId,
|
|
2729
|
+
userEmail: checklistDetails.userEmail,
|
|
2730
|
+
userName: checklistDetails.userName,
|
|
2731
|
+
checkListName: checklistDetails.checkListName,
|
|
2732
|
+
// sectionName: section?.sectionName || '',
|
|
2733
|
+
// qname: question?.qname || '',
|
|
2734
|
+
lastSubmittedBy: checklistDetails.userName || checklistDetails.userEmail || '--',
|
|
2735
|
+
lastSubmissionDate: checklistDetails.submitTime_string,
|
|
2736
|
+
};
|
|
2737
|
+
|
|
2738
|
+
// Skip checklists that don't have recurring flag configured — same gate that recurringFlagAlert uses
|
|
2739
|
+
// when picking which checklists to email for. Avoids creating tracker docs that would never be acted on.
|
|
2740
|
+
const checklistConfig = await CLconfig.findOne( { _id: body.sourceCheckList_id }, { recurringFlag: 1, publish: 1 } );
|
|
2741
|
+
if ( !checklistConfig ) {
|
|
2742
|
+
return res.sendError( 'Checklist not found', 404 );
|
|
2743
|
+
}
|
|
2744
|
+
const hasRecurring = ( Array.isArray( checklistConfig?.recurringFlag?.users ) && checklistConfig.recurringFlag.users.length > 0 ) ||
|
|
2745
|
+
( Array.isArray( checklistConfig?.recurringFlag?.notifyType ) && checklistConfig.recurringFlag.notifyType.length > 0 );
|
|
2746
|
+
if ( !hasRecurring ) {
|
|
2747
|
+
return res.sendSuccess( { message: 'Recurring flag not configured for this checklist', noop: true } );
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
const isUserBased = ( body.coverage === 'user' ) || ( !body.store_id && ( body.user_id || body.userEmail ) );
|
|
2751
|
+
const storeId = isUserBased ? '' : ( body.store_id || '' );
|
|
2752
|
+
const userId = isUserBased ? ( body.user_id ? String( body.user_id ) : ( body.userEmail || '' ) ) : '';
|
|
2753
|
+
if ( !isUserBased && !storeId ) return res.sendError( 'store_id is required for store-based', 400 );
|
|
2754
|
+
if ( isUserBased && !userId ) return res.sendError( 'user_id or userEmail is required for user-based', 400 );
|
|
2755
|
+
|
|
2756
|
+
const date = body.date;
|
|
2757
|
+
const filter = {
|
|
2758
|
+
client_id: body.client_id,
|
|
2759
|
+
sourceCheckList_id: body.sourceCheckList_id,
|
|
2760
|
+
section_id: body.section_id,
|
|
2761
|
+
qno: String( body.qno ),
|
|
2762
|
+
...( isUserBased ? { user_id: userId } : { store_id: storeId } ),
|
|
2763
|
+
};
|
|
2764
|
+
|
|
2765
|
+
const existing = await recurringFlagTracker.findOne( filter, { lastRunAIFlaggedDate: 1 } );
|
|
2766
|
+
if ( existing && existing.lastRunAIFlaggedDate === date ) {
|
|
2767
|
+
return res.sendSuccess( { message: 'Already counted for this date', noop: true } );
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
const setOnInsert = {
|
|
2771
|
+
coverage: isUserBased ? 'user' : 'store',
|
|
2772
|
+
checkListName: body.checkListName || '',
|
|
2773
|
+
sectionName: body.sectionName || '',
|
|
2774
|
+
qname: body.qname || '',
|
|
2775
|
+
storeName: isUserBased ? '' : ( body.storeName || '' ),
|
|
2776
|
+
userName: body.userName || '',
|
|
2777
|
+
userEmail: body.userEmail || '',
|
|
2778
|
+
};
|
|
2779
|
+
|
|
2780
|
+
console.log( setOnInsert );
|
|
2781
|
+
|
|
2782
|
+
await recurringFlagTracker.bulkWrite( [
|
|
2783
|
+
{
|
|
2784
|
+
updateOne: {
|
|
2785
|
+
filter,
|
|
2786
|
+
update: {
|
|
2787
|
+
$setOnInsert: setOnInsert,
|
|
2788
|
+
$set: {
|
|
2789
|
+
lastRunAIFlaggedDate: date,
|
|
2790
|
+
...( body.lastSubmittedBy ? { lastSubmittedBy: body.lastSubmittedBy } : {} ),
|
|
2791
|
+
...( body.lastSubmissionDate ? { lastSubmissionDate: body.lastSubmissionDate } : {} ),
|
|
2792
|
+
},
|
|
2793
|
+
$inc: { runAICount: 1 },
|
|
2794
|
+
},
|
|
2795
|
+
upsert: true,
|
|
2796
|
+
},
|
|
2797
|
+
},
|
|
2798
|
+
] );
|
|
2799
|
+
|
|
2800
|
+
return res.sendSuccess( { message: 'runAI recurring count updated' } );
|
|
2801
|
+
} catch ( e ) {
|
|
2802
|
+
logger.error( { functionName: 'incrementRunAIRecurring', error: e } );
|
|
2803
|
+
return res.sendError( e, 500 );
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2700
2807
|
export async function getRunAIQuestions( req, res ) {
|
|
2701
2808
|
try {
|
|
2702
2809
|
let requestData = req.body;
|
|
@@ -3747,7 +3854,6 @@ export async function checklistAutoMailList( req, res ) {
|
|
|
3747
3854
|
}
|
|
3748
3855
|
}
|
|
3749
3856
|
|
|
3750
|
-
|
|
3751
3857
|
export const downloadInsertPdfOld = async ( req, res ) => {
|
|
3752
3858
|
try {
|
|
3753
3859
|
setImmediate( async () => {
|
|
@@ -4225,17 +4331,18 @@ export async function getEyetestStream( req, res ) {
|
|
|
4225
4331
|
}
|
|
4226
4332
|
}
|
|
4227
4333
|
|
|
4228
|
-
function buildRecurringFlagExcel( rows ) {
|
|
4334
|
+
function buildRecurringFlagExcel( rows, subjectLabel = 'Store' ) {
|
|
4229
4335
|
const workbook = new ExcelJS.Workbook();
|
|
4230
4336
|
const sheet = workbook.addWorksheet( 'Recurring Flags' );
|
|
4231
4337
|
sheet.columns = [
|
|
4232
|
-
{ header:
|
|
4338
|
+
{ header: `${subjectLabel} Name`, key: 'storeName', width: 25 },
|
|
4233
4339
|
{ header: 'Checklist Name', key: 'checklistName', width: 30 },
|
|
4234
4340
|
{ header: 'Section', key: 'sectionName', width: 25 },
|
|
4235
4341
|
{ header: 'Question', key: 'questionName', width: 40 },
|
|
4236
4342
|
{ header: 'Last Submitted By', key: 'lastSubmittedBy', width: 25 },
|
|
4237
4343
|
{ header: 'Last Submission Date', key: 'lastSubmissionDate', width: 22 },
|
|
4238
4344
|
{ header: 'Recurring Days', key: 'days', width: 16 },
|
|
4345
|
+
{ header: 'Run AI Flags', key: 'runAICount', width: 14 },
|
|
4239
4346
|
];
|
|
4240
4347
|
sheet.getRow( 1 ).font = { bold: true };
|
|
4241
4348
|
rows.forEach( ( r ) => sheet.addRow( r ) );
|
|
@@ -4253,7 +4360,6 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4253
4360
|
],
|
|
4254
4361
|
},
|
|
4255
4362
|
}, { _id: 1, checkListName: 1, recurringFlag: 1, notifyFlags: 1, approver: 1, client_id: 1 } );
|
|
4256
|
-
console.log( JSON.stringify( checklistDetails ) );
|
|
4257
4363
|
if ( !checklistDetails.length ) {
|
|
4258
4364
|
return res.sendSuccess( 'No checklists configured for recurring flag' );
|
|
4259
4365
|
}
|
|
@@ -4266,7 +4372,6 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4266
4372
|
const threshold = cl?.recurringFlag?.threshold || 3;
|
|
4267
4373
|
const notifyType = cl?.recurringFlag?.notifyType || [];
|
|
4268
4374
|
const users = cl?.recurringFlag?.users || [];
|
|
4269
|
-
|
|
4270
4375
|
let recipients = [];
|
|
4271
4376
|
if ( notifyType.includes( 'sameAsNotify' ) ) {
|
|
4272
4377
|
const nfType = cl?.notifyFlags?.notifyType || [];
|
|
@@ -4277,39 +4382,69 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4277
4382
|
recipients = [ ...recipients, ...nfUsers.map( ( u ) => u?.value ).filter( Boolean ) ];
|
|
4278
4383
|
}
|
|
4279
4384
|
if ( notifyType.includes( 'approver' ) && Array.isArray( cl.approver ) ) {
|
|
4280
|
-
recipients = cl.approver.map( ( a ) => a?.value ).filter( Boolean );
|
|
4385
|
+
recipients = [ ...recipients, ...cl.approver.map( ( a ) => a?.value ).filter( Boolean ) ];
|
|
4281
4386
|
}
|
|
4282
4387
|
recipients = [ ...recipients, ...users.map( ( u ) => u?.value ).filter( Boolean ) ];
|
|
4283
4388
|
recipients = [ ...new Set( recipients ) ];
|
|
4284
4389
|
|
|
4285
4390
|
if ( !recipients.length ) return;
|
|
4286
4391
|
|
|
4287
|
-
// Read tracker rows
|
|
4288
|
-
//
|
|
4392
|
+
// Read tracker rows where EITHER the sop streak or the runAI count has hit threshold and the
|
|
4393
|
+
// current trigger hasn't been emailed yet (lastEmailDate vs the relevant flagged date).
|
|
4289
4394
|
const trackerRows = await recurringFlagTracker.find( {
|
|
4290
4395
|
sourceCheckList_id: cl._id,
|
|
4291
|
-
|
|
4292
|
-
|
|
4396
|
+
$or: [
|
|
4397
|
+
{
|
|
4398
|
+
consecutiveCount: { $gte: threshold },
|
|
4399
|
+
$expr: { $ne: [ { $ifNull: [ '$lastEmailDate', '' ] }, { $ifNull: [ '$lastFlaggedDate', '' ] } ] },
|
|
4400
|
+
},
|
|
4401
|
+
{
|
|
4402
|
+
runAICount: { $gte: threshold },
|
|
4403
|
+
$expr: { $ne: [ { $ifNull: [ '$lastEmailDate', '' ] }, { $ifNull: [ '$lastRunAIFlaggedDate', '' ] } ] },
|
|
4404
|
+
},
|
|
4405
|
+
],
|
|
4293
4406
|
} );
|
|
4294
4407
|
|
|
4295
4408
|
for ( const t of trackerRows ) {
|
|
4409
|
+
const isUserBased = t.coverage === 'user' || ( !t.store_id && ( t.user_id || t.userEmail ) );
|
|
4410
|
+
// For user-based checklists, group/identify by userEmail; for store-based, by store_id.
|
|
4411
|
+
const subjectId = isUserBased ? ( t.userEmail || t.user_id || '' ) : ( t.store_id || '' );
|
|
4412
|
+
const subjectName = isUserBased ? ( t.userName || t.userEmail || '--' ) : ( t.storeName || '--' );
|
|
4413
|
+
// Determine which streak crossed threshold for this row — drives reset granularity below.
|
|
4414
|
+
const sopFired = ( t.consecutiveCount || 0 ) >= threshold && t.lastEmailDate !== t.lastFlaggedDate;
|
|
4415
|
+
const runAIFired = ( t.runAICount || 0 ) >= threshold && t.lastEmailDate !== t.lastRunAIFlaggedDate;
|
|
4296
4416
|
for ( const recipient of recipients ) {
|
|
4297
4417
|
triggers.push( {
|
|
4298
4418
|
recipient,
|
|
4299
4419
|
clientId: t.client_id,
|
|
4300
|
-
|
|
4301
|
-
|
|
4420
|
+
coverage: isUserBased ? 'user' : 'store',
|
|
4421
|
+
subjectId,
|
|
4422
|
+
subjectName,
|
|
4423
|
+
storeId: t.store_id || '',
|
|
4424
|
+
storeName: t.storeName || '',
|
|
4425
|
+
userId: t.user_id || '',
|
|
4426
|
+
userName: t.userName || '',
|
|
4427
|
+
userEmail: t.userEmail || '',
|
|
4302
4428
|
checklistId: cl._id.toString(),
|
|
4303
4429
|
checklistName: cl.checkListName?.trim() || t.checkListName || '',
|
|
4304
4430
|
sectionName: t.sectionName,
|
|
4305
4431
|
qno: t.qno,
|
|
4306
4432
|
qname: t.qname,
|
|
4307
4433
|
days: t.consecutiveCount,
|
|
4434
|
+
runAICount: t.runAICount || 0,
|
|
4435
|
+
sopFired,
|
|
4436
|
+
runAIFired,
|
|
4308
4437
|
lastSubmittedBy: t.lastSubmittedBy || '--',
|
|
4309
4438
|
lastSubmissionDate: t.lastSubmissionDate || t.lastFlaggedDate || '',
|
|
4310
4439
|
} );
|
|
4311
4440
|
}
|
|
4312
|
-
trackerIdsToReset.push( {
|
|
4441
|
+
trackerIdsToReset.push( {
|
|
4442
|
+
_id: t._id,
|
|
4443
|
+
lastFlaggedDate: t.lastFlaggedDate,
|
|
4444
|
+
lastRunAIFlaggedDate: t.lastRunAIFlaggedDate,
|
|
4445
|
+
sopFired,
|
|
4446
|
+
runAIFired,
|
|
4447
|
+
} );
|
|
4313
4448
|
}
|
|
4314
4449
|
} ) );
|
|
4315
4450
|
|
|
@@ -4317,6 +4452,7 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4317
4452
|
return res.sendSuccess( 'No recurring flags reached threshold' );
|
|
4318
4453
|
}
|
|
4319
4454
|
|
|
4455
|
+
|
|
4320
4456
|
// Group triggers by recipient.
|
|
4321
4457
|
const byRecipient = new Map();
|
|
4322
4458
|
for ( const t of triggers ) {
|
|
@@ -4331,21 +4467,77 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4331
4467
|
const sentSummary = [];
|
|
4332
4468
|
|
|
4333
4469
|
await Promise.all( [ ...byRecipient.entries() ].map( async ( [ recipient, items ] ) => {
|
|
4334
|
-
const
|
|
4470
|
+
const subjects = new Set( items.map( ( i ) => i.subjectId ) );
|
|
4335
4471
|
const checklists = new Set( items.map( ( i ) => i.checklistId ) );
|
|
4336
|
-
const isMultiStore =
|
|
4472
|
+
const isMultiStore = subjects.size > 1; // "isMultiStore" name retained for template back-compat
|
|
4337
4473
|
const isMultiChecklist = !isMultiStore && checklists.size > 1;
|
|
4338
|
-
//
|
|
4474
|
+
// Sub-mode of multi-store when the recipient's flagged subjects all share a single checklist.
|
|
4475
|
+
// Drives a tighter email layout (no Checklist column, no "Total Checklists" line, checklist name in intro).
|
|
4476
|
+
const isMultiStoreSingleChecklist = isMultiStore && checklists.size === 1;
|
|
4477
|
+
// Threshold for the message line — when grouping spans multiple checklists/subjects, take min threshold seen.
|
|
4339
4478
|
const thresholdShown = items.reduce( ( acc, it ) => Math.min( acc, it.days ), items[0].days );
|
|
4340
4479
|
|
|
4341
|
-
|
|
4342
|
-
|
|
4480
|
+
// If every trigger for this recipient is user-based, label as User. Mixed sets fall back to Store.
|
|
4481
|
+
const coverages = new Set( items.map( ( i ) => i.coverage ) );
|
|
4482
|
+
const isAllUser = coverages.size === 1 && coverages.has( 'user' );
|
|
4483
|
+
const subjectLabel = isAllUser ? 'User' : 'Store';
|
|
4484
|
+
const subjectLabelPlural = isAllUser ? 'Users' : 'Stores';
|
|
4485
|
+
const subjectLabelLower = isAllUser ? 'user' : 'store';
|
|
4486
|
+
const subjectLabelPluralLower = isAllUser ? 'users' : 'stores';
|
|
4487
|
+
|
|
4488
|
+
// Aggregate triggers per (subject, checklist) — each table row counts how many distinct questions
|
|
4489
|
+
// hit the recurring threshold for that pair. The streak length on each question is no longer surfaced
|
|
4490
|
+
// in the email body; it remains in the per-question Excel breakdown below.
|
|
4491
|
+
const parseSubmissionDate = ( s ) => {
|
|
4492
|
+
if ( !s ) return 0;
|
|
4493
|
+
const d = dayjs( s, 'hh:mm A, DD MMM YYYY' );
|
|
4494
|
+
return d.isValid() ? d.valueOf() : 0;
|
|
4495
|
+
};
|
|
4496
|
+
const groupMap = new Map();
|
|
4497
|
+
for ( const i of items ) {
|
|
4498
|
+
const k = `${i.subjectId}::${i.checklistId}`;
|
|
4499
|
+
if ( !groupMap.has( k ) ) {
|
|
4500
|
+
groupMap.set( k, {
|
|
4501
|
+
subjectId: i.subjectId,
|
|
4502
|
+
subjectName: i.subjectName,
|
|
4503
|
+
checklistId: i.checklistId,
|
|
4504
|
+
checklistName: i.checklistName,
|
|
4505
|
+
questionCount: 0,
|
|
4506
|
+
runAICount: 0,
|
|
4507
|
+
lastSubmittedBy: i.lastSubmittedBy,
|
|
4508
|
+
lastSubmissionDate: i.lastSubmissionDate,
|
|
4509
|
+
} );
|
|
4510
|
+
}
|
|
4511
|
+
const g = groupMap.get( k );
|
|
4512
|
+
g.questionCount += 1;
|
|
4513
|
+
g.runAICount += ( i.runAICount || 0 );
|
|
4514
|
+
if ( parseSubmissionDate( i.lastSubmissionDate ) > parseSubmissionDate( g.lastSubmissionDate ) ) {
|
|
4515
|
+
g.lastSubmissionDate = i.lastSubmissionDate;
|
|
4516
|
+
g.lastSubmittedBy = i.lastSubmittedBy;
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
const rows = [ ...groupMap.values() ].map( ( g ) => ( {
|
|
4520
|
+
subjectName: g.subjectName,
|
|
4521
|
+
storeName: g.subjectName, // legacy field name still consumed by template fallbacks
|
|
4522
|
+
checklistName: g.checklistName,
|
|
4523
|
+
lastSubmittedBy: g.lastSubmittedBy,
|
|
4524
|
+
lastSubmissionDate: g.lastSubmissionDate,
|
|
4525
|
+
days: g.questionCount, // legacy alias kept for back-compat with older template builds
|
|
4526
|
+
flagCount: g.questionCount,
|
|
4527
|
+
runAICount: g.runAICount,
|
|
4528
|
+
totalFlags: g.questionCount + g.runAICount,
|
|
4529
|
+
} ) );
|
|
4530
|
+
|
|
4531
|
+
// Excel attachment keeps per-question detail (one row per flagged question).
|
|
4532
|
+
const excelRows = items.map( ( i ) => ( {
|
|
4533
|
+
storeName: i.subjectName,
|
|
4343
4534
|
checklistName: i.checklistName,
|
|
4344
4535
|
sectionName: i.sectionName,
|
|
4345
4536
|
questionName: i.qname,
|
|
4346
4537
|
lastSubmittedBy: i.lastSubmittedBy,
|
|
4347
4538
|
lastSubmissionDate: i.lastSubmissionDate,
|
|
4348
4539
|
days: i.days,
|
|
4540
|
+
runAICount: i.runAICount || 0,
|
|
4349
4541
|
} ) );
|
|
4350
4542
|
|
|
4351
4543
|
const ATTACHMENT_THRESHOLD = 10;
|
|
@@ -4356,31 +4548,42 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4356
4548
|
threshold: thresholdShown,
|
|
4357
4549
|
isMultiStore,
|
|
4358
4550
|
isMultiChecklist,
|
|
4551
|
+
isMultiStoreSingleChecklist,
|
|
4359
4552
|
showTable: isMultiStore || isMultiChecklist,
|
|
4360
4553
|
hasAttachment,
|
|
4361
4554
|
domain: flagDomain,
|
|
4362
4555
|
rows: displayRows,
|
|
4556
|
+
subjectLabel,
|
|
4557
|
+
subjectLabelPlural,
|
|
4558
|
+
subjectLabelLower,
|
|
4559
|
+
subjectLabelPluralLower,
|
|
4363
4560
|
};
|
|
4364
4561
|
|
|
4365
4562
|
if ( isMultiStore ) {
|
|
4366
4563
|
data.highlights = {
|
|
4367
|
-
|
|
4564
|
+
totalSubjects: subjects.size,
|
|
4565
|
+
totalStores: subjects.size, // legacy alias
|
|
4368
4566
|
totalChecklists: checklists.size,
|
|
4369
4567
|
totalFlags: items.length,
|
|
4370
4568
|
};
|
|
4569
|
+
if ( isMultiStoreSingleChecklist ) {
|
|
4570
|
+
// Show the single checklist name in the intro line for this sub-mode.
|
|
4571
|
+
data.checklistName = items[0].checklistName;
|
|
4572
|
+
}
|
|
4371
4573
|
} else if ( isMultiChecklist ) {
|
|
4372
|
-
data.
|
|
4574
|
+
data.subjectName = items[0].subjectName;
|
|
4575
|
+
data.storeName = items[0].subjectName;
|
|
4373
4576
|
} else {
|
|
4374
|
-
|
|
4375
|
-
|
|
4577
|
+
// Single mode: one (subject, checklist) — totalFlags = sop question flags + runAI flags.
|
|
4578
|
+
const single = rows[0];
|
|
4579
|
+
data.subjectName = single.subjectName;
|
|
4580
|
+
data.storeName = single.subjectName;
|
|
4376
4581
|
data.checklistName = single.checklistName;
|
|
4377
|
-
data.questionName = single.qname;
|
|
4378
4582
|
data.lastSubmittedBy = single.lastSubmittedBy;
|
|
4379
4583
|
data.lastSubmissionDate = single.lastSubmissionDate;
|
|
4380
|
-
data.
|
|
4381
|
-
data.
|
|
4382
|
-
data.
|
|
4383
|
-
data.flagCountPlural = false;
|
|
4584
|
+
data.flagCount = single.flagCount;
|
|
4585
|
+
data.runAICount = single.runAICount;
|
|
4586
|
+
data.totalFlags = single.totalFlags;
|
|
4384
4587
|
}
|
|
4385
4588
|
|
|
4386
4589
|
const html = compiled( { data } );
|
|
@@ -4395,7 +4598,7 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4395
4598
|
|
|
4396
4599
|
if ( hasAttachment ) {
|
|
4397
4600
|
try {
|
|
4398
|
-
const buf = await buildRecurringFlagExcel(
|
|
4601
|
+
const buf = await buildRecurringFlagExcel( excelRows, subjectLabel );
|
|
4399
4602
|
params.attachment = {
|
|
4400
4603
|
filename: 'Recurring-Flags-Summary.xlsx',
|
|
4401
4604
|
content: Buffer.from( buf ),
|
|
@@ -4405,22 +4608,31 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4405
4608
|
logger.error( { functionName: 'recurringFlagAlert.buildExcel', error: e } );
|
|
4406
4609
|
}
|
|
4407
4610
|
}
|
|
4408
|
-
|
|
4409
4611
|
sendEmailWithSES( params.toEmail, params.mailSubject, params.htmlBody, params.attachment, params.sourceEmail );
|
|
4410
4612
|
sentSummary.push( { recipient, count: items.length, mode: isMultiStore ? 'multi-store' : ( isMultiChecklist ? 'multi-checklist' : 'single' ) } );
|
|
4411
4613
|
} ) );
|
|
4412
4614
|
|
|
4413
|
-
// Reset
|
|
4615
|
+
// Reset only the streak(s) that crossed threshold. Stamp lastEmailDate to the most recent triggering
|
|
4616
|
+
// flag date so dedup remains correct. Append that date to emailHistory (rolling last 60).
|
|
4414
4617
|
if ( trackerIdsToReset.length ) {
|
|
4415
|
-
const resetOps = trackerIdsToReset.map( (
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4618
|
+
const resetOps = trackerIdsToReset.map( ( r ) => {
|
|
4619
|
+
const set = {};
|
|
4620
|
+
if ( r.sopFired ) set.consecutiveCount = 0;
|
|
4621
|
+
if ( r.runAIFired ) set.runAICount = 0;
|
|
4622
|
+
const stampDate = r.runAIFired ?
|
|
4623
|
+
( r.lastRunAIFlaggedDate || r.lastFlaggedDate || '' ) :
|
|
4624
|
+
( r.lastFlaggedDate || r.lastRunAIFlaggedDate || '' );
|
|
4625
|
+
set.lastEmailDate = stampDate;
|
|
4626
|
+
return {
|
|
4627
|
+
updateOne: {
|
|
4628
|
+
filter: { _id: r._id },
|
|
4629
|
+
update: {
|
|
4630
|
+
$set: set,
|
|
4631
|
+
$push: { emailHistory: { $each: [ stampDate ], $slice: -60 } },
|
|
4632
|
+
},
|
|
4421
4633
|
},
|
|
4422
|
-
}
|
|
4423
|
-
} )
|
|
4634
|
+
};
|
|
4635
|
+
} );
|
|
4424
4636
|
await recurringFlagTracker.bulkWrite( resetOps );
|
|
4425
4637
|
}
|
|
4426
4638
|
|
|
@@ -4565,7 +4777,7 @@ export async function weeklyWrapAlert( req, res ) {
|
|
|
4565
4777
|
const endDateLabel = dayjs( range.weekEnd ).format( 'DD/MM/YY' );
|
|
4566
4778
|
|
|
4567
4779
|
// TEMP: static recipient for dev. Replace with client-config lookup later.
|
|
4568
|
-
const STATIC_RECIPIENT = '
|
|
4780
|
+
const STATIC_RECIPIENT = 'gopisjkg@gmail.com';
|
|
4569
4781
|
|
|
4570
4782
|
// Aggregate flag data for both weeks across all stores in one shot.
|
|
4571
4783
|
const [ flagsThis, flagsPrev, recurThis, recurPrev ] = await Promise.all( [
|
|
@@ -4773,3 +4985,4 @@ export async function updateStoreLatLong( req, res ) {
|
|
|
4773
4985
|
return res.sendError( e, 500 );
|
|
4774
4986
|
}
|
|
4775
4987
|
}
|
|
4988
|
+
|
|
@@ -1438,7 +1438,7 @@ export async function sopMobilechecklistMultiSectionFormatterv1( req, res, next
|
|
|
1438
1438
|
let des = [];
|
|
1439
1439
|
if ( requestSection[i].answer != 'null' && requestSection[i].answer != '' && requestSection[i].answer != null ) {
|
|
1440
1440
|
let validationAnswer = '';
|
|
1441
|
-
if ( requestSection[i].answer.split( '?' ).length > 1 ) {
|
|
1441
|
+
if ( requestSection[i].answer.split( '?' ).length > 1 || requestSection[i].answer.includes( 'https://' ) ) {
|
|
1442
1442
|
validationAnswer = decodeURIComponent( requestSection[i].answer.split( '?' )[0] );
|
|
1443
1443
|
}
|
|
1444
1444
|
if ( validationAnswer.length ) {
|
|
@@ -1932,7 +1932,7 @@ export async function sopMobileTaskMultiSectionFormatter( req, res, next ) {
|
|
|
1932
1932
|
let des = [];
|
|
1933
1933
|
if ( requestSection[i].answer != 'null' && requestSection[i].answer != '' && requestSection[i].answer != null ) {
|
|
1934
1934
|
let validationAnswer = '';
|
|
1935
|
-
if ( requestSection[i].answer.split( '?' ).length > 1 ) {
|
|
1935
|
+
if ( requestSection[i].answer.split( '?' ).length > 1 || requestSection[i].answer.includes( 'https://' ) ) {
|
|
1936
1936
|
validationAnswer = decodeURIComponent( requestSection[i].answer.split( '?' )[0] );
|
|
1937
1937
|
}
|
|
1938
1938
|
if ( validationAnswer.length ) {
|
|
@@ -2031,7 +2031,13 @@ function QuestionFlag( req, res ) {
|
|
|
2031
2031
|
|
|
2032
2032
|
async function updateRecurringFlagTracker( processedChecklist, questionAnswers, currentDateTime ) {
|
|
2033
2033
|
try {
|
|
2034
|
-
if ( !processedChecklist?.
|
|
2034
|
+
if ( !processedChecklist?.sourceCheckList_id ) return;
|
|
2035
|
+
|
|
2036
|
+
const isUserBased = processedChecklist?.coverage === 'user';
|
|
2037
|
+
const storeId = isUserBased ? '' : ( processedChecklist?.store_id || '' );
|
|
2038
|
+
const userId = isUserBased ? ( processedChecklist?.userId ? String( processedChecklist.userId ) : ( processedChecklist?.userEmail || '' ) ) : '';
|
|
2039
|
+
if ( !isUserBased && !storeId ) return;
|
|
2040
|
+
if ( isUserBased && !userId ) return;
|
|
2035
2041
|
|
|
2036
2042
|
const today = currentDateTime ? currentDateTime.format( 'YYYY-MM-DD' ) : dayjs().format( 'YYYY-MM-DD' );
|
|
2037
2043
|
const yesterday = dayjs( today, 'YYYY-MM-DD' ).subtract( 1, 'day' ).format( 'YYYY-MM-DD' );
|
|
@@ -2039,7 +2045,7 @@ async function updateRecurringFlagTracker( processedChecklist, questionAnswers,
|
|
|
2039
2045
|
const trackerKeyBase = {
|
|
2040
2046
|
client_id: processedChecklist.client_id,
|
|
2041
2047
|
sourceCheckList_id: processedChecklist.sourceCheckList_id,
|
|
2042
|
-
store_id:
|
|
2048
|
+
...( isUserBased ) ? { user_id: userId } : { store_id: storeId },
|
|
2043
2049
|
};
|
|
2044
2050
|
|
|
2045
2051
|
const existingRows = await recurringFlagTracker.find( trackerKeyBase, { section_id: 1, qno: 1, consecutiveCount: 1, lastFlaggedDate: 1 } );
|
|
@@ -2059,7 +2065,7 @@ async function updateRecurringFlagTracker( processedChecklist, questionAnswers,
|
|
|
2059
2065
|
const flaggedNow = Array.isArray( question?.userAnswer ) && question.userAnswer.some( ( a ) => a?.sopFlag === true );
|
|
2060
2066
|
const key = `${sectionId}::${qno}`;
|
|
2061
2067
|
const existing = existingMap.get( key );
|
|
2062
|
-
|
|
2068
|
+
console.log( flaggedNow, 'test' );
|
|
2063
2069
|
if ( flaggedNow ) {
|
|
2064
2070
|
let newCount;
|
|
2065
2071
|
if ( !existing ) {
|
|
@@ -2079,8 +2085,11 @@ async function updateRecurringFlagTracker( processedChecklist, questionAnswers,
|
|
|
2079
2085
|
filter: { ...trackerKeyBase, section_id: sectionId, qno },
|
|
2080
2086
|
update: {
|
|
2081
2087
|
$set: {
|
|
2088
|
+
coverage: isUserBased ? 'user' : 'store',
|
|
2082
2089
|
checkListName: processedChecklist.checkListName,
|
|
2083
|
-
storeName: processedChecklist.storeName,
|
|
2090
|
+
storeName: isUserBased ? '' : ( processedChecklist.storeName || '' ),
|
|
2091
|
+
userName: processedChecklist.userName || '',
|
|
2092
|
+
userEmail: processedChecklist.userEmail || '',
|
|
2084
2093
|
sectionName: section?.sectionName || '',
|
|
2085
2094
|
qname: question?.qname || '',
|
|
2086
2095
|
consecutiveCount: newCount,
|
|
@@ -2103,9 +2112,9 @@ async function updateRecurringFlagTracker( processedChecklist, questionAnswers,
|
|
|
2103
2112
|
}
|
|
2104
2113
|
} );
|
|
2105
2114
|
} );
|
|
2106
|
-
|
|
2107
2115
|
if ( ops.length ) {
|
|
2108
|
-
await recurringFlagTracker.bulkWrite( ops );
|
|
2116
|
+
let data = await recurringFlagTracker.bulkWrite( ops );
|
|
2117
|
+
console.log( data );
|
|
2109
2118
|
}
|
|
2110
2119
|
} catch ( error ) {
|
|
2111
2120
|
logger.error( { function: 'updateRecurringFlagTracker', error } );
|
|
@@ -2247,6 +2256,7 @@ export async function submitChecklist( req, res ) {
|
|
|
2247
2256
|
// };
|
|
2248
2257
|
// await detectionService.create( detectionData );
|
|
2249
2258
|
if ( requestData.submittype == 'submit' ) {
|
|
2259
|
+
console.log( checklistDetails?.recurringFlag?.notifyType?.length );
|
|
2250
2260
|
if ( checklistDetails?.recurringFlag?.notifyType?.length ) {
|
|
2251
2261
|
updateRecurringFlagTracker( getchecklist[0], requestData.questionAnswers, currentDateTime );
|
|
2252
2262
|
}
|
|
@@ -67,12 +67,14 @@
|
|
|
67
67
|
<div style="margin-top: 0px;margin-bottom: 0px;font-size: 16px;line-height: 28px;color: #82899a;">
|
|
68
68
|
<span style="font-weight: 400;color: #121A26;line-height: 140%;">
|
|
69
69
|
Hi,<br/>
|
|
70
|
-
{{#if data.
|
|
71
|
-
Recurring flags has been detected across multiple
|
|
70
|
+
{{#if data.isMultiStoreSingleChecklist}}
|
|
71
|
+
Recurring flags has been detected across multiple {{data.subjectLabelPluralLower}} in recent <b>{{data.checklistName}}</b> on recent submissions, exceeding the configured threshold of {{data.threshold}} occurrences.
|
|
72
|
+
{{else if data.isMultiStore}}
|
|
73
|
+
Recurring flags has been detected across multiple {{data.subjectLabelPluralLower}} and across multiple checklists on recent submissions, exceeding the configured threshold of {{data.threshold}} occurrences.
|
|
72
74
|
{{else if data.isMultiChecklist}}
|
|
73
|
-
A Recurring flags has been identified for
|
|
75
|
+
A Recurring flags has been identified for {{data.subjectLabelLower}} <b>{{data.subjectName}}</b> across multiple checklists on recent submissions, exceeding the configured threshold of {{data.threshold}} occurrences.
|
|
74
76
|
{{else}}
|
|
75
|
-
A recurring flag has been identified for
|
|
77
|
+
A recurring flag has been identified for {{data.subjectLabelLower}} <b>{{data.subjectName}}</b>.where a question has been flagged multiple times in recent {{data.checklistName}} submissions, exceeding the configured threshold of {{data.threshold}} occurrences.
|
|
76
78
|
{{/if}}
|
|
77
79
|
</span>
|
|
78
80
|
</div>
|
|
@@ -86,8 +88,10 @@
|
|
|
86
88
|
<div class="highlight">
|
|
87
89
|
<b>Key Highlights:</b>
|
|
88
90
|
<ul style="margin:8px 0 0 0;padding-left:18px;">
|
|
89
|
-
<li>Total
|
|
91
|
+
<li>Total {{data.subjectLabelPlural}} with Recurring Flags: {{data.highlights.totalSubjects}}</li>
|
|
92
|
+
{{#unless data.isMultiStoreSingleChecklist}}
|
|
90
93
|
<li>Total Checklists with Recurring Flags: {{data.highlights.totalChecklists}}</li>
|
|
94
|
+
{{/unless}}
|
|
91
95
|
<li>Total Recurring Flags: {{data.highlights.totalFlags}}</li>
|
|
92
96
|
</ul>
|
|
93
97
|
</div>
|
|
@@ -102,7 +106,7 @@
|
|
|
102
106
|
{{#if data.isMultiStore}}
|
|
103
107
|
Details of the Recurring Flags are as follows:
|
|
104
108
|
{{else if data.isMultiChecklist}}
|
|
105
|
-
Details of the Recurring Flags from
|
|
109
|
+
Details of the Recurring Flags from {{data.subjectLabel}} {{data.subjectName}} are as follows:
|
|
106
110
|
{{else}}
|
|
107
111
|
Details of the Recurring Flags from checklist are as follows:
|
|
108
112
|
{{/if}}
|
|
@@ -119,9 +123,9 @@
|
|
|
119
123
|
<td align="center" bgcolor="#dbe5ea" style="padding:0 10px 0 10px">
|
|
120
124
|
<table align="center" bgcolor="#ffffff" border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 680px;">
|
|
121
125
|
<tr bgcolor="#ffffff">
|
|
122
|
-
<td class="flagText" style="padding-left:30px;line-height: 24px;color:#000000">
|
|
126
|
+
<td class="flagText" style="padding-left:30px;line-height: 24px;color:#000000">{{data.subjectLabel}} Name :</td>
|
|
123
127
|
<td></td><td></td>
|
|
124
|
-
<td class="flagText">{{data.
|
|
128
|
+
<td class="flagText">{{data.subjectName}}</td>
|
|
125
129
|
</tr>
|
|
126
130
|
<tr bgcolor="#ffffff">
|
|
127
131
|
<td class="flagText" style="padding-left:30px;line-height: 24px;">Checklist Name :</td>
|
|
@@ -141,7 +145,7 @@
|
|
|
141
145
|
<tr bgcolor="#ffffff">
|
|
142
146
|
<td class="flagText" style="padding-left:30px;line-height: 24px;">No of Flags :</td>
|
|
143
147
|
<td></td><td></td>
|
|
144
|
-
<td class="flagText">{{data.
|
|
148
|
+
<td class="flagText">{{data.totalFlags}} (Question Flags: {{data.flagCount}}, Run AI Flags: {{data.runAICount}})</td>
|
|
145
149
|
</tr>
|
|
146
150
|
</table>
|
|
147
151
|
</td>
|
|
@@ -158,9 +162,8 @@
|
|
|
158
162
|
<table class="rfTable">
|
|
159
163
|
<thead>
|
|
160
164
|
<tr>
|
|
161
|
-
{{#if data.isMultiStore}}<th>
|
|
162
|
-
<th>Checklist Name</th>
|
|
163
|
-
<th>Question</th>
|
|
165
|
+
{{#if data.isMultiStore}}<th>{{data.subjectLabel}} Name</th>{{/if}}
|
|
166
|
+
{{#unless data.isMultiStoreSingleChecklist}}<th>Checklist Name</th>{{/unless}}
|
|
164
167
|
<th>Last Submitted By</th>
|
|
165
168
|
<th>Last Submission Date</th>
|
|
166
169
|
<th>Total Recurring Flags</th>
|
|
@@ -169,12 +172,11 @@
|
|
|
169
172
|
<tbody>
|
|
170
173
|
{{#each data.rows}}
|
|
171
174
|
<tr>
|
|
172
|
-
{{#if ../data.isMultiStore}}<td>{{this.
|
|
173
|
-
<td>{{this.checklistName}}</td>
|
|
174
|
-
<td>{{this.questionName}}</td>
|
|
175
|
+
{{#if ../data.isMultiStore}}<td>{{this.subjectName}}</td>{{/if}}
|
|
176
|
+
{{#unless ../data.isMultiStoreSingleChecklist}}<td>{{this.checklistName}}</td>{{/unless}}
|
|
175
177
|
<td>{{this.lastSubmittedBy}}</td>
|
|
176
178
|
<td>{{this.lastSubmissionDate}}</td>
|
|
177
|
-
<td>{{this.
|
|
179
|
+
<td>{{this.totalFlags}} (Question Flags: {{this.flagCount}}, Run AI Flags: {{this.runAICount}})</td>
|
|
178
180
|
</tr>
|
|
179
181
|
{{/each}}
|
|
180
182
|
</tbody>
|
package/src/hbs/template.hbs
CHANGED
|
@@ -197,9 +197,12 @@
|
|
|
197
197
|
{{#each data.questionAnswers}}
|
|
198
198
|
<div class="pdfcontent">
|
|
199
199
|
<div style="padding-bottom:20px;">
|
|
200
|
-
<
|
|
201
|
-
<
|
|
202
|
-
|
|
200
|
+
<table style="width:100%;border-collapse:collapse;">
|
|
201
|
+
<tr>
|
|
202
|
+
<td class="sectionname" style="white-space:nowrap;padding-right:8px;width:1%;">{{sectionName}}</td>
|
|
203
|
+
<td style="width:99%;"><hr class="horizondal-line" style="margin:0;border:none;border-top:1px solid #98A2B3;"/></td>
|
|
204
|
+
</tr>
|
|
205
|
+
</table>
|
|
203
206
|
</div>
|
|
204
207
|
{{#each questions}}
|
|
205
208
|
<div style="padding-bottom:20px">
|
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
.q-answer-text.flagged{color:#a32d2d}
|
|
71
71
|
.q-answer-media{margin-top:8px}
|
|
72
72
|
.q-answer-media img,.q-answer-media video,.q-answer-item td img{display:block;width:200px;height:180px;object-fit:cover;border-radius:6px;margin-bottom:6px}
|
|
73
|
+
.img-grid{display:flex;flex-wrap:wrap;gap:8px}
|
|
74
|
+
.img-grid img{margin-bottom:0}
|
|
73
75
|
.q-answer-link{font-size:12px;color:#0085D2;text-decoration:underline;word-break:break-all}
|
|
74
76
|
.q-answer-caption{font-size:11px;color:#666;margin-bottom:4px}
|
|
75
77
|
.q-answer-remarks{font-size:11px;color:#666;margin-top:6px;white-space:pre-line}
|
|
@@ -188,9 +190,11 @@
|
|
|
188
190
|
{{#if this.multiQuestionReferenceImage.length}}
|
|
189
191
|
<div class="q-answer-media">
|
|
190
192
|
<div class="q-answer-caption">Question Reference Images</div>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
<div class="img-grid">
|
|
194
|
+
{{#each this.multiQuestionReferenceImage}}
|
|
195
|
+
<img src="{{this}}" alt="Reference Image" />
|
|
196
|
+
{{/each}}
|
|
197
|
+
</div>
|
|
194
198
|
</div>
|
|
195
199
|
{{/if}}
|
|
196
200
|
{{!-- <span class="q-ans {{#if this.isYes}}ans-yes{{else}}{{#if this.isNo}}ans-no{{/if}}{{/if}}">{{#if this.isYes}}✓ Yes{{else}}{{#if this.isNo}}✗ No{{else}}{{this.answerDisplay}}{{/if}}{{/if}}</span> --}}
|
|
@@ -198,25 +202,6 @@
|
|
|
198
202
|
<div class="q-answer-list">
|
|
199
203
|
{{#each this.userAnswer}}
|
|
200
204
|
<div class="q-answer-item">
|
|
201
|
-
{{#neq ../answerType 'image/video'}}
|
|
202
|
-
{{#neq ../answerType 'multipleImage'}}
|
|
203
|
-
{{#if this.multiReferenceImage.length}}
|
|
204
|
-
<div class="q-answer-media">
|
|
205
|
-
<div class="q-answer-caption">Reference Images</div>
|
|
206
|
-
{{#each this.multiReferenceImage}}
|
|
207
|
-
<img src="{{this}}" alt="Reference Image" />
|
|
208
|
-
{{/each}}
|
|
209
|
-
</div>
|
|
210
|
-
{{else}}
|
|
211
|
-
{{#if this.referenceImage}}
|
|
212
|
-
<div class="q-answer-media">
|
|
213
|
-
<div class="q-answer-caption">Reference Image</div>
|
|
214
|
-
<img src="{{this.referenceImage}}" alt="Reference Image" />
|
|
215
|
-
</div>
|
|
216
|
-
{{/if}}
|
|
217
|
-
{{/if}}
|
|
218
|
-
{{/neq}}
|
|
219
|
-
{{/neq}}
|
|
220
205
|
{{#eq this.answerType 'text'}}
|
|
221
206
|
{{#if this.answer}}
|
|
222
207
|
<div class="q-answer-text {{#if this.sopFlag}}flagged{{/if}}">{{this.answer}}</div>
|
|
@@ -231,8 +216,29 @@
|
|
|
231
216
|
{{/if}}
|
|
232
217
|
{{/eq}}
|
|
233
218
|
|
|
234
|
-
<table style="width:100%;margin-top:8px"><tr>
|
|
235
|
-
<td style="vertical-align:top">
|
|
219
|
+
<table style="width:100%;margin-top:8px;table-layout:fixed"><tr>
|
|
220
|
+
<td style="width:50%;vertical-align:top;padding-right:8px">
|
|
221
|
+
{{#neq ../answerType 'image/video'}}
|
|
222
|
+
{{#neq ../answerType 'multipleImage'}}
|
|
223
|
+
{{#if this.multiReferenceImage.length}}
|
|
224
|
+
<div class="q-answer-media">
|
|
225
|
+
<div class="q-answer-caption">Reference Images</div>
|
|
226
|
+
{{#each this.multiReferenceImage}}
|
|
227
|
+
<img src="{{this}}" alt="Reference Image" />
|
|
228
|
+
{{/each}}
|
|
229
|
+
</div>
|
|
230
|
+
{{else}}
|
|
231
|
+
{{#if this.referenceImage}}
|
|
232
|
+
<div class="q-answer-media">
|
|
233
|
+
<div class="q-answer-caption">Reference Image</div>
|
|
234
|
+
<img src="{{this.referenceImage}}" alt="Reference Image" />
|
|
235
|
+
</div>
|
|
236
|
+
{{/if}}
|
|
237
|
+
{{/if}}
|
|
238
|
+
{{/neq}}
|
|
239
|
+
{{/neq}}
|
|
240
|
+
</td>
|
|
241
|
+
<td style="width:50%;vertical-align:top;padding-left:8px">
|
|
236
242
|
{{#eq this.answerType 'image'}}
|
|
237
243
|
{{#if this.answer}}
|
|
238
244
|
<div class="q-answer-caption">Uploaded Image</div>
|
|
@@ -37,6 +37,7 @@ internalTraxRouter
|
|
|
37
37
|
.post( '/getSubmissionDetails', isAllowedInternalAPIHandler, internalController.checklistTaskSubmissionDetails )
|
|
38
38
|
.post( '/posblock', isAllowedInternalAPIHandler, internalController.getStoreTaskDetails )
|
|
39
39
|
.post( '/runAIFlag', isAllowedInternalAPIHandler, internalController.runAIFlag )
|
|
40
|
+
.post( '/incrementRunAIRecurring', isAllowedInternalAPIHandler, internalController.incrementRunAIRecurring )
|
|
40
41
|
.post( '/recurringFlag', isAllowedInternalAPIHandler, internalController.recurringFlagAlert )
|
|
41
42
|
.post( '/weeklyWrap', isAllowedInternalAPIHandler, internalController.weeklyWrapAlert )
|
|
42
43
|
.post( '/downloadInsertPdf', isAllowedInternalAPIHandler, internalController.downloadInsertPdf )
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import model from 'tango-api-schema';
|
|
2
|
-
|
|
3
|
-
export const findOne = async ( query={}, field={} ) => {
|
|
4
|
-
return model.recurringFlagTrackerModel.findOne( query, field );
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export const find = async ( query={}, field={} ) => {
|
|
8
|
-
return model.recurringFlagTrackerModel.find( query, field );
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const create = async ( document = {} ) => {
|
|
12
|
-
return model.recurringFlagTrackerModel.create( document );
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const updateOne = async ( query = {}, record={} ) => {
|
|
16
|
-
return model.recurringFlagTrackerModel.updateOne( query, { $set: record }, { upsert: true } );
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const updateMany = async ( query = {}, record={} ) => {
|
|
20
|
-
return model.recurringFlagTrackerModel.updateMany( query, { $set: record } );
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const deleteOne = async ( query = {} ) => {
|
|
24
|
-
return model.recurringFlagTrackerModel.deleteOne( query );
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export const aggregate = async ( query = [] ) => {
|
|
28
|
-
return model.recurringFlagTrackerModel.aggregate( query );
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const bulkWrite = async ( ops = [] ) => {
|
|
32
|
-
return model.recurringFlagTrackerModel.bulkWrite( ops );
|
|
33
|
-
};
|
|
1
|
+
import model from 'tango-api-schema';
|
|
2
|
+
|
|
3
|
+
export const findOne = async ( query={}, field={} ) => {
|
|
4
|
+
return model.recurringFlagTrackerModel.findOne( query, field );
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const find = async ( query={}, field={} ) => {
|
|
8
|
+
return model.recurringFlagTrackerModel.find( query, field );
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const create = async ( document = {} ) => {
|
|
12
|
+
return model.recurringFlagTrackerModel.create( document );
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const updateOne = async ( query = {}, record={} ) => {
|
|
16
|
+
return model.recurringFlagTrackerModel.updateOne( query, { $set: record }, { upsert: true } );
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const updateMany = async ( query = {}, record={} ) => {
|
|
20
|
+
return model.recurringFlagTrackerModel.updateMany( query, { $set: record } );
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const deleteOne = async ( query = {} ) => {
|
|
24
|
+
return model.recurringFlagTrackerModel.deleteOne( query );
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const aggregate = async ( query = [] ) => {
|
|
28
|
+
return model.recurringFlagTrackerModel.aggregate( query );
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const bulkWrite = async ( ops = [] ) => {
|
|
32
|
+
return model.recurringFlagTrackerModel.bulkWrite( ops );
|
|
33
|
+
};
|
|
@@ -169,13 +169,15 @@ function buildQuestionAnswerEntries( question ) {
|
|
|
169
169
|
|
|
170
170
|
const rawMultiRefSources = [
|
|
171
171
|
userAnswer?.multiReferenceImage,
|
|
172
|
-
matchedAnswer?.multiReferenceImage,
|
|
173
|
-
question?.answers?.[0]?.multiReferenceImage,
|
|
172
|
+
// matchedAnswer?.multiReferenceImage,
|
|
173
|
+
// question?.answers?.[0]?.multiReferenceImage,
|
|
174
174
|
];
|
|
175
175
|
let multiReferenceImage = [];
|
|
176
176
|
for ( const src of rawMultiRefSources ) {
|
|
177
177
|
const flat = flattenImageRefs( src );
|
|
178
|
-
if ( flat.length ) {
|
|
178
|
+
if ( flat.length ) {
|
|
179
|
+
multiReferenceImage = flat; break;
|
|
180
|
+
}
|
|
179
181
|
}
|
|
180
182
|
const validationImage = flattenImageRefs( userAnswer?.validationImage );
|
|
181
183
|
|
|
@@ -920,7 +922,6 @@ export function resolveTemplateUrls( templateData, baseUrl = 'https://d1r0hc2ssk
|
|
|
920
922
|
q.multiQuestionReferenceImage = q.multiQuestionReferenceImage.map( ( ele ) => resolveUrl( ele ) );
|
|
921
923
|
}
|
|
922
924
|
q.userAnswer?.forEach( ( ua ) => {
|
|
923
|
-
|
|
924
925
|
if ( ua.multiReferenceImage?.length ) {
|
|
925
926
|
ua.multiReferenceImage = ua.multiReferenceImage.map( ( ele ) => resolveUrl( ele ) );
|
|
926
927
|
}
|