tango-app-api-trax 3.7.86 → 3.7.88
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 +315 -2
- package/src/controllers/mobileTrax.controller.js +5 -3
- package/src/hbs/autoEmail.hbs +216 -0
- package/src/hbs/visit-checklist.hbs +21 -14
- package/src/routes/internalTraxApi.router.js +1 -0
- package/src/utils/visitChecklistPdf.utils.js +558 -258
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.88",
|
|
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().diff( dayjs( 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
|
+
}
|
|
@@ -666,10 +666,12 @@ export async function sopMobilechecklistQuestionValidatorv1( req, res, next ) {
|
|
|
666
666
|
let sectionQuestion = requestSection.filter( ( secQuestion ) => secQuestion.qname == question.oldQname || secQuestion.qname == question.qname );
|
|
667
667
|
if ( sectionQuestion.length ) {
|
|
668
668
|
if ( requestData.submittype == 'submit' ) {
|
|
669
|
-
if ( (
|
|
670
|
-
|
|
669
|
+
if ( ( [ 'multipleImage', 'multiplechoicemultiple' ].includes( question.answerType ) || ( question.answerType =='dropdown' && question.allowMultiple ) ) ) {
|
|
670
|
+
if ( ( !sectionQuestion[0].linkType || ( sectionQuestion[0].linkType && sectionQuestion[0].linkquestionenabled ) ) && ( sectionQuestion[0].Multianswer == null || sectionQuestion[0].Multianswer == '' || !sectionQuestion[0].Multianswer.length ) ) {
|
|
671
|
+
validationCount++;
|
|
672
|
+
}
|
|
671
673
|
} else {
|
|
672
|
-
if ( ![ 'multiplechoicemultiple', 'multipleImage' ].includes( question.answerType ) && ( question.answerType =='dropdown' && !question.allowMultiple ) && ( ( !sectionQuestion[0].linkType && ( sectionQuestion[0].answer == null || sectionQuestion[0].answer == '' ) ) || ( sectionQuestion[0].linkType && sectionQuestion[0].linkquestionenabled && ( sectionQuestion[0].answer == null || sectionQuestion[0].answer == '' ) ) ) ) {
|
|
674
|
+
if ( ( ![ 'multiplechoicemultiple', 'multipleImage' ].includes( question.answerType ) && ( question.answerType =='dropdown' && !question.allowMultiple ) ) && ( ( !sectionQuestion[0].linkType && ( sectionQuestion[0].answer == null || sectionQuestion[0].answer == '' ) ) || ( sectionQuestion[0].linkType && sectionQuestion[0].linkquestionenabled && ( sectionQuestion[0].answer == null || sectionQuestion[0].answer == '' ) ) ) ) {
|
|
673
675
|
validationCount++;
|
|
674
676
|
}
|
|
675
677
|
}
|
|
@@ -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>
|