tango-app-api-trax 3.7.93 → 3.7.95

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.93",
3
+ "version": "3.7.95",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -33,6 +33,13 @@ import puppeteer from 'puppeteer';
33
33
  import { getBrowser as getBrowserInstance } from '../utils/browserPool.utils.js';
34
34
  import fs from 'fs';
35
35
  import path from 'path';
36
+ import { fileURLToPath as toPath } from 'url';
37
+
38
+ const __ctrlDir = path.dirname( toPath( import.meta.url ) );
39
+ const tangoyeLogoSvg = fs.readFileSync(
40
+ path.join( __ctrlDir, '../hbs/partials/tangoye-footer-logo.hbs' ),
41
+ 'utf8',
42
+ ).replaceAll( '{{gid}}', 'ft' );
36
43
 
37
44
 
38
45
  const ObjectId = mongoose.Types.ObjectId;
@@ -3546,7 +3553,23 @@ export const downloadInsertPdf = async ( req, res ) => {
3546
3553
  pdfBuffer = await page.pdf( {
3547
3554
  format: 'A4',
3548
3555
  printBackground: true,
3549
- margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
3556
+ preferCSSPageSize: true,
3557
+ displayHeaderFooter: true,
3558
+ headerTemplate: '<span></span>',
3559
+ footerTemplate: [
3560
+ '<div style="width:100%;padding:0 10mm;font-size:10px;',
3561
+ 'font-family:Arial,sans-serif;display:flex;',
3562
+ 'justify-content:space-between;align-items:center;',
3563
+ 'border-top:1px solid #d9d9d9;padding-top:4px;">',
3564
+ '<span style="color:#999;">Page ',
3565
+ '<span class="pageNumber"></span>',
3566
+ ' of <span class="totalPages"></span></span>',
3567
+ '<span style="display:flex;align-items:center;gap:6px;">',
3568
+ '<span style="color:#666;font-weight:400;font-size:10px;">',
3569
+ 'Generated by</span>',
3570
+ tangoyeLogoSvg,
3571
+ '</span></div>',
3572
+ ].join( '' ),
3550
3573
  } );
3551
3574
  } catch ( err ) {
3552
3575
  logger.error( { functionName: 'downloadInsertPdf', message: 'PDF generation failed', error: err } );
@@ -3562,9 +3585,16 @@ export const downloadInsertPdf = async ( req, res ) => {
3562
3585
  pdfBuffer :
3563
3586
  Buffer.from( pdfBuffer );
3564
3587
 
3565
- const pdfName = `${safeName(
3566
- doc.store_id + '_' + ( doc.storeName || 'store' ),
3567
- )}.pdf`;
3588
+ let pdfName;
3589
+ if ( doc.store_id ) {
3590
+ pdfName = `${safeName(
3591
+ doc.store_id + '_' + ( doc.storeName || 'store' ),
3592
+ )}.pdf`;
3593
+ } else {
3594
+ pdfName = `${safeName(
3595
+ doc.userName + '_' + ( doc.sourceCheckList_id || 'store' ),
3596
+ )}.pdf`;
3597
+ }
3568
3598
 
3569
3599
  res.set( {
3570
3600
  'Content-Type': 'application/pdf',
@@ -3590,12 +3620,14 @@ export async function checklistAutoMailList( req, res ) {
3590
3620
  '$expr': {
3591
3621
  $gt: [ { $size: { $ifNull: [ '$autoEmail.type', [] ] } }, 0 ],
3592
3622
  },
3593
- }, { _id: 1, autoEmail: 1 } );
3623
+ }, { _id: 1, autoEmail: 1, approver: 1, coverage: 1 } );
3594
3624
 
3595
3625
  let result = [];
3596
3626
 
3597
3627
  await Promise.all( checklistInfoList.map( async ( ele ) => {
3598
- for ( let email of ele?.autoEmail?.users ) {
3628
+ let emailList = ele?.autoEmail?.type?.includes( 'approver' ) ? ele?.approver: [];
3629
+ emailList = [ ...emailList, ...ele?.autoEmail?.users ];
3630
+ for ( let email of emailList ) {
3599
3631
  let stores = [];
3600
3632
  let userDetails = await userService.findOne( { email: email.value } );
3601
3633
  if ( userDetails ) {
@@ -3664,6 +3696,7 @@ export async function checklistAutoMailList( req, res ) {
3664
3696
  email: email?.value,
3665
3697
  stores,
3666
3698
  role: userDetails.role,
3699
+ type: ele.coverage,
3667
3700
  };
3668
3701
  result.push( data );
3669
3702
  }
@@ -608,6 +608,8 @@ export const zoneList = async ( req, res ) => {
608
608
  'Mobile usage detection',
609
609
  'Store TV Compliance',
610
610
  'Queue Wait Time Breach',
611
+ 'Uniform detection',
612
+ 'Store Hygiene Monitoring',
611
613
  ];
612
614
  const allowedChecklistsStreams = [
613
615
  'Camera Angle Change Compliance',
@@ -5163,3 +5165,14 @@ export async function downloadQuestionTemplate( req, res ) {
5163
5165
  return res.sendError( e, 500 );
5164
5166
  }
5165
5167
  }
5168
+
5169
+
5170
+ export async function getTeamList( req, res ) {
5171
+ try {
5172
+ let teamsDetails = await teamsServices.findteams( { clientId: req.query.clientId, status: 'active' } );
5173
+ return res.sendSuccess( teamsDetails );
5174
+ } catch ( e ) {
5175
+ logger.error( { functionName: 'getTeamList', error: e } );
5176
+ return res.sendError( e, 500 );
5177
+ }
5178
+ }
@@ -7,15 +7,15 @@
7
7
  <style>
8
8
  *{box-sizing:border-box;margin:0;padding:0}
9
9
  body{font-family:Arial,Helvetica,sans-serif;background:#fff;padding:0}
10
- .page{width:794px;min-height:1123px;background:#fff;padding:0;overflow:hidden;position:relative;page-break-after:always}
11
- .cover-wrap{position:relative;width:794px;height:1123px;background:#fff;overflow:hidden;font-family:Arial,Helvetica,sans-serif}
10
+ .page{width:794px;background:#fff;padding:0;overflow:hidden;position:relative;page-break-after:always}
11
+ .cover-wrap{position:relative;width:794px;height:284mm;background:#fff;overflow:hidden;font-family:Arial,Helvetica,sans-serif}
12
12
  /* Cover — match brand PDF (cyan title, right geometry, grey footer rule) */
13
13
  /* Right-edge artwork: flush right; height 1043px leaves ~80px for footer band on 1123px cover */
14
- .cover-deco{position:absolute;top:0;right:0;left:auto;width:493px;height:1043px;pointer-events:none;z-index:0}
14
+ .cover-deco{position:absolute;top:0;right:0;bottom:0;left:auto;width:450px;pointer-events:none;z-index:0;}
15
15
  .cover-brand{position:absolute;top:48px;left:48px;display:flex;align-items:center;gap:10px;z-index:1}
16
16
  .cover-brand-name{font-size:22px;font-weight:600;color:#1a1a1a;letter-spacing:.02em;text-transform:lowercase}
17
- .cover-title-block{position:absolute;top:188px;left:48px;max-width:440px;z-index:1}
18
- .cover-title-line1,.cover-title-line2{font-size:54px;font-weight:600;color:#00AEEF;line-height:1.06;letter-spacing:-.5px}
17
+ .cover-title-block{position:absolute;top:188px;left:48px;max-width:400px;z-index:1}
18
+ .cover-title-line1,.cover-title-line2{font-size:40px;font-weight:600;color:#00AEEF;line-height:1.15;letter-spacing:-.5px;word-wrap:break-word;overflow-wrap:break-word}
19
19
  .cover-meta-block{position:absolute;top:418px;left:48px;z-index:1}
20
20
  .cover-ref{font-size:22px;font-weight:700;color:#1a1a1a;letter-spacing:.02em}
21
21
  .cover-datetime{font-size:15px;color:#1a1a1a;font-weight:400;margin-top:10px}
@@ -28,12 +28,12 @@
28
28
  .cover-footer-page{font-size:11px;color:#999}
29
29
  .cover-footer-gen{display:flex;align-items:center;gap:8px;font-size:12px;color:#00AEEF;font-weight:600}
30
30
  /* Score page */
31
- .score-page{padding:40px}
31
+ .score-page{margin-bottom:40px;padding-bottom:20px;border-bottom:1px solid #e0e0e0}
32
32
  .score-hero{text-align:center;margin-bottom:36px}
33
33
  .score-pct{font-size:72px;font-weight:700;color:#00AEEF;line-height:1}
34
34
  .score-sub{font-size:18px;color:#444;margin-top:8px}
35
35
  .score-date{font-size:14px;color:#888;margin-top:4px}
36
- .section-title{font-size:16px;font-weight:700;color:#1a1a2e;margin-bottom:16px;padding-bottom:8px;border-bottom:2px solid #00AEEF}
36
+ .section-title{font-size:16px;font-weight:700;color:#1a1a2e;margin-bottom:30px;padding-bottom:10px;border-bottom:2px solid #00AEEF}
37
37
  .history-bars{display:flex;align-items:flex-end;gap:8px;height:160px;margin-bottom:36px}
38
38
  .bar-wrap{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px}
39
39
  .bar{width:100%;background:#00AEEF;border-radius:4px 4px 0 0;display:flex;align-items:flex-start;justify-content:center}
@@ -50,9 +50,9 @@
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;break-inside:auto}
54
- .q-row{break-inside:avoid}
55
- .q-answer-item{break-inside:avoid}
53
+ .detail-page{padding:32px 40px}
54
+ .q-row{break-inside:auto}
55
+ .q-answer-item{break-inside:auto}
56
56
  .dp-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:12px;border-bottom:2px solid #00AEEF}
57
57
  .dp-header h2{font-size:18px;font-weight:700;color:#1a1a2e}
58
58
  .dp-score{font-size:15px;font-weight:700;color:#000000}
@@ -79,19 +79,20 @@
79
79
  .footer-gen-by{color:#666;font-weight:400}
80
80
  .cover-footer-gen .footer-gen-by{color:#1a1a1a}
81
81
  .footer-tangoye-logo{display:block;flex-shrink:0}
82
- @page{size:A4;margin:0}
82
+ @page{size:A4;margin:5mm 0mm 15mm 0mm}
83
+ @page:first{margin:0}
83
84
  </style>
84
85
  </head>
85
86
  <body>
86
87
 
87
88
  {{!-- PAGE 1: COVER --}}
88
89
  <div class="page cover-wrap">
89
- <svg class="cover-deco" width="493" height="1043" viewBox="0 0 398 842" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMaxYMin meet" aria-hidden="true">
90
- <path opacity="0.8" d="M397.199 842V586L334.199 292L222.199 842H397.199Z" fill="#99DAFF"/>
91
- <path opacity="0.7" d="M125.199 0H142.544L397.199 204.045V380L125.199 0Z" fill="#51C1FF"/>
92
- <path opacity="0.7" d="M84.7003 -0.00271425L125.197 -0.00675424L334.347 292.188L298.195 469L84.7003 -0.00271425Z" fill="#99DAFF"/>
93
- <path opacity="0.7" d="M397.199 360.707L334.199 292L397.199 586V360.707Z" fill="#6BCAFF"/>
94
- <path opacity="0.6" d="M397.199 220L125.199 0H397.199V220Z" fill="#009BF3"/>
90
+ <svg xmlns="http://www.w3.org/2000/svg" class="cover-deco" viewBox="0 0 650 2000" fill="none">
91
+ <path opacity="0.8" d="M711.5 2000V1391.92L568.063 693.586L313.062 2000H711.5Z" fill="#99DAFF" />
92
+ <path opacity="0.7" d="M92.2148 0.0078125H131.706L711.501 484.677V902.622L92.2148 0.0078125Z" fill="#51C1FF" />
93
+ <path opacity="0.7" d="M0 0.00959645L92.203 0L568.392 694.051L486.082 1114.03L0 0.00959645Z" fill="#99DAFF" />
94
+ <path opacity="0.7" d="M711.5 856.786L568.062 693.586L711.5 1391.92V856.786Z" fill="#6BCAFF" />
95
+ <path opacity="0.6" d="M711.501 522.574L92.2148 0.0078125H711.501V522.574Z" fill="#009BF3" />
95
96
  </svg>
96
97
 
97
98
  <div class="cover-brand">
@@ -115,25 +116,25 @@
115
116
  <div class="cover-sum-row"><span class="cover-sum-label">Country</span><span class="cover-sum-colon">:</span><span class="cover-sum-val">{{country}}</span></div>
116
117
  </div>
117
118
 
118
- <div class="cover-footer">
119
+ {{!-- <div class="cover-footer">
119
120
  <span class="cover-footer-page"></span>
120
121
  <div class="cover-footer-gen">
121
122
  <span class="footer-gen-by">Generated by</span>
122
123
  {{> tangoyeFooterLogo gid="cover"}}
123
124
  </div>
124
- </div>
125
+ </div> --}}
125
126
  </div>
126
127
 
127
- {{!-- PAGE 2: SCORE SUMMARY --}}
128
+ {{!-- SCORE SUMMARY + DETAIL (continuous flow) --}}
129
+ <div class="detail-page">
128
130
  {{#if hasCompliancePage}}
129
- <div class="page">
130
131
  <div class="score-page">
131
132
  <div class="score-hero">
132
133
  <div class="score-pct">{{totalPercentage}}%</div>
133
134
  <div class="score-sub">Total: <strong>{{totalScore}}</strong> out of <strong>{{maxScore}}</strong></div>
134
135
  <div class="score-date">{{reportDate}}</div>
135
136
  </div>
136
-
137
+
137
138
  {{#if historyData}}
138
139
  <div class="section-title">History — Last 7 Days</div>
139
140
  <div style="display:flex;flex-direction:column;margin-bottom:36px">
@@ -148,7 +149,7 @@
148
149
  <div class="bar-axis"></div>
149
150
  </div>
150
151
  {{/if}}
151
-
152
+
152
153
  <div class="section-title">Section Wise Insights</div>
153
154
  <table class="sw-table">
154
155
  <thead><tr><th>Sections</th><th>Target Score</th><th>Actual Scrore</th><th>%</th></tr></thead>
@@ -164,18 +165,8 @@
164
165
  </tbody>
165
166
  </table>
166
167
  </div>
167
- {{!-- <div class="page-footer">
168
- <span>Page 2 of {{totalPages}}</span>
169
- <span class="footer-brand"><span class="footer-gen-by">Generated by</span>{{> tangoyeFooterLogo gid="p2"}}</span>
170
- </div>
171
- </div> --}}
172
168
  {{/if}}
173
-
174
- {{!-- PAGES 3+: DETAIL - Question sections --}}
175
- {{#each detailPageGroups}}
176
- <div class="page">
177
- <div class="detail-page">
178
- {{#each this.sections}}
169
+ {{#each sections}}
179
170
  <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
171
  <div class="sec-questions">
181
172
  {{#each this.questions}}
@@ -247,22 +238,7 @@
247
238
  {{/each}}
248
239
  </div>
249
240
  {{/each}}
250
-
251
- {{!-- {{#if this.isLastGroup}}
252
- {{#each ../flags}}
253
- <div style="margin-top:12px;padding:12px;background:#faeeda;border-radius:8px;border-left:4px solid #ef9f27">
254
- <strong style="font-size:13px;color:#854f0b">⚠ Flag: {{this.sectionName}} — {{this.qname}}</strong>
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>
256
- </div>
257
- {{/each}}
258
- {{/if}} --}}
259
- </div>
260
- {{!-- <div class="page-footer">
261
- <span>Page {{this.pageNumber}} of {{../totalPages}}</span>
262
- <span class="footer-brand"><span class="footer-gen-by">Generated by</span>{{> tangoyeFooterLogo gid=(strConcat 'd' @index)}}</span>
263
- </div> --}}
264
241
  </div>
265
- {{/each}}
266
242
 
267
243
  </body>
268
244
  </html>
@@ -36,6 +36,8 @@ traxRouter
36
36
  .post( '/updateRunAIRequest', isAllowedSessionHandler, validate( runAIRequestValidation ), traxController.updateRunAIRequest )
37
37
  .post( '/createChecklistName', isAllowedSessionHandler, validate( createChecklistNameValidation ), traxController.createChecklistName )
38
38
  .post( '/updateOSProcessedData', isAllowedInternalAPIHandler, validate( updateOSDataValidation ), traxController.updateOSProcessedData )
39
- .post( '/exportQuestions', isAllowedSessionHandler, traxController.downloadQuestionTemplate );
39
+ .post( '/exportQuestions', isAllowedSessionHandler, traxController.downloadQuestionTemplate )
40
+ .get( '/getTeamList', isAllowedSessionHandler, traxController.getTeamList )
41
+ ;
40
42
 
41
43
  // isAllowedSessionHandler, isAllowedClient, accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoTrax', name: 'checklist', permissions: [ ] } ] } ),
@@ -214,7 +214,19 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
214
214
  const max = q.compliance ? Math.max( ...q?.answers.map( ( o ) => o?.complianceScore ?? Math.max( o?.matchedCount ?? 0, o?.notMatchedCount ?? 0 ) ) ) : 0;
215
215
 
216
216
 
217
- const score = q.compliance ? Math.max( ...q?.userAnswer?.map( ( o ) => o?.complianceScore ?? 0 ) ) : 0;
217
+ let score = q.compliance ? Math.max( ...q?.userAnswer?.map( ( o ) => o?.complianceScore ?? 0 ) ) : 0;
218
+
219
+
220
+ if ( q.answerType == 'image' && q.compliance && q.userAnswer?.[0]?.runAIData ) {
221
+ let find = q.userAnswer?.[0]?.runAIData?.find( ( run ) => run?.featureName == 'Matched/Not Matched' );
222
+ if ( find ) {
223
+ if ( find?.value == 'True' ) {
224
+ score = q?.answers?.[0]?.matchedCount;
225
+ } else {
226
+ score = q?.answers?.[0]?.notMatchedCount;
227
+ }
228
+ }
229
+ }
218
230
 
219
231
 
220
232
  sectionScore += score;
@@ -433,6 +445,7 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
433
445
  } );
434
446
  }
435
447
 
448
+ let referenceId = doc.coverage == 'store' ? doc?.storeName : doc?.userName;
436
449
 
437
450
  return {
438
451
 
@@ -449,7 +462,7 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
449
462
 
450
463
  titleLine2,
451
464
 
452
- referenceId: doc?.storeName ?? doc?.userEmail,
465
+ referenceId: referenceId,
453
466
 
454
467
  date: formattedDate,
455
468
 
@@ -491,6 +504,8 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
491
504
 
492
505
  detailPageGroups,
493
506
 
507
+ sections: detailPageGroups.flatMap( ( g ) => g.sections ),
508
+
494
509
  flags,
495
510
 
496
511
  };
@@ -629,6 +644,8 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
629
644
 
630
645
  detailPageGroups,
631
646
 
647
+ sections: detailPageGroups.flatMap( ( g ) => g.sections ),
648
+
632
649
  flags,
633
650
 
634
651
  complianceCount: hasCompliancePage,
@@ -753,6 +770,8 @@ export function createImageCache() {
753
770
  group.sections?.forEach( collectFromSection );
754
771
  } );
755
772
 
773
+ resolvedData.sections?.forEach( collectFromSection );
774
+
756
775
 
757
776
  // Fetch all unique URLs in parallel (max 20 concurrent)
758
777
 
@@ -798,6 +817,8 @@ export function createImageCache() {
798
817
  group.sections?.forEach( replaceInSection );
799
818
  } );
800
819
 
820
+ resolvedData.sections?.forEach( replaceInSection );
821
+
801
822
 
802
823
  return resolvedData;
803
824
  }
@@ -862,6 +883,8 @@ export function resolveTemplateUrls( templateData, baseUrl = 'https://d1r0hc2ssk
862
883
  group.sections?.forEach( resolveQuestionMedia );
863
884
  } );
864
885
 
886
+ resolvedData.sections?.forEach( resolveQuestionMedia );
887
+
865
888
 
866
889
  if ( resolvedData.brandLogo && !resolvedData.brandLogo.startsWith( 'http' ) ) {
867
890
  resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );
@@ -977,6 +1000,7 @@ export async function generateVisitChecklistPDF( templateData, baseUrl = 'https:
977
1000
  resolvedData.detailPageGroups?.forEach( ( group ) => {
978
1001
  group.sections?.forEach( resolveQuestionMedia );
979
1002
  } );
1003
+ resolvedData.sections?.forEach( resolveQuestionMedia );
980
1004
 
981
1005
  if ( resolvedData.brandLogo && !resolvedData.brandLogo.startsWith( 'http' ) ) {
982
1006
  resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );