tango-app-api-trax 3.8.21 → 3.8.22
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 +23 -47
- 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.22",
|
|
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
|
}
|
|
@@ -3579,7 +3573,7 @@ export async function questionList( req, res ) {
|
|
|
3579
3573
|
return res.sendError( 'Check List Got Edited Please Fill Again', 422 );
|
|
3580
3574
|
} else {
|
|
3581
3575
|
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 );
|
|
3576
|
+
// let bucket = JSON.parse( process.env.BUCKET );
|
|
3583
3577
|
let cdnurl = JSON.parse( process.env.CDNURL );
|
|
3584
3578
|
for ( let [ secIndex, section ] of getchecklist[0].questionAnswers.entries() ) {
|
|
3585
3579
|
for ( let [ questionIndex, question ] of section.questions.entries() ) {
|
|
@@ -3605,7 +3599,7 @@ export async function questionList( req, res ) {
|
|
|
3605
3599
|
let checkvalidation = null;
|
|
3606
3600
|
if ( answer.validationAnswer && answer.validationAnswer !='' ) {
|
|
3607
3601
|
if ( answer.validationType != 'Descriptive Answer' ) {
|
|
3608
|
-
checkvalidation =
|
|
3602
|
+
checkvalidation = `${cdnurl.TraxAnswerCDN}${answer.validationAnswer}`;
|
|
3609
3603
|
} else {
|
|
3610
3604
|
checkvalidation = answer.validationAnswer;
|
|
3611
3605
|
}
|
|
@@ -3626,23 +3620,23 @@ export async function questionList( req, res ) {
|
|
|
3626
3620
|
answer.referenceImage= `${cdnurl.TraxAnswerCDN}${answer.referenceImage}`;
|
|
3627
3621
|
}
|
|
3628
3622
|
if ( ( answer.validationType == 'Capture Image' || answer.validationType == 'Capture Video' ) && answer.validationAnswer && answer.validationAnswer != '' ) {
|
|
3629
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].validationAnswer =
|
|
3623
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].validationAnswer = `${cdnurl.TraxAnswerCDN}${answer.validationAnswer}`;
|
|
3630
3624
|
}
|
|
3631
3625
|
}
|
|
3632
3626
|
if ( question?.userAnswer ) {
|
|
3633
3627
|
for ( let [ userAnsIndex, userAns ] of question.userAnswer.entries() ) {
|
|
3634
3628
|
if ( ( userAns.validationType == 'Capture Image' || userAns.validationType == 'Capture Video' ) && userAns.validationAnswer && userAns.validationAnswer != '' ) {
|
|
3635
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationAnswer =
|
|
3629
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationAnswer = `${cdnurl.TraxAnswerCDN}${userAns.validationAnswer}`;
|
|
3636
3630
|
}
|
|
3637
3631
|
if ( userAns.validationType == 'Capture Multiple Image with description' ) {
|
|
3638
3632
|
let imageAnswers = userAns.validationImage;
|
|
3639
3633
|
userAns.validationImage = [];
|
|
3640
3634
|
for ( let image of imageAnswers ) {
|
|
3641
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationImage.push(
|
|
3635
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationImage.push( `${cdnurl.TraxAnswerCDN}${image}` );
|
|
3642
3636
|
}
|
|
3643
3637
|
}
|
|
3644
3638
|
if ( [ 'image', 'descriptiveImage', 'video' ].includes( question.answerType ) && userAns.answer != '' ) {
|
|
3645
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].answer
|
|
3639
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].answer =`${cdnurl.TraxAnswerCDN}${userAns.answer}`;
|
|
3646
3640
|
}
|
|
3647
3641
|
if ( userAns.referenceImage != '' ) {
|
|
3648
3642
|
// getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].referenceImage = await signedUrl( { file_path: decodeURIComponent( userAns.referenceImage ), Bucket: bucket.sop } );
|
|
@@ -3949,7 +3943,7 @@ export async function taskQuestionList( req, res ) {
|
|
|
3949
3943
|
return res.sendError( 'Task Got Edited Please Fill Again', 422 );
|
|
3950
3944
|
} else {
|
|
3951
3945
|
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 );
|
|
3946
|
+
// let bucket = JSON.parse( process.env.BUCKET );
|
|
3953
3947
|
let cdnurl = JSON.parse( process.env.CDNURL );
|
|
3954
3948
|
for ( let [ secIndex, section ] of getchecklist[0].questionAnswers.entries() ) {
|
|
3955
3949
|
for ( let [ questionIndex, question ] of section.questions.entries() ) {
|
|
@@ -3960,13 +3954,10 @@ export async function taskQuestionList( req, res ) {
|
|
|
3960
3954
|
if ( Array.isArray( questionReferenceImage ) ) {
|
|
3961
3955
|
questionReferenceImage = questionReferenceImage.filter( ( item ) => item != '' );
|
|
3962
3956
|
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].questionReferenceImage = await Promise.all(
|
|
3963
|
-
questionReferenceImage.map( async ( image ) => image ?
|
|
3957
|
+
questionReferenceImage.map( async ( image ) => image ? `${cdnurl.TraxAnswerCDN}${image}` : '' ),
|
|
3964
3958
|
);
|
|
3965
3959
|
} else if ( questionReferenceImage !== '' ) {
|
|
3966
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].questionReferenceImage =
|
|
3967
|
-
file_path: decodeURIComponent( questionReferenceImage ),
|
|
3968
|
-
Bucket: bucket.sop,
|
|
3969
|
-
} );
|
|
3960
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].questionReferenceImage = `${cdnurl.TraxAnswerCDN}${questionReferenceImage}`;
|
|
3970
3961
|
}
|
|
3971
3962
|
}
|
|
3972
3963
|
|
|
@@ -3977,13 +3968,10 @@ export async function taskQuestionList( req, res ) {
|
|
|
3977
3968
|
if ( Array.isArray( referenceImage ) ) {
|
|
3978
3969
|
referenceImage = referenceImage.filter( ( item ) => item != '' );
|
|
3979
3970
|
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].referenceImage = await Promise.all(
|
|
3980
|
-
referenceImage.map( async ( image ) => image ?
|
|
3971
|
+
referenceImage.map( async ( image ) => image ? `${cdnurl.TraxAnswerCDN}${image}` : '' ),
|
|
3981
3972
|
);
|
|
3982
3973
|
} else if ( referenceImage !== '' ) {
|
|
3983
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].referenceImage =
|
|
3984
|
-
file_path: decodeURIComponent( referenceImage ),
|
|
3985
|
-
Bucket: bucket.sop,
|
|
3986
|
-
} );
|
|
3974
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].referenceImage = `${cdnurl.TraxAnswerCDN}${referenceImage}`;
|
|
3987
3975
|
}
|
|
3988
3976
|
}
|
|
3989
3977
|
|
|
@@ -3991,7 +3979,7 @@ export async function taskQuestionList( req, res ) {
|
|
|
3991
3979
|
let checkvalidation = null;
|
|
3992
3980
|
if ( answer.validationAnswer && answer.validationAnswer != '' ) {
|
|
3993
3981
|
if ( answer.validationType != 'Descriptive Answer' ) {
|
|
3994
|
-
checkvalidation =
|
|
3982
|
+
checkvalidation = `${cdnurl.TraxAnswerCDN}${answer.validationAnswer}`;
|
|
3995
3983
|
} else {
|
|
3996
3984
|
checkvalidation = answer.validationAnswer;
|
|
3997
3985
|
}
|
|
@@ -4000,10 +3988,7 @@ export async function taskQuestionList( req, res ) {
|
|
|
4000
3988
|
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].index = ansIndex;
|
|
4001
3989
|
}
|
|
4002
3990
|
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
|
-
} );
|
|
3991
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].answers[ansIndex].validationAnswer = `${cdnurl.TraxAnswerCDN}${answer.validationAnswer}`;
|
|
4007
3992
|
}
|
|
4008
3993
|
}
|
|
4009
3994
|
|
|
@@ -4014,25 +3999,16 @@ export async function taskQuestionList( req, res ) {
|
|
|
4014
3999
|
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].linearType = question.answers[0].linearType;
|
|
4015
4000
|
}
|
|
4016
4001
|
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
|
-
} );
|
|
4002
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].validationAnswer = `${cdnurl.TraxAnswerCDN}${userAns.validationAnswer}`;
|
|
4021
4003
|
}
|
|
4022
4004
|
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
|
-
} );
|
|
4005
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].answer = `${cdnurl.TraxAnswerCDN}${rawAnswer}`;
|
|
4027
4006
|
}
|
|
4028
4007
|
if ( question.answerType == 'image/video' && rawAnswer != '' ) {
|
|
4029
4008
|
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].answer = `${cdnurl.TraxAnswerCDN}${rawAnswer}`;
|
|
4030
4009
|
}
|
|
4031
4010
|
if ( userAns.referenceImage != '' ) {
|
|
4032
|
-
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].referenceImage =
|
|
4033
|
-
file_path: decodeURIComponent( userAns.referenceImage ),
|
|
4034
|
-
Bucket: bucket.sop,
|
|
4035
|
-
} );
|
|
4011
|
+
getchecklist[0].questionAnswers[secIndex].questions[questionIndex].userAnswer[userAnsIndex].referenceImage = `${cdnurl.TraxAnswerCDN}${userAns.referenceImage}`;
|
|
4036
4012
|
}
|
|
4037
4013
|
if ( question.answerType == 'multiplechoicemultiple' || ( question.answerType == 'dropdown' && question.allowMultiple ) ) {
|
|
4038
4014
|
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 )
|