tango-app-api-trax 3.7.90 → 3.7.92

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.90",
3
+ "version": "3.7.92",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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, signedUrl } from 'tango-app-api-middleware';
21
+ import { sendPushNotification, sendAiPushNotification, sendEmailWithSES, signedUrl, fileUpload, getOpenSearchData } 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';
@@ -3455,8 +3455,158 @@ async function getBrandInfo( clientId ) {
3455
3455
 
3456
3456
  export const downloadInsertPdf = async ( req, res ) => {
3457
3457
  try {
3458
- res.sendSuccess( 'PDF generation started' );
3458
+ if ( !req.body.checklistId ) {
3459
+ return res.sendError( 'Checklist Id is required', 400 );
3460
+ }
3461
+ const cdnBase = JSON.parse( process.env.CDNURL )?.TraxAnswerCDN || '';
3462
+ const pdfTemplate = getCompiledVisitChecklistTemplate();
3463
+ const imageCache = createImageCache();
3464
+
3465
+ const browser = await puppeteer.launch( {
3466
+ headless: 'new',
3467
+ args: [ '--no-sandbox', '--disable-dev-shm-usage' ],
3468
+ } );
3469
+
3470
+ try {
3471
+ const page = await browser.newPage();
3472
+
3473
+ await page.setViewport( {
3474
+ width: 1280,
3475
+ height: 1800,
3476
+ } );
3477
+
3478
+ const safeName = ( str ) =>
3479
+ ( str || '' ).toString().replace( /[<>:"/\\|?*]+/g, '_' );
3480
+
3481
+ let query = {
3482
+ query: {
3483
+ bool: {
3484
+ must: [
3485
+ {
3486
+ term: {
3487
+ _id: req.body.checklistId,
3488
+ },
3489
+ },
3490
+ ],
3491
+ },
3492
+ },
3493
+ };
3494
+
3495
+ let aiDetails = await getOpenSearchData( JSON.parse( process.env.OPENSEARCH ).traxIndex, query );
3496
+ if ( aiDetails?.statusCode != 200 || !aiDetails?.body?.hits?.hits.length ) {
3497
+ return res.sendError( 'Checklist not found', 404 );
3498
+ }
3499
+
3500
+ const doc = { ...aiDetails.body.hits.hits[0]._source };
3501
+
3502
+ const brandInfo = await getBrandInfo( doc.client_id );
3503
+
3504
+ const detectionPayload = {
3505
+ 'storeId': [ doc.store_id ],
3506
+ 'userEmail': doc.userEmail,
3507
+ 'sourceChecklist_id': doc.sourceCheckList_id,
3508
+ };
3509
+
3510
+ let complianceURL = JSON.parse( process.env.LAMBDAURL ).complianceHistory;
3511
+ const complianceData = await LamdaServiceCall( complianceURL, detectionPayload );
3512
+ if ( complianceData?.data.length ) {
3513
+ doc['historyData'] = complianceData.data;
3514
+ }
3515
+ // CDN fix
3516
+ ( doc.questionAnswers || [] ).forEach( ( section ) => {
3517
+ ( section.questions || [] ).forEach( ( question ) => {
3518
+ ( question.userAnswer || [] ).forEach( ( answer ) => {
3519
+ if ( answer?.referenceImage?.trim() ) {
3520
+ answer.referenceImage =
3521
+ cdnBase + answer.referenceImage;
3522
+ }
3523
+ } );
3524
+ } );
3525
+ } );
3526
+
3527
+ const templateData = buildVisitChecklistTemplateData(
3528
+ doc,
3529
+ brandInfo,
3530
+ );
3531
+
3532
+ const resolvedData = resolveTemplateUrls( templateData, cdnBase );
3533
+ await imageCache.resolveAllImages( resolvedData );
3534
+
3535
+ const html = pdfTemplate( resolvedData );
3536
+
3537
+ try {
3538
+ await page.setContent( html, {
3539
+ waitUntil: 'networkidle0',
3540
+ timeout: 30000,
3541
+ } );
3542
+
3543
+ await page.emulateMediaType( 'screen' );
3544
+ } catch ( err ) {
3545
+ logger.error( { functionName: 'setContent failed:', error: err } );
3546
+ }
3547
+
3548
+ let pdfBuffer;
3549
+
3550
+ try {
3551
+ pdfBuffer = await page.pdf( {
3552
+ format: 'A4',
3553
+ printBackground: true,
3554
+ margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
3555
+ } );
3556
+ } catch ( err ) {
3557
+ logger.error( { functionName: 'downloadInsertPdfOld', message: 'PDF generation failed', error: err } );
3558
+ return res.sendError( 'PDF generation failed', 500 );
3559
+ }
3560
+
3561
+ if ( !pdfBuffer || pdfBuffer.length === 0 ) {
3562
+ logger.error( { functionName: 'downloadInsertPdfOld', message: 'Empty PDF', docId: doc._id } );
3563
+ return res.sendError( 'PDF generation resulted in empty buffer', 500 );
3564
+ }
3565
+
3566
+ const finalBuffer = Buffer.isBuffer( pdfBuffer ) ?
3567
+ pdfBuffer :
3568
+ Buffer.from( pdfBuffer );
3569
+
3570
+ const pdfName = `${safeName(
3571
+ doc.storeName || doc.store_id || doc._id || 'store',
3572
+ )}.pdf`;
3573
+
3574
+ res.set( {
3575
+ 'Content-Type': 'application/pdf',
3576
+ 'Content-Disposition': `attachment; filename="${pdfName}"`,
3577
+ 'Content-Length': finalBuffer.length,
3578
+ } );
3579
+
3580
+ return res.send( finalBuffer );
3581
+ } finally {
3582
+ await browser.close();
3583
+ }
3584
+ } catch ( e ) {
3585
+ console.log( e );
3586
+ logger.error( { functionName: 'downloadInsertPdfOld', error: e } );
3587
+ return res.sendError( e, 500 );
3588
+ }
3589
+ };
3590
+
3591
+ export async function checklistAutoMailList( req, res ) {
3592
+ try {
3593
+ const checklistInfoList = await CLconfig.find( {
3594
+ 'autoEmail.type': { $exists: true },
3595
+ '$expr': {
3596
+ $gt: [ { $size: { $ifNull: [ '$autoEmail.type', [] ] } }, 0 ],
3597
+ },
3598
+ }, { _id: 1 } );
3599
+
3600
+ return res.sendSuccess( checklistInfoList?.map( ( ele ) => ele?._id ) );
3601
+ } catch ( e ) {
3602
+ logger.error( { functionName: 'checklistAutoMailList', error: e } );
3603
+ return res.sendError( e, 500 );
3604
+ }
3605
+ }
3606
+
3459
3607
 
3608
+ export const downloadInsertPdfOld = async ( req, res ) => {
3609
+ try {
3460
3610
  setImmediate( async () => {
3461
3611
  try {
3462
3612
  const cdnBase = JSON.parse( process.env.CDNURL )?.TraxAnswerCDN || '';
@@ -3489,16 +3639,8 @@ export const downloadInsertPdf = async ( req, res ) => {
3489
3639
  const safeName = ( str ) =>
3490
3640
  ( str || '' ).toString().replace( /[<>:"/\\|?*]+/g, '_' );
3491
3641
  for ( const checklistInfo of checklistInfoList ) {
3492
- console.log( dayjs.utc().diff( dayjs.utc( checklistInfo.scheduleEndTime, 'hh:mm A' ), 'minute' ) );
3642
+ // console.log( dayjs.utc().diff( dayjs.utc( checklistInfo.scheduleEndTime, 'hh:mm A' ), 'minute' ) );
3493
3643
  // 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
3644
  let emailList = [];
3503
3645
 
3504
3646
  if ( checklistInfo?.autoEmail?.type?.includes( 'approver' ) ) {
@@ -3532,28 +3674,25 @@ export const downloadInsertPdf = async ( req, res ) => {
3532
3674
  // );
3533
3675
 
3534
3676
  const ZIP_LIMIT = 100;
3677
+ let appendedCount = 0;
3535
3678
 
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`;
3679
+ const zipName = `${safeName(
3680
+ checklistInfo.checkListName,
3681
+ )}_${todayStr}.zip`;
3542
3682
 
3543
- const zipPath = path.join( '/tmp', zipName );
3683
+ const zipPath = path.join( '/tmp', zipName );
3684
+ const output = fs.createWriteStream( zipPath );
3685
+ const archive = archiver( 'zip', { zlib: { level: 9 } } );
3544
3686
 
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
- } );
3687
+ archive.on( 'error', ( err ) => {
3688
+ logger.error( { message: 'Archiver error', error: err } );
3689
+ throw err;
3690
+ } );
3553
3691
 
3554
- archive.pipe( output );
3692
+ archive.pipe( output );
3555
3693
 
3556
- let appendedCount = 0;
3694
+ for ( let i = 0; i < submittedDetails.length; i += ZIP_LIMIT ) {
3695
+ const batch = submittedDetails.slice( i, i + ZIP_LIMIT );
3557
3696
 
3558
3697
  for ( const checklistDetails of batch ) {
3559
3698
  const doc =
@@ -3607,11 +3746,10 @@ export const downloadInsertPdf = async ( req, res ) => {
3607
3746
 
3608
3747
  try {
3609
3748
  await page.setContent( html, {
3610
- waitUntil: 'domcontentloaded',
3611
- timeout: 0,
3749
+ waitUntil: 'networkidle0',
3750
+ timeout: 30000,
3612
3751
  } );
3613
3752
 
3614
- await new Promise( ( r ) => setTimeout( r, 300 ) );
3615
3753
  await page.emulateMediaType( 'screen' );
3616
3754
  } catch ( err ) {
3617
3755
  logger.error( { functionName: 'setContent failed:', error: err } );
@@ -3656,49 +3794,75 @@ export const downloadInsertPdf = async ( req, res ) => {
3656
3794
  archive.append( finalBuffer, { name: pdfName } );
3657
3795
  appendedCount++;
3658
3796
  }
3797
+ }
3798
+ if ( appendedCount === 0 ) {
3799
+ logger.error( { functionName: 'No PDFs generated for batch, skipping ZIP', error: zipName } );
3800
+ // console.error( `❌ No PDFs generated for batch, skipping ZIP: ${zipName}` );
3801
+ archive.abort();
3802
+ output.destroy();
3803
+ continue;
3804
+ }
3659
3805
 
3660
- if ( appendedCount === 0 ) {
3661
- logger.error( { functionName: 'No PDFs generated for batch, skipping ZIP', error: zipName } );
3662
- // console.error( `❌ No PDFs generated for batch, skipping ZIP: ${zipName}` );
3663
- archive.abort();
3664
- output.destroy();
3665
- continue;
3666
- }
3806
+ logger.info( { functionName: 'downloadInsertPdf', message: `${appendedCount} PDFs added to ${zipName}` } );
3667
3807
 
3668
- // console.log( `📄 ${appendedCount} PDFs added to ${zipName}` );
3808
+ await new Promise( ( resolve, reject ) => {
3809
+ output.on( 'close', resolve );
3810
+ output.on( 'error', reject );
3811
+ archive.on( 'error', reject );
3669
3812
 
3670
- await new Promise( ( resolve, reject ) => {
3671
- output.on( 'close', resolve );
3672
- output.on( 'error', reject );
3673
- archive.on( 'error', reject );
3813
+ archive.finalize();
3814
+ } );
3674
3815
 
3675
- archive.finalize();
3676
- } );
3816
+ const zipBuffer = fs.readFileSync( zipPath );
3817
+ // console.log( `✅ ZIP ready: ${zipName}, archive pointer: ${archive.pointer()} bytes, file size: ${zipBuffer.length} bytes` );
3677
3818
 
3678
- const zipBuffer = fs.readFileSync( zipPath );
3679
- // console.log( `✅ ZIP ready: ${zipName}, archive pointer: ${archive.pointer()} bytes, file size: ${zipBuffer.length} bytes` );
3819
+ // const attachment = {
3820
+ // filename: zipName,
3821
+ // content: zipBuffer,
3822
+ // contentType: 'application/zip',
3823
+ // };
3680
3824
 
3681
- const attachment = {
3682
- filename: zipName,
3683
- content: zipBuffer,
3684
- contentType: 'application/zip',
3685
- };
3825
+ let bucket = JSON.parse( process.env.BUCKET );
3826
+
3827
+ let params = {
3828
+ fileName: zipName,
3829
+ Key: 'reports/',
3830
+ Bucket: bucket.sop,
3831
+ body: zipBuffer,
3832
+ ContentType: 'application/zip',
3833
+ };
3834
+
3835
+ let zipURL = await fileUpload( params );
3686
3836
 
3687
- await Promise.all(
3688
- emailList.map( ( email ) =>
3689
- sendEmailWithSES(
3690
- email,
3691
- 'Checklist Report',
3692
- emailHtml,
3693
- attachment,
3694
- sourceEmail,
3695
- ),
3837
+ zipURL = await signedUrl( { file_path: zipURL.Key, Bucket: bucket.sop } );
3838
+
3839
+ let data = {
3840
+ checklistName: checklistInfo.checkListName,
3841
+ date: dayjs( todayStr, 'YYYY-MM-DD' ).format( 'DD-MM-YYYY' ),
3842
+ total: submittedDetails.length,
3843
+ zipURL: zipURL,
3844
+ };
3845
+ const emailFileContent = fs.readFileSync( path.resolve( path.dirname( '' ) ) + '/src/hbs/autoEmail.hbs', 'utf8' );
3846
+ const emailHtmlContent = handlebars.compile( emailFileContent );
3847
+ const emailHtml = emailHtmlContent( { data: data } );
3848
+
3849
+ await Promise.all(
3850
+ emailList.map( ( email ) =>
3851
+ sendEmailWithSES(
3852
+ email,
3853
+ 'Checklist Report',
3854
+ emailHtml,
3855
+ '',
3856
+ sourceEmail,
3696
3857
  ),
3697
- );
3858
+ ),
3859
+ );
3698
3860
 
3699
- // console.log( `📧 Emails sent for ${zipName}` );
3861
+ try {
3862
+ fs.unlinkSync( zipPath );
3863
+ } catch ( cleanupErr ) {
3864
+ logger.error( { functionName: 'downloadInsertPdf', message: 'Failed to cleanup tmp file', error: cleanupErr } );
3700
3865
  }
3701
- // }
3702
3866
  }
3703
3867
  } finally {
3704
3868
  await browser.close();
@@ -1,216 +1,235 @@
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
-
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 0 10px">
185
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 680px;">
186
+ <tr>
187
+ <td bgcolor="#ffffff" style="padding: 20px;padding-top:15px;padding-left:30px;">
188
+ <table border="0" cellpadding="0" cellspacing="0">
189
+ <tr>
190
+ <td align="center" bgcolor="#00A3FF" style="border-radius: 6px;height:50px;">
191
+ <a href="{{data.zipURL}}" target="_blank"
192
+ style="display: inline-block; padding: 16px 36px; font-size: 16px; color: #ffffff; text-decoration: none; border-radius: 6px;cursor:pointer">Download
193
+ </a>
194
+ </td>
195
+ </tr>
196
+ </table>
197
+ </td>
198
+ </tr>
199
+ </table>
200
+ </td>
201
+ </tr>
202
+ <tr>
203
+ <td align="center" bgcolor="#dbe5ea" style="padding:0 10px 32px 10px;">
204
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 680px;">
205
+ <tr>
206
+ <td class="o_bg-white o_px-md o_py-xl o_xs-py-md"
207
+ style="background-color: #ffffff;padding-left: 30px;padding-right: 24px;padding-top:5px">
208
+ <div class="o_col-6s o_sans o_text-md o_text-light o_center"
209
+ style="margin-top: 0px;margin-bottom: 0px;font-size: 12px;color: #202B3C;font-style: normal;font-weight: 400;font-size: 12px;line-height: 150%;">
210
+ <p>
211
+ If you have any questions or need assistance, please reach out to us at
212
+ support@tangotech.co.in.
213
+ </p>
214
+ </div>
215
+ </td>
216
+ </tr>
217
+ <tr>
218
+ <td class="o_bg-white o_px-md o_py-xl o_xs-py-md"
219
+ style="background-color: #ffffff;padding-left: 30px;padding-right: 24px;padding-bottom:15px">
220
+ <div class="o_col-6s o_sans o_text-md o_text-light o_center"
221
+ style="margin-top: 0px;margin-bottom: 0px;font-size: 12px;color: #202B3C;font-style: normal;font-weight: 400;font-size: 12px;line-height: 150%;">
222
+ <p>
223
+ © Tango Eye. All rights reserved.</p>
224
+ </div>
225
+ </td>
226
+ </tr>
227
+ </table>
228
+ </td>
229
+ </tr>
230
+
231
+ </table>
232
+
233
+ </body>
234
+
216
235
  </html>
@@ -176,7 +176,7 @@
176
176
  <div class="page">
177
177
  <div class="detail-page">
178
178
  {{#each this.sections}}
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>
179
+ <div class="dp-header" {{#unless @first}}style="margin-top:20px"{{/unless}}><h2>{{this.sectionName}}</h2>{{#if this.maxScore}}<span class="dp-score">{{this.currentScore}}/{{this.maxScore}}</span>{{/if}}</div>
180
180
  <div class="sec-questions">
181
181
  {{#each this.questions}}
182
182
  <div class="q-row">
@@ -38,6 +38,7 @@ internalTraxRouter
38
38
  .post( '/posblock', isAllowedInternalAPIHandler, internalController.getStoreTaskDetails )
39
39
  .post( '/runAIFlag', isAllowedInternalAPIHandler, internalController.runAIFlag )
40
40
  .post( '/downloadInsertPdf', isAllowedInternalAPIHandler, internalController.downloadInsertPdf )
41
+ .post( '/checklistAutoMailList', isAllowedInternalAPIHandler, internalController.checklistAutoMailList )
41
42
  ;
42
43
 
43
44
 
@@ -449,7 +449,7 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
449
449
 
450
450
  titleLine2,
451
451
 
452
- referenceId: doc.store_id || doc.storeName || refFromId,
452
+ referenceId: doc?.storeName ?? doc?.userEmail,
453
453
 
454
454
  date: formattedDate,
455
455