tango-app-api-trax 3.8.21 → 3.8.23
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 +312 -44
- package/src/controllers/mobileTrax.controller.js +26 -48
- package/src/hbs/template.hbs +22 -18
- package/src/hbs/visit-checklist.hbs +2 -2
- package/src/hbs/weeklyWrap.hbs +218 -0
- package/src/routes/internalTraxApi.router.js +1 -0
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.23",
|
|
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.87",
|
|
33
33
|
"tango-app-api-middleware": "^3.5.2",
|
|
34
34
|
"url": "^0.11.4",
|
|
35
35
|
"winston": "^3.13.1",
|
|
@@ -2119,7 +2119,6 @@ export async function getPDFCSVChecklistDetails( req, res ) {
|
|
|
2119
2119
|
}
|
|
2120
2120
|
}
|
|
2121
2121
|
|
|
2122
|
-
|
|
2123
2122
|
export async function AiPushNotificationAlert( req, res ) {
|
|
2124
2123
|
try {
|
|
2125
2124
|
// console.log( req.body );
|
|
@@ -2176,7 +2175,6 @@ export async function liveAiPushNotificationAlert( req, res ) {
|
|
|
2176
2175
|
}
|
|
2177
2176
|
}
|
|
2178
2177
|
|
|
2179
|
-
|
|
2180
2178
|
export async function taskPushNotification( req, res ) {
|
|
2181
2179
|
try {
|
|
2182
2180
|
let query = [ {
|
|
@@ -4249,12 +4247,12 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4249
4247
|
const checklistDetails = await CLconfig.find( {
|
|
4250
4248
|
publish: true,
|
|
4251
4249
|
$expr: {
|
|
4252
|
-
$
|
|
4253
|
-
{ $size: { $cond: [ { $isArray: '$recurringFlag.users' }, '$recurringFlag.users', [] ] } },
|
|
4254
|
-
0,
|
|
4250
|
+
$or: [
|
|
4251
|
+
{ $gt: [ { $size: { $cond: [ { $isArray: '$recurringFlag.users' }, '$recurringFlag.users', [] ] } }, 0 ] },
|
|
4252
|
+
{ $gt: [ { $size: { $cond: [ { $isArray: '$recurringFlag.notifyType' }, '$recurringFlag.notifyType', [] ] } }, 0 ] },
|
|
4255
4253
|
],
|
|
4256
4254
|
},
|
|
4257
|
-
}, { _id: 1, checkListName: 1, recurringFlag: 1, approver: 1, client_id: 1 } );
|
|
4255
|
+
}, { _id: 1, checkListName: 1, recurringFlag: 1, notifyFlags: 1, approver: 1, client_id: 1 } );
|
|
4258
4256
|
|
|
4259
4257
|
if ( !checklistDetails.length ) {
|
|
4260
4258
|
return res.sendSuccess( 'No checklists configured for recurring flag' );
|
|
@@ -4270,10 +4268,19 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4270
4268
|
const users = cl?.recurringFlag?.users || [];
|
|
4271
4269
|
|
|
4272
4270
|
let recipients = [];
|
|
4273
|
-
if ( notifyType.includes( '
|
|
4274
|
-
|
|
4271
|
+
if ( notifyType.includes( 'sameAsNotify' ) ) {
|
|
4272
|
+
const nfType = cl?.notifyFlags?.notifyType || [];
|
|
4273
|
+
const nfUsers = cl?.notifyFlags?.users || [];
|
|
4274
|
+
if ( nfType.includes( 'approver' ) && Array.isArray( cl.approver ) ) {
|
|
4275
|
+
recipients = cl.approver.map( ( a ) => a?.value ).filter( Boolean );
|
|
4276
|
+
}
|
|
4277
|
+
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 ) ];
|
|
4275
4283
|
}
|
|
4276
|
-
recipients = [ ...recipients, ...users.map( ( u ) => u?.value ).filter( Boolean ) ];
|
|
4277
4284
|
recipients = [ ...new Set( recipients ) ];
|
|
4278
4285
|
|
|
4279
4286
|
if ( !recipients.length ) return;
|
|
@@ -4404,12 +4411,15 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4404
4411
|
sentSummary.push( { recipient, count: items.length, mode: isMultiStore ? 'multi-store' : ( isMultiChecklist ? 'multi-checklist' : 'single' ) } );
|
|
4405
4412
|
} ) );
|
|
4406
4413
|
|
|
4407
|
-
// Reset counters
|
|
4414
|
+
// Reset counters, stamp lastEmailDate, and append the fire date to emailHistory (capped at last 60).
|
|
4408
4415
|
if ( trackerIdsToReset.length ) {
|
|
4409
4416
|
const resetOps = trackerIdsToReset.map( ( { _id, lastFlaggedDate } ) => ( {
|
|
4410
4417
|
updateOne: {
|
|
4411
4418
|
filter: { _id },
|
|
4412
|
-
update: {
|
|
4419
|
+
update: {
|
|
4420
|
+
$set: { consecutiveCount: 0, lastEmailDate: lastFlaggedDate },
|
|
4421
|
+
$push: { emailHistory: { $each: [ lastFlaggedDate ], $slice: -60 } },
|
|
4422
|
+
},
|
|
4413
4423
|
},
|
|
4414
4424
|
} ) );
|
|
4415
4425
|
await recurringFlagTracker.bulkWrite( resetOps );
|
|
@@ -4422,41 +4432,299 @@ export async function recurringFlagAlert( req, res ) {
|
|
|
4422
4432
|
}
|
|
4423
4433
|
}
|
|
4424
4434
|
|
|
4435
|
+
function getLastWeekRange( ref = dayjs() ) {
|
|
4436
|
+
const today = ref.startOf( 'day' );
|
|
4437
|
+
const dow = today.day(); // 0 Sun ... 6 Sat
|
|
4438
|
+
const daysToThisMonday = ( dow + 6 ) % 7; // Mon=0, Tue=1, ..., Sun=6
|
|
4439
|
+
const thisMonday = today.subtract( daysToThisMonday, 'day' );
|
|
4440
|
+
const weekStart = thisMonday.subtract( 7, 'day' );
|
|
4441
|
+
const weekEnd = thisMonday.subtract( 1, 'day' );
|
|
4442
|
+
const prevWeekStart = weekStart.subtract( 7, 'day' );
|
|
4443
|
+
const prevWeekEnd = weekStart.subtract( 1, 'day' );
|
|
4444
|
+
return {
|
|
4445
|
+
weekStart: weekStart.format( 'YYYY-MM-DD' ),
|
|
4446
|
+
weekEnd: weekEnd.format( 'YYYY-MM-DD' ),
|
|
4447
|
+
prevWeekStart: prevWeekStart.format( 'YYYY-MM-DD' ),
|
|
4448
|
+
prevWeekEnd: prevWeekEnd.format( 'YYYY-MM-DD' ),
|
|
4449
|
+
};
|
|
4450
|
+
}
|
|
4451
|
+
|
|
4452
|
+
function computeWow( current, previous ) {
|
|
4453
|
+
if ( !previous ) {
|
|
4454
|
+
if ( current ) return { value: '100%', direction: 'up' };
|
|
4455
|
+
return { value: '', direction: 'up' };
|
|
4456
|
+
}
|
|
4457
|
+
const delta = current - previous;
|
|
4458
|
+
if ( delta === 0 ) return { value: '0%', direction: 'up' };
|
|
4459
|
+
const pct = Math.min( 100, Math.round( ( Math.abs( delta ) / previous ) * 100 ) );
|
|
4460
|
+
return { value: `${pct}%`, direction: delta > 0 ? 'up' : 'down' };
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
async function aggregateWeeklyFlagsByStore( weekStart, weekEnd ) {
|
|
4464
|
+
const rows = await processedchecklist.aggregate( [
|
|
4465
|
+
{ $match: { date_string: { $gte: weekStart, $lte: weekEnd }, isdeleted: { $ne: true } } },
|
|
4466
|
+
{ $group: {
|
|
4467
|
+
_id: { client_id: '$client_id', store_id: '$store_id' },
|
|
4468
|
+
storeName: { $last: '$storeName' },
|
|
4469
|
+
questionFlag: { $sum: { $ifNull: [ '$questionFlag', 0 ] } },
|
|
4470
|
+
timeFlag: { $sum: { $ifNull: [ '$timeFlag', 0 ] } },
|
|
4471
|
+
runAIFlag: { $sum: { $ifNull: [ '$runAIFlag', 0 ] } },
|
|
4472
|
+
flaggedChecklistIds: { $addToSet: {
|
|
4473
|
+
$cond: [
|
|
4474
|
+
{ $or: [
|
|
4475
|
+
{ $gt: [ { $ifNull: [ '$questionFlag', 0 ] }, 0 ] },
|
|
4476
|
+
{ $gt: [ { $ifNull: [ '$timeFlag', 0 ] }, 0 ] },
|
|
4477
|
+
{ $gt: [ { $ifNull: [ '$runAIFlag', 0 ] }, 0 ] },
|
|
4478
|
+
] },
|
|
4479
|
+
'$sourceCheckList_id',
|
|
4480
|
+
null,
|
|
4481
|
+
],
|
|
4482
|
+
} },
|
|
4483
|
+
} },
|
|
4484
|
+
] );
|
|
4485
|
+
const map = new Map();
|
|
4486
|
+
for ( const r of rows ) {
|
|
4487
|
+
const key = `${r._id.client_id}::${r._id.store_id}`;
|
|
4488
|
+
const flaggedChecklists = ( r.flaggedChecklistIds || [] ).filter( ( id ) => id ).map( ( id ) => String( id ) );
|
|
4489
|
+
const totalFlags = ( r.questionFlag || 0 ) + ( r.timeFlag || 0 ) + ( r.runAIFlag || 0 );
|
|
4490
|
+
map.set( key, {
|
|
4491
|
+
client_id: r._id.client_id,
|
|
4492
|
+
store_id: r._id.store_id,
|
|
4493
|
+
storeName: r.storeName,
|
|
4494
|
+
questionFlag: r.questionFlag || 0,
|
|
4495
|
+
timeFlag: r.timeFlag || 0,
|
|
4496
|
+
runAIFlag: r.runAIFlag || 0,
|
|
4497
|
+
flaggedChecklists,
|
|
4498
|
+
totalFlags,
|
|
4499
|
+
} );
|
|
4500
|
+
}
|
|
4501
|
+
return map;
|
|
4502
|
+
}
|
|
4503
|
+
|
|
4504
|
+
async function aggregateWeeklyRecurringByStore( weekStart, weekEnd ) {
|
|
4505
|
+
const rows = await recurringFlagTracker.aggregate( [
|
|
4506
|
+
{ $match: { emailHistory: { $elemMatch: { $gte: weekStart, $lte: weekEnd } } } },
|
|
4507
|
+
{ $project: {
|
|
4508
|
+
client_id: 1,
|
|
4509
|
+
store_id: 1,
|
|
4510
|
+
storeName: 1,
|
|
4511
|
+
firedInWeek: {
|
|
4512
|
+
$size: {
|
|
4513
|
+
$filter: {
|
|
4514
|
+
input: { $ifNull: [ '$emailHistory', [] ] },
|
|
4515
|
+
as: 'd',
|
|
4516
|
+
cond: { $and: [ { $gte: [ '$$d', weekStart ] }, { $lte: [ '$$d', weekEnd ] } ] },
|
|
4517
|
+
},
|
|
4518
|
+
},
|
|
4519
|
+
},
|
|
4520
|
+
} },
|
|
4521
|
+
{ $group: {
|
|
4522
|
+
_id: { client_id: '$client_id', store_id: '$store_id' },
|
|
4523
|
+
storeName: { $last: '$storeName' },
|
|
4524
|
+
count: { $sum: '$firedInWeek' },
|
|
4525
|
+
} },
|
|
4526
|
+
] );
|
|
4527
|
+
const map = new Map();
|
|
4528
|
+
for ( const r of rows ) {
|
|
4529
|
+
map.set( `${r._id.client_id}::${r._id.store_id}`, {
|
|
4530
|
+
client_id: r._id.client_id,
|
|
4531
|
+
store_id: r._id.store_id,
|
|
4532
|
+
storeName: r.storeName,
|
|
4533
|
+
count: r.count,
|
|
4534
|
+
} );
|
|
4535
|
+
}
|
|
4536
|
+
return map;
|
|
4537
|
+
}
|
|
4538
|
+
|
|
4539
|
+
function buildWeeklyWrapExcel( header, perStoreRows ) {
|
|
4540
|
+
const workbook = new ExcelJS.Workbook();
|
|
4541
|
+
const sheet = workbook.addWorksheet( 'Weekly Summary' );
|
|
4542
|
+
sheet.addRow( [ header ] );
|
|
4543
|
+
sheet.getRow( 1 ).font = { bold: true, size: 14 };
|
|
4544
|
+
sheet.addRow( [] );
|
|
4545
|
+
sheet.columns = [
|
|
4546
|
+
{ header: 'Store ID', key: 'storeId', width: 18 },
|
|
4547
|
+
{ header: 'Store Name', key: 'storeName', width: 28 },
|
|
4548
|
+
{ header: 'Question Flags', key: 'questionFlag', width: 16 },
|
|
4549
|
+
{ header: 'Not Submitted', key: 'timeFlag', width: 16 },
|
|
4550
|
+
{ header: 'Run AI Flags', key: 'runAIFlag', width: 16 },
|
|
4551
|
+
{ header: 'Recurring Flags', key: 'recurringFlag', width: 18 },
|
|
4552
|
+
{ header: 'Total Flags', key: 'totalFlags', width: 14 },
|
|
4553
|
+
{ header: 'Last Week Total', key: 'prevTotal', width: 18 },
|
|
4554
|
+
{ header: 'WoW %', key: 'wow', width: 12 },
|
|
4555
|
+
];
|
|
4556
|
+
sheet.getRow( 3 ).font = { bold: true };
|
|
4557
|
+
sheet.getRow( 3 ).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF1F5F9' } };
|
|
4558
|
+
for ( const r of perStoreRows ) sheet.addRow( r );
|
|
4559
|
+
return workbook.xlsx.writeBuffer();
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4562
|
+
export async function weeklyWrapAlert( req, res ) {
|
|
4563
|
+
try {
|
|
4564
|
+
const range = getLastWeekRange();
|
|
4565
|
+
const startDateLabel = dayjs( range.weekStart ).format( 'DD/MM/YY' );
|
|
4566
|
+
const endDateLabel = dayjs( range.weekEnd ).format( 'DD/MM/YY' );
|
|
4567
|
+
|
|
4568
|
+
// TEMP: static recipient for dev. Replace with client-config lookup later.
|
|
4569
|
+
const STATIC_RECIPIENT = 'sh9628hs@gmail.com';
|
|
4570
|
+
|
|
4571
|
+
// Aggregate flag data for both weeks across all stores in one shot.
|
|
4572
|
+
const [ flagsThis, flagsPrev, recurThis, recurPrev ] = await Promise.all( [
|
|
4573
|
+
aggregateWeeklyFlagsByStore( range.weekStart, range.weekEnd ),
|
|
4574
|
+
aggregateWeeklyFlagsByStore( range.prevWeekStart, range.prevWeekEnd ),
|
|
4575
|
+
aggregateWeeklyRecurringByStore( range.weekStart, range.weekEnd ),
|
|
4576
|
+
aggregateWeeklyRecurringByStore( range.prevWeekStart, range.prevWeekEnd ),
|
|
4577
|
+
] );
|
|
4578
|
+
|
|
4579
|
+
// Compute per-client highest flagged store and highest recurring flagged store (this week).
|
|
4580
|
+
const topByClient = new Map(); // clientId -> { topFlag, topRecurring }
|
|
4581
|
+
const upsertTop = ( client, key, candidate ) => {
|
|
4582
|
+
if ( !topByClient.has( client ) ) topByClient.set( client, { topFlag: null, topRecurring: null } );
|
|
4583
|
+
const entry = topByClient.get( client );
|
|
4584
|
+
if ( !entry[key] || candidate.value > entry[key].value ) entry[key] = candidate;
|
|
4585
|
+
};
|
|
4586
|
+
for ( const v of flagsThis.values() ) {
|
|
4587
|
+
upsertTop( v.client_id, 'topFlag', { storeId: v.store_id, storeName: v.storeName, value: v.totalFlags } );
|
|
4588
|
+
}
|
|
4589
|
+
for ( const v of recurThis.values() ) {
|
|
4590
|
+
upsertTop( v.client_id, 'topRecurring', { storeId: v.store_id, storeName: v.storeName, value: v.count } );
|
|
4591
|
+
}
|
|
4592
|
+
|
|
4593
|
+
const fileContent = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/weeklyWrap.hbs', 'utf8' );
|
|
4594
|
+
const compiled = handlebars.compile( fileContent );
|
|
4595
|
+
const flagDomain = `${JSON.parse( process.env.URL ).domain}/manage/trax/dashboard`;
|
|
4596
|
+
const sentSummary = [];
|
|
4597
|
+
|
|
4598
|
+
// Build one digest per client present in the data and email the static recipient.
|
|
4599
|
+
const clientIds = new Set();
|
|
4600
|
+
for ( const v of flagsThis.values() ) clientIds.add( v.client_id );
|
|
4601
|
+
for ( const v of recurThis.values() ) clientIds.add( v.client_id );
|
|
4602
|
+
|
|
4603
|
+
await Promise.all( [ ...clientIds ].map( async ( clientId ) => {
|
|
4604
|
+
try {
|
|
4605
|
+
let storesFlagged = 0;
|
|
4606
|
+
let totalFlags = 0;
|
|
4607
|
+
let prevTotalFlags = 0;
|
|
4608
|
+
let prevStoresFlagged = 0;
|
|
4609
|
+
const flaggedChecklistsThis = new Set();
|
|
4610
|
+
const flaggedChecklistsPrev = new Set();
|
|
4611
|
+
let questionFlags = 0;
|
|
4612
|
+
let notSubmittedFlags = 0;
|
|
4613
|
+
let runAIFlags = 0;
|
|
4614
|
+
let recurringFlags = 0;
|
|
4615
|
+
const excelRows = [];
|
|
4616
|
+
|
|
4617
|
+
// Iterate only stores belonging to this client (key prefix match).
|
|
4618
|
+
const clientPrefix = `${clientId}::`;
|
|
4619
|
+
const clientStoreKeys = new Set();
|
|
4620
|
+
for ( const k of flagsThis.keys() ) if ( k.startsWith( clientPrefix ) ) clientStoreKeys.add( k );
|
|
4621
|
+
for ( const k of flagsPrev.keys() ) if ( k.startsWith( clientPrefix ) ) clientStoreKeys.add( k );
|
|
4622
|
+
for ( const k of recurThis.keys() ) if ( k.startsWith( clientPrefix ) ) clientStoreKeys.add( k );
|
|
4623
|
+
|
|
4624
|
+
for ( const k of clientStoreKeys ) {
|
|
4625
|
+
const cur = flagsThis.get( k );
|
|
4626
|
+
const prev = flagsPrev.get( k );
|
|
4627
|
+
const recCur = recurThis.get( k );
|
|
4628
|
+
const recPrev = recurPrev.get( k );
|
|
4629
|
+
const storeRecurring = recCur?.count || 0;
|
|
4630
|
+
const storeTotal = ( cur?.totalFlags || 0 ) + storeRecurring;
|
|
4631
|
+
const prevStoreTotal = ( prev?.totalFlags || 0 ) + ( recPrev?.count || 0 );
|
|
4632
|
+
|
|
4633
|
+
if ( storeTotal > 0 ) storesFlagged += 1;
|
|
4634
|
+
if ( prevStoreTotal > 0 ) prevStoresFlagged += 1;
|
|
4635
|
+
totalFlags += storeTotal;
|
|
4636
|
+
prevTotalFlags += prevStoreTotal;
|
|
4637
|
+
questionFlags += cur?.questionFlag || 0;
|
|
4638
|
+
notSubmittedFlags += cur?.timeFlag || 0;
|
|
4639
|
+
runAIFlags += cur?.runAIFlag || 0;
|
|
4640
|
+
recurringFlags += storeRecurring;
|
|
4641
|
+
|
|
4642
|
+
( cur?.flaggedChecklists || [] ).forEach( ( id ) => flaggedChecklistsThis.add( id ) );
|
|
4643
|
+
( prev?.flaggedChecklists || [] ).forEach( ( id ) => flaggedChecklistsPrev.add( id ) );
|
|
4644
|
+
|
|
4645
|
+
if ( storeTotal > 0 || prevStoreTotal > 0 ) {
|
|
4646
|
+
const w = computeWow( storeTotal, prevStoreTotal );
|
|
4647
|
+
excelRows.push( {
|
|
4648
|
+
storeId: ( cur?.store_id || prev?.store_id || k.slice( clientPrefix.length ) ),
|
|
4649
|
+
storeName: cur?.storeName || prev?.storeName || '',
|
|
4650
|
+
questionFlag: cur?.questionFlag || 0,
|
|
4651
|
+
timeFlag: cur?.timeFlag || 0,
|
|
4652
|
+
runAIFlag: cur?.runAIFlag || 0,
|
|
4653
|
+
recurringFlag: storeRecurring,
|
|
4654
|
+
totalFlags: storeTotal,
|
|
4655
|
+
prevTotal: prevStoreTotal,
|
|
4656
|
+
wow: w.value ? `${w.direction === 'up' ? '+' : '-'}${w.value}` : '0%',
|
|
4657
|
+
} );
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4660
|
+
|
|
4661
|
+
if ( totalFlags === 0 && prevTotalFlags === 0 ) return; // skip silent clients
|
|
4662
|
+
|
|
4663
|
+
const top = topByClient.get( clientId ) || { topFlag: null, topRecurring: null };
|
|
4664
|
+
let highestFlaggedStoreWow = { value: '', direction: 'up' };
|
|
4665
|
+
if ( top.topFlag ) {
|
|
4666
|
+
const prev = flagsPrev.get( `${clientId}::${top.topFlag.storeId}` );
|
|
4667
|
+
highestFlaggedStoreWow = computeWow( top.topFlag.value, prev?.totalFlags || 0 );
|
|
4668
|
+
}
|
|
4669
|
+
let highestRecurringStoreWow = { value: '', direction: 'up' };
|
|
4670
|
+
if ( top.topRecurring ) {
|
|
4671
|
+
const prev = recurPrev.get( `${clientId}::${top.topRecurring.storeId}` );
|
|
4672
|
+
highestRecurringStoreWow = computeWow( top.topRecurring.value, prev?.count || 0 );
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4675
|
+
const excelHeader = `Weekly Summary ${startDateLabel} - ${endDateLabel}`;
|
|
4676
|
+
const buf = await buildWeeklyWrapExcel( excelHeader, excelRows );
|
|
4677
|
+
const attachmentBuffer = Buffer.from( buf );
|
|
4678
|
+
const sizeKb = Math.max( 1, Math.round( attachmentBuffer.length / 1024 ) );
|
|
4679
|
+
|
|
4680
|
+
const data = {
|
|
4681
|
+
startDate: startDateLabel,
|
|
4682
|
+
endDate: endDateLabel,
|
|
4683
|
+
storesFlagged,
|
|
4684
|
+
storesFlaggedWow: computeWow( storesFlagged, prevStoresFlagged ),
|
|
4685
|
+
totalFlags,
|
|
4686
|
+
totalFlagsWow: computeWow( totalFlags, prevTotalFlags ),
|
|
4687
|
+
checklistFlags: flaggedChecklistsThis.size,
|
|
4688
|
+
checklistFlagsWow: computeWow( flaggedChecklistsThis.size, flaggedChecklistsPrev.size ),
|
|
4689
|
+
questionFlags,
|
|
4690
|
+
notSubmittedFlags,
|
|
4691
|
+
runAIFlags,
|
|
4692
|
+
recurringFlags,
|
|
4693
|
+
highestFlaggedStore: top.topFlag?.storeName || top.topFlag?.storeId || '--',
|
|
4694
|
+
highestFlaggedStoreWow,
|
|
4695
|
+
highestRecurringStore: top.topRecurring?.storeName || top.topRecurring?.storeId || '--',
|
|
4696
|
+
highestRecurringStoreWow,
|
|
4697
|
+
attachmentName: `${excelHeader}.xlsx`,
|
|
4698
|
+
attachmentSize: `${sizeKb} KB`,
|
|
4699
|
+
domain: flagDomain,
|
|
4700
|
+
};
|
|
4701
|
+
|
|
4702
|
+
const html = compiled( { data } );
|
|
4703
|
+
const attachment = {
|
|
4704
|
+
filename: `${excelHeader}.xlsx`,
|
|
4705
|
+
content: attachmentBuffer,
|
|
4706
|
+
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
4707
|
+
};
|
|
4708
|
+
const sourceEmail = JSON.parse( process.env.SES ).adminEmail;
|
|
4709
|
+
const subject = `TangoEye | Weekly Wrap (${startDateLabel} - ${endDateLabel})`;
|
|
4710
|
+
|
|
4711
|
+
sendEmailWithSES( STATIC_RECIPIENT, subject, html, attachment, sourceEmail );
|
|
4712
|
+
sentSummary.push( { recipient: STATIC_RECIPIENT, clientId, totalFlags } );
|
|
4713
|
+
} catch ( e ) {
|
|
4714
|
+
logger.error( { functionName: 'weeklyWrapAlert.client', clientId, error: e } );
|
|
4715
|
+
}
|
|
4716
|
+
} ) );
|
|
4717
|
+
|
|
4718
|
+
return res.sendSuccess( { message: 'Weekly wrap dispatched', range, sent: sentSummary } );
|
|
4719
|
+
} catch ( e ) {
|
|
4720
|
+
logger.error( { functionName: 'weeklyWrapAlert', error: e } );
|
|
4721
|
+
return res.sendError( e, 500 );
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
|
|
4425
4725
|
export async function updateStoreLatLong( req, res ) {
|
|
4426
4726
|
try {
|
|
4427
|
-
const defaultStores = [
|
|
4428
|
-
{ 'storeName': 'OFLBTM', 'lat': 12.9059052, 'long': 77.6057203 },
|
|
4429
|
-
{ 'storeName': 'OFLRRN', 'lat': 12.9096852, 'long': 77.5135813 },
|
|
4430
|
-
{ 'storeName': 'OFLGUN', 'lat': 12.9286282, 'long': 77.738055 },
|
|
4431
|
-
{ 'storeName': 'OFLJPN', 'lat': 12.8915891, 'long': 77.5776564 },
|
|
4432
|
-
{ 'storeName': 'OFLAECS', 'lat': 12.9638564, 'long': 77.7125467 },
|
|
4433
|
-
{ 'storeName': 'Seegehalli', 'lat': 13.0083064, 'long': 77.7588426 },
|
|
4434
|
-
{ 'storeName': 'OFLJPNS', 'lat': 12.8695409, 'long': 77.5820004 },
|
|
4435
|
-
{ 'storeName': 'OFLHAR', 'lat': 12.9136122, 'long': 77.6649999 },
|
|
4436
|
-
{ 'storeName': 'OFLKSNR', 'lat': 13.0051196, 'long': 77.6601987 },
|
|
4437
|
-
{ 'storeName': 'Hosa Road', 'lat': 12.8792369, 'long': 77.6721843 },
|
|
4438
|
-
{ 'storeName': 'OFLDEV', 'lat': 12.8942057, 'long': 77.6019696 },
|
|
4439
|
-
{ 'storeName': 'OFLAYN', 'lat': 12.958161, 'long': 77.570055 },
|
|
4440
|
-
{ 'storeName': 'OFLKAG', 'lat': 12.9845196, 'long': 77.6758945 },
|
|
4441
|
-
{ 'storeName': 'OFLBVR', 'lat': 12.9858428, 'long': 77.5424725 },
|
|
4442
|
-
{ 'storeName': 'OFLMTK', 'lat': 13.0279313, 'long': 77.5587934 },
|
|
4443
|
-
{ 'storeName': 'OFLMAG', 'lat': 12.9837929, 'long': 77.5325324 },
|
|
4444
|
-
{ 'storeName': 'OFLBEL', 'lat': 13.0370029, 'long': 77.5622911 },
|
|
4445
|
-
{ 'storeName': 'OFLKDU', 'lat': 12.8835726, 'long': 77.517709 },
|
|
4446
|
-
{ 'storeName': 'OFLAMLI', 'lat': 13.0685032, 'long': 77.5972815 },
|
|
4447
|
-
{ 'storeName': 'OFLSAN', 'lat': 19.0603798, 'long': 73.0041633 },
|
|
4448
|
-
{ 'storeName': 'OFLLOK', 'lat': 19.1471544, 'long': 72.5405682 },
|
|
4449
|
-
{ 'storeName': 'OFLTSTG', 'lat': 19.2471119, 'long': 72.9769107 },
|
|
4450
|
-
{ 'storeName': 'OFLKHGRS', 'lat': 19.0583611, 'long': 73.0584353 },
|
|
4451
|
-
{ 'storeName': 'OFLMAL', 'lat': 12.9673877, 'long': 77.499519 },
|
|
4452
|
-
{ 'storeName': 'OFLBAG', 'lat': 13.1218427, 'long': 77.6234015 },
|
|
4453
|
-
{ 'storeName': 'OFLYEL', 'lat': 13.114551, 'long': 77.5401312 },
|
|
4454
|
-
{ 'storeName': 'OFLTCP', 'lat': 13.0240695, 'long': 77.6928401 },
|
|
4455
|
-
{ 'storeName': 'OFLECTN', 'lat': 12.8185795, 'long': 77.6520784 },
|
|
4456
|
-
{ 'storeName': 'OFLHBL', 'lat': 13.0559812, 'long': 77.593941 },
|
|
4457
|
-
{ 'storeName': 'OFLBWR', 'lat': 12.9691559, 'long': 77.739599 },
|
|
4458
|
-
{ 'storeName': 'OFLHSR', 'lat': 12.9183666, 'long': 77.5793797 },
|
|
4459
|
-
];
|
|
4727
|
+
const defaultStores = [];
|
|
4460
4728
|
|
|
4461
4729
|
const list = Array.isArray( req.body?.stores ) && req.body.stores.length ? req.body.stores : defaultStores;
|
|
4462
4730
|
|
|
@@ -234,15 +234,15 @@ export async function startChecklist( req, res ) {
|
|
|
234
234
|
},
|
|
235
235
|
} );
|
|
236
236
|
let getupdatedchecklist = await processedchecklist.aggregate( findQuery );
|
|
237
|
-
let bucket = JSON.parse( process.env.BUCKET );
|
|
237
|
+
// let bucket = JSON.parse( process.env.BUCKET );
|
|
238
238
|
getupdatedchecklist[0].questionAnswers.forEach( ( section ) => {
|
|
239
239
|
section.questions.forEach( async ( question ) => {
|
|
240
240
|
if ( question.questionReferenceImage && question.questionReferenceImage!='' ) {
|
|
241
|
-
question.questionReferenceImage =
|
|
241
|
+
question.questionReferenceImage = `${cdnurl.TraxAnswerCDN}${question.questionReferenceImage}`;
|
|
242
242
|
}
|
|
243
243
|
question.answers.forEach( async ( answer ) => {
|
|
244
244
|
if ( answer.referenceImage != '' ) {
|
|
245
|
-
answer.referenceImage =
|
|
245
|
+
answer.referenceImage = `${cdnurl.TraxAnswerCDN}${answer.referenceImage}`;
|
|
246
246
|
}
|
|
247
247
|
} );
|
|
248
248
|
} );
|
|
@@ -469,17 +469,14 @@ export async function startTask( req, res ) {
|
|
|
469
469
|
|
|
470
470
|
|
|
471
471
|
if ( !( task.checkListFrom && task.checkListFrom == 'api' ) ) {
|
|
472
|
-
const bucket = JSON.parse( process.env.BUCKET );
|
|
472
|
+
// const bucket = JSON.parse( process.env.BUCKET );
|
|
473
473
|
await Promise.all(
|
|
474
474
|
getUpdatedTask[0].questionAnswers.map( ( section ) =>
|
|
475
475
|
section.questions.map( async ( question ) => {
|
|
476
476
|
if ( question.questionReferenceImage.length > 0 ) {
|
|
477
477
|
question.questionReferenceImage = await Promise.all(
|
|
478
478
|
question.questionReferenceImage.map( async ( image ) => {
|
|
479
|
-
return
|
|
480
|
-
Bucket: bucket.sop,
|
|
481
|
-
file_path: decodeURIComponent( image ),
|
|
482
|
-
} );
|
|
479
|
+
return `${cdnurl.TraxAnswerCDN}${image}`;
|
|
483
480
|
} ),
|
|
484
481
|
);
|
|
485
482
|
}
|
|
@@ -489,10 +486,7 @@ export async function startTask( req, res ) {
|
|
|
489
486
|
if ( answer.referenceImage.length > 0 ) {
|
|
490
487
|
answer.referenceImage = await Promise.all(
|
|
491
488
|
answer.referenceImage.map( async ( image ) => {
|
|
492
|
-
return
|
|
493
|
-
Bucket: bucket.sop,
|
|
494
|
-
file_path: decodeURIComponent( image ),
|
|
495
|
-
} );
|
|
489
|
+
return `${cdnurl.TraxAnswerCDN}${image}`;
|
|
496
490
|
} ),
|
|
497
491
|
);
|
|
498
492
|
}
|
|
@@ -2103,7 +2097,7 @@ async function updateRecurringFlagTracker( processedChecklist, questionAnswers,
|
|
|
2103
2097
|
ops.push( {
|
|
2104
2098
|
updateOne: {
|
|
2105
2099
|
filter: { ...trackerKeyBase, section_id: sectionId, qno },
|
|
2106
|
-
update: { $set: {
|
|
2100
|
+
update: { $set: { lastFlaggedDate: today } },
|
|
2107
2101
|
},
|
|
2108
2102
|
} );
|
|
2109
2103
|
}
|
|
@@ -2253,7 +2247,9 @@ export async function submitChecklist( req, res ) {
|
|
|
2253
2247
|
// };
|
|
2254
2248
|
// await detectionService.create( detectionData );
|
|
2255
2249
|
if ( requestData.submittype == 'submit' ) {
|
|
2256
|
-
|
|
2250
|
+
if ( checklistDetails?.recurringFlag?.notifyType?.length ) {
|
|
2251
|
+
updateRecurringFlagTracker( getchecklist[0], requestData.questionAnswers, currentDateTime );
|
|
2252
|
+
}
|
|
2257
2253
|
updateOpenSearch( req.user, requestData );
|
|
2258
2254
|
let openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
2259
2255
|
// console.log( 'openSearch', openSearch );
|
|
@@ -3579,7 +3575,7 @@ export async function questionList( req, res ) {
|
|
|
3579
3575
|
return res.sendError( 'Check List Got Edited Please Fill Again', 422 );
|
|
3580
3576
|
} else {
|
|
3581
3577
|
logger.info( `v5 => Checklist Continue => store Name: ${getchecklist[0].storeName}, User Email: ${getchecklist[0].userEmail}, Checklist Name: ${getchecklist[0].checkListName}` );
|
|
3582
|
-
let bucket = JSON.parse( process.env.BUCKET );
|
|
3578
|
+
// let bucket = JSON.parse( process.env.BUCKET );
|
|
3583
3579
|
let cdnurl = JSON.parse( process.env.CDNURL );
|
|
3584
3580
|
for ( let [ secIndex, section ] of getchecklist[0].questionAnswers.entries() ) {
|
|
3585
3581
|
for ( let [ questionIndex, question ] of section.questions.entries() ) {
|
|
@@ -3605,7 +3601,7 @@ export async function questionList( req, res ) {
|
|
|
3605
3601
|
let checkvalidation = null;
|
|
3606
3602
|
if ( answer.validationAnswer && answer.validationAnswer !='' ) {
|
|
3607
3603
|
if ( answer.validationType != 'Descriptive Answer' ) {
|
|
3608
|
-
checkvalidation =
|
|
3604
|
+
checkvalidation = `${cdnurl.TraxAnswerCDN}${answer.validationAnswer}`;
|
|
3609
3605
|
} else {
|
|
3610
3606
|
checkvalidation = answer.validationAnswer;
|
|
3611
3607
|
}
|
|
@@ -3626,23 +3622,23 @@ export async function questionList( req, res ) {
|
|
|
3626
3622
|
answer.referenceImage= `${cdnurl.TraxAnswerCDN}${answer.referenceImage}`;
|
|
3627
3623
|
}
|
|
3628
3624
|
if ( ( answer.validationType == 'Capture Image' || answer.validationType == 'Capture Video' ) && answer.validationAnswer && answer.validationAnswer != '' ) {
|
|
3629
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].validationAnswer =
|
|
3625
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].validationAnswer = `${cdnurl.TraxAnswerCDN}${answer.validationAnswer}`;
|
|
3630
3626
|
}
|
|
3631
3627
|
}
|
|
3632
3628
|
if ( question?.userAnswer ) {
|
|
3633
3629
|
for ( let [ userAnsIndex, userAns ] of question.userAnswer.entries() ) {
|
|
3634
3630
|
if ( ( userAns.validationType == 'Capture Image' || userAns.validationType == 'Capture Video' ) && userAns.validationAnswer && userAns.validationAnswer != '' ) {
|
|
3635
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationAnswer =
|
|
3631
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationAnswer = `${cdnurl.TraxAnswerCDN}${userAns.validationAnswer}`;
|
|
3636
3632
|
}
|
|
3637
3633
|
if ( userAns.validationType == 'Capture Multiple Image with description' ) {
|
|
3638
3634
|
let imageAnswers = userAns.validationImage;
|
|
3639
3635
|
userAns.validationImage = [];
|
|
3640
3636
|
for ( let image of imageAnswers ) {
|
|
3641
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationImage.push(
|
|
3637
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationImage.push( `${cdnurl.TraxAnswerCDN}${image}` );
|
|
3642
3638
|
}
|
|
3643
3639
|
}
|
|
3644
3640
|
if ( [ 'image', 'descriptiveImage', 'video' ].includes( question.answerType ) && userAns.answer != '' ) {
|
|
3645
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].answer
|
|
3641
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].answer =`${cdnurl.TraxAnswerCDN}${userAns.answer}`;
|
|
3646
3642
|
}
|
|
3647
3643
|
if ( userAns.referenceImage != '' ) {
|
|
3648
3644
|
// getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].referenceImage = await signedUrl( { file_path: decodeURIComponent( userAns.referenceImage ), Bucket: bucket.sop } );
|
|
@@ -3949,7 +3945,7 @@ export async function taskQuestionList( req, res ) {
|
|
|
3949
3945
|
return res.sendError( 'Task Got Edited Please Fill Again', 422 );
|
|
3950
3946
|
} else {
|
|
3951
3947
|
logger.info( `v5 => Task Continue => store Name: ${getchecklist[0].storeName}, User Email: ${getchecklist[0].userEmail}, Task Name: ${getchecklist[0].checkListName}` );
|
|
3952
|
-
let bucket = JSON.parse( process.env.BUCKET );
|
|
3948
|
+
// let bucket = JSON.parse( process.env.BUCKET );
|
|
3953
3949
|
let cdnurl = JSON.parse( process.env.CDNURL );
|
|
3954
3950
|
for ( let [ secIndex, section ] of getchecklist[0].questionAnswers.entries() ) {
|
|
3955
3951
|
for ( let [ questionIndex, question ] of section.questions.entries() ) {
|
|
@@ -3960,13 +3956,10 @@ export async function taskQuestionList( req, res ) {
|
|
|
3960
3956
|
if ( Array.isArray( questionReferenceImage ) ) {
|
|
3961
3957
|
questionReferenceImage = questionReferenceImage.filter( ( item ) => item != '' );
|
|
3962
3958
|
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].questionReferenceImage = await Promise.all(
|
|
3963
|
-
questionReferenceImage.map( async ( image ) => image ?
|
|
3959
|
+
questionReferenceImage.map( async ( image ) => image ? `${cdnurl.TraxAnswerCDN}${image}` : '' ),
|
|
3964
3960
|
);
|
|
3965
3961
|
} else if ( questionReferenceImage !== '' ) {
|
|
3966
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].questionReferenceImage =
|
|
3967
|
-
file_path: decodeURIComponent( questionReferenceImage ),
|
|
3968
|
-
Bucket: bucket.sop,
|
|
3969
|
-
} );
|
|
3962
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].questionReferenceImage = `${cdnurl.TraxAnswerCDN}${questionReferenceImage}`;
|
|
3970
3963
|
}
|
|
3971
3964
|
}
|
|
3972
3965
|
|
|
@@ -3977,13 +3970,10 @@ export async function taskQuestionList( req, res ) {
|
|
|
3977
3970
|
if ( Array.isArray( referenceImage ) ) {
|
|
3978
3971
|
referenceImage = referenceImage.filter( ( item ) => item != '' );
|
|
3979
3972
|
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].referenceImage = await Promise.all(
|
|
3980
|
-
referenceImage.map( async ( image ) => image ?
|
|
3973
|
+
referenceImage.map( async ( image ) => image ? `${cdnurl.TraxAnswerCDN}${image}` : '' ),
|
|
3981
3974
|
);
|
|
3982
3975
|
} else if ( referenceImage !== '' ) {
|
|
3983
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].referenceImage =
|
|
3984
|
-
file_path: decodeURIComponent( referenceImage ),
|
|
3985
|
-
Bucket: bucket.sop,
|
|
3986
|
-
} );
|
|
3976
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].referenceImage = `${cdnurl.TraxAnswerCDN}${referenceImage}`;
|
|
3987
3977
|
}
|
|
3988
3978
|
}
|
|
3989
3979
|
|
|
@@ -3991,7 +3981,7 @@ export async function taskQuestionList( req, res ) {
|
|
|
3991
3981
|
let checkvalidation = null;
|
|
3992
3982
|
if ( answer.validationAnswer && answer.validationAnswer != '' ) {
|
|
3993
3983
|
if ( answer.validationType != 'Descriptive Answer' ) {
|
|
3994
|
-
checkvalidation =
|
|
3984
|
+
checkvalidation = `${cdnurl.TraxAnswerCDN}${answer.validationAnswer}`;
|
|
3995
3985
|
} else {
|
|
3996
3986
|
checkvalidation = answer.validationAnswer;
|
|
3997
3987
|
}
|
|
@@ -4000,10 +3990,7 @@ export async function taskQuestionList( req, res ) {
|
|
|
4000
3990
|
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].index = ansIndex;
|
|
4001
3991
|
}
|
|
4002
3992
|
if ( ( answer.validationType == 'Capture Image' || answer.validationType == 'Capture Video' ) && answer.validationAnswer && answer.validationAnswer != '' ) {
|
|
4003
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].validationAnswer =
|
|
4004
|
-
file_path: decodeURIComponent( answer.validationAnswer ),
|
|
4005
|
-
Bucket: bucket.sop,
|
|
4006
|
-
} );
|
|
3993
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].validationAnswer = `${cdnurl.TraxAnswerCDN}${answer.validationAnswer}`;
|
|
4007
3994
|
}
|
|
4008
3995
|
}
|
|
4009
3996
|
|
|
@@ -4014,25 +4001,16 @@ export async function taskQuestionList( req, res ) {
|
|
|
4014
4001
|
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].linearType = question.answers[0].linearType;
|
|
4015
4002
|
}
|
|
4016
4003
|
if ( ( userAns.validationType == 'Capture Image' || userAns.validationType == 'Capture Video' ) && userAns.validationAnswer && userAns.validationAnswer != '' ) {
|
|
4017
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationAnswer =
|
|
4018
|
-
file_path: decodeURIComponent( userAns.validationAnswer ),
|
|
4019
|
-
Bucket: bucket.sop,
|
|
4020
|
-
} );
|
|
4004
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationAnswer = `${cdnurl.TraxAnswerCDN}${userAns.validationAnswer}`;
|
|
4021
4005
|
}
|
|
4022
4006
|
if ( [ 'image', 'descriptiveImage', 'video' ].includes( question.answerType ) && rawAnswer != '' ) {
|
|
4023
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].answer =
|
|
4024
|
-
file_path: decodeURIComponent( rawAnswer ),
|
|
4025
|
-
Bucket: bucket.sop,
|
|
4026
|
-
} );
|
|
4007
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].answer = `${cdnurl.TraxAnswerCDN}${rawAnswer}`;
|
|
4027
4008
|
}
|
|
4028
4009
|
if ( question.answerType == 'image/video' && rawAnswer != '' ) {
|
|
4029
4010
|
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].answer = `${cdnurl.TraxAnswerCDN}${rawAnswer}`;
|
|
4030
4011
|
}
|
|
4031
4012
|
if ( userAns.referenceImage != '' ) {
|
|
4032
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].referenceImage =
|
|
4033
|
-
file_path: decodeURIComponent( userAns.referenceImage ),
|
|
4034
|
-
Bucket: bucket.sop,
|
|
4035
|
-
} );
|
|
4013
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].referenceImage = `${cdnurl.TraxAnswerCDN}${userAns.referenceImage}`;
|
|
4036
4014
|
}
|
|
4037
4015
|
if ( question.answerType == 'multiplechoicemultiple' || ( question.answerType == 'dropdown' && question.allowMultiple ) ) {
|
|
4038
4016
|
let ansIndex = Multianswer.findIndex( ( item ) => item.answer == rawAnswer );
|
package/src/hbs/template.hbs
CHANGED
|
@@ -206,12 +206,12 @@
|
|
|
206
206
|
{{#if userAnswer.length}}
|
|
207
207
|
<span class="question">{{qno}}.</span> <span class="question">{{qname}}</span>
|
|
208
208
|
{{#neq questionReferenceImage ''}}
|
|
209
|
-
<div class="Reference"><span>Reference Image</span><br>
|
|
209
|
+
<div class="Reference"><span>Question Reference Image</span><br>
|
|
210
210
|
<img src="{{questionReferenceImage}}" width="200" height="180" />
|
|
211
211
|
</div>
|
|
212
212
|
{{/neq}}
|
|
213
213
|
{{#if multiQuestionReferenceImage.length}}
|
|
214
|
-
<div class="Reference"><span>Reference
|
|
214
|
+
<div class="Reference"><span>Question Reference Image</span><br>
|
|
215
215
|
{{#each multiQuestionReferenceImage}}
|
|
216
216
|
<img src="{{this}}" width="200" height="180" />
|
|
217
217
|
{{/each}}
|
|
@@ -274,15 +274,15 @@
|
|
|
274
274
|
{{!-- {{#each ../answers}} --}}
|
|
275
275
|
<table style="width:100%">
|
|
276
276
|
<tr>
|
|
277
|
-
{{#
|
|
277
|
+
{{#if multiReferenceImage.length}}
|
|
278
278
|
<td style="width:50%">
|
|
279
|
-
|
|
280
|
-
|
|
279
|
+
<div class="Reference"><span>Reference Image</span><br>
|
|
280
|
+
{{#each multiReferenceImage}}
|
|
281
281
|
<img src="{{this}}" width="200" height="180" />
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
{{/each}}
|
|
283
|
+
</div>
|
|
284
284
|
</td>
|
|
285
|
-
{{/
|
|
285
|
+
{{/if}}
|
|
286
286
|
<td style="width:50%">
|
|
287
287
|
<div class="Reference"><span>Uploaded Image</span><br>
|
|
288
288
|
<img src="{{answer}}" width="200" height="180" />
|
|
@@ -296,15 +296,15 @@
|
|
|
296
296
|
{{!-- {{#each ../answers}} --}}
|
|
297
297
|
<table style="width:100%">
|
|
298
298
|
<tr>
|
|
299
|
-
{{#
|
|
299
|
+
{{#if multiReferenceImage.length}}
|
|
300
300
|
<td style="width:50%">
|
|
301
|
-
{{#each multiReferenceImage}}
|
|
302
301
|
<div class="Reference"><span>Reference Image</span><br>
|
|
302
|
+
{{#each multiReferenceImage}}
|
|
303
303
|
<img src="{{this}}" width="200" height="180" />
|
|
304
|
+
{{/each}}
|
|
304
305
|
</div>
|
|
305
|
-
{{/each}}
|
|
306
306
|
</td>
|
|
307
|
-
{{/
|
|
307
|
+
{{/if}}
|
|
308
308
|
<td style="width:50%">
|
|
309
309
|
<div class="Reference"><span>Uploaded Image</span><br>
|
|
310
310
|
<img src="{{answer}}" width="200" height="180" />
|
|
@@ -318,13 +318,15 @@
|
|
|
318
318
|
{{!-- {{#each ../answers}} --}}
|
|
319
319
|
<table style="width:100%">
|
|
320
320
|
<tr>
|
|
321
|
-
{{#
|
|
321
|
+
{{#if multiReferenceImage.length}}
|
|
322
322
|
<td style="width:50%">
|
|
323
323
|
<div class="Reference"><span>Reference Image</span><br>
|
|
324
|
-
|
|
324
|
+
{{#each multiReferenceImage}}
|
|
325
|
+
<img src="{{this}}" width="200" height="180" />
|
|
326
|
+
{{/each}}
|
|
325
327
|
</div>
|
|
326
328
|
</td>
|
|
327
|
-
{{/
|
|
329
|
+
{{/if}}
|
|
328
330
|
{{#eq answerType 'image'}}
|
|
329
331
|
<td style="width:50%">
|
|
330
332
|
<div class="Reference"><span>Uploaded Image</span><br>
|
|
@@ -345,13 +347,15 @@
|
|
|
345
347
|
{{!-- {{#each ../answers}} --}}
|
|
346
348
|
<table style="width:100%">
|
|
347
349
|
<tr>
|
|
348
|
-
{{#
|
|
350
|
+
{{#if multiReferenceImage.length}}
|
|
349
351
|
<td style="width:50%">
|
|
350
352
|
<div class="Reference"><span>Reference Image</span><br>
|
|
351
|
-
|
|
353
|
+
{{#each multiReferenceImage}}
|
|
354
|
+
<img src="{{this}}" width="200" height="180" />
|
|
355
|
+
{{/each}}
|
|
352
356
|
</div>
|
|
353
357
|
</td>
|
|
354
|
-
{{/
|
|
358
|
+
{{/if}}
|
|
355
359
|
<td style="width:50%">
|
|
356
360
|
<div class="Reference"><span>Uploaded Image</span><br>
|
|
357
361
|
<img src="{{answer}}" width="200" height="180" />
|
|
@@ -181,13 +181,13 @@
|
|
|
181
181
|
</div>
|
|
182
182
|
{{#if this.questionReferenceImage}}
|
|
183
183
|
<div class="q-answer-media">
|
|
184
|
-
<div class="q-answer-caption">Reference Image</div>
|
|
184
|
+
<div class="q-answer-caption">Question Reference Image</div>
|
|
185
185
|
<img src="{{this.questionReferenceImage}}" alt="Reference Image" />
|
|
186
186
|
</div>
|
|
187
187
|
{{/if}}
|
|
188
188
|
{{#if this.multiQuestionReferenceImage.length}}
|
|
189
189
|
<div class="q-answer-media">
|
|
190
|
-
<div class="q-answer-caption">Reference Images</div>
|
|
190
|
+
<div class="q-answer-caption">Question Reference Images</div>
|
|
191
191
|
{{#each this.multiQuestionReferenceImage}}
|
|
192
192
|
<img src="{{this}}" alt="Reference Image" />
|
|
193
193
|
{{/each}}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
6
|
+
<title>Weekly Wrap</title>
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<style type="text/css">
|
|
9
|
+
body { font-family: "Inter", sans-serif !important; margin:0; padding:0; background:#dbe5ea; }
|
|
10
|
+
table { border-collapse: collapse; }
|
|
11
|
+
a { color:#1a82e2; }
|
|
12
|
+
.card {
|
|
13
|
+
background:#F4F8FB;
|
|
14
|
+
border-radius:8px;
|
|
15
|
+
padding:20px 22px;
|
|
16
|
+
}
|
|
17
|
+
.cardLabel { font-size:13px; color:#475569; margin-bottom:6px; }
|
|
18
|
+
.cardValue { font-size:22px; font-weight:700; color:#0F172A; }
|
|
19
|
+
.wow { font-size:12px; font-weight:600; margin-left:6px; }
|
|
20
|
+
.wow.up { color:#16A34A; }
|
|
21
|
+
.wow.down { color:#DC2626; }
|
|
22
|
+
.wowLabel { font-size:11px; color:#94A3B8; margin-left:4px; }
|
|
23
|
+
.sectionTitle { font-size:16px; font-weight:700; color:#0F172A; margin:18px 0 10px; }
|
|
24
|
+
.breakdownRow { font-size:14px; color:#1E293B; line-height:24px; }
|
|
25
|
+
.insightCard {
|
|
26
|
+
background:#EFF7FF;
|
|
27
|
+
border-radius:8px;
|
|
28
|
+
padding:20px 22px;
|
|
29
|
+
}
|
|
30
|
+
.attach {
|
|
31
|
+
background:#F1F5F9;
|
|
32
|
+
border-radius:8px;
|
|
33
|
+
padding:12px 14px;
|
|
34
|
+
display:flex;
|
|
35
|
+
align-items:center;
|
|
36
|
+
gap:10px;
|
|
37
|
+
font-size:13px;
|
|
38
|
+
color:#0F172A;
|
|
39
|
+
}
|
|
40
|
+
.ctaBtn {
|
|
41
|
+
display:inline-block;
|
|
42
|
+
background:#00A3FF;
|
|
43
|
+
color:#FFFFFF !important;
|
|
44
|
+
padding:14px 36px;
|
|
45
|
+
font-weight:600;
|
|
46
|
+
font-size:15px;
|
|
47
|
+
border-radius:8px;
|
|
48
|
+
text-decoration:none;
|
|
49
|
+
}
|
|
50
|
+
.footerText { font-size:12px; color:#475569; line-height:18px; }
|
|
51
|
+
</style>
|
|
52
|
+
</head>
|
|
53
|
+
<body>
|
|
54
|
+
<table width="100%" cellpadding="0" cellspacing="0" style="background:#dbe5ea;padding:24px 10px;">
|
|
55
|
+
<tr>
|
|
56
|
+
<td align="center" style="padding-top:10px;padding-bottom:10px">
|
|
57
|
+
<table width="100%" cellpadding="0" cellspacing="0" style="max-width:680px;background:#FFFFFF;border:1px dashed #93B6CC;border-radius:10px;overflow:hidden;">
|
|
58
|
+
|
|
59
|
+
{{!-- Header --}}
|
|
60
|
+
<tr>
|
|
61
|
+
<td style="padding:20px 24px 0 24px;">
|
|
62
|
+
<img src="https://devtangoretail-api.tangoeye.ai/logo.png" width="160" alt="TangoEye"/>
|
|
63
|
+
</td>
|
|
64
|
+
</tr>
|
|
65
|
+
|
|
66
|
+
{{!-- Banner --}}
|
|
67
|
+
<tr>
|
|
68
|
+
<td style="padding:14px 24px 0 24px;">
|
|
69
|
+
<div style="position:relative;background:#1F2A37;border-radius:10px;color:#FFFFFF;padding:36px 20px;text-align:center;">
|
|
70
|
+
<div style="font-size:22px;font-weight:700;">Weekly Wrap</div>
|
|
71
|
+
<div style="font-size:13px;color:#CBD5E1;margin-top:6px;">
|
|
72
|
+
Your Store's Flags Insights for {{data.startDate}} - {{data.endDate}}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</td>
|
|
76
|
+
</tr>
|
|
77
|
+
|
|
78
|
+
{{!-- Greeting --}}
|
|
79
|
+
<tr>
|
|
80
|
+
<td style="padding:18px 24px 0 24px;font-size:14px;color:#1E293B;line-height:22px;">
|
|
81
|
+
Hi Team,<br/>
|
|
82
|
+
Here is your weekly checklist insights report, highlighting key observations, recurring issues, and areas that may require attention to improve store performance and compliance.
|
|
83
|
+
</td>
|
|
84
|
+
</tr>
|
|
85
|
+
|
|
86
|
+
{{!-- Weekly Overview --}}
|
|
87
|
+
<tr>
|
|
88
|
+
<td style="padding:8px 24px 0 24px;">
|
|
89
|
+
<div class="sectionTitle">Weekly Overview</div>
|
|
90
|
+
<table width="100%" cellpadding="0" cellspacing="8">
|
|
91
|
+
<tr>
|
|
92
|
+
<td width="30%" style="padding-right:10px">
|
|
93
|
+
<div class="card">
|
|
94
|
+
<div class="cardLabel">Stores Flagged</div>
|
|
95
|
+
<div>
|
|
96
|
+
<span class="cardValue">{{data.storesFlagged}}</span>
|
|
97
|
+
{{#if data.storesFlaggedWow.value}}
|
|
98
|
+
<span class="wow {{data.storesFlaggedWow.direction}}">{{#eq data.storesFlaggedWow.direction "up"}}↗{{else}}↘{{/eq}} {{data.storesFlaggedWow.value}}</span>
|
|
99
|
+
<span class="wowLabel">WoW</span>
|
|
100
|
+
{{/if}}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</td>
|
|
104
|
+
<td width="30%" style="padding-right:10px">
|
|
105
|
+
<div class="card">
|
|
106
|
+
<div class="cardLabel">Total Flags</div>
|
|
107
|
+
<div>
|
|
108
|
+
<span class="cardValue">{{data.totalFlags}}</span>
|
|
109
|
+
{{#if data.totalFlagsWow.value}}
|
|
110
|
+
<span class="wow {{data.totalFlagsWow.direction}}">{{#eq data.totalFlagsWow.direction "up"}}↗{{else}}↘{{/eq}} {{data.totalFlagsWow.value}}</span>
|
|
111
|
+
<span class="wowLabel">WoW</span>
|
|
112
|
+
{{/if}}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</td>
|
|
116
|
+
<td width="30%" style="padding-right:10px">
|
|
117
|
+
<div class="card">
|
|
118
|
+
<div class="cardLabel">Checklist Flags</div>
|
|
119
|
+
<div>
|
|
120
|
+
<span class="cardValue">{{data.checklistFlags}}</span>
|
|
121
|
+
{{#if data.checklistFlagsWow.value}}
|
|
122
|
+
<span class="wow {{data.checklistFlagsWow.direction}}">{{#eq data.checklistFlagsWow.direction "up"}}↗{{else}}↘{{/eq}} {{data.checklistFlagsWow.value}}</span>
|
|
123
|
+
<span class="wowLabel">WoW</span>
|
|
124
|
+
{{/if}}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</td>
|
|
128
|
+
</tr>
|
|
129
|
+
</table>
|
|
130
|
+
</td>
|
|
131
|
+
</tr>
|
|
132
|
+
|
|
133
|
+
{{!-- Flags Breakdown --}}
|
|
134
|
+
<tr>
|
|
135
|
+
<td style="padding:8px 24px 0 24px;">
|
|
136
|
+
<div class="sectionTitle">Flags Breakdown</div>
|
|
137
|
+
<div class="breakdownRow">
|
|
138
|
+
Question Flags : {{data.questionFlags}}
|
|
139
|
+
Not Submitted Flags : {{data.notSubmittedFlags}}
|
|
140
|
+
Run AI Flags : {{data.runAIFlags}}
|
|
141
|
+
Recurring Flags : {{data.recurringFlags}}
|
|
142
|
+
</div>
|
|
143
|
+
</td>
|
|
144
|
+
</tr>
|
|
145
|
+
|
|
146
|
+
{{!-- Key Insights --}}
|
|
147
|
+
<tr>
|
|
148
|
+
<td style="padding:8px 24px 0 24px;">
|
|
149
|
+
<div class="sectionTitle">Key Insights</div>
|
|
150
|
+
<table width="100%" cellpadding="0" cellspacing="8">
|
|
151
|
+
<tr>
|
|
152
|
+
<td width="40%" style="padding-right:10px">
|
|
153
|
+
<div class="insightCard">
|
|
154
|
+
<div class="cardLabel">Highest Flagged Store</div>
|
|
155
|
+
<div>
|
|
156
|
+
<span class="cardValue">{{data.highestFlaggedStore}}</span>
|
|
157
|
+
{{#if data.highestFlaggedStoreWow.value}}
|
|
158
|
+
<span class="wow {{data.highestFlaggedStoreWow.direction}}">{{#eq data.highestFlaggedStoreWow.direction "up"}}↗{{else}}↘{{/eq}} {{data.highestFlaggedStoreWow.value}}</span>
|
|
159
|
+
<span class="wowLabel">WoW</span>
|
|
160
|
+
{{/if}}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</td>
|
|
164
|
+
<td width="40%">
|
|
165
|
+
<div class="insightCard">
|
|
166
|
+
<div class="cardLabel">Highest Recurring Flagged Store</div>
|
|
167
|
+
<div>
|
|
168
|
+
<span class="cardValue">{{data.highestRecurringStore}}</span>
|
|
169
|
+
{{#if data.highestRecurringStoreWow.value}}
|
|
170
|
+
<span class="wow {{data.highestRecurringStoreWow.direction}}">{{#eq data.highestRecurringStoreWow.direction "up"}}↗{{else}}↘{{/eq}} {{data.highestRecurringStoreWow.value}}</span>
|
|
171
|
+
<span class="wowLabel">WoW</span>
|
|
172
|
+
{{/if}}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</td>
|
|
176
|
+
</tr>
|
|
177
|
+
</table>
|
|
178
|
+
</td>
|
|
179
|
+
</tr>
|
|
180
|
+
|
|
181
|
+
{{!-- Attachment hint --}}
|
|
182
|
+
<tr>
|
|
183
|
+
<td style="padding:18px 24px 0 24px;">
|
|
184
|
+
<div style="color:#1D2939;font-family: Inter;font-weight: 400;font-style: Regular;font-size: 14px;leading-trim: NONE;line-height: 100%;letter-spacing: 0.2px;">
|
|
185
|
+
Refer to the attached report for the weekly summary of the stores assigned to you.
|
|
186
|
+
</div>
|
|
187
|
+
<div style="color:#384860;font-family: Inter;font-weight: 400;font-style: Regular;font-size: 10px;leading-trim: NONE;line-height: 150%;letter-spacing: 0.2px;padding-top:10px">
|
|
188
|
+
If you have any questions or need assistance, please reach out to us at support@tangotech.co.in.
|
|
189
|
+
</div>
|
|
190
|
+
{{!-- <div class="attach">
|
|
191
|
+
<span style="display:inline-block;width:22px;height:22px;background:#21A366;color:#FFFFFF;border-radius:4px;text-align:center;line-height:22px;font-size:12px;font-weight:700;">X</span>
|
|
192
|
+
<span><b>{{data.attachmentName}}</b>{{#if data.attachmentSize}} <span style="color:#94A3B8;">{{data.attachmentSize}}</span>{{/if}}</span>
|
|
193
|
+
</div> --}}
|
|
194
|
+
</td>
|
|
195
|
+
</tr>
|
|
196
|
+
|
|
197
|
+
{{!-- CTA --}}
|
|
198
|
+
<tr>
|
|
199
|
+
<td style="padding:18px 24px 8px 24px;text-align:center;">
|
|
200
|
+
<a href="{{data.domain}}" class="ctaBtn">View Detailed Insights</a>
|
|
201
|
+
</td>
|
|
202
|
+
</tr>
|
|
203
|
+
|
|
204
|
+
{{!-- Footer --}}
|
|
205
|
+
<tr>
|
|
206
|
+
<td style="padding:18px 24px 24px 24px;border-top:1px solid #E2E8F0;">
|
|
207
|
+
<div class="footerText">
|
|
208
|
+
©2024 Tango IT Solutions India Pvt. Ltd. All rights reserved.<br/>
|
|
209
|
+
Old Number 12 and New, 39, Haddows Rd, Thousand Lights West, Nungambakkam, Chennai, Tamil Nadu 600006
|
|
210
|
+
</div>
|
|
211
|
+
</td>
|
|
212
|
+
</tr>
|
|
213
|
+
</table>
|
|
214
|
+
</td>
|
|
215
|
+
</tr>
|
|
216
|
+
</table>
|
|
217
|
+
</body>
|
|
218
|
+
</html>
|
|
@@ -38,6 +38,7 @@ internalTraxRouter
|
|
|
38
38
|
.post( '/posblock', isAllowedInternalAPIHandler, internalController.getStoreTaskDetails )
|
|
39
39
|
.post( '/runAIFlag', isAllowedInternalAPIHandler, internalController.runAIFlag )
|
|
40
40
|
.post( '/recurringFlag', isAllowedInternalAPIHandler, internalController.recurringFlagAlert )
|
|
41
|
+
.post( '/weeklyWrap', isAllowedInternalAPIHandler, internalController.weeklyWrapAlert )
|
|
41
42
|
.post( '/downloadInsertPdf', isAllowedInternalAPIHandler, internalController.downloadInsertPdf )
|
|
42
43
|
.get( '/checklistAutoMailList', isAllowedInternalAPIHandler, internalController.checklistAutoMailList )
|
|
43
44
|
.post( '/sendAIEmailList', isAllowedInternalAPIHandler, internalController.sendAIEmailList )
|