tango-app-api-trax 3.9.71 → 3.9.72
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 +1 -1
- package/src/controllers/internalTrax.controller.js +125 -191
- package/src/controllers/mobileTrax.controller.js +38 -39
- package/src/controllers/teaxFlag.controller.js +5 -5
- package/src/hbs/visit-checklist.hbs +4 -6
- package/src/utils/browserPool.utils.js +0 -40
- package/src/utils/visitChecklistPdf.utils.js +37 -40
package/package.json
CHANGED
|
@@ -29,7 +29,8 @@ import { insertSingleProcessData } from '../controllers/trax.controller.js';
|
|
|
29
29
|
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
|
-
import
|
|
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
|
import { fileURLToPath as toPath } from 'url';
|
|
@@ -1565,17 +1566,18 @@ export async function list( req, res ) {
|
|
|
1565
1566
|
|
|
1566
1567
|
export async function aomupdateCollection( req, res ) {
|
|
1567
1568
|
try {
|
|
1568
|
-
|
|
1569
|
+
const lenskart = JSON.parse( process.env.LENSKART );
|
|
1570
|
+
let url = lenskart.authUrl;
|
|
1569
1571
|
let authOptions = {
|
|
1570
1572
|
method: 'POST',
|
|
1571
1573
|
headers: {
|
|
1572
1574
|
'Content-Type': 'application/json',
|
|
1573
1575
|
'Accept': 'application/json',
|
|
1574
|
-
'X-Lenskart-App-Id':
|
|
1575
|
-
'X-Lenskart-API-Key':
|
|
1576
|
-
'Cookie':
|
|
1576
|
+
'X-Lenskart-App-Id': lenskart.appId,
|
|
1577
|
+
'X-Lenskart-API-Key': lenskart.apiKey,
|
|
1578
|
+
'Cookie': lenskart.authCookie,
|
|
1577
1579
|
},
|
|
1578
|
-
body: JSON.stringify( { isEncodingRequired: true, password:
|
|
1580
|
+
body: JSON.stringify( { isEncodingRequired: true, password: lenskart.password, userName: lenskart.userName } ),
|
|
1579
1581
|
};
|
|
1580
1582
|
const authResponse = await fetch( url, authOptions );
|
|
1581
1583
|
|
|
@@ -1584,16 +1586,16 @@ export async function aomupdateCollection( req, res ) {
|
|
|
1584
1586
|
}
|
|
1585
1587
|
|
|
1586
1588
|
let response = await authResponse.json();
|
|
1587
|
-
url =
|
|
1589
|
+
url = `${lenskart.amMappingUrl}?pageNumber=1&pageSize=100`;
|
|
1588
1590
|
let mappingOptions = {
|
|
1589
1591
|
method: 'GET',
|
|
1590
1592
|
headers: {
|
|
1591
1593
|
'accept': '*/*',
|
|
1592
|
-
'X-Lenskart-API-Key':
|
|
1593
|
-
'X-Lenskart-App-Id':
|
|
1594
|
+
'X-Lenskart-API-Key': lenskart.apiKey,
|
|
1595
|
+
'X-Lenskart-App-Id': lenskart.appId,
|
|
1594
1596
|
'X-Lenskart-Session-Token': `${response.sessionToken}`,
|
|
1595
1597
|
'X-Api-Client': 'tango',
|
|
1596
|
-
'Cookie':
|
|
1598
|
+
'Cookie': lenskart.dataCookie,
|
|
1597
1599
|
},
|
|
1598
1600
|
};
|
|
1599
1601
|
let mappingResponse = await fetch( url, mappingOptions );
|
|
@@ -1601,16 +1603,16 @@ export async function aomupdateCollection( req, res ) {
|
|
|
1601
1603
|
const mappingPages = mappingResponse?.content.pageable.total_pages;
|
|
1602
1604
|
for ( let index = mappingPages; index > 0; index-- ) {
|
|
1603
1605
|
const page = index;
|
|
1604
|
-
url =
|
|
1606
|
+
url = `${lenskart.amMappingUrl}?pageNumber=${page}&pageSize=100`;
|
|
1605
1607
|
const mappingOptions = {
|
|
1606
1608
|
method: 'GET',
|
|
1607
1609
|
headers: {
|
|
1608
1610
|
'accept': '*/*',
|
|
1609
|
-
'X-Lenskart-API-Key':
|
|
1610
|
-
'X-Lenskart-App-Id':
|
|
1611
|
+
'X-Lenskart-API-Key': lenskart.apiKey,
|
|
1612
|
+
'X-Lenskart-App-Id': lenskart.appId,
|
|
1611
1613
|
'X-Lenskart-Session-Token': `${response.sessionToken}`,
|
|
1612
1614
|
'X-Api-Client': 'tango',
|
|
1613
|
-
'Cookie':
|
|
1615
|
+
'Cookie': lenskart.dataCookie,
|
|
1614
1616
|
},
|
|
1615
1617
|
};
|
|
1616
1618
|
let mappingResponse = await fetch( url, mappingOptions );
|
|
@@ -1637,21 +1639,18 @@ export async function aomupdateCollection( req, res ) {
|
|
|
1637
1639
|
|
|
1638
1640
|
export async function saleUpdateCollection( req, res ) {
|
|
1639
1641
|
try {
|
|
1640
|
-
|
|
1642
|
+
const lenskart = JSON.parse( process.env.LENSKART );
|
|
1643
|
+
let url = lenskart.authUrl;
|
|
1641
1644
|
const authOptions = {
|
|
1642
1645
|
method: 'POST',
|
|
1643
1646
|
headers: {
|
|
1644
1647
|
'Content-Type': 'application/json',
|
|
1645
1648
|
'Accept': 'application/json',
|
|
1646
|
-
'X-Lenskart-App-Id':
|
|
1647
|
-
'X-Lenskart-API-Key':
|
|
1648
|
-
'Cookie':
|
|
1649
|
+
'X-Lenskart-App-Id': lenskart.appId,
|
|
1650
|
+
'X-Lenskart-API-Key': lenskart.apiKey,
|
|
1651
|
+
'Cookie': lenskart.authCookie,
|
|
1649
1652
|
},
|
|
1650
|
-
body: JSON.stringify( {
|
|
1651
|
-
isEncodingRequired: true,
|
|
1652
|
-
password: '55eyetango123',
|
|
1653
|
-
userName: 'tango.eye',
|
|
1654
|
-
} ),
|
|
1653
|
+
body: JSON.stringify( { isEncodingRequired: true, password: lenskart.password, userName: lenskart.userName } ),
|
|
1655
1654
|
};
|
|
1656
1655
|
let authResponse = await fetch( url, authOptions );
|
|
1657
1656
|
|
|
@@ -1661,16 +1660,16 @@ export async function saleUpdateCollection( req, res ) {
|
|
|
1661
1660
|
|
|
1662
1661
|
authResponse = await authResponse.json();
|
|
1663
1662
|
|
|
1664
|
-
url
|
|
1663
|
+
url=`${lenskart.salesmanUrl}?pageNumber=1&pageSize=100`;
|
|
1665
1664
|
const salesmanDetailsOptions = {
|
|
1666
1665
|
method: 'GET',
|
|
1667
1666
|
headers: {
|
|
1668
1667
|
'accept': '*/*',
|
|
1669
|
-
'X-Lenskart-API-Key':
|
|
1670
|
-
'X-Lenskart-App-Id':
|
|
1668
|
+
'X-Lenskart-API-Key': lenskart.apiKey,
|
|
1669
|
+
'X-Lenskart-App-Id': lenskart.appId,
|
|
1671
1670
|
'X-Lenskart-Session-Token': `${authResponse.sessionToken}`,
|
|
1672
1671
|
'X-Api-Client': 'tango',
|
|
1673
|
-
'Cookie':
|
|
1672
|
+
'Cookie': lenskart.dataCookie,
|
|
1674
1673
|
},
|
|
1675
1674
|
};
|
|
1676
1675
|
let salesmanDetailsResponse = await fetch( url, salesmanDetailsOptions );
|
|
@@ -1678,16 +1677,16 @@ export async function saleUpdateCollection( req, res ) {
|
|
|
1678
1677
|
const salesmanDetailsPages = salesmanDetailsResponse?.content.pageable.total_pages;
|
|
1679
1678
|
for ( let index = salesmanDetailsPages; index > 0; index-- ) {
|
|
1680
1679
|
const page = index;
|
|
1681
|
-
url=
|
|
1680
|
+
url= `${lenskart.salesmanUrl}?pageNumber=${page}&pageSize=100`;
|
|
1682
1681
|
const mappingOptions = {
|
|
1683
1682
|
method: 'GET',
|
|
1684
1683
|
headers: {
|
|
1685
1684
|
'accept': '*/*',
|
|
1686
|
-
'X-Lenskart-API-Key':
|
|
1687
|
-
'X-Lenskart-App-Id':
|
|
1685
|
+
'X-Lenskart-API-Key': lenskart.apiKey,
|
|
1686
|
+
'X-Lenskart-App-Id': lenskart.appId,
|
|
1688
1687
|
'X-Lenskart-Session-Token': `${authResponse.sessionToken}`,
|
|
1689
1688
|
'X-Api-Client': 'tango',
|
|
1690
|
-
'Cookie':
|
|
1689
|
+
'Cookie': lenskart.dataCookie,
|
|
1691
1690
|
},
|
|
1692
1691
|
};
|
|
1693
1692
|
let salesmanDetailsResponse = await fetch( url, mappingOptions );
|
|
@@ -2433,7 +2432,6 @@ export async function internalSendPushNotification( req, res ) {
|
|
|
2433
2432
|
}
|
|
2434
2433
|
|
|
2435
2434
|
let fcmToken;
|
|
2436
|
-
// fcmToken = 'dwm2tz9WfUq1jdz1H8hfSf:APA91bF9BhTsG9ZDnutQfseGueXk7FZ4RhB8v3G_xJOZhkiZz8vsw3SUWk5su8ZN37lx3-H50eouBKinbwg4zE_br6f483jUswA_44f1XG8k7Sok995f77M';
|
|
2437
2435
|
if ( requestHeader.email && requestHeader.email !='' ) {
|
|
2438
2436
|
fcmToken = await getUserToken( requestHeader.clientid, requestHeader.email );
|
|
2439
2437
|
} else {
|
|
@@ -3764,34 +3762,57 @@ export const downloadInsertPdf = async ( req, res ) => {
|
|
|
3764
3762
|
}
|
|
3765
3763
|
const cdnBase = JSON.parse( process.env.CDNURL )?.TraxAnswerCDN || '';
|
|
3766
3764
|
const pdfTemplate = getCompiledVisitChecklistTemplate();
|
|
3765
|
+
const imageCache = createImageCache();
|
|
3767
3766
|
|
|
3768
3767
|
const safeName = ( str ) =>
|
|
3769
3768
|
( str || '' ).toString().replace( /[<>:"/\\|?*]+/g, '_' );
|
|
3770
3769
|
|
|
3771
|
-
//
|
|
3772
|
-
//
|
|
3773
|
-
//
|
|
3774
|
-
|
|
3770
|
+
// const query = {
|
|
3771
|
+
// query: {
|
|
3772
|
+
// bool: {
|
|
3773
|
+
// must: [
|
|
3774
|
+
// {
|
|
3775
|
+
// term: {
|
|
3776
|
+
// _id: req.body.checklistId,
|
|
3777
|
+
// },
|
|
3778
|
+
// },
|
|
3779
|
+
// ],
|
|
3780
|
+
// },
|
|
3781
|
+
// },
|
|
3782
|
+
// };
|
|
3783
|
+
|
|
3784
|
+
|
|
3785
|
+
// 1) Launch browser page + fetch OpenSearch data in parallel
|
|
3786
|
+
const [ browser, aiDetails ] = await Promise.all( [
|
|
3787
|
+
getBrowserInstance(),
|
|
3788
|
+
// getOpenSearchData( JSON.parse( process.env.OPENSEARCH ).traxIndex, query ),
|
|
3789
|
+
processedchecklist.findOne( { _id: req.body.checklistId } ),
|
|
3790
|
+
] );
|
|
3791
|
+
console.log( aiDetails );
|
|
3775
3792
|
|
|
3776
3793
|
if ( !aiDetails ) {
|
|
3777
3794
|
return res.sendError( 'Checklist not found', 404 );
|
|
3778
3795
|
}
|
|
3779
3796
|
|
|
3780
|
-
const doc = aiDetails
|
|
3797
|
+
const doc = { ...aiDetails?.toObject() };
|
|
3781
3798
|
|
|
3782
|
-
|
|
3799
|
+
console.log( doc );
|
|
3800
|
+
|
|
3801
|
+
// 2) Fetch brandInfo + compliance data in parallel
|
|
3783
3802
|
const complianceURL = JSON.parse( process.env.LAMBDAURL ).complianceHistory;
|
|
3803
|
+
const detectionPayload = {
|
|
3804
|
+
'storeId': [ doc.store_id ],
|
|
3805
|
+
'userEmail': doc.userEmail,
|
|
3806
|
+
'sourceChecklist_id': doc.sourceCheckList_id,
|
|
3807
|
+
};
|
|
3808
|
+
|
|
3784
3809
|
const [ brandInfo, complianceData ] = await Promise.all( [
|
|
3785
3810
|
getBrandInfo( doc.client_id ),
|
|
3786
|
-
LamdaServiceCall( complianceURL,
|
|
3787
|
-
storeId: [ doc.store_id ],
|
|
3788
|
-
userEmail: doc.userEmail,
|
|
3789
|
-
sourceChecklist_id: doc.sourceCheckList_id,
|
|
3790
|
-
} ),
|
|
3811
|
+
LamdaServiceCall( complianceURL, detectionPayload ),
|
|
3791
3812
|
] );
|
|
3792
3813
|
|
|
3793
3814
|
if ( complianceData?.data?.length ) {
|
|
3794
|
-
doc
|
|
3815
|
+
doc['historyData'] = complianceData.data;
|
|
3795
3816
|
}
|
|
3796
3817
|
|
|
3797
3818
|
// CDN fix
|
|
@@ -3806,146 +3827,62 @@ export const downloadInsertPdf = async ( req, res ) => {
|
|
|
3806
3827
|
} );
|
|
3807
3828
|
|
|
3808
3829
|
const templateData = buildVisitChecklistTemplateData( doc, brandInfo );
|
|
3809
|
-
// Keep images as CDN URLs (do NOT inline as base64) — Chrome fetches them
|
|
3810
|
-
// at render time. Inlining produced a multi-MB HTML string that crashed
|
|
3811
|
-
// puppeteer's setContent CDP transfer ("Target closed").
|
|
3812
3830
|
const resolvedData = resolveTemplateUrls( templateData, cdnBase );
|
|
3831
|
+
await imageCache.resolveAllImages( resolvedData );
|
|
3813
3832
|
|
|
3814
3833
|
const html = pdfTemplate( resolvedData );
|
|
3815
3834
|
|
|
3816
|
-
// 3)
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
// "Execution context is not available in detached frame".
|
|
3820
|
-
// Render inside the concurrency slot; the slot returns just the PDF buffer.
|
|
3821
|
-
const finalBuffer = await withPdfSlot( async () => {
|
|
3822
|
-
const MAX_RENDER_ATTEMPTS = 2;
|
|
3823
|
-
let buffer = null;
|
|
3824
|
-
let lastError;
|
|
3825
|
-
|
|
3826
|
-
for ( let attempt = 1; attempt <= MAX_RENDER_ATTEMPTS; attempt++ ) {
|
|
3827
|
-
const browser = await getBrowserInstance();
|
|
3828
|
-
page = await browser.newPage();
|
|
3835
|
+
// 3) Create page and render — images are already base64 so use domcontentloaded
|
|
3836
|
+
page = await browser.newPage();
|
|
3837
|
+
await page.setViewport( { width: 1280, height: 1800 } );
|
|
3829
3838
|
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
if ( !nw || !nh ) continue;
|
|
3866
|
-
const scale = maxDim / Math.max( nw, nh );
|
|
3867
|
-
if ( scale >= 1 ) continue; // already small enough
|
|
3868
|
-
const canvas = document.createElement( 'canvas' );
|
|
3869
|
-
canvas.width = Math.round( nw * scale );
|
|
3870
|
-
canvas.height = Math.round( nh * scale );
|
|
3871
|
-
canvas.getContext( '2d' ).drawImage( img, 0, 0, canvas.width, canvas.height );
|
|
3872
|
-
const dataUrl = canvas.toDataURL( 'image/jpeg', 0.75 );
|
|
3873
|
-
canvas.width = 0;
|
|
3874
|
-
canvas.height = 0;
|
|
3875
|
-
img.src = dataUrl;
|
|
3876
|
-
if ( img.decode ) await img.decode().catch( () => {} );
|
|
3877
|
-
} catch ( e ) {
|
|
3878
|
-
// Keep the original image if downscaling fails.
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
}, 900 );
|
|
3882
|
-
|
|
3883
|
-
const pdfBuffer = await page.pdf( {
|
|
3884
|
-
format: 'A4',
|
|
3885
|
-
printBackground: true,
|
|
3886
|
-
preferCSSPageSize: true,
|
|
3887
|
-
displayHeaderFooter: true,
|
|
3888
|
-
headerTemplate: '<span></span>',
|
|
3889
|
-
footerTemplate: [
|
|
3890
|
-
'<div style="width:100%;padding:0 10mm;font-size:10px;',
|
|
3891
|
-
'font-family:Arial,sans-serif;display:flex;',
|
|
3892
|
-
'justify-content:space-between;align-items:center;',
|
|
3893
|
-
'border-top:1px solid #d9d9d9;padding-top:4px;">',
|
|
3894
|
-
'<span style="color:#999;">Page ',
|
|
3895
|
-
'<span class="pageNumber"></span>',
|
|
3896
|
-
' of <span class="totalPages"></span></span>',
|
|
3897
|
-
'<span style="display:flex;align-items:center;gap:6px;">',
|
|
3898
|
-
'<span style="color:#666;font-weight:400;font-size:10px;">',
|
|
3899
|
-
'Generated by</span>',
|
|
3900
|
-
tangoyeLogoSvg,
|
|
3901
|
-
'</span></div>',
|
|
3902
|
-
].join( '' ),
|
|
3903
|
-
} );
|
|
3904
|
-
|
|
3905
|
-
if ( !pdfBuffer || pdfBuffer.length === 0 ) {
|
|
3906
|
-
throw new Error( 'PDF generation resulted in empty buffer' );
|
|
3907
|
-
}
|
|
3908
|
-
|
|
3909
|
-
buffer = Buffer.isBuffer( pdfBuffer ) ? pdfBuffer : Buffer.from( pdfBuffer );
|
|
3910
|
-
break;
|
|
3911
|
-
} catch ( err ) {
|
|
3912
|
-
lastError = err;
|
|
3913
|
-
logger.error( {
|
|
3914
|
-
functionName: 'downloadInsertPdf',
|
|
3915
|
-
message: `PDF render attempt ${attempt} failed`,
|
|
3916
|
-
docId: doc?._id,
|
|
3917
|
-
error: err,
|
|
3918
|
-
} );
|
|
3919
|
-
|
|
3920
|
-
const msg = String( err?.message || '' );
|
|
3921
|
-
const retriable =
|
|
3922
|
-
msg.includes( 'detached frame' ) ||
|
|
3923
|
-
msg.includes( 'Execution context' ) ||
|
|
3924
|
-
msg.includes( 'Target closed' ) ||
|
|
3925
|
-
msg.includes( 'Session closed' );
|
|
3926
|
-
|
|
3927
|
-
if ( !retriable || attempt === MAX_RENDER_ATTEMPTS ) break;
|
|
3928
|
-
} finally {
|
|
3929
|
-
// Free the page (and its renderer) as soon as this attempt ends.
|
|
3930
|
-
if ( page ) {
|
|
3931
|
-
await page.close().catch( () => {} );
|
|
3932
|
-
page = null;
|
|
3933
|
-
}
|
|
3934
|
-
}
|
|
3935
|
-
}
|
|
3936
|
-
|
|
3937
|
-
if ( !buffer ) {
|
|
3938
|
-
logger.error( { functionName: 'downloadInsertPdf', message: 'PDF generation failed after retries', error: lastError } );
|
|
3939
|
-
}
|
|
3940
|
-
return buffer;
|
|
3941
|
-
} );
|
|
3942
|
-
|
|
3943
|
-
if ( !finalBuffer ) {
|
|
3839
|
+
try {
|
|
3840
|
+
await page.setContent( html, {
|
|
3841
|
+
waitUntil: 'domcontentloaded',
|
|
3842
|
+
timeout: 15000,
|
|
3843
|
+
} );
|
|
3844
|
+
await page.emulateMediaType( 'screen' );
|
|
3845
|
+
} catch ( err ) {
|
|
3846
|
+
logger.error( { functionName: 'downloadInsertPdf', message: 'setContent failed', error: err } );
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
let pdfBuffer;
|
|
3850
|
+
try {
|
|
3851
|
+
pdfBuffer = await page.pdf( {
|
|
3852
|
+
format: 'A4',
|
|
3853
|
+
printBackground: true,
|
|
3854
|
+
preferCSSPageSize: true,
|
|
3855
|
+
displayHeaderFooter: true,
|
|
3856
|
+
headerTemplate: '<span></span>',
|
|
3857
|
+
footerTemplate: [
|
|
3858
|
+
'<div style="width:100%;padding:0 10mm;font-size:10px;',
|
|
3859
|
+
'font-family:Arial,sans-serif;display:flex;',
|
|
3860
|
+
'justify-content:space-between;align-items:center;',
|
|
3861
|
+
'border-top:1px solid #d9d9d9;padding-top:4px;">',
|
|
3862
|
+
'<span style="color:#999;">Page ',
|
|
3863
|
+
'<span class="pageNumber"></span>',
|
|
3864
|
+
' of <span class="totalPages"></span></span>',
|
|
3865
|
+
'<span style="display:flex;align-items:center;gap:6px;">',
|
|
3866
|
+
'<span style="color:#666;font-weight:400;font-size:10px;">',
|
|
3867
|
+
'Generated by</span>',
|
|
3868
|
+
tangoyeLogoSvg,
|
|
3869
|
+
'</span></div>',
|
|
3870
|
+
].join( '' ),
|
|
3871
|
+
} );
|
|
3872
|
+
} catch ( err ) {
|
|
3873
|
+
logger.error( { functionName: 'downloadInsertPdf', message: 'PDF generation failed', error: err } );
|
|
3944
3874
|
return res.sendError( 'PDF generation failed', 500 );
|
|
3945
3875
|
}
|
|
3946
3876
|
|
|
3947
|
-
|
|
3948
|
-
|
|
3877
|
+
if ( !pdfBuffer || pdfBuffer.length === 0 ) {
|
|
3878
|
+
logger.error( { functionName: 'downloadInsertPdf', message: 'Empty PDF', docId: doc._id } );
|
|
3879
|
+
return res.sendError( 'PDF generation resulted in empty buffer', 500 );
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
const finalBuffer = Buffer.isBuffer( pdfBuffer ) ?
|
|
3883
|
+
pdfBuffer :
|
|
3884
|
+
Buffer.from( pdfBuffer );
|
|
3885
|
+
|
|
3949
3886
|
let pdfName;
|
|
3950
3887
|
if ( doc?.store_id ) {
|
|
3951
3888
|
pdfName = `${safeName(
|
|
@@ -3957,17 +3894,13 @@ export const downloadInsertPdf = async ( req, res ) => {
|
|
|
3957
3894
|
)}.pdf`;
|
|
3958
3895
|
}
|
|
3959
3896
|
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
Bucket: bucket.sop,
|
|
3965
|
-
body: finalBuffer,
|
|
3966
|
-
ContentType: 'application/pdf',
|
|
3897
|
+
res.set( {
|
|
3898
|
+
'Content-Type': 'application/pdf',
|
|
3899
|
+
'Content-Disposition': `attachment; filename="${pdfName}"`,
|
|
3900
|
+
'Content-Length': finalBuffer.length,
|
|
3967
3901
|
} );
|
|
3968
|
-
const downloadUrl = await signedUrl( { file_path: uploaded.Key, Bucket: bucket.sop } );
|
|
3969
3902
|
|
|
3970
|
-
return res.
|
|
3903
|
+
return res.send( finalBuffer );
|
|
3971
3904
|
} catch ( e ) {
|
|
3972
3905
|
logger.error( { functionName: 'downloadInsertPdf', error: e } );
|
|
3973
3906
|
return res.sendError( e, 500 );
|
|
@@ -4094,11 +4027,13 @@ export const downloadInsertPdfOld = async ( req, res ) => {
|
|
|
4094
4027
|
},
|
|
4095
4028
|
} );
|
|
4096
4029
|
|
|
4097
|
-
const browser = await
|
|
4098
|
-
|
|
4030
|
+
const browser = await puppeteer.launch( {
|
|
4031
|
+
headless: 'new',
|
|
4032
|
+
args: [ '--no-sandbox', '--disable-dev-shm-usage' ],
|
|
4033
|
+
} );
|
|
4099
4034
|
|
|
4100
4035
|
try {
|
|
4101
|
-
page = await browser.newPage();
|
|
4036
|
+
const page = await browser.newPage();
|
|
4102
4037
|
|
|
4103
4038
|
await page.setViewport( {
|
|
4104
4039
|
width: 1280,
|
|
@@ -4177,8 +4112,7 @@ export const downloadInsertPdfOld = async ( req, res ) => {
|
|
|
4177
4112
|
};
|
|
4178
4113
|
|
|
4179
4114
|
|
|
4180
|
-
|
|
4181
|
-
let complianceURL = 'https://pbxdm4nstcpbaz6gf6c53ofeee0zvmfy.lambda-url.ap-south-1.on.aws/';
|
|
4115
|
+
let complianceURL = JSON.parse( process.env.LAMBDAURL ).complianceHistory;
|
|
4182
4116
|
const complianceData = await LamdaServiceCall( complianceURL, detectionPayload );
|
|
4183
4117
|
if ( complianceData?.data.length ) {
|
|
4184
4118
|
doc['historyData'] = complianceData.data;
|
|
@@ -4334,7 +4268,7 @@ export const downloadInsertPdfOld = async ( req, res ) => {
|
|
|
4334
4268
|
}
|
|
4335
4269
|
}
|
|
4336
4270
|
} finally {
|
|
4337
|
-
|
|
4271
|
+
await browser.close();
|
|
4338
4272
|
}
|
|
4339
4273
|
|
|
4340
4274
|
// console.log( '🎉 All processing completed' );
|
|
@@ -28,7 +28,7 @@ import isSameOrBefore from 'dayjs/plugin/isSameOrBefore.js';
|
|
|
28
28
|
import * as cameraService from '../services/camera.service.js';
|
|
29
29
|
import * as recurringFlagTracker from '../services/recurringFlagTracker.service.js';
|
|
30
30
|
dayjs.extend( isSameOrBefore );
|
|
31
|
-
import
|
|
31
|
+
import puppeteer from 'puppeteer';
|
|
32
32
|
import handlebars from './handlebar-helper.js';
|
|
33
33
|
import fs from 'fs';
|
|
34
34
|
import path from 'path';
|
|
@@ -237,13 +237,12 @@ export async function startChecklist( req, res ) {
|
|
|
237
237
|
} );
|
|
238
238
|
let getupdatedchecklist = await processedchecklist.aggregate( findQuery );
|
|
239
239
|
// let bucket = JSON.parse( process.env.BUCKET );
|
|
240
|
-
const cdnurl = JSON.parse( process.env.CDNURL );
|
|
241
240
|
getupdatedchecklist[0].questionAnswers.forEach( ( section ) => {
|
|
242
|
-
section.questions.forEach( ( question ) => {
|
|
241
|
+
section.questions.forEach( async ( question ) => {
|
|
243
242
|
if ( question.questionReferenceImage && question.questionReferenceImage!='' ) {
|
|
244
243
|
question.questionReferenceImage = `${cdnurl.TraxAnswerCDN}${question.questionReferenceImage}`;
|
|
245
244
|
}
|
|
246
|
-
question.answers.forEach( ( answer ) => {
|
|
245
|
+
question.answers.forEach( async ( answer ) => {
|
|
247
246
|
if ( answer.referenceImage != '' ) {
|
|
248
247
|
answer.referenceImage = `${cdnurl.TraxAnswerCDN}${answer.referenceImage}`;
|
|
249
248
|
}
|
|
@@ -5511,7 +5510,6 @@ export async function chunkUpload( req, res ) {
|
|
|
5511
5510
|
}
|
|
5512
5511
|
|
|
5513
5512
|
export async function downloadChecklist( req, res ) {
|
|
5514
|
-
let page = null;
|
|
5515
5513
|
try {
|
|
5516
5514
|
let requestData = req.body;
|
|
5517
5515
|
|
|
@@ -5596,53 +5594,54 @@ export async function downloadChecklist( req, res ) {
|
|
|
5596
5594
|
const template = handlebars.compile( templateHtml );
|
|
5597
5595
|
const html = template( { data: checklistDetails } );
|
|
5598
5596
|
|
|
5599
|
-
// Generate PDF
|
|
5600
|
-
|
|
5601
|
-
|
|
5597
|
+
// Generate PDF using puppeteer instead of deprecated html-pdf
|
|
5598
|
+
const browser = await puppeteer.launch( {
|
|
5599
|
+
headless: 'new',
|
|
5600
|
+
args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage' ],
|
|
5601
|
+
} );
|
|
5602
5602
|
|
|
5603
|
-
|
|
5603
|
+
const page = await browser.newPage();
|
|
5604
5604
|
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5605
|
+
await page.setContent( html, {
|
|
5606
|
+
waitUntil: 'domcontentloaded',
|
|
5607
|
+
} );
|
|
5608
5608
|
|
|
5609
|
-
|
|
5609
|
+
/* -------- WAIT FOR ALL IMAGES TO LOAD -------- */
|
|
5610
5610
|
|
|
5611
|
-
|
|
5612
|
-
|
|
5611
|
+
await page.evaluate( async () => {
|
|
5612
|
+
const images = Array.from( document.images );
|
|
5613
5613
|
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5614
|
+
await Promise.all(
|
|
5615
|
+
images.map( ( img ) => {
|
|
5616
|
+
if ( img.complete ) return;
|
|
5617
5617
|
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5618
|
+
return new Promise( ( resolve ) => {
|
|
5619
|
+
img.onload = img.onerror = resolve;
|
|
5620
|
+
} );
|
|
5621
|
+
} ),
|
|
5622
|
+
);
|
|
5623
|
+
} );
|
|
5624
5624
|
|
|
5625
|
-
|
|
5625
|
+
/* ---------------- GENERATE PDF ---------------- */
|
|
5626
5626
|
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5627
|
+
const pdfBuffer = await page.pdf( {
|
|
5628
|
+
format: 'A4',
|
|
5629
|
+
printBackground: true,
|
|
5630
|
+
} );
|
|
5631
5631
|
|
|
5632
|
-
|
|
5632
|
+
await browser.close();
|
|
5633
5633
|
|
|
5634
|
-
|
|
5635
|
-
res.setHeader(
|
|
5636
|
-
'Content-Disposition',
|
|
5637
|
-
'attachment; filename=checkListDetails.pdf',
|
|
5638
|
-
);
|
|
5634
|
+
/* ---------------- RESPONSE ---------------- */
|
|
5639
5635
|
|
|
5640
|
-
|
|
5641
|
-
|
|
5636
|
+
res.setHeader( 'Content-Type', 'application/pdf' );
|
|
5637
|
+
res.setHeader(
|
|
5638
|
+
'Content-Disposition',
|
|
5639
|
+
'attachment; filename=checkListDetails.pdf',
|
|
5640
|
+
);
|
|
5641
|
+
|
|
5642
|
+
return res.end( pdfBuffer );
|
|
5642
5643
|
} catch ( e ) {
|
|
5643
5644
|
logger.error( { functionName: 'getChecklistQuestionAnswers', error: e } );
|
|
5644
5645
|
return res.sendError( e, 500 );
|
|
5645
|
-
} finally {
|
|
5646
|
-
if ( page ) await page.close().catch( () => {} );
|
|
5647
5646
|
}
|
|
5648
5647
|
}
|
|
@@ -447,7 +447,7 @@ export const flagCardsV1 = async ( req, res ) => {
|
|
|
447
447
|
// These three lookups are independent of one another, so run them concurrently
|
|
448
448
|
// instead of sequentially (Mongo aggregation + config find + detection Lambda).
|
|
449
449
|
const detectionPayload = { fromDate, toDate, storeId, clientId };
|
|
450
|
-
const lambdaURL =
|
|
450
|
+
const lambdaURL = JSON.parse( process.env.LAMBDAURL ).flagTablesV2;
|
|
451
451
|
|
|
452
452
|
const [ getOverallChecklistData, publishedAiChecklists, resultData ] = await Promise.all( [
|
|
453
453
|
processedchecklistService.aggregate( findQuery ),
|
|
@@ -598,7 +598,7 @@ export const flagComparisonCardsV1 = async ( req, res ) => {
|
|
|
598
598
|
return 0;
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
-
const LamdaURL =
|
|
601
|
+
const LamdaURL = JSON.parse( process.env.LAMBDAURL ).flagTablesV2;
|
|
602
602
|
const resultData = await LamdaServiceCall( LamdaURL, detectionPayload );
|
|
603
603
|
const published = publishedAiChecklists.map( ( val ) => val?.checkListType );
|
|
604
604
|
if ( resultData?.status_code === '200' ) {
|
|
@@ -854,7 +854,7 @@ export const flagTablesV1 = async ( req, res ) => {
|
|
|
854
854
|
'clientId': requestData.clientId,
|
|
855
855
|
};
|
|
856
856
|
|
|
857
|
-
let LamdaURL =
|
|
857
|
+
let LamdaURL = JSON.parse( process.env.LAMBDAURL ).flagTablesV2;
|
|
858
858
|
let resultData = await LamdaServiceCall( LamdaURL, detectionPayload );
|
|
859
859
|
if ( resultData ) {
|
|
860
860
|
if ( resultData.status_code == '200' ) {
|
|
@@ -2005,7 +2005,7 @@ export const flagComparisonCardsV2 = async ( req, res ) => {
|
|
|
2005
2005
|
};
|
|
2006
2006
|
|
|
2007
2007
|
// The config lookup and the detection Lambda are independent — run them together.
|
|
2008
|
-
const LamdaURL =
|
|
2008
|
+
const LamdaURL = JSON.parse( process.env.LAMBDAURL ).flagTablesV2;
|
|
2009
2009
|
const [ publishedAiChecklists, resultData ] = await Promise.all( [
|
|
2010
2010
|
checklistconfigService.find(
|
|
2011
2011
|
{
|
|
@@ -2291,7 +2291,7 @@ export const flagTablesV2 = async ( req, res ) => {
|
|
|
2291
2291
|
'storeId': requestData.storeId,
|
|
2292
2292
|
'clientId': requestData.clientId,
|
|
2293
2293
|
};
|
|
2294
|
-
// const LamdaURL =
|
|
2294
|
+
// const LamdaURL = JSON.parse( process.env.LAMBDAURL ).flagTablesV2;
|
|
2295
2295
|
|
|
2296
2296
|
let LamdaURL = lamdaUrl.flagTablesV2;
|
|
2297
2297
|
|
|
@@ -82,13 +82,11 @@
|
|
|
82
82
|
.q-answer-link{font-size:12px;color:#0085D2;text-decoration:underline;word-break:break-all}
|
|
83
83
|
.q-answer-caption{font-size:11px;color:#666;margin-bottom:4px}
|
|
84
84
|
.q-answer-remarks{font-size:11px;color:#666;margin-top:6px;white-space:pre-line}
|
|
85
|
-
/* User verification (
|
|
85
|
+
/* User verification (Submitted By) */
|
|
86
86
|
.user-verify{margin-top:28px;padding-top:18px;border-top:1px solid #d9d9d9;break-inside:avoid}
|
|
87
87
|
.user-verify-title{font-size:15px;font-weight:700;color:#1a1a1a;margin-bottom:14px}
|
|
88
88
|
.user-verify-photo{display:block;width:200px;height:240px;object-fit:cover;border-radius:8px;margin-bottom:10px}
|
|
89
89
|
.user-verify-name{font-size:14px;color:#1a1a1a;font-weight:600}
|
|
90
|
-
.user-verify-ack{font-size:14px;color:#1a1a1a;line-height:1.5;max-width:240px;margin-top:10px}
|
|
91
|
-
.user-verify-ack strong{font-weight:700}
|
|
92
90
|
.user-verify-sign-label{font-size:12px;color:#666;margin-top:16px;margin-bottom:6px;font-weight:600}
|
|
93
91
|
.user-verify-signature{display:block;width:220px;height:auto;max-height:110px;object-fit:contain;border:1px solid #eee;border-radius:6px;padding:6px;background:#fff}
|
|
94
92
|
/* Footer */
|
|
@@ -301,12 +299,12 @@
|
|
|
301
299
|
{{/each}}
|
|
302
300
|
{{#if showUserVerification}}
|
|
303
301
|
<div class="user-verify">
|
|
304
|
-
<div class="user-verify-title">
|
|
302
|
+
<div class="user-verify-title">Submitted By</div>
|
|
305
303
|
{{#if userImage}}
|
|
306
|
-
<img class="user-verify-photo" src="{{userImage}}" alt="
|
|
304
|
+
<img class="user-verify-photo" src="{{userImage}}" alt="Submitted by photo" />
|
|
307
305
|
{{/if}}
|
|
308
306
|
{{#if submittedBy}}
|
|
309
|
-
<div class="user-verify-
|
|
307
|
+
<div class="user-verify-name">{{userSignature}}</div>
|
|
310
308
|
{{/if}}
|
|
311
309
|
</div>
|
|
312
310
|
{{/if}}
|
|
@@ -70,43 +70,3 @@ export async function getBrowser() {
|
|
|
70
70
|
return launchPromise;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
* Concurrency limiter for PDF generation.
|
|
77
|
-
|
|
78
|
-
* Caps how many PDF jobs (pages/renderers) run at once so a burst of requests
|
|
79
|
-
|
|
80
|
-
* can't spawn unbounded Chrome work and exhaust memory. Configurable via
|
|
81
|
-
|
|
82
|
-
* MAX_CONCURRENT_PDF (default 3). Run each PDF job inside withPdfSlot(); the
|
|
83
|
-
|
|
84
|
-
* slot is always released, even if the task throws.
|
|
85
|
-
|
|
86
|
-
*/
|
|
87
|
-
|
|
88
|
-
const MAX_CONCURRENT_PDF = Number( process.env.MAX_CONCURRENT_PDF ) || 3;
|
|
89
|
-
|
|
90
|
-
let activePdf = 0;
|
|
91
|
-
|
|
92
|
-
const pdfWaiters = [];
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
export async function withPdfSlot( task ) {
|
|
96
|
-
if ( activePdf >= MAX_CONCURRENT_PDF ) {
|
|
97
|
-
await new Promise( ( resolve ) => pdfWaiters.push( resolve ) );
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
activePdf++;
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
return await task();
|
|
104
|
-
} finally {
|
|
105
|
-
activePdf--;
|
|
106
|
-
|
|
107
|
-
const next = pdfWaiters.shift();
|
|
108
|
-
|
|
109
|
-
if ( next ) next();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import puppeteer from 'puppeteer';
|
|
8
8
|
|
|
9
9
|
import handlebars from '../controllers/handlebar-helper.js';
|
|
10
10
|
|
|
@@ -1285,19 +1285,14 @@ export function createImageCache() {
|
|
|
1285
1285
|
}
|
|
1286
1286
|
|
|
1287
1287
|
|
|
1288
|
-
// Compiled once and reused for every request. Reading the .hbs from disk and
|
|
1289
|
-
// recompiling it on each PDF request was pure per-request overhead.
|
|
1290
|
-
// NOTE: editing visit-checklist.hbs now requires a server restart to take effect.
|
|
1291
|
-
let compiledVisitChecklistTemplate = null;
|
|
1292
|
-
|
|
1293
1288
|
export function getCompiledVisitChecklistTemplate() {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
const templateHtml = fs.readFileSync( templatePath, 'utf8' );
|
|
1297
|
-
compiledVisitChecklistTemplate = handlebars.compile( templateHtml );
|
|
1298
|
-
}
|
|
1289
|
+
const templatePath = path.join( __dirname, '../hbs/visit-checklist.hbs' );
|
|
1290
|
+
|
|
1299
1291
|
|
|
1300
|
-
|
|
1292
|
+
const templateHtml = fs.readFileSync( templatePath, 'utf8' );
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
return handlebars.compile( templateHtml );
|
|
1301
1296
|
}
|
|
1302
1297
|
|
|
1303
1298
|
|
|
@@ -1534,51 +1529,53 @@ export async function generateVisitChecklistPDF( templateData, baseUrl = 'https:
|
|
|
1534
1529
|
const html = template( resolvedData );
|
|
1535
1530
|
|
|
1536
1531
|
|
|
1537
|
-
|
|
1538
|
-
return withPdfSlot( async () => {
|
|
1539
|
-
const browser = await getBrowser();
|
|
1532
|
+
const browser = await puppeteer.launch( {
|
|
1540
1533
|
|
|
1541
|
-
|
|
1534
|
+
headless: 'new',
|
|
1542
1535
|
|
|
1543
|
-
|
|
1544
|
-
page = await browser.newPage();
|
|
1536
|
+
args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage' ],
|
|
1545
1537
|
|
|
1546
|
-
|
|
1538
|
+
} );
|
|
1547
1539
|
|
|
1548
1540
|
|
|
1549
|
-
|
|
1550
|
-
|
|
1541
|
+
try {
|
|
1542
|
+
const page = await browser.newPage();
|
|
1551
1543
|
|
|
1552
|
-
|
|
1544
|
+
await page.setContent( html, { waitUntil: 'domcontentloaded' } );
|
|
1553
1545
|
|
|
1554
|
-
images.map( ( img ) => {
|
|
1555
|
-
if ( img.complete ) return;
|
|
1556
1546
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
} );
|
|
1560
|
-
} ),
|
|
1547
|
+
await page.evaluate( async () => {
|
|
1548
|
+
const images = Array.from( document.images );
|
|
1561
1549
|
|
|
1562
|
-
|
|
1563
|
-
} );
|
|
1550
|
+
await Promise.all(
|
|
1564
1551
|
|
|
1552
|
+
images.map( ( img ) => {
|
|
1553
|
+
if ( img.complete ) return;
|
|
1565
1554
|
|
|
1566
|
-
|
|
1555
|
+
return new Promise( ( resolve ) => {
|
|
1556
|
+
img.onload = img.onerror = resolve;
|
|
1557
|
+
} );
|
|
1558
|
+
} ),
|
|
1567
1559
|
|
|
1568
|
-
|
|
1560
|
+
);
|
|
1561
|
+
} );
|
|
1569
1562
|
|
|
1570
|
-
printBackground: true,
|
|
1571
1563
|
|
|
1572
|
-
|
|
1564
|
+
const pdfBuffer = await page.pdf( {
|
|
1573
1565
|
|
|
1574
|
-
|
|
1566
|
+
format: 'A4',
|
|
1575
1567
|
|
|
1568
|
+
printBackground: true,
|
|
1576
1569
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1570
|
+
margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
|
|
1571
|
+
|
|
1572
|
+
} );
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
return pdfBuffer;
|
|
1576
|
+
} finally {
|
|
1577
|
+
await browser.close();
|
|
1578
|
+
}
|
|
1582
1579
|
}
|
|
1583
1580
|
|
|
1584
1581
|
|