tango-app-api-trax 3.7.87 → 3.7.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tango-app-api-trax",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.89",
|
|
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.68",
|
|
33
33
|
"tango-app-api-middleware": "^3.5.2",
|
|
34
34
|
"url": "^0.11.4",
|
|
35
35
|
"winston": "^3.13.1",
|
|
@@ -18,7 +18,7 @@ import timeZone from 'dayjs/plugin/timezone.js';
|
|
|
18
18
|
import utc from 'dayjs/plugin/utc.js';
|
|
19
19
|
import { logger } from 'tango-app-api-middleware';
|
|
20
20
|
import mongoose from 'mongoose';
|
|
21
|
-
import { sendPushNotification, sendAiPushNotification, sendEmailWithSES } from 'tango-app-api-middleware';
|
|
21
|
+
import { sendPushNotification, sendAiPushNotification, sendEmailWithSES, signedUrl } from 'tango-app-api-middleware';
|
|
22
22
|
// import * as planoService from '../services/planogram.service.js';
|
|
23
23
|
import * as clusterServices from '../services/cluster.service.js';
|
|
24
24
|
import * as teamsServices from '../services/teams.service.js';
|
|
@@ -27,6 +27,9 @@ import * as runAIRequestServices from '../services/runAIRequest.services.js';
|
|
|
27
27
|
import * as traxApprover from '../services/approver.service.js';
|
|
28
28
|
import { insertSingleProcessData } from '../controllers/trax.controller.js';
|
|
29
29
|
import handlebars from './handlebar-helper.js';
|
|
30
|
+
import { buildVisitChecklistTemplateData, getCompiledVisitChecklistTemplate, createImageCache, resolveTemplateUrls } from '../utils/visitChecklistPdf.utils.js';
|
|
31
|
+
import archiver from 'archiver';
|
|
32
|
+
import puppeteer from 'puppeteer';
|
|
30
33
|
import fs from 'fs';
|
|
31
34
|
import path from 'path';
|
|
32
35
|
|
|
@@ -3375,9 +3378,10 @@ export async function getStoreTaskDetails( req, res ) {
|
|
|
3375
3378
|
export async function runAIFlag( req, res ) {
|
|
3376
3379
|
try {
|
|
3377
3380
|
let checklistDetails = await CLconfig.find( { publish: true, $expr: { $ne: [ { $size: '$notifyFlags.notifyType' }, 0 ] }, runAIQuestionCount: { $ne: 0 } }, { _id: 1, notifyFlags: 1, approver: 1 } );
|
|
3381
|
+
let date = dayjs().subtract( 1, 'day' ).format( 'YYYY-MM-DD' );
|
|
3378
3382
|
if ( checklistDetails.length ) {
|
|
3379
3383
|
await Promise.all( checklistDetails.map( async ( ele ) => {
|
|
3380
|
-
let submitDetails = await processedchecklist.find( { sourceCheckList_id: ele._id, checklistStatus: 'submit' }, { storeName: 1, checkListName: 1, questionAnswers: 1 } );
|
|
3384
|
+
let submitDetails = await processedchecklist.find( { sourceCheckList_id: ele._id, checklistStatus: 'submit', date_string: date }, { storeName: 1, checkListName: 1, questionAnswers: 1 } );
|
|
3381
3385
|
await Promise.all( submitDetails.map( ( store ) => {
|
|
3382
3386
|
if ( store.questionAnswers.length ) {
|
|
3383
3387
|
let runAIFlag = 0;
|
|
@@ -3424,3 +3428,312 @@ export async function runAIFlag( req, res ) {
|
|
|
3424
3428
|
return res.sendError( e, 500 );
|
|
3425
3429
|
}
|
|
3426
3430
|
}
|
|
3431
|
+
|
|
3432
|
+
async function getBrandInfo( clientId ) {
|
|
3433
|
+
const brandInfo = { clientName: 'lenskart', brandLogo: '' };
|
|
3434
|
+
|
|
3435
|
+
if ( !clientId ) return brandInfo;
|
|
3436
|
+
|
|
3437
|
+
const clientDetails = await clientService.findOne( { clientId }, { clientName: 1, profileDetails: 1 } );
|
|
3438
|
+
if ( !clientDetails?.clientName ) return brandInfo;
|
|
3439
|
+
|
|
3440
|
+
if ( clientDetails?.profileDetails?.logo ) {
|
|
3441
|
+
const bucketpath = clientId + '/logo';
|
|
3442
|
+
const params = {
|
|
3443
|
+
Bucket: JSON.parse( process.env.BUCKET )?.assets,
|
|
3444
|
+
file_path: `${bucketpath}/${clientDetails?.profileDetails?.logo}`,
|
|
3445
|
+
};
|
|
3446
|
+
const brandImage = await signedUrl( params );
|
|
3447
|
+
if ( brandImage ) {
|
|
3448
|
+
brandInfo.brandLogo = brandImage;
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
brandInfo.clientName = clientDetails.clientName;
|
|
3453
|
+
return brandInfo;
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
export const downloadInsertPdf = async ( req, res ) => {
|
|
3457
|
+
try {
|
|
3458
|
+
res.sendSuccess( 'PDF generation started' );
|
|
3459
|
+
|
|
3460
|
+
setImmediate( async () => {
|
|
3461
|
+
try {
|
|
3462
|
+
const cdnBase = JSON.parse( process.env.CDNURL )?.TraxAnswerCDN || '';
|
|
3463
|
+
const sourceEmail = JSON.parse( process.env.SES ).adminEmail;
|
|
3464
|
+
const todayStr = dayjs().format( 'YYYY-MM-DD' );
|
|
3465
|
+
|
|
3466
|
+
const pdfTemplate = getCompiledVisitChecklistTemplate();
|
|
3467
|
+
const imageCache = createImageCache();
|
|
3468
|
+
|
|
3469
|
+
const checklistInfoList = await CLconfig.find( {
|
|
3470
|
+
'autoEmail.type': { $exists: true },
|
|
3471
|
+
'$expr': {
|
|
3472
|
+
$gt: [ { $size: { $ifNull: [ '$autoEmail.type', [] ] } }, 0 ],
|
|
3473
|
+
},
|
|
3474
|
+
} );
|
|
3475
|
+
|
|
3476
|
+
const browser = await puppeteer.launch( {
|
|
3477
|
+
headless: 'new',
|
|
3478
|
+
args: [ '--no-sandbox', '--disable-dev-shm-usage' ],
|
|
3479
|
+
} );
|
|
3480
|
+
|
|
3481
|
+
try {
|
|
3482
|
+
const page = await browser.newPage();
|
|
3483
|
+
|
|
3484
|
+
await page.setViewport( {
|
|
3485
|
+
width: 1280,
|
|
3486
|
+
height: 1800,
|
|
3487
|
+
} );
|
|
3488
|
+
|
|
3489
|
+
const safeName = ( str ) =>
|
|
3490
|
+
( str || '' ).toString().replace( /[<>:"/\\|?*]+/g, '_' );
|
|
3491
|
+
for ( const checklistInfo of checklistInfoList ) {
|
|
3492
|
+
console.log( dayjs.utc().diff( dayjs.utc( checklistInfo.scheduleEndTime, 'hh:mm A' ), 'minute' ) );
|
|
3493
|
+
if ( dayjs.utc().diff( dayjs.utc( checklistInfo.scheduleEndTime, 'hh:mm A' ), 'minute' ) == 30 ) {
|
|
3494
|
+
let data = {
|
|
3495
|
+
checklistName: checklistInfo.checkListName,
|
|
3496
|
+
date: todayStr,
|
|
3497
|
+
total: checklistInfo?.storeCount,
|
|
3498
|
+
};
|
|
3499
|
+
const emailFileContent = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/autoEmail.hbs', 'utf8' );
|
|
3500
|
+
const emailHtmlContent = handlebars.compile( emailFileContent );
|
|
3501
|
+
const emailHtml = emailHtmlContent( { data: data } );
|
|
3502
|
+
let emailList = [];
|
|
3503
|
+
|
|
3504
|
+
if ( checklistInfo?.autoEmail?.type?.includes( 'approver' ) ) {
|
|
3505
|
+
emailList = ( checklistInfo.approver || [] )
|
|
3506
|
+
.map( ( e ) => e?.value )
|
|
3507
|
+
.filter( Boolean );
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
if ( Array.isArray( checklistInfo?.autoEmail?.users ) ) {
|
|
3511
|
+
emailList.push(
|
|
3512
|
+
...checklistInfo.autoEmail.users
|
|
3513
|
+
.map( ( e ) => e?.value )
|
|
3514
|
+
.filter( Boolean ),
|
|
3515
|
+
);
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
if ( !emailList.length ) continue;
|
|
3519
|
+
|
|
3520
|
+
const brandInfo = await getBrandInfo( checklistInfo.client_id );
|
|
3521
|
+
|
|
3522
|
+
const submittedDetails = await processedchecklist.find( {
|
|
3523
|
+
sourceCheckList_id: checklistInfo._id,
|
|
3524
|
+
checklistStatus: 'submit',
|
|
3525
|
+
date_string: todayStr,
|
|
3526
|
+
} );
|
|
3527
|
+
|
|
3528
|
+
if ( !submittedDetails.length ) continue;
|
|
3529
|
+
|
|
3530
|
+
// console.log(
|
|
3531
|
+
// `Processing checklist: ${checklistInfo.checkListName} (${submittedDetails.length} records)`,
|
|
3532
|
+
// );
|
|
3533
|
+
|
|
3534
|
+
const ZIP_LIMIT = 100;
|
|
3535
|
+
|
|
3536
|
+
for ( let i = 0; i < submittedDetails.length; i += ZIP_LIMIT ) {
|
|
3537
|
+
const batch = submittedDetails.slice( i, i + ZIP_LIMIT );
|
|
3538
|
+
|
|
3539
|
+
const zipName = `${safeName(
|
|
3540
|
+
checklistInfo.checkListName,
|
|
3541
|
+
)}_${todayStr}_part_${Math.floor( i / ZIP_LIMIT ) + 1}.zip`;
|
|
3542
|
+
|
|
3543
|
+
const zipPath = path.join( '/tmp', zipName );
|
|
3544
|
+
|
|
3545
|
+
const output = fs.createWriteStream( zipPath );
|
|
3546
|
+
const archive = archiver( 'zip', { zlib: { level: 9 } } );
|
|
3547
|
+
|
|
3548
|
+
archive.on( 'error', ( err ) => {
|
|
3549
|
+
logger.error( { message: 'Archiver error', error: err } );
|
|
3550
|
+
// console.error( '❌ Archiver error:', err );
|
|
3551
|
+
throw err;
|
|
3552
|
+
} );
|
|
3553
|
+
|
|
3554
|
+
archive.pipe( output );
|
|
3555
|
+
|
|
3556
|
+
let appendedCount = 0;
|
|
3557
|
+
|
|
3558
|
+
for ( const checklistDetails of batch ) {
|
|
3559
|
+
const doc =
|
|
3560
|
+
typeof checklistDetails.toObject === 'function' ?
|
|
3561
|
+
checklistDetails.toObject() :
|
|
3562
|
+
{ ...checklistDetails };
|
|
3563
|
+
|
|
3564
|
+
|
|
3565
|
+
const detectionPayload = {
|
|
3566
|
+
'storeId': [ doc.store_id ],
|
|
3567
|
+
'userEmail': doc.userEmail,
|
|
3568
|
+
'sourceChecklist_id': doc.sourceCheckList_id,
|
|
3569
|
+
};
|
|
3570
|
+
|
|
3571
|
+
|
|
3572
|
+
let complianceURL = JSON.parse( process.env.LAMBDAURL ).complianceHistory;
|
|
3573
|
+
const complianceData = await LamdaServiceCall( complianceURL, detectionPayload );
|
|
3574
|
+
if ( complianceData?.data.length ) {
|
|
3575
|
+
doc['historyData'] = complianceData.data;
|
|
3576
|
+
}
|
|
3577
|
+
// CDN fix
|
|
3578
|
+
( doc.questionAnswers || [] ).forEach( ( section ) => {
|
|
3579
|
+
( section.questions || [] ).forEach( ( question ) => {
|
|
3580
|
+
( question.userAnswer || [] ).forEach( ( answer ) => {
|
|
3581
|
+
if ( answer?.referenceImage?.trim() ) {
|
|
3582
|
+
answer.referenceImage =
|
|
3583
|
+
cdnBase + answer.referenceImage;
|
|
3584
|
+
}
|
|
3585
|
+
} );
|
|
3586
|
+
} );
|
|
3587
|
+
} );
|
|
3588
|
+
|
|
3589
|
+
const templateData = buildVisitChecklistTemplateData(
|
|
3590
|
+
doc,
|
|
3591
|
+
brandInfo,
|
|
3592
|
+
);
|
|
3593
|
+
|
|
3594
|
+
const resolvedData = resolveTemplateUrls( templateData, cdnBase );
|
|
3595
|
+
await imageCache.resolveAllImages( resolvedData );
|
|
3596
|
+
|
|
3597
|
+
const html = pdfTemplate( resolvedData );
|
|
3598
|
+
|
|
3599
|
+
// console.log( 'HTML length:', html?.length );
|
|
3600
|
+
|
|
3601
|
+
if ( !html || html.length < 50 ) {
|
|
3602
|
+
logger.error( { functionName: 'Invalid HTML skipped:', error: doc._id } );
|
|
3603
|
+
// console.error( '❌ Invalid HTML skipped:', doc._id );
|
|
3604
|
+
continue;
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
try {
|
|
3608
|
+
await page.setContent( html, {
|
|
3609
|
+
waitUntil: 'domcontentloaded',
|
|
3610
|
+
timeout: 0,
|
|
3611
|
+
} );
|
|
3612
|
+
|
|
3613
|
+
await new Promise( ( r ) => setTimeout( r, 300 ) );
|
|
3614
|
+
await page.emulateMediaType( 'screen' );
|
|
3615
|
+
} catch ( err ) {
|
|
3616
|
+
logger.error( { functionName: 'setContent failed:', error: err } );
|
|
3617
|
+
// console.error( '❌ setContent failed:', err );
|
|
3618
|
+
continue;
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
let pdfBuffer;
|
|
3622
|
+
|
|
3623
|
+
try {
|
|
3624
|
+
pdfBuffer = await page.pdf( {
|
|
3625
|
+
format: 'A4',
|
|
3626
|
+
printBackground: true,
|
|
3627
|
+
margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
|
|
3628
|
+
} );
|
|
3629
|
+
|
|
3630
|
+
// console.log( 'PDF size:', pdfBuffer?.length );
|
|
3631
|
+
// console.log(
|
|
3632
|
+
// 'IsBuffer:',
|
|
3633
|
+
// Buffer.isBuffer( pdfBuffer ),
|
|
3634
|
+
// 'Type:',
|
|
3635
|
+
// pdfBuffer?.constructor?.name,
|
|
3636
|
+
// );
|
|
3637
|
+
} catch ( err ) {
|
|
3638
|
+
logger.error( { functionName: 'downloadInsertPdf', message: 'PDF generation failed', error: err } );
|
|
3639
|
+
continue;
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
if ( !pdfBuffer || pdfBuffer.length === 0 ) {
|
|
3643
|
+
logger.error( { functionName: 'downloadInsertPdf', message: 'Empty PDF skipped', docId: doc._id } );
|
|
3644
|
+
continue;
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
const finalBuffer = Buffer.isBuffer( pdfBuffer ) ?
|
|
3648
|
+
pdfBuffer :
|
|
3649
|
+
Buffer.from( pdfBuffer );
|
|
3650
|
+
|
|
3651
|
+
const pdfName = `${safeName(
|
|
3652
|
+
doc.storeName || doc.store_id || doc._id || 'store',
|
|
3653
|
+
)}.pdf`;
|
|
3654
|
+
|
|
3655
|
+
archive.append( finalBuffer, { name: pdfName } );
|
|
3656
|
+
appendedCount++;
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
if ( appendedCount === 0 ) {
|
|
3660
|
+
logger.error( { functionName: 'No PDFs generated for batch, skipping ZIP', error: zipName } );
|
|
3661
|
+
// console.error( `❌ No PDFs generated for batch, skipping ZIP: ${zipName}` );
|
|
3662
|
+
archive.abort();
|
|
3663
|
+
output.destroy();
|
|
3664
|
+
continue;
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
// console.log( `📄 ${appendedCount} PDFs added to ${zipName}` );
|
|
3668
|
+
|
|
3669
|
+
await new Promise( ( resolve, reject ) => {
|
|
3670
|
+
output.on( 'close', resolve );
|
|
3671
|
+
output.on( 'error', reject );
|
|
3672
|
+
archive.on( 'error', reject );
|
|
3673
|
+
|
|
3674
|
+
archive.finalize();
|
|
3675
|
+
} );
|
|
3676
|
+
|
|
3677
|
+
const zipBuffer = fs.readFileSync( zipPath );
|
|
3678
|
+
// console.log( `✅ ZIP ready: ${zipName}, archive pointer: ${archive.pointer()} bytes, file size: ${zipBuffer.length} bytes` );
|
|
3679
|
+
|
|
3680
|
+
const attachment = {
|
|
3681
|
+
filename: zipName,
|
|
3682
|
+
content: zipBuffer,
|
|
3683
|
+
contentType: 'application/zip',
|
|
3684
|
+
};
|
|
3685
|
+
|
|
3686
|
+
await Promise.all(
|
|
3687
|
+
emailList.map( ( email ) =>
|
|
3688
|
+
sendEmailWithSES(
|
|
3689
|
+
email,
|
|
3690
|
+
'Checklist Report',
|
|
3691
|
+
emailHtml,
|
|
3692
|
+
attachment,
|
|
3693
|
+
sourceEmail,
|
|
3694
|
+
),
|
|
3695
|
+
),
|
|
3696
|
+
);
|
|
3697
|
+
|
|
3698
|
+
// console.log( `📧 Emails sent for ${zipName}` );
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
} finally {
|
|
3703
|
+
await browser.close();
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
// console.log( '🎉 All processing completed' );
|
|
3707
|
+
} catch ( err ) {
|
|
3708
|
+
logger.error( { functionName: 'Background job failed', error: err } );
|
|
3709
|
+
// console.error( '❌ Background job failed:', err );
|
|
3710
|
+
}
|
|
3711
|
+
} );
|
|
3712
|
+
} catch ( e ) {
|
|
3713
|
+
logger.error( { functionName: 'downloadInsertPdf', error: e } );
|
|
3714
|
+
return res.sendError( e, 500 );
|
|
3715
|
+
}
|
|
3716
|
+
};
|
|
3717
|
+
|
|
3718
|
+
async function LamdaServiceCall( url, data ) {
|
|
3719
|
+
try {
|
|
3720
|
+
const requestOptions = {
|
|
3721
|
+
method: 'POST',
|
|
3722
|
+
headers: {
|
|
3723
|
+
'Content-Type': 'application/json',
|
|
3724
|
+
},
|
|
3725
|
+
body: JSON.stringify( data ),
|
|
3726
|
+
};
|
|
3727
|
+
const response = await fetch( url, requestOptions );
|
|
3728
|
+
if ( !response.ok ) {
|
|
3729
|
+
throw new Error( `Response status: ${response.status}` );
|
|
3730
|
+
return false;
|
|
3731
|
+
}
|
|
3732
|
+
const json = await response.json();
|
|
3733
|
+
return json;
|
|
3734
|
+
} catch ( error ) {
|
|
3735
|
+
console.log( error );
|
|
3736
|
+
logger.error( { error: error, message: data, function: 'LamdaServiceCall' } );
|
|
3737
|
+
return false;
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
|
|
6
|
+
<meta charset="utf-8">
|
|
7
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
8
|
+
<title>Trial Intiate Email</title>
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
10
|
+
<style type="text/css">
|
|
11
|
+
@media screen {
|
|
12
|
+
@font-face {
|
|
13
|
+
font-family: 'Inter';
|
|
14
|
+
font-style: normal;
|
|
15
|
+
font-weight: 400;
|
|
16
|
+
font-display: swap;
|
|
17
|
+
src: local("Inter"), local("Inter-Regular"), url(https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZJhiI2B.woff2) format('woff2');
|
|
18
|
+
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
body {
|
|
23
|
+
font-family: "Inter", sans-serif !important;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body,
|
|
27
|
+
table,
|
|
28
|
+
td,
|
|
29
|
+
a {
|
|
30
|
+
-ms-text-size-adjust: 100%;
|
|
31
|
+
-webkit-text-size-adjust: 100%;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
table,
|
|
35
|
+
td {
|
|
36
|
+
mso-table-rspace: 0pt;
|
|
37
|
+
mso-table-lspace: 0pt;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
img {
|
|
41
|
+
-ms-interpolation-mode: bicubic;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
a[x-apple-data-detectors] {
|
|
45
|
+
font-family: "inherit" !important;
|
|
46
|
+
font-size: inherit !important;
|
|
47
|
+
font-weight: inherit !important;
|
|
48
|
+
line-height: inherit !important;
|
|
49
|
+
color: inherit !important;
|
|
50
|
+
text-decoration: none !important;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
div[style*="margin: 16px 0;"
|
|
55
|
+
|
|
56
|
+
] {
|
|
57
|
+
margin: 0 !important;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
body {
|
|
61
|
+
width: 100% !important;
|
|
62
|
+
height: 100% !important;
|
|
63
|
+
padding: 0 !important;
|
|
64
|
+
margin: 0 !important;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
table {
|
|
69
|
+
border-collapse: collapse !important;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
a {
|
|
73
|
+
color: #1a82e2;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
img {
|
|
77
|
+
height: auto;
|
|
78
|
+
line-height: 100%;
|
|
79
|
+
text-decoration: none;
|
|
80
|
+
border: 0;
|
|
81
|
+
outline: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.flagText {
|
|
85
|
+
/* font-family: 'Inter'; */
|
|
86
|
+
/* color: #667085 !important; */
|
|
87
|
+
font-size: 16px;
|
|
88
|
+
font-weight: 600;
|
|
89
|
+
line-height: 24px;
|
|
90
|
+
text-align: left;
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
94
|
+
|
|
95
|
+
</head>
|
|
96
|
+
|
|
97
|
+
<body style="background-color: #dbe5ea;">
|
|
98
|
+
|
|
99
|
+
<div class="preheader"
|
|
100
|
+
style="display: none; max-width: 0; max-height: 0; overflow: hidden; font-size: 1px; line-height: 1px; color: #fff; opacity: 0;">
|
|
101
|
+
Email Summary (Hidden)
|
|
102
|
+
</div>
|
|
103
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="padding-left:10px;padding-right:10px">
|
|
104
|
+
<tr>
|
|
105
|
+
<td bgcolor="#dbe5ea" style="padding:32px 10px 0 10px">
|
|
106
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 680px;" align="center">
|
|
107
|
+
<tr>
|
|
108
|
+
<td class="o_bg-white o_px-md o_py-md o_sans o_text"
|
|
109
|
+
style="margin-top: 0px;margin-bottom: 0px;font-size: 16px;line-height: 24px;background-color: #ffffff;padding-left: 18px;padding-right: 24px;padding-top: 24px;padding-bottom: 24px;">
|
|
110
|
+
<p style="margin-top: 0px;margin-bottom: 0px;"><a class="o_text-white"
|
|
111
|
+
href="https://tangoeye.ai/"
|
|
112
|
+
style="text-decoration: none;outline: none;color: #ffffff;"><img
|
|
113
|
+
src="https://devtangoretail-api.tangoeye.ai/logo.png" width="200" height="100"
|
|
114
|
+
alt="SimpleApp"
|
|
115
|
+
style="-ms-interpolation-mode: bicubic;vertical-align: middle;border: 0;line-height: 100%;height: auto;outline: none;text-decoration: none;"></a>
|
|
116
|
+
</p>
|
|
117
|
+
</td>
|
|
118
|
+
</tr>
|
|
119
|
+
<tr>
|
|
120
|
+
<td align="left" bgcolor="#ffffff"
|
|
121
|
+
style="padding-left: 30px;padding-right: 24px; font-size: 14px; line-height: 24px;">
|
|
122
|
+
<p class="o_heading o_mb-xxs"
|
|
123
|
+
style="width: 624px;height: 0px;border: 1px solid #CBD5E1;flex: none;order: 1;flex-grow: 0;">
|
|
124
|
+
</p>
|
|
125
|
+
</td>
|
|
126
|
+
</tr>
|
|
127
|
+
<tr>
|
|
128
|
+
<td class="o_bg-white o_px-md o_py-xl o_xs-py-md"
|
|
129
|
+
style="background-color: #ffffff;padding-left: 30px;padding-right: 24px;padding-bottom: 10px;">
|
|
130
|
+
<div class="o_col-6s o_sans o_text-md o_text-light o_center"
|
|
131
|
+
style="margin-top: 0px;margin-bottom: 0px;font-size: 16px;line-height: 28px;color: #82899a;">
|
|
132
|
+
<span class="o_heading o_text-dark o_mb-xxs"
|
|
133
|
+
style="font-weight: 400;margin-top: 0px;margin-bottom: 4px;color: #121A26;line-height: 140%;">
|
|
134
|
+
Hi,<br />
|
|
135
|
+
Store compliance summary has been generated for a recently completed checklist across multiple stores. Review the
|
|
136
|
+
compliance performance and identify stores that require attention from the dashboard.
|
|
137
|
+
</span>
|
|
138
|
+
</div>
|
|
139
|
+
</td>
|
|
140
|
+
</tr>
|
|
141
|
+
<tr>
|
|
142
|
+
<td class="o_bg-white o_px-md o_py-xl o_xs-py-md"
|
|
143
|
+
style="background-color: #ffffff;padding-left: 30px;padding-right: 24px;padding-bottom: 10px;">
|
|
144
|
+
<div class="o_col-6s o_sans o_text-md o_text-light o_center"
|
|
145
|
+
style="margin-top: 0px;margin-bottom: 0px;font-size: 16px;line-height: 28px;color: #82899a;">
|
|
146
|
+
<span class="o_heading o_text-dark o_mb-xxs"
|
|
147
|
+
style="font-weight: 400;margin-top: 0px;margin-bottom: 4px;color: #121A26;line-height: 140%;">
|
|
148
|
+
Details of the checklist are as follows:
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
</td>
|
|
152
|
+
</tr>
|
|
153
|
+
</table>
|
|
154
|
+
</td>
|
|
155
|
+
</tr>
|
|
156
|
+
<tr>
|
|
157
|
+
<td align="center" bgcolor="#dbe5ea" style="padding:0 10px 0 10px">
|
|
158
|
+
<table align="center" bgcolor="#ffffff" border="0" cellpadding="0" cellspacing="0" width="100%"
|
|
159
|
+
style="max-width: 680px;">
|
|
160
|
+
<tr bgcolor="#ffffff" style="border:none;margin-top:0px;">
|
|
161
|
+
<td class="flagText" style="padding-left:30px; line-height: 24px;color:#000000">Checklist Name:
|
|
162
|
+
</td>
|
|
163
|
+
<td></td>
|
|
164
|
+
<td></td>
|
|
165
|
+
<td class="flagText">{{data.checklistName}}</td>
|
|
166
|
+
</tr>
|
|
167
|
+
<tr bgcolor="#ffffff" style="border:none;margin-top:3px;">
|
|
168
|
+
<td class="flagText" style="padding-left:30px; line-height: 24px;">Total Stores :</td>
|
|
169
|
+
<td></td>
|
|
170
|
+
<td></td>
|
|
171
|
+
<td class="flagText">{{data.total}}</td>
|
|
172
|
+
</tr>
|
|
173
|
+
|
|
174
|
+
<tr bgcolor="#ffffff" style="border:none;margin-top:3px;">
|
|
175
|
+
<td class="flagText" style="padding-left:30px; line-height: 24px;">Date :</td>
|
|
176
|
+
<td></td>
|
|
177
|
+
<td></td>
|
|
178
|
+
<td class="flagText">{{data.date}}</td>
|
|
179
|
+
</tr>
|
|
180
|
+
</table>
|
|
181
|
+
</td>
|
|
182
|
+
</tr>
|
|
183
|
+
<tr>
|
|
184
|
+
<td align="center" bgcolor="#dbe5ea" style="padding:0 10px 32px 10px;">
|
|
185
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 680px;">
|
|
186
|
+
<tr>
|
|
187
|
+
<td class="o_bg-white o_px-md o_py-xl o_xs-py-md"
|
|
188
|
+
style="background-color: #ffffff;padding-left: 30px;padding-right: 24px;padding-top:5px">
|
|
189
|
+
<div class="o_col-6s o_sans o_text-md o_text-light o_center"
|
|
190
|
+
style="margin-top: 0px;margin-bottom: 0px;font-size: 12px;color: #202B3C;font-style: normal;font-weight: 400;font-size: 12px;line-height: 150%;">
|
|
191
|
+
<p>
|
|
192
|
+
If you have any questions or need assistance, please reach out to us at
|
|
193
|
+
support@tangotech.co.in.
|
|
194
|
+
</p>
|
|
195
|
+
</div>
|
|
196
|
+
</td>
|
|
197
|
+
</tr>
|
|
198
|
+
<tr>
|
|
199
|
+
<td class="o_bg-white o_px-md o_py-xl o_xs-py-md"
|
|
200
|
+
style="background-color: #ffffff;padding-left: 30px;padding-right: 24px;padding-bottom:15px">
|
|
201
|
+
<div class="o_col-6s o_sans o_text-md o_text-light o_center"
|
|
202
|
+
style="margin-top: 0px;margin-bottom: 0px;font-size: 12px;color: #202B3C;font-style: normal;font-weight: 400;font-size: 12px;line-height: 150%;">
|
|
203
|
+
<p>
|
|
204
|
+
© Tango Eye. All rights reserved.</p>
|
|
205
|
+
</div>
|
|
206
|
+
</td>
|
|
207
|
+
</tr>
|
|
208
|
+
</table>
|
|
209
|
+
</td>
|
|
210
|
+
</tr>
|
|
211
|
+
|
|
212
|
+
</table>
|
|
213
|
+
|
|
214
|
+
</body>
|
|
215
|
+
|
|
216
|
+
</html>
|
|
@@ -50,10 +50,12 @@
|
|
|
50
50
|
.pct-mid{background:#faeeda;color:#854f0b}
|
|
51
51
|
.pct-lo{background:#fcebeb;color:#a32d2d}
|
|
52
52
|
/* Detail pages */
|
|
53
|
-
.detail-page{padding:32px 40px}
|
|
53
|
+
.detail-page{padding:32px 40px;break-inside:auto}
|
|
54
|
+
.q-row{break-inside:avoid}
|
|
55
|
+
.q-answer-item{break-inside:avoid}
|
|
54
56
|
.dp-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:12px;border-bottom:2px solid #00AEEF}
|
|
55
57
|
.dp-header h2{font-size:18px;font-weight:700;color:#1a1a2e}
|
|
56
|
-
.dp-score{font-size:15px;font-weight:700;color:#
|
|
58
|
+
.dp-score{font-size:15px;font-weight:700;color:#000000}
|
|
57
59
|
.q-row{display:flex;gap:12px;margin-bottom:14px;padding:12px;border-radius:8px;background:transparent}
|
|
58
60
|
.q-num{font-size:12px;font-weight:700;color:#000;min-width:22px}
|
|
59
61
|
.q-body{flex:1}
|
|
@@ -114,7 +116,7 @@
|
|
|
114
116
|
</div>
|
|
115
117
|
|
|
116
118
|
<div class="cover-footer">
|
|
117
|
-
<span class="cover-footer-page"
|
|
119
|
+
<span class="cover-footer-page"></span>
|
|
118
120
|
<div class="cover-footer-gen">
|
|
119
121
|
<span class="footer-gen-by">Generated by</span>
|
|
120
122
|
{{> tangoyeFooterLogo gid="cover"}}
|
|
@@ -154,19 +156,19 @@
|
|
|
154
156
|
{{#each sectionInsights}}
|
|
155
157
|
<tr>
|
|
156
158
|
<td>{{this.sectionName}}</td>
|
|
157
|
-
<td>{{this.
|
|
158
|
-
<td>{{this.
|
|
159
|
-
<td
|
|
159
|
+
<td>{{this.targetScore}}</td>
|
|
160
|
+
<td>{{this.actualScore}}</td>
|
|
161
|
+
<td>{{this.percentage}}%</td>
|
|
160
162
|
</tr>
|
|
161
163
|
{{/each}}
|
|
162
164
|
</tbody>
|
|
163
165
|
</table>
|
|
164
166
|
</div>
|
|
165
|
-
<div class="page-footer">
|
|
167
|
+
{{!-- <div class="page-footer">
|
|
166
168
|
<span>Page 2 of {{totalPages}}</span>
|
|
167
169
|
<span class="footer-brand"><span class="footer-gen-by">Generated by</span>{{> tangoyeFooterLogo gid="p2"}}</span>
|
|
168
170
|
</div>
|
|
169
|
-
</div>
|
|
171
|
+
</div> --}}
|
|
170
172
|
{{/if}}
|
|
171
173
|
|
|
172
174
|
{{!-- PAGES 3+: DETAIL - Question sections --}}
|
|
@@ -174,13 +176,18 @@
|
|
|
174
176
|
<div class="page">
|
|
175
177
|
<div class="detail-page">
|
|
176
178
|
{{#each this.sections}}
|
|
177
|
-
<div class="dp-header" {{#unless @first}}style="margin-top:20px"{{/unless}}><h2>{{this.sectionName}}</h2><span class="dp-score">{{this.currentScore}}/{{this.maxScore}}
|
|
179
|
+
<div class="dp-header" {{#unless @first}}style="margin-top:20px"{{/unless}}><h2>{{this.sectionName}}</h2><span class="dp-score">{{this.currentScore}}/{{this.maxScore}}</span></div>
|
|
178
180
|
<div class="sec-questions">
|
|
179
181
|
{{#each this.questions}}
|
|
180
182
|
<div class="q-row">
|
|
181
183
|
<span class="q-num">{{this.qno}}</span>
|
|
182
184
|
<div class="q-body">
|
|
183
|
-
<div class="q-text"
|
|
185
|
+
<div class="q-text" style="display:flex;justify-content:space-between">
|
|
186
|
+
<div>{{this.qname}}</div>
|
|
187
|
+
{{#if this.compliance}}
|
|
188
|
+
<div>Score:{{this.score}}</div>
|
|
189
|
+
{{/if}}
|
|
190
|
+
</div>
|
|
184
191
|
{{!-- <span class="q-ans {{#if this.isYes}}ans-yes{{else}}{{#if this.isNo}}ans-no{{/if}}{{/if}}">{{#if this.isYes}}✓ Yes{{else}}{{#if this.isNo}}✗ No{{else}}{{this.answerDisplay}}{{/if}}{{/if}}</span> --}}
|
|
185
192
|
{{#if this.userAnswer.length}}
|
|
186
193
|
<div class="q-answer-list">
|
|
@@ -241,19 +248,19 @@
|
|
|
241
248
|
</div>
|
|
242
249
|
{{/each}}
|
|
243
250
|
|
|
244
|
-
{{#if this.isLastGroup}}
|
|
251
|
+
{{!-- {{#if this.isLastGroup}}
|
|
245
252
|
{{#each ../flags}}
|
|
246
253
|
<div style="margin-top:12px;padding:12px;background:#faeeda;border-radius:8px;border-left:4px solid #ef9f27">
|
|
247
254
|
<strong style="font-size:13px;color:#854f0b">⚠ Flag: {{this.sectionName}} — {{this.qname}}</strong>
|
|
248
255
|
<p style="font-size:12px;color:#854f0b;margin-top:4px">Q{{this.qno}} ({{this.sectionName}}): "{{this.qname}}" — Answered: <strong>{{this.answer}}</strong>. Action required.</p>
|
|
249
256
|
</div>
|
|
250
257
|
{{/each}}
|
|
251
|
-
{{/if}}
|
|
258
|
+
{{/if}} --}}
|
|
252
259
|
</div>
|
|
253
|
-
<div class="page-footer">
|
|
260
|
+
{{!-- <div class="page-footer">
|
|
254
261
|
<span>Page {{this.pageNumber}} of {{../totalPages}}</span>
|
|
255
262
|
<span class="footer-brand"><span class="footer-gen-by">Generated by</span>{{> tangoyeFooterLogo gid=(strConcat 'd' @index)}}</span>
|
|
256
|
-
</div>
|
|
263
|
+
</div> --}}
|
|
257
264
|
</div>
|
|
258
265
|
{{/each}}
|
|
259
266
|
|
|
@@ -37,6 +37,7 @@ internalTraxRouter
|
|
|
37
37
|
.post( '/getSubmissionDetails', isAllowedInternalAPIHandler, internalController.checklistTaskSubmissionDetails )
|
|
38
38
|
.post( '/posblock', isAllowedInternalAPIHandler, internalController.getStoreTaskDetails )
|
|
39
39
|
.post( '/runAIFlag', isAllowedInternalAPIHandler, internalController.runAIFlag )
|
|
40
|
+
.post( '/downloadInsertPdf', isAllowedInternalAPIHandler, internalController.downloadInsertPdf )
|
|
40
41
|
;
|
|
41
42
|
|
|
42
43
|
|