tango-app-api-trax 3.8.23 → 3.8.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-trax",
3
- "version": "3.8.23",
3
+ "version": "3.8.25",
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.87",
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: 'Store Name', key: 'storeName', width: 25 },
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,7 @@ 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
-
4363
+ console.log( JSON.stringify( checklistDetails ) );
4257
4364
  if ( !checklistDetails.length ) {
4258
4365
  return res.sendSuccess( 'No checklists configured for recurring flag' );
4259
4366
  }
@@ -4266,7 +4373,6 @@ export async function recurringFlagAlert( req, res ) {
4266
4373
  const threshold = cl?.recurringFlag?.threshold || 3;
4267
4374
  const notifyType = cl?.recurringFlag?.notifyType || [];
4268
4375
  const users = cl?.recurringFlag?.users || [];
4269
-
4270
4376
  let recipients = [];
4271
4377
  if ( notifyType.includes( 'sameAsNotify' ) ) {
4272
4378
  const nfType = cl?.notifyFlags?.notifyType || [];
@@ -4275,42 +4381,71 @@ export async function recurringFlagAlert( req, res ) {
4275
4381
  recipients = cl.approver.map( ( a ) => a?.value ).filter( Boolean );
4276
4382
  }
4277
4383
  recipients = [ ...recipients, ...nfUsers.map( ( u ) => u?.value ).filter( Boolean ) ];
4278
- } else {
4279
- if ( notifyType.includes( 'approver' ) && Array.isArray( cl.approver ) ) {
4280
- recipients = cl.approver.map( ( a ) => a?.value ).filter( Boolean );
4281
- }
4282
- recipients = [ ...recipients, ...users.map( ( u ) => u?.value ).filter( Boolean ) ];
4283
4384
  }
4385
+ if ( notifyType.includes( 'approver' ) && Array.isArray( cl.approver ) ) {
4386
+ recipients = [ ...recipients, ...cl.approver.map( ( a ) => a?.value ).filter( Boolean ) ];
4387
+ }
4388
+ recipients = [ ...recipients, ...users.map( ( u ) => u?.value ).filter( Boolean ) ];
4284
4389
  recipients = [ ...new Set( recipients ) ];
4285
4390
 
4286
4391
  if ( !recipients.length ) return;
4287
4392
 
4288
- // Read tracker rows that have hit threshold and have not yet been emailed for the current streak.
4289
- // Submit-time updates already maintained consecutiveCount + lastFlaggedDate per (store, section, qno).
4393
+ // Read tracker rows where EITHER the sop streak or the runAI count has hit threshold and the
4394
+ // current trigger hasn't been emailed yet (lastEmailDate vs the relevant flagged date).
4290
4395
  const trackerRows = await recurringFlagTracker.find( {
4291
4396
  sourceCheckList_id: cl._id,
4292
- consecutiveCount: { $gte: threshold },
4293
- $expr: { $ne: [ { $ifNull: [ '$lastEmailDate', '' ] }, { $ifNull: [ '$lastFlaggedDate', '' ] } ] },
4397
+ $or: [
4398
+ {
4399
+ consecutiveCount: { $gte: threshold },
4400
+ $expr: { $ne: [ { $ifNull: [ '$lastEmailDate', '' ] }, { $ifNull: [ '$lastFlaggedDate', '' ] } ] },
4401
+ },
4402
+ {
4403
+ runAICount: { $gte: threshold },
4404
+ $expr: { $ne: [ { $ifNull: [ '$lastEmailDate', '' ] }, { $ifNull: [ '$lastRunAIFlaggedDate', '' ] } ] },
4405
+ },
4406
+ ],
4294
4407
  } );
4295
4408
 
4296
4409
  for ( const t of trackerRows ) {
4410
+ const isUserBased = t.coverage === 'user' || ( !t.store_id && ( t.user_id || t.userEmail ) );
4411
+ // For user-based checklists, group/identify by userEmail; for store-based, by store_id.
4412
+ const subjectId = isUserBased ? ( t.userEmail || t.user_id || '' ) : ( t.store_id || '' );
4413
+ const subjectName = isUserBased ? ( t.userName || t.userEmail || '--' ) : ( t.storeName || '--' );
4414
+ // Determine which streak crossed threshold for this row — drives reset granularity below.
4415
+ const sopFired = ( t.consecutiveCount || 0 ) >= threshold && t.lastEmailDate !== t.lastFlaggedDate;
4416
+ const runAIFired = ( t.runAICount || 0 ) >= threshold && t.lastEmailDate !== t.lastRunAIFlaggedDate;
4297
4417
  for ( const recipient of recipients ) {
4298
4418
  triggers.push( {
4299
4419
  recipient,
4300
4420
  clientId: t.client_id,
4301
- storeId: t.store_id,
4302
- storeName: t.storeName,
4421
+ coverage: isUserBased ? 'user' : 'store',
4422
+ subjectId,
4423
+ subjectName,
4424
+ storeId: t.store_id || '',
4425
+ storeName: t.storeName || '',
4426
+ userId: t.user_id || '',
4427
+ userName: t.userName || '',
4428
+ userEmail: t.userEmail || '',
4303
4429
  checklistId: cl._id.toString(),
4304
4430
  checklistName: cl.checkListName?.trim() || t.checkListName || '',
4305
4431
  sectionName: t.sectionName,
4306
4432
  qno: t.qno,
4307
4433
  qname: t.qname,
4308
4434
  days: t.consecutiveCount,
4435
+ runAICount: t.runAICount || 0,
4436
+ sopFired,
4437
+ runAIFired,
4309
4438
  lastSubmittedBy: t.lastSubmittedBy || '--',
4310
4439
  lastSubmissionDate: t.lastSubmissionDate || t.lastFlaggedDate || '',
4311
4440
  } );
4312
4441
  }
4313
- trackerIdsToReset.push( { _id: t._id, lastFlaggedDate: t.lastFlaggedDate } );
4442
+ trackerIdsToReset.push( {
4443
+ _id: t._id,
4444
+ lastFlaggedDate: t.lastFlaggedDate,
4445
+ lastRunAIFlaggedDate: t.lastRunAIFlaggedDate,
4446
+ sopFired,
4447
+ runAIFired,
4448
+ } );
4314
4449
  }
4315
4450
  } ) );
4316
4451
 
@@ -4318,6 +4453,7 @@ export async function recurringFlagAlert( req, res ) {
4318
4453
  return res.sendSuccess( 'No recurring flags reached threshold' );
4319
4454
  }
4320
4455
 
4456
+
4321
4457
  // Group triggers by recipient.
4322
4458
  const byRecipient = new Map();
4323
4459
  for ( const t of triggers ) {
@@ -4332,21 +4468,74 @@ export async function recurringFlagAlert( req, res ) {
4332
4468
  const sentSummary = [];
4333
4469
 
4334
4470
  await Promise.all( [ ...byRecipient.entries() ].map( async ( [ recipient, items ] ) => {
4335
- const stores = new Set( items.map( ( i ) => i.storeId ) );
4471
+ const subjects = new Set( items.map( ( i ) => i.subjectId ) );
4336
4472
  const checklists = new Set( items.map( ( i ) => i.checklistId ) );
4337
- const isMultiStore = stores.size > 1;
4473
+ console.log( checklists );
4474
+ const isMultiStore = subjects.size > 1; // "isMultiStore" name retained for template back-compat
4338
4475
  const isMultiChecklist = !isMultiStore && checklists.size > 1;
4339
- // Threshold for the message line — when grouping spans multiple checklists/stores, take min threshold seen.
4476
+ // Threshold for the message line — when grouping spans multiple checklists/subjects, take min threshold seen.
4340
4477
  const thresholdShown = items.reduce( ( acc, it ) => Math.min( acc, it.days ), items[0].days );
4341
4478
 
4342
- const rows = items.map( ( i ) => ( {
4343
- storeName: i.storeName,
4479
+ // If every trigger for this recipient is user-based, label as User. Mixed sets fall back to Store.
4480
+ const coverages = new Set( items.map( ( i ) => i.coverage ) );
4481
+ const isAllUser = coverages.size === 1 && coverages.has( 'user' );
4482
+ const subjectLabel = isAllUser ? 'User' : 'Store';
4483
+ const subjectLabelPlural = isAllUser ? 'Users' : 'Stores';
4484
+ const subjectLabelLower = isAllUser ? 'user' : 'store';
4485
+ const subjectLabelPluralLower = isAllUser ? 'users' : 'stores';
4486
+
4487
+ // Aggregate triggers per (subject, checklist) — each table row counts how many distinct questions
4488
+ // hit the recurring threshold for that pair. The streak length on each question is no longer surfaced
4489
+ // in the email body; it remains in the per-question Excel breakdown below.
4490
+ const parseSubmissionDate = ( s ) => {
4491
+ if ( !s ) return 0;
4492
+ const d = dayjs( s, 'hh:mm A, DD MMM YYYY' );
4493
+ return d.isValid() ? d.valueOf() : 0;
4494
+ };
4495
+ const groupMap = new Map();
4496
+ for ( const i of items ) {
4497
+ const k = `${i.subjectId}::${i.checklistId}`;
4498
+ if ( !groupMap.has( k ) ) {
4499
+ groupMap.set( k, {
4500
+ subjectId: i.subjectId,
4501
+ subjectName: i.subjectName,
4502
+ checklistId: i.checklistId,
4503
+ checklistName: i.checklistName,
4504
+ questionCount: 0,
4505
+ runAICount: 0,
4506
+ lastSubmittedBy: i.lastSubmittedBy,
4507
+ lastSubmissionDate: i.lastSubmissionDate,
4508
+ } );
4509
+ }
4510
+ const g = groupMap.get( k );
4511
+ g.questionCount += 1;
4512
+ g.runAICount += ( i.runAICount || 0 );
4513
+ if ( parseSubmissionDate( i.lastSubmissionDate ) > parseSubmissionDate( g.lastSubmissionDate ) ) {
4514
+ g.lastSubmissionDate = i.lastSubmissionDate;
4515
+ g.lastSubmittedBy = i.lastSubmittedBy;
4516
+ }
4517
+ }
4518
+ const rows = [ ...groupMap.values() ].map( ( g ) => ( {
4519
+ subjectName: g.subjectName,
4520
+ storeName: g.subjectName, // legacy field name still consumed by template fallbacks
4521
+ checklistName: g.checklistName,
4522
+ lastSubmittedBy: g.lastSubmittedBy,
4523
+ lastSubmissionDate: g.lastSubmissionDate,
4524
+ days: g.questionCount, // template column "Total Recurring Flags" reads `days`
4525
+ flagCount: g.questionCount,
4526
+ runAICount: g.runAICount,
4527
+ } ) );
4528
+
4529
+ // Excel attachment keeps per-question detail (one row per flagged question).
4530
+ const excelRows = items.map( ( i ) => ( {
4531
+ storeName: i.subjectName,
4344
4532
  checklistName: i.checklistName,
4345
4533
  sectionName: i.sectionName,
4346
4534
  questionName: i.qname,
4347
4535
  lastSubmittedBy: i.lastSubmittedBy,
4348
4536
  lastSubmissionDate: i.lastSubmissionDate,
4349
4537
  days: i.days,
4538
+ runAICount: i.runAICount || 0,
4350
4539
  } ) );
4351
4540
 
4352
4541
  const ATTACHMENT_THRESHOLD = 10;
@@ -4361,27 +4550,34 @@ export async function recurringFlagAlert( req, res ) {
4361
4550
  hasAttachment,
4362
4551
  domain: flagDomain,
4363
4552
  rows: displayRows,
4553
+ subjectLabel,
4554
+ subjectLabelPlural,
4555
+ subjectLabelLower,
4556
+ subjectLabelPluralLower,
4364
4557
  };
4365
4558
 
4366
4559
  if ( isMultiStore ) {
4367
4560
  data.highlights = {
4368
- totalStores: stores.size,
4561
+ totalSubjects: subjects.size,
4562
+ totalStores: subjects.size, // legacy alias
4369
4563
  totalChecklists: checklists.size,
4370
4564
  totalFlags: items.length,
4371
4565
  };
4372
4566
  } else if ( isMultiChecklist ) {
4373
- data.storeName = items[0].storeName;
4567
+ data.subjectName = items[0].subjectName;
4568
+ data.storeName = items[0].subjectName;
4374
4569
  } else {
4375
- const single = items[0];
4376
- data.storeName = single.storeName;
4570
+ // Single mode: one (subject, checklist) — flagCount is the number of questions that hit threshold.
4571
+ const single = rows[0];
4572
+ data.subjectName = single.subjectName;
4573
+ data.storeName = single.subjectName;
4377
4574
  data.checklistName = single.checklistName;
4378
- data.questionName = single.qname;
4379
4575
  data.lastSubmittedBy = single.lastSubmittedBy;
4380
4576
  data.lastSubmissionDate = single.lastSubmissionDate;
4381
- data.days = single.days;
4382
- data.daysPlural = single.days > 1;
4383
- data.flagCount = 1;
4384
- data.flagCountPlural = false;
4577
+ data.flagCount = single.flagCount;
4578
+ data.flagCountPlural = single.flagCount > 1;
4579
+ data.runAICount = single.runAICount;
4580
+ data.runAICountPlural = single.runAICount > 1;
4385
4581
  }
4386
4582
 
4387
4583
  const html = compiled( { data } );
@@ -4396,7 +4592,7 @@ export async function recurringFlagAlert( req, res ) {
4396
4592
 
4397
4593
  if ( hasAttachment ) {
4398
4594
  try {
4399
- const buf = await buildRecurringFlagExcel( rows );
4595
+ const buf = await buildRecurringFlagExcel( excelRows, subjectLabel );
4400
4596
  params.attachment = {
4401
4597
  filename: 'Recurring-Flags-Summary.xlsx',
4402
4598
  content: Buffer.from( buf ),
@@ -4406,22 +4602,32 @@ export async function recurringFlagAlert( req, res ) {
4406
4602
  logger.error( { functionName: 'recurringFlagAlert.buildExcel', error: e } );
4407
4603
  }
4408
4604
  }
4409
-
4605
+ console.log( params.toEmail );
4410
4606
  sendEmailWithSES( params.toEmail, params.mailSubject, params.htmlBody, params.attachment, params.sourceEmail );
4411
4607
  sentSummary.push( { recipient, count: items.length, mode: isMultiStore ? 'multi-store' : ( isMultiChecklist ? 'multi-checklist' : 'single' ) } );
4412
4608
  } ) );
4413
4609
 
4414
- // Reset counters, stamp lastEmailDate, and append the fire date to emailHistory (capped at last 60).
4610
+ // Reset only the streak(s) that crossed threshold. Stamp lastEmailDate to the most recent triggering
4611
+ // flag date so dedup remains correct. Append that date to emailHistory (rolling last 60).
4415
4612
  if ( trackerIdsToReset.length ) {
4416
- const resetOps = trackerIdsToReset.map( ( { _id, lastFlaggedDate } ) => ( {
4417
- updateOne: {
4418
- filter: { _id },
4419
- update: {
4420
- $set: { consecutiveCount: 0, lastEmailDate: lastFlaggedDate },
4421
- $push: { emailHistory: { $each: [ lastFlaggedDate ], $slice: -60 } },
4613
+ const resetOps = trackerIdsToReset.map( ( r ) => {
4614
+ const set = {};
4615
+ if ( r.sopFired ) set.consecutiveCount = 0;
4616
+ if ( r.runAIFired ) set.runAICount = 0;
4617
+ const stampDate = r.runAIFired ?
4618
+ ( r.lastRunAIFlaggedDate || r.lastFlaggedDate || '' ) :
4619
+ ( r.lastFlaggedDate || r.lastRunAIFlaggedDate || '' );
4620
+ set.lastEmailDate = stampDate;
4621
+ return {
4622
+ updateOne: {
4623
+ filter: { _id: r._id },
4624
+ update: {
4625
+ $set: set,
4626
+ $push: { emailHistory: { $each: [ stampDate ], $slice: -60 } },
4627
+ },
4422
4628
  },
4423
- },
4424
- } ) );
4629
+ };
4630
+ } );
4425
4631
  await recurringFlagTracker.bulkWrite( resetOps );
4426
4632
  }
4427
4633
 
@@ -4566,7 +4772,7 @@ export async function weeklyWrapAlert( req, res ) {
4566
4772
  const endDateLabel = dayjs( range.weekEnd ).format( 'DD/MM/YY' );
4567
4773
 
4568
4774
  // TEMP: static recipient for dev. Replace with client-config lookup later.
4569
- const STATIC_RECIPIENT = 'sh9628hs@gmail.com';
4775
+ const STATIC_RECIPIENT = 'gopisjkg@gmail.com';
4570
4776
 
4571
4777
  // Aggregate flag data for both weeks across all stores in one shot.
4572
4778
  const [ flagsThis, flagsPrev, recurThis, recurPrev ] = await Promise.all( [
@@ -4774,3 +4980,4 @@ export async function updateStoreLatLong( req, res ) {
4774
4980
  return res.sendError( e, 500 );
4775
4981
  }
4776
4982
  }
4983
+
@@ -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?.store_id || !processedChecklist?.sourceCheckList_id ) return;
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: processedChecklist.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
  }
@@ -68,11 +68,11 @@
68
68
  <span style="font-weight: 400;color: #121A26;line-height: 140%;">
69
69
  Hi,<br/>
70
70
  {{#if data.isMultiStore}}
71
- Recurring flags has been detected across multiple stores and across multiple checklists on recent submissions, exceeding the configured threshold of {{data.threshold}} occurrences.
71
+ 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
72
  {{else if data.isMultiChecklist}}
73
- A Recurring flags has been identified for store <b>{{data.storeName}}</b> across multiple checklists on recent submissions, exceeding the configured threshold of {{data.threshold}} occurrences.
73
+ 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
74
  {{else}}
75
- A recurring flag has been identified for store <b>{{data.storeName}}</b>.where a question has been flagged multiple times in recent {{data.checklistName}} submissions, exceeding the configured threshold of {{data.threshold}} occurrences.
75
+ 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
76
  {{/if}}
77
77
  </span>
78
78
  </div>
@@ -86,7 +86,7 @@
86
86
  <div class="highlight">
87
87
  <b>Key Highlights:</b>
88
88
  <ul style="margin:8px 0 0 0;padding-left:18px;">
89
- <li>Total Stores with Recurring Flags: {{data.highlights.totalStores}}</li>
89
+ <li>Total {{data.subjectLabelPlural}} with Recurring Flags: {{data.highlights.totalSubjects}}</li>
90
90
  <li>Total Checklists with Recurring Flags: {{data.highlights.totalChecklists}}</li>
91
91
  <li>Total Recurring Flags: {{data.highlights.totalFlags}}</li>
92
92
  </ul>
@@ -102,7 +102,7 @@
102
102
  {{#if data.isMultiStore}}
103
103
  Details of the Recurring Flags are as follows:
104
104
  {{else if data.isMultiChecklist}}
105
- Details of the Recurring Flags from Store {{data.storeName}} (last {{data.threshold}} days):
105
+ Details of the Recurring Flags from {{data.subjectLabel}} {{data.subjectName}} are as follows:
106
106
  {{else}}
107
107
  Details of the Recurring Flags from checklist are as follows:
108
108
  {{/if}}
@@ -119,9 +119,9 @@
119
119
  <td align="center" bgcolor="#dbe5ea" style="padding:0 10px 0 10px">
120
120
  <table align="center" bgcolor="#ffffff" border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 680px;">
121
121
  <tr bgcolor="#ffffff">
122
- <td class="flagText" style="padding-left:30px;line-height: 24px;color:#000000">Store Name :</td>
122
+ <td class="flagText" style="padding-left:30px;line-height: 24px;color:#000000">{{data.subjectLabel}} Name :</td>
123
123
  <td></td><td></td>
124
- <td class="flagText">{{data.storeName}}</td>
124
+ <td class="flagText">{{data.subjectName}}</td>
125
125
  </tr>
126
126
  <tr bgcolor="#ffffff">
127
127
  <td class="flagText" style="padding-left:30px;line-height: 24px;">Checklist Name :</td>
@@ -141,7 +141,12 @@
141
141
  <tr bgcolor="#ffffff">
142
142
  <td class="flagText" style="padding-left:30px;line-height: 24px;">No of Flags :</td>
143
143
  <td></td><td></td>
144
- <td class="flagText">{{data.flagCount}} Question Flag{{#if data.flagCountPlural}}s{{/if}} - "{{data.questionName}}" has flagged for {{data.days}} day{{#if data.daysPlural}}s{{/if}} in a Row</td>
144
+ <td class="flagText">{{data.flagCount}} Question Flag{{#if data.flagCountPlural}}s{{/if}}</td>
145
+ </tr>
146
+ <tr bgcolor="#ffffff">
147
+ <td class="flagText" style="padding-left:30px;line-height: 24px;">Run AI Flags :</td>
148
+ <td></td><td></td>
149
+ <td class="flagText">{{data.runAICount}} Run AI Flag{{#if data.runAICountPlural}}s{{/if}}</td>
145
150
  </tr>
146
151
  </table>
147
152
  </td>
@@ -158,23 +163,23 @@
158
163
  <table class="rfTable">
159
164
  <thead>
160
165
  <tr>
161
- {{#if data.isMultiStore}}<th>Store Name</th>{{/if}}
166
+ {{#if data.isMultiStore}}<th>{{data.subjectLabel}} Name</th>{{/if}}
162
167
  <th>Checklist Name</th>
163
- <th>Question</th>
164
168
  <th>Last Submitted By</th>
165
169
  <th>Last Submission Date</th>
166
170
  <th>Total Recurring Flags</th>
171
+ <th>Run AI Flags</th>
167
172
  </tr>
168
173
  </thead>
169
174
  <tbody>
170
175
  {{#each data.rows}}
171
176
  <tr>
172
- {{#if ../data.isMultiStore}}<td>{{this.storeName}}</td>{{/if}}
177
+ {{#if ../data.isMultiStore}}<td>{{this.subjectName}}</td>{{/if}}
173
178
  <td>{{this.checklistName}}</td>
174
- <td>{{this.questionName}}</td>
175
179
  <td>{{this.lastSubmittedBy}}</td>
176
180
  <td>{{this.lastSubmissionDate}}</td>
177
181
  <td>{{this.days}}</td>
182
+ <td>{{this.runAICount}}</td>
178
183
  </tr>
179
184
  {{/each}}
180
185
  </tbody>
@@ -197,9 +197,12 @@
197
197
  {{#each data.questionAnswers}}
198
198
  <div class="pdfcontent">
199
199
  <div style="padding-bottom:20px;">
200
- <span class="sectionname">{{sectionName}} </span>&nbsp;<div class="horizondal-line">
201
- <hr />
202
- </div>
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">
@@ -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
+ };