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
|
@@ -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
|
|
3466
|
-
|
|
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
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
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
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
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
|
-
|
|
3490
|
+
if ( aiDetails?.statusCode != 200 || !aiDetails?.body?.hits?.hits.length ) {
|
|
3491
|
+
return res.sendError( 'Checklist not found', 404 );
|
|
3492
|
+
}
|
|
3501
3493
|
|
|
3502
|
-
|
|
3494
|
+
const doc = { ...aiDetails.body.hits.hits[0]._source };
|
|
3503
3495
|
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
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
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
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
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
);
|
|
3509
|
+
if ( complianceData?.data?.length ) {
|
|
3510
|
+
doc['historyData'] = complianceData.data;
|
|
3511
|
+
}
|
|
3531
3512
|
|
|
3532
|
-
|
|
3533
|
-
|
|
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
|
-
|
|
3524
|
+
const templateData = buildVisitChecklistTemplateData( doc, brandInfo );
|
|
3525
|
+
const resolvedData = resolveTemplateUrls( templateData, cdnBase );
|
|
3526
|
+
await imageCache.resolveAllImages( resolvedData );
|
|
3536
3527
|
|
|
3537
|
-
|
|
3538
|
-
await page.setContent( html, {
|
|
3539
|
-
waitUntil: 'networkidle0',
|
|
3540
|
-
timeout: 30000,
|
|
3541
|
-
} );
|
|
3528
|
+
const html = pdfTemplate( resolvedData );
|
|
3542
3529
|
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
3557
|
-
|
|
3558
|
-
|
|
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
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
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
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3561
|
+
const finalBuffer = Buffer.isBuffer( pdfBuffer ) ?
|
|
3562
|
+
pdfBuffer :
|
|
3563
|
+
Buffer.from( pdfBuffer );
|
|
3569
3564
|
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3565
|
+
const pdfName = `${safeName(
|
|
3566
|
+
doc.store_id + '_' + ( doc.storeName || 'store' ),
|
|
3567
|
+
)}.pdf`;
|
|
3573
3568
|
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3569
|
+
res.set( {
|
|
3570
|
+
'Content-Type': 'application/pdf',
|
|
3571
|
+
'Content-Disposition': `attachment; filename="${pdfName}"`,
|
|
3572
|
+
'Content-Length': finalBuffer.length,
|
|
3573
|
+
} );
|
|
3579
3574
|
|
|
3580
|
-
|
|
3581
|
-
} finally {
|
|
3582
|
-
await browser.close();
|
|
3583
|
-
}
|
|
3575
|
+
return res.send( finalBuffer );
|
|
3584
3576
|
} catch ( e ) {
|
|
3585
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
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
|
+
|