tango-app-api-trax 3.7.92 → 3.7.93

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.92",
3
+ "version": "3.7.93",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -30,6 +30,7 @@ import handlebars from './handlebar-helper.js';
30
30
  import { buildVisitChecklistTemplateData, getCompiledVisitChecklistTemplate, createImageCache, resolveTemplateUrls } from '../utils/visitChecklistPdf.utils.js';
31
31
  import archiver from 'archiver';
32
32
  import puppeteer from 'puppeteer';
33
+ import { getBrowser as getBrowserInstance } from '../utils/browserPool.utils.js';
33
34
  import fs from 'fs';
34
35
  import path from 'path';
35
36
 
@@ -2711,9 +2712,9 @@ export async function getRunAIList( req, res ) {
2711
2712
  if ( qn.answers.some( ( ele ) => ele.runAI ) ) {
2712
2713
  let checkList = runAIList.findIndex( ( run ) => run.checklist.toString() == check._id.toString() );
2713
2714
  if ( checkList == -1 ) {
2714
- runAIList.push( { checklist: check._id, questions: [ qn.uniqueqid ] } );
2715
+ runAIList.push( { checklist: check._id, checklistName: check.checkListName, questions: [ { name: qn.qname, qid: qn.uniqueqid } ] } );
2715
2716
  } else {
2716
- runAIList[checkList].questions.push( qn.uniqueqid );
2717
+ runAIList[checkList].questions.push( { name: qn.qname, qid: qn.uniqueqid } );
2717
2718
  }
2718
2719
  }
2719
2720
  } );
@@ -3454,6 +3455,7 @@ async function getBrandInfo( clientId ) {
3454
3455
  }
3455
3456
 
3456
3457
  export const downloadInsertPdf = async ( req, res ) => {
3458
+ let page = null;
3457
3459
  try {
3458
3460
  if ( !req.body.checklistId ) {
3459
3461
  return res.sendError( 'Checklist Id is required', 400 );
@@ -3462,129 +3464,122 @@ export const downloadInsertPdf = async ( req, res ) => {
3462
3464
  const pdfTemplate = getCompiledVisitChecklistTemplate();
3463
3465
  const imageCache = createImageCache();
3464
3466
 
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
- } );
3467
+ const safeName = ( str ) =>
3468
+ ( str || '' ).toString().replace( /[<>:"/\\|?*]+/g, '_' );
3477
3469
 
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
- },
3470
+ const query = {
3471
+ query: {
3472
+ bool: {
3473
+ must: [
3474
+ {
3475
+ term: {
3476
+ _id: req.body.checklistId,
3489
3477
  },
3490
- ],
3491
- },
3478
+ },
3479
+ ],
3492
3480
  },
3493
- };
3481
+ },
3482
+ };
3494
3483
 
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
- }
3484
+ // 1) Launch browser page + fetch OpenSearch data in parallel
3485
+ const [ browser, aiDetails ] = await Promise.all( [
3486
+ getBrowserInstance(),
3487
+ getOpenSearchData( JSON.parse( process.env.OPENSEARCH ).traxIndex, query ),
3488
+ ] );
3499
3489
 
3500
- const doc = { ...aiDetails.body.hits.hits[0]._source };
3490
+ if ( aiDetails?.statusCode != 200 || !aiDetails?.body?.hits?.hits.length ) {
3491
+ return res.sendError( 'Checklist not found', 404 );
3492
+ }
3501
3493
 
3502
- const brandInfo = await getBrandInfo( doc.client_id );
3494
+ const doc = { ...aiDetails.body.hits.hits[0]._source };
3503
3495
 
3504
- const detectionPayload = {
3505
- 'storeId': [ doc.store_id ],
3506
- 'userEmail': doc.userEmail,
3507
- 'sourceChecklist_id': doc.sourceCheckList_id,
3508
- };
3496
+ // 2) Fetch brandInfo + compliance data in parallel
3497
+ const complianceURL = JSON.parse( process.env.LAMBDAURL ).complianceHistory;
3498
+ const detectionPayload = {
3499
+ 'storeId': [ doc.store_id ],
3500
+ 'userEmail': doc.userEmail,
3501
+ 'sourceChecklist_id': doc.sourceCheckList_id,
3502
+ };
3509
3503
 
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
- } );
3504
+ const [ brandInfo, complianceData ] = await Promise.all( [
3505
+ getBrandInfo( doc.client_id ),
3506
+ LamdaServiceCall( complianceURL, detectionPayload ),
3507
+ ] );
3526
3508
 
3527
- const templateData = buildVisitChecklistTemplateData(
3528
- doc,
3529
- brandInfo,
3530
- );
3509
+ if ( complianceData?.data?.length ) {
3510
+ doc['historyData'] = complianceData.data;
3511
+ }
3531
3512
 
3532
- const resolvedData = resolveTemplateUrls( templateData, cdnBase );
3533
- await imageCache.resolveAllImages( resolvedData );
3513
+ // CDN fix
3514
+ ( doc.questionAnswers || [] ).forEach( ( section ) => {
3515
+ ( section.questions || [] ).forEach( ( question ) => {
3516
+ ( question.userAnswer || [] ).forEach( ( answer ) => {
3517
+ if ( answer?.referenceImage?.trim() ) {
3518
+ answer.referenceImage = cdnBase + answer.referenceImage;
3519
+ }
3520
+ } );
3521
+ } );
3522
+ } );
3534
3523
 
3535
- const html = pdfTemplate( resolvedData );
3524
+ const templateData = buildVisitChecklistTemplateData( doc, brandInfo );
3525
+ const resolvedData = resolveTemplateUrls( templateData, cdnBase );
3526
+ await imageCache.resolveAllImages( resolvedData );
3536
3527
 
3537
- try {
3538
- await page.setContent( html, {
3539
- waitUntil: 'networkidle0',
3540
- timeout: 30000,
3541
- } );
3528
+ const html = pdfTemplate( resolvedData );
3542
3529
 
3543
- await page.emulateMediaType( 'screen' );
3544
- } catch ( err ) {
3545
- logger.error( { functionName: 'setContent failed:', error: err } );
3546
- }
3530
+ // 3) Create page and render — images are already base64 so use domcontentloaded
3531
+ page = await browser.newPage();
3532
+ await page.setViewport( { width: 1280, height: 1800 } );
3547
3533
 
3548
- let pdfBuffer;
3534
+ try {
3535
+ await page.setContent( html, {
3536
+ waitUntil: 'domcontentloaded',
3537
+ timeout: 15000,
3538
+ } );
3539
+ await page.emulateMediaType( 'screen' );
3540
+ } catch ( err ) {
3541
+ logger.error( { functionName: 'downloadInsertPdf', message: 'setContent failed', error: err } );
3542
+ }
3549
3543
 
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
- }
3544
+ let pdfBuffer;
3545
+ try {
3546
+ pdfBuffer = await page.pdf( {
3547
+ format: 'A4',
3548
+ printBackground: true,
3549
+ margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
3550
+ } );
3551
+ } catch ( err ) {
3552
+ logger.error( { functionName: 'downloadInsertPdf', message: 'PDF generation failed', error: err } );
3553
+ return res.sendError( 'PDF generation failed', 500 );
3554
+ }
3560
3555
 
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
- }
3556
+ if ( !pdfBuffer || pdfBuffer.length === 0 ) {
3557
+ logger.error( { functionName: 'downloadInsertPdf', message: 'Empty PDF', docId: doc._id } );
3558
+ return res.sendError( 'PDF generation resulted in empty buffer', 500 );
3559
+ }
3565
3560
 
3566
- const finalBuffer = Buffer.isBuffer( pdfBuffer ) ?
3567
- pdfBuffer :
3568
- Buffer.from( pdfBuffer );
3561
+ const finalBuffer = Buffer.isBuffer( pdfBuffer ) ?
3562
+ pdfBuffer :
3563
+ Buffer.from( pdfBuffer );
3569
3564
 
3570
- const pdfName = `${safeName(
3571
- doc.storeName || doc.store_id || doc._id || 'store',
3572
- )}.pdf`;
3565
+ const pdfName = `${safeName(
3566
+ doc.store_id + '_' + ( doc.storeName || 'store' ),
3567
+ )}.pdf`;
3573
3568
 
3574
- res.set( {
3575
- 'Content-Type': 'application/pdf',
3576
- 'Content-Disposition': `attachment; filename="${pdfName}"`,
3577
- 'Content-Length': finalBuffer.length,
3578
- } );
3569
+ res.set( {
3570
+ 'Content-Type': 'application/pdf',
3571
+ 'Content-Disposition': `attachment; filename="${pdfName}"`,
3572
+ 'Content-Length': finalBuffer.length,
3573
+ } );
3579
3574
 
3580
- return res.send( finalBuffer );
3581
- } finally {
3582
- await browser.close();
3583
- }
3575
+ return res.send( finalBuffer );
3584
3576
  } catch ( e ) {
3585
- console.log( e );
3586
- logger.error( { functionName: 'downloadInsertPdfOld', error: e } );
3577
+ logger.error( { functionName: 'downloadInsertPdf', error: e } );
3587
3578
  return res.sendError( e, 500 );
3579
+ } finally {
3580
+ if ( page ) {
3581
+ await page.close().catch( () => {} );
3582
+ }
3588
3583
  }
3589
3584
  };
3590
3585
 
@@ -3595,10 +3590,89 @@ export async function checklistAutoMailList( req, res ) {
3595
3590
  '$expr': {
3596
3591
  $gt: [ { $size: { $ifNull: [ '$autoEmail.type', [] ] } }, 0 ],
3597
3592
  },
3598
- }, { _id: 1 } );
3593
+ }, { _id: 1, autoEmail: 1 } );
3594
+
3595
+ let result = [];
3596
+
3597
+ await Promise.all( checklistInfoList.map( async ( ele ) => {
3598
+ for ( let email of ele?.autoEmail?.users ) {
3599
+ let stores = [];
3600
+ let userDetails = await userService.findOne( { email: email.value } );
3601
+ if ( userDetails ) {
3602
+ if ( userDetails.userType === 'client' && userDetails.role !== 'superadmin' ) {
3603
+ let storeIds = new Set( userDetails.assignedStores.map( ( store ) => store.storeId ) );
3604
+
3605
+ // Fetch clusters and teams in parallel
3606
+ const [ clustersList, teamsList ] = await Promise.all( [
3607
+ clusterServices.findcluster( { clientId: userDetails.clientId, Teamlead: { $elemMatch: { email: userDetails.email } } } ),
3608
+ teamsServices.findteams( { clientId: userDetails.clientId, Teamlead: { $elemMatch: { email: userDetails.email } } } ),
3609
+ ] );
3610
+
3611
+ // Process clusters
3612
+ if ( clustersList.length > 0 ) {
3613
+ for ( let cluster of clustersList ) {
3614
+ cluster.stores.forEach( ( store ) => storeIds.add( store.storeId ) );
3615
+ }
3616
+ }
3599
3617
 
3600
- return res.sendSuccess( checklistInfoList?.map( ( ele ) => ele?._id ) );
3618
+ // Process teams
3619
+ if ( teamsList.length > 0 ) {
3620
+ for ( let team of teamsList ) {
3621
+ for ( let user of team.users ) {
3622
+ let findUser = await userService.findOne( { _id: user.userId } );
3623
+ if ( findUser && findUser.assignedStores?.length > 0 ) {
3624
+ findUser.assignedStores.forEach( ( store ) => storeIds.add( store.storeId ) );
3625
+ }
3626
+
3627
+ // Fetch clusters for the user
3628
+ let userClustersList = await clusterServices.findcluster( { clientId: userDetails.clientId, Teamlead: { $elemMatch: { email: findUser.email } } } );
3629
+ if ( userClustersList.length > 0 ) {
3630
+ for ( let cluster of userClustersList ) {
3631
+ cluster.stores.forEach( ( store ) => storeIds.add( store.storeId ) );
3632
+ }
3633
+ }
3634
+ }
3635
+ }
3636
+ }
3637
+ let TeamMember = await teamsServices.findteams( { clientId: userDetails.clientId, users: { $elemMatch: { email: userDetails.email } } } );
3638
+ if ( TeamMember&&TeamMember.length>0 ) {
3639
+ for ( let team of TeamMember ) {
3640
+ let clusterList = await clusterServices.findcluster( { clientId: userDetails.clientId, teams: { $elemMatch: { name: team.teamName } } } );
3641
+ if ( clusterList.length > 0 ) {
3642
+ for ( let cluster of clusterList ) {
3643
+ cluster.stores.forEach( ( store ) => storeIds.add( store.storeId ) );
3644
+ }
3645
+ }
3646
+ }
3647
+ }
3648
+ let TeamLeader = await teamsServices.findteams( { clientId: userDetails.clientId, Teamlead: { $elemMatch: { email: userDetails.email } } } );
3649
+ if ( TeamLeader&&TeamLeader.length>0 ) {
3650
+ for ( let team of TeamLeader ) {
3651
+ let clusterList = await clusterServices.findcluster( { clientId: userDetails.clientId, teams: { $elemMatch: { name: team.teamName } } } );
3652
+ if ( clusterList.length > 0 ) {
3653
+ for ( let cluster of clusterList ) {
3654
+ cluster.stores.forEach( ( store ) => storeIds.add( store.storeId ) );
3655
+ }
3656
+ }
3657
+ }
3658
+ }
3659
+ // Convert Set back to Array if needed
3660
+ stores = Array.from( storeIds );
3661
+ }
3662
+ let data = {
3663
+ id: ele._id,
3664
+ email: email?.value,
3665
+ stores,
3666
+ role: userDetails.role,
3667
+ };
3668
+ result.push( data );
3669
+ }
3670
+ }
3671
+ } ) );
3672
+
3673
+ return res.sendSuccess( result );
3601
3674
  } catch ( e ) {
3675
+ console.log( e );
3602
3676
  logger.error( { functionName: 'checklistAutoMailList', error: e } );
3603
3677
  return res.sendError( e, 500 );
3604
3678
  }
@@ -38,7 +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
+ .get( '/checklistAutoMailList', isAllowedInternalAPIHandler, internalController.checklistAutoMailList )
42
42
  ;
43
43
 
44
44
 
@@ -0,0 +1,72 @@
1
+ import puppeteer from 'puppeteer';
2
+
3
+
4
+ const LAUNCH_ARGS = {
5
+
6
+ headless: 'new',
7
+
8
+ args: [ '--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu' ],
9
+
10
+ };
11
+
12
+
13
+ let browserInstance = null;
14
+
15
+ let launchPromise = null;
16
+
17
+
18
+ async function launchBrowser() {
19
+ const browser = await puppeteer.launch( LAUNCH_ARGS );
20
+
21
+ browser.on( 'disconnected', () => {
22
+ if ( browserInstance === browser ) {
23
+ browserInstance = null;
24
+
25
+ launchPromise = null;
26
+ }
27
+ } );
28
+
29
+ return browser;
30
+ }
31
+
32
+
33
+ /**
34
+
35
+ * Returns a shared browser instance, launching one if needed.
36
+
37
+ * Callers must close their pages, NOT the browser.
38
+
39
+ */
40
+
41
+ export async function getBrowser() {
42
+ if ( browserInstance ) {
43
+ try {
44
+ // verify browser is still alive
45
+
46
+ await browserInstance.version();
47
+
48
+ return browserInstance;
49
+ } catch {
50
+ browserInstance = null;
51
+
52
+ launchPromise = null;
53
+ }
54
+ }
55
+
56
+
57
+ if ( !launchPromise ) {
58
+ launchPromise = launchBrowser().then( ( b ) => {
59
+ browserInstance = b;
60
+
61
+ return b;
62
+ } ).catch( ( err ) => {
63
+ launchPromise = null;
64
+
65
+ throw err;
66
+ } );
67
+ }
68
+
69
+
70
+ return launchPromise;
71
+ }
72
+