pdf generation

This commit is contained in:
Kallesh B S 2025-08-07 20:18:55 +05:30
parent ad748be61d
commit c9508e47e9
6 changed files with 2603 additions and 1491 deletions

3
.gitignore vendored
View File

@ -3,6 +3,9 @@
/node_modules
/build
# pdf
/public/carnet-pdf/*.pdf
# Logs
logs
*.log

3370
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,9 @@
"oracledb": "^6.7.2",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"pdf-lib": "^1.17.1",
"pdf-merger-js": "^5.1.2",
"pdfkit": "^0.17.1",
"pg": "^8.13.3",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",

View File

@ -1,4 +1,4 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, UseGuards } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Res, StreamableFile, UseGuards } from '@nestjs/common';
import { CarnetApplicationService } from './carnet-application.service';
import { ApiTags } from '@nestjs/swagger';
@ -14,6 +14,10 @@ import {
import { Roles } from 'src/decorators/roles.decorator';
import { JwtAuthGuard } from 'src/guards/jwt-auth.guard';
import { RolesGuard } from 'src/guards/roles.guard';
import { Response } from 'express';
import { join } from 'path';
import { createReadStream } from 'fs';
import { deleteFilesAsync } from 'src/utils/helper';
@ApiTags('Carnet Application - Oracle')
@ -152,8 +156,31 @@ export class CarnetApplicationController {
// [PRINT_PKG]
@Post('PrintCarnet')
PrintCarnet(@Body() body: PrintCarnetDTO) {
return this.carnetApplicationService.PrintCarnet(body);
@Roles('ca', 'sa', 'ua')
async PrintCarnet(@Body() body: PrintCarnetDTO, @Res({ passthrough: true }) res: Response) {
const fileName = 'carnet.pdf';
const filePath = join(process.cwd(), 'public/carnet-pdf', fileName);
const filesArray = ['p2.pdf', fileName];
try {
await this.carnetApplicationService.PrintCarnet(body);
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${fileName}"`,
});
const stream = createReadStream(filePath);
// Clean up files after response finishes streaming
res.on('finish', async () => {
await deleteFilesAsync(filesArray);
});
return new StreamableFile(stream);
} catch (error) {
return error.message;
}
}
}

View File

@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { OracleDBService } from 'src/db/db.service';
import * as oracledb from 'oracledb'
import { InternalServerException } from 'src/exceptions/internalServerError.exception';
import { closeOracleDbConnection, fetchCursor, handleError, setEmptyStringsToNull } from 'src/utils/helper';
import { closeOracleDbConnection, deleteFilesAsync, fetchCursor, generateFinalPDF, generateGeneralistPDF1, generateGeneralistPDF2, handleError, pdfCarnetData, pdfGLData, setEmptyStringsToNull } from 'src/utils/helper';
import {
CarnetProcessingCenterDTO, SaveCarnetApplicationDTO, TransmitApplicationtoProcessDTO,
@ -1114,7 +1114,8 @@ export class CarnetApplicationService {
}
// [PRINT_PKG]
async PrintCarnet(body: PrintCarnetDTO) {
async getPrintingData(body: PrintCarnetDTO) {
let connection;
try {
connection = await this.oracleDBService.getConnection();
@ -1147,12 +1148,17 @@ export class CarnetApplicationService {
const fres: any = await fetchCursor(outBinds.P_CARNETDATACURSOR, CarnetApplicationService.name);
const fres1: any = await fetchCursor(outBinds.P_GLDATACURSOR, CarnetApplicationService.name);
if (fres.length === 0 || fres1.length === 0) {
throw new BadRequestException("Print failed please check the submitted details");
}
if (fres.length > 0 && fres[0].ERRORMESG) {
this.logger.warn(fres[0].ERRORMESG);
throw new BadRequestException(fres[0].ERRORMESG)
}
return { P_CARNETDATACURSOR: fres, P_GLDATACURSOR: fres1 };
return { fres: fres, fres1: fres1 }
} catch (error) {
handleError(error, CarnetApplicationService.name)
} finally {
@ -1160,4 +1166,57 @@ export class CarnetApplicationService {
}
}
async PrintCarnet(body: PrintCarnetDTO) {
try {
const { fres }: any = await this.getPrintingData(body);
const { fres1 }: any = await this.getPrintingData(body);
if (fres.length === 0 || fres1.length === 0) {
throw new BadRequestException("Print failed please check the submitted details");
}
if (fres.length > 0 && fres[0].ERRORMESG) {
this.logger.warn(fres[0].ERRORMESG);
throw new BadRequestException(fres[0].ERRORMESG)
}
try {
const glData: pdfGLData[] = fres1.map(x => {
return {
itemNo: x.ITEMNO,
description: x.ITEMDESCRIPTION,
noOfPieces: x.NOOFPIECES,
weight: x.WEIGHT,
itemValue: x.ITEMVALUE,
countryOfOrigin: x.GOODSORIGINCOUNTRY
};
});
const carnetData: pdfCarnetData = {
carnetNo: fres[0].CARNETNO,
contPageNo: 0
}
let p2Status: string = "";
if (body.P_PRINTGL === 'Y') {
p2Status = await generateGeneralistPDF1(glData, carnetData);
} else {
p2Status = await generateGeneralistPDF2(carnetData.carnetNo);
}
if (p2Status === "p2 done") {
await generateFinalPDF(['p2.pdf'], 'carnet.pdf');
}
} catch (error) {
throw new InternalServerException(error.message)
}
} catch (error) {
handleError(error, CarnetApplicationService.name)
}
}
}

View File

@ -5,10 +5,19 @@ import {
} from '@nestjs/common';
import * as oracledb from 'oracledb';
const PDFDocument = require('pdfkit');
const fs = require('fs');
const path = require('path');
const PDFMerger = require('pdf-merger-js').default;
import * as fsPromises from 'fs/promises';
import { BadRequestException as BR } from 'src/exceptions/badRequest.exception';
import { existsSync, readFileSync } from 'fs';
import { PDFDocument as pdfl } from 'pdf-lib';
const logger = new Logger('Helper');
export const handleError = (error: any, context: string = 'UnknownService'): never => {
if (error instanceof BadRequestException) {
if (error instanceof BadRequestException || error instanceof BR) {
logger.warn(`[${context}] ${error.message}`);
throw error;
}
@ -57,3 +66,612 @@ export const setEmptyStringsToNull = (obj: any): void => {
}
});
}
export interface pdfGLData {
itemNo: number;
description: string;
noOfPieces: number;
weight: string;
itemValue: number;
countryOfOrigin: string
}
export interface pdfCarnetData {
carnetNo: string;
contPageNo: number;
}
function formatNumber(num) {
if (isNaN(num)) return 'N/A';
return num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
export const generateGeneralistPDF1 = async (jsonData: pdfGLData[], headerData: pdfCarnetData) => {
const doc = new PDFDocument({
size: 'A4',
margins: {
top: 50,
bottom: 50,
left: 50,
right: 50
}
});
// 2. Define the file path
const generalistFilePath = path.join(__dirname, '../../../public/carnet-pdf', 'p2.pdf');
// 3. Create a write stream and pipe the document to it
const writeStream = fs.createWriteStream(generalistFilePath);
doc.pipe(writeStream);
// --- Table Configuration ---
const table = {
x: doc.x,
y: doc.y + 20, // Start a bit below the current cursor
cellTopPadding: 5,
cellBottomPadding: 2,
cellHorizontalPadding: 5,
cellSpacing: 0,
headerColor: '#FFFFFF',
rowColor: '#FFFFFF',
alternatingRowColor: '#FFFFFF',
lineColor: '#000000',
headerFontColor: '#000000',
bodyFontColor: '#000000',
fontSize: 10,
headerFontSize: 10,
lineWidth: 0.5,
widths: [50, 120, 70, 80, 70, 100], //
totalRowPaddingTop: 10 // New padding for the total row
};
// Define the maximum number of lines for the entire page
const maxLinesPerPage = 37;
const fixedHeaderRowHeight = 22; // You can tweak this value
const fixedHeaderFontSize = 10; // Same font size for all rows
// Fixed header rows, which are not part of the dynamic data
function getFixedHeaderData(headerValues) {
return [
{
isMergedRow: true,
cols: [
{ text: 'General List', startCol: 0, endCol: 5, }
]
},
{
isMergedRow: true,
cols: [
{ text: '', startCol: 0, endCol: 2 },
{ text: 'Carnet No:', startCol: 3, endCol: 4 },
{ text: headerValues.carnetNo, startCol: 5, endCol: 5 },
]
},
{
isMergedRow: true,
cols: [
{ text: '', startCol: 0, endCol: 2 },
{ text: 'Contn.Sheet No:', startCol: 3, endCol: 4 },
{ text: headerValues.contPageNo, startCol: 5, endCol: 5 },
]
},
{ // This is the new blank row
isMergedRow: true,
cols: [
{ text: '', startCol: 0, endCol: 5 }
]
},
['Item No', 'Description', 'No of Pieces', 'Weight', 'Item Value', 'Country Of Origin']
];
}
// Helper function to draw a single horizontal line
function drawHorizontalLine(doc, x, y, width, lineColor, lineWidth) {
doc.strokeColor(lineColor)
.lineWidth(lineWidth)
.moveTo(x, y)
.lineTo(x + width, y)
.stroke();
}
// Helper function to draw vertical lines between columns for a specific height range
function drawVerticalLines(doc, table, startY, endY) {
doc.strokeColor(table.lineColor)
.lineWidth(table.lineWidth);
let verticalX = table.x;
for (let i = 0; i < table.widths.length; i++) {
doc.moveTo(verticalX, startY)
.lineTo(verticalX, endY)
.stroke();
verticalX += table.widths[i];
}
// Draw the final right vertical line
doc.moveTo(verticalX, startY)
.lineTo(verticalX, endY)
.stroke();
}
// Helper function to draw the table header
function drawHeader(doc, table, headerData, cursorY, initialX, totalTableWidth) {
const totalVerticalPadding = table.cellTopPadding + table.cellBottomPadding;
const totalHorizontalPadding = table.cellHorizontalPadding * 2;
let headerHeight = 0;
let maxTextHeight = 0;
doc.fontSize(table.headerFontSize).font('Helvetica');
for (let i = 0; i < headerData.length; i++) {
const textOptions = {
width: table.widths[i] - totalHorizontalPadding,
lineGap: 3
};
const h = doc.heightOfString(headerData[i], textOptions);
if (h > maxTextHeight) maxTextHeight = h;
}
headerHeight = maxTextHeight + totalVerticalPadding;
const headerLines = Math.ceil(maxTextHeight / doc.heightOfString('a', { lineGap: 3 }));
let currentX = initialX;
for (let i = 0; i < headerData.length; i++) {
doc.rect(currentX, cursorY, table.widths[i], headerHeight).fill(table.headerColor);
currentX += table.widths[i];
}
currentX = initialX;
for (let i = 0; i < headerData.length; i++) {
doc.fillColor(table.headerFontColor);
doc.text(headerData[i], currentX + table.cellHorizontalPadding, cursorY + table.cellTopPadding, {
width: table.widths[i] - totalHorizontalPadding,
lineGap: 3
});
currentX += table.widths[i];
}
return { height: headerHeight, lines: headerLines };
}
// Helper function to draw a merged row
function drawMergedRow(doc, table, rowData, cursorY, initialX) {
const totalHorizontalPadding = table.cellHorizontalPadding * 2;
const rowHeight = fixedHeaderRowHeight; // Use fixed height
const fontSize = fixedHeaderFontSize;
let currentX = initialX;
doc.fontSize(fontSize).font('Helvetica'); // Ensure consistent font
for (let i = 0; i < table.widths.length; i++) {
const cell = rowData.cols.find(c => c.startCol === i);
if (cell) {
let mergedWidth = 0;
for (let j = cell.startCol; j <= cell.endCol; j++) {
mergedWidth += table.widths[j];
}
const fillColor = table.rowColor;
doc.rect(currentX, cursorY, mergedWidth, rowHeight).fill(fillColor);
if (cell.text) {
doc.fillColor(table.bodyFontColor);
doc.text(cell.text, currentX + table.cellHorizontalPadding, cursorY + table.cellTopPadding, {
width: mergedWidth - totalHorizontalPadding,
height: rowHeight - table.cellTopPadding - table.cellBottomPadding,
lineGap: 3
});
}
currentX += mergedWidth;
i = cell.endCol;
} else {
currentX += table.widths[i];
}
}
return { height: rowHeight, lines: 1 };
}
// Helper function to draw the fixed header block
function drawFixedHeader(doc, table, fixedHeaderData, cursorY, initialX, totalTableWidth) {
let headerBlockHeight = 0;
let headerBlockLines = 0;
let horizontalLineYCoords: number[] = [];
horizontalLineYCoords.push(cursorY); // Top of the entire header block
// Draw merged rows (first 4 rows, including the new blank one)
for (let i = 0; i < 4; i++) {
const rowData = fixedHeaderData[i];
const { height: rowHeight, lines: rowLines } = drawMergedRow(doc, table, rowData, cursorY, initialX);
cursorY += rowHeight;
headerBlockHeight += rowHeight;
headerBlockLines += rowLines;
horizontalLineYCoords.push(cursorY);
}
// Draw the main header
const headerRow = fixedHeaderData[4];
const { height: headerHeight, lines: headerLines } = drawHeader(doc, table, headerRow, cursorY, initialX, totalTableWidth);
cursorY += headerHeight;
headerBlockHeight += headerHeight;
headerBlockLines += headerLines;
horizontalLineYCoords.push(cursorY);
// Now draw the horizontal lines for the entire header block
for (const yCoord of horizontalLineYCoords) {
drawHorizontalLine(doc, initialX, yCoord, totalTableWidth, table.lineColor, table.lineWidth);
}
// Now, manually draw the vertical lines for each header row based on its merged cells
// Outer left border for the entire header block
doc.moveTo(initialX, horizontalLineYCoords[0]).lineTo(initialX, cursorY).stroke();
// Outer right border for the entire header block
doc.moveTo(initialX + totalTableWidth, horizontalLineYCoords[0]).lineTo(initialX + totalTableWidth, cursorY).stroke();
// Loop through the fixed header rows to draw internal vertical lines
for (let i = 0; i < fixedHeaderData.length; i++) {
const rowData = fixedHeaderData[i];
const topY = horizontalLineYCoords[i];
const bottomY = horizontalLineYCoords[i + 1];
let currentX = initialX;
if (Array.isArray(rowData)) {
// Draw all vertical lines for the non-merged header row
for (let j = 0; j < table.widths.length - 1; j++) {
currentX += table.widths[j];
doc.moveTo(currentX, topY).lineTo(currentX, bottomY).stroke();
}
} else if (i === 3) {
// Special case for the blank merged row (4th row, index 3)
// We want to draw all the vertical lines as if it's a regular row
for (let j = 0; j < table.widths.length - 1; j++) {
currentX += table.widths[j];
doc.moveTo(currentX, topY).lineTo(currentX, bottomY).stroke();
}
}
else {
// Draw internal lines for other merged rows
for (const cell of rowData.cols) {
let mergedWidth = 0;
for (let j = cell.startCol; j <= cell.endCol; j++) {
mergedWidth += table.widths[j];
}
currentX += mergedWidth;
// Don't draw a vertical line if it's the very last column's right border
if (cell.endCol < table.widths.length - 1) {
doc.moveTo(currentX, topY).lineTo(currentX, bottomY).stroke();
}
}
}
}
return { height: headerBlockHeight, endY: cursorY, lines: headerBlockLines };
}
// Helper function to draw the total row without borders or vertical lines
function drawTotalRow(doc, table, totalValue, cursorY, initialX) {
// const totalRowDataArray = ['', '', '', 'Total', totalValue.toFixed(2), ''];
const totalRowDataArray = ['', '', '', 'Total', formatNumber(totalValue), ''];
const totalVerticalPadding = table.cellTopPadding + table.cellBottomPadding;
const totalHorizontalPadding = table.cellHorizontalPadding * 2;
const totalTableWidth = table.widths.reduce((a, b) => a + b, 0);
let totalRowHeight = 0;
let maxTotalHeightInRow = 0;
for (let j = 0; j < totalRowDataArray.length; j++) {
const textOptions = {
width: table.widths[j] - totalHorizontalPadding,
lineGap: 3
};
const h = doc.heightOfString(totalRowDataArray[j], textOptions);
if (h > maxTotalHeightInRow) {
maxTotalHeightInRow = h;
}
}
totalRowHeight = maxTotalHeightInRow + totalVerticalPadding;
const linesInTotalRow = Math.ceil(maxTotalHeightInRow / doc.heightOfString('a', { lineGap: 3 }));
const startY = cursorY + table.totalRowPaddingTop;
// Draw the text only, without any rectangles or lines
let currentX = initialX;
doc.fontSize(table.fontSize);
for (let j = 0; j < totalRowDataArray.length; j++) {
doc.fillColor(table.bodyFontColor);
doc.text(totalRowDataArray[j], currentX + table.cellHorizontalPadding, startY + table.cellTopPadding, {
width: table.widths[j] - totalHorizontalPadding,
lineGap: 3
});
currentX += table.widths[j];
}
return { height: totalRowHeight + table.totalRowPaddingTop, lines: linesInTotalRow, endY: startY + totalRowHeight };
}
// --- Function to manually draw a single data row ---
function drawDataRow(doc, table, rowData, cursorY, initialX, isEvenRow) {
const totalVerticalPadding = table.cellTopPadding + table.cellBottomPadding;
const totalHorizontalPadding = table.cellHorizontalPadding * 2;
const totalTableWidth = table.widths.reduce((a, b) => a + b, 0);
let rowHeight = 0;
let maxHeightInRow = 0;
const rowDataArray = [
rowData.itemNo,
rowData.description,
rowData.noOfPieces,
rowData.weight,
formatNumber(rowData.itemValue), // formatted Item Value
rowData.countryOfOrigin
];
for (let j = 0; j < rowDataArray.length; j++) {
const textOptions = { width: table.widths[j] - totalHorizontalPadding, lineGap: 3 };
const h = doc.heightOfString(rowDataArray[j] || '', textOptions);
if (h > maxHeightInRow) {
maxHeightInRow = h;
}
}
rowHeight = maxHeightInRow + totalVerticalPadding;
const linesInRow = Math.ceil(maxHeightInRow / doc.heightOfString('a', { lineGap: 3 }));
// Draw the data row
const fillColor = isEvenRow ? table.alternatingRowColor : table.rowColor;
doc.rect(initialX, cursorY, totalTableWidth, rowHeight).fill(fillColor); // Fill the entire row rectangle
let currentX = initialX;
doc.fontSize(table.fontSize);
// for (let j = 0; j < rowDataArray.length; j++) {
// doc.fillColor(table.bodyFontColor);
// doc.text(rowDataArray[j], currentX + table.cellHorizontalPadding, cursorY + table.cellTopPadding, {
// width: table.widths[j] - totalHorizontalPadding,
// lineGap: 3
// });
// currentX += table.widths[j];
// }
for (let j = 0; j < rowDataArray.length; j++) {
doc.fillColor(table.bodyFontColor);
const alignRight = (j === 2 || j === 4); // right-align No of Pieces and Item Value
doc.text(rowDataArray[j], currentX + table.cellHorizontalPadding, cursorY + table.cellTopPadding, {
width: table.widths[j] - totalHorizontalPadding,
lineGap: 3,
align: alignRight ? 'right' : 'left'
});
currentX += table.widths[j];
}
return { height: rowHeight, lines: linesInRow, endY: cursorY + rowHeight };
}
// --- Function to manually draw the table from JSON data ---
function drawTable(doc, table, jsonData, headerData) {
let cursorY = table.y;
const initialX = table.x;
let continuationPageNo = 0;
const totalTableWidth = table.widths.reduce((a, b) => a + b, 0);
let rowStart = 0;
while (rowStart < jsonData.length) {
let linesUsed = 0;
let rowsOnPage: any = [];
let currentY = cursorY;
let currentPageTotal = 0;
// Draw fixed header block and count its visual lines
const fixedHeaderData = getFixedHeaderData({ ...headerData, contPageNo: String(continuationPageNo) });
const fixedHeaderInfo = drawFixedHeader(doc, table, fixedHeaderData, currentY, initialX, totalTableWidth);
currentY = fixedHeaderInfo.endY;
linesUsed += fixedHeaderInfo.lines;
// Collect data rows that can fit in remaining lines
for (let i = rowStart; i < jsonData.length; i++) {
const rowData = jsonData[i];
const isEvenRow = i % 2 === 0;
const { height, lines } = drawDataRow(doc, table, rowData, -1000, initialX, isEvenRow); // simulate row height
const remainingRows = jsonData.length - i - 1;
const willBeLastPage = remainingRows === 0;
const extraLines = 1 + (willBeLastPage ? 1 : 0);
if (linesUsed + lines + extraLines + 1 > maxLinesPerPage) break; // +1 for total row
rowsOnPage.push({ data: rowData, isEven: isEvenRow, height, lines });
linesUsed += lines;
rowStart++;
currentPageTotal += parseFloat(rowData.itemValue);
}
// Render data rows
let horizontalLineYCoords: number[] = [currentY];
for (const row of rowsOnPage as any) {
drawDataRow(doc, table, row.data, currentY, initialX, row.isEven);
currentY += row.height;
horizontalLineYCoords.push(currentY);
}
// Draw horizontal lines for data rows
for (const yCoord of horizontalLineYCoords) {
drawHorizontalLine(doc, initialX, yCoord, totalTableWidth, table.lineColor, table.lineWidth);
}
// Draw vertical lines for data block
drawVerticalLines(doc, table, horizontalLineYCoords[0], currentY);
// Draw total row
const totalRowInfo = drawTotalRow(doc, table, currentPageTotal, currentY, initialX);
currentY = totalRowInfo.endY;
const isLastDataPage = rowStart >= jsonData.length;
if (isLastDataPage) {
const endMessage = '***** End of list, No item can be added *****';
const fontSize = 10;
const verticalGap = 10;
doc.fontSize(fontSize).font('Helvetica-Bold');
const textWidth = doc.widthOfString(endMessage);
const centerX = (doc.page.width - textWidth) / 2;
const endY = currentY + verticalGap;
doc.text(endMessage, centerX, endY);
currentY = endY + doc.heightOfString(endMessage); // update Y if needed
}
// Add new page if more data remains
if (rowStart < jsonData.length) {
doc.addPage();
continuationPageNo++;
cursorY = doc.page.margins.top;
}
}
return continuationPageNo + 1;
}
// Draw the custom table and get the total page count
const totalPages = drawTable(doc, table, jsonData, headerData);
// Check if the total number of pages is even and add a "VOID" page if it is
if (totalPages % 2 === 0) {
doc.addPage();
const voidText = 'VOID';
doc.fontSize(14).font('Helvetica');
const textWidth = doc.widthOfString(voidText);
const centerX = (doc.page.width - textWidth) / 2;
doc.text(voidText, centerX, doc.page.margins.top);
}
// Finalize the PDF file
doc.end();
return new Promise<string>((resolve, reject) => {
writeStream.on('finish', () => {
console.log("PDF p2.pdf has been successfully created and saved.");
resolve("p2 done");
});
writeStream.on('error', (err) => {
console.error("Error writing PDF to file:", err);
reject(err);
});
});
}
export const generateGeneralistPDF2 = async (carnetNumber: string) => {
// 1. Create a new PDF document instance
const doc = new PDFDocument({
size: 'A4',
margins: {
top: 50,
bottom: 50,
left: 50,
right: 50
}
});
// 2. Define the file path
const generalistFilePath = path.join(__dirname, '../../../public/carnet-pdf', 'p2.pdf');
// 3. Create a write stream and pipe the document to it
const writeStream = fs.createWriteStream(generalistFilePath);
doc.pipe(writeStream);
// 4. Add content to the PDF
const pageWidth = doc.page.width;
const seeAttachedText = 'See Attached';
const seeAttachedWidth = doc.widthOfString(seeAttachedText);
const uniqueNoText = `Carnet No: ${carnetNumber}`;
const uniqueNoWidth = doc.widthOfString(uniqueNoText);
const centerX = pageWidth / 2;
const seeAttachedX = centerX - (seeAttachedWidth / 2);
const spacing = 90;
const uniqueNoX = seeAttachedX + seeAttachedWidth + spacing;
const y = 100;
let adjustedUniqueNoX = uniqueNoX;
if (uniqueNoX + uniqueNoWidth > pageWidth - 50) {
adjustedUniqueNoX = pageWidth - uniqueNoWidth - 50;
}
doc.text(seeAttachedText, seeAttachedX, y);
doc.text(uniqueNoText, adjustedUniqueNoX, y);
// 5. Finalize the PDF document
doc.end();
// 6. Return a Promise that resolves when the file is fully written
return new Promise<string>((resolve, reject) => {
writeStream.on('finish', () => {
console.log("PDF p2.pdf has been successfully created and saved.");
resolve("p2 done");
});
writeStream.on('error', (err) => {
console.error("Error writing PDF to file:", err);
reject(err);
});
});
};
export const generateGreenCoverPDF = async () => {
const templatePath = path.join(__dirname, '../../..', 'public/pdf-templates', '2GeneralList.pdf');
const existingPdfBytes = fs.readFileSync(templatePath);
const pdfDoc = await pdfl.load(existingPdfBytes);
const form = pdfDoc.getForm();
let fields = form.getFields();
}
export const generateFinalPDF = async (inputFiles: string[], outputFile: string) => {
const merger = new PDFMerger();
for (const fileName of inputFiles) {
const filePath = path.join(__dirname, '../../../public/carnet-pdf', fileName);
console.log(`Does the file exist? ${existsSync(filePath)}`);
console.log(`📄 Adding: ${filePath}`);
try {
const fileData = readFileSync(filePath);
await merger.add(fileData);
} catch (err) {
console.error(`❌ Failed to add PDF: ${fileName}`, err);
}
}
const outputPath = path.join(__dirname, '../../../public/carnet-pdf', outputFile);
try {
await merger.save(outputPath);
console.log(`✅ Merged PDF saved to ${outputPath}`);
} catch (err) {
console.error(`❌ Failed to save merged PDF`, err);
throw new Error("Failed to save merged PDF");
}
};
export const deleteFilesAsync = async (relativeFilePaths: string[]): Promise<void> => {
for (const relativeFilePath of relativeFilePaths) {
const filePath = path.join(__dirname, '../../../public/carnet-pdf', relativeFilePath);
if (fs.existsSync(filePath)) {
try {
await fsPromises.unlink(filePath);
console.log(`🗑️ Deleted: ${filePath}`);
} catch (err) {
console.error(`❌ Error deleting file: ${filePath}`, err);
}
} else {
console.warn(`⚠️ File not found: ${filePath}`);
}
}
};