653 lines
21 KiB
TypeScript
653 lines
21 KiB
TypeScript
import fs from "fs";
|
|
import axios from "axios";
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Routes
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| This file is dedicated for defining HTTP routes. A single file is enough
|
|
| for majority of projects, however you can define routes in different
|
|
| files and just make sure to import them inside this file. For example
|
|
|
|
|
| Define routes in following two files
|
|
| ├── start/routes/cart.ts
|
|
| ├── start/routes/customer.ts
|
|
|
|
|
| and then import them inside `start/routes.ts` as follows
|
|
|
|
|
| import './routes/cart'
|
|
| import './routes/customer'
|
|
|
|
|
*/
|
|
|
|
import Route from "@ioc:Adonis/Core/Route";
|
|
import { runtimeCheckLogs } from "App/utils/runtimeCheckLogs";
|
|
import Env from "@ioc:Adonis/Core/Env";
|
|
import { sendMessToZulip } from "App/utils/sendMessToZulip";
|
|
import moment from "moment";
|
|
import Product from "App/Models/Product";
|
|
import { sendDeviceInfora } from "App/utils/sendDeviceInfor";
|
|
import InfoDevice from "App/Models/InfoDevice";
|
|
import LogReport from "App/Models/LogReport";
|
|
import Cache from "@ioc:Kaperskyguru/Adonis-Cache";
|
|
import { checkDateWiki } from "App/utils/helper";
|
|
const util = require("util");
|
|
const exec = util.promisify(require("child_process").exec);
|
|
const { DocumentProcessorServiceClient } =
|
|
require("@google-cloud/documentai").v1;
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
runtimeCheckLogs(Env.get("FOLDER_LOGS"));
|
|
|
|
Route.post("/api/getIndexSerialNumber", "ErpsController.getIndexSerialNumber")
|
|
.middleware("checkToken")
|
|
.middleware("writeLog");
|
|
// .middleware("writeLog");
|
|
|
|
Route.post("/api/getParagraph", "ErpsController.getParagraph")
|
|
.middleware("checkToken")
|
|
.middleware("writeLog");
|
|
|
|
//Users
|
|
Route.post("/api/account/createUser", "UsersController.create").middleware(
|
|
"writeLog"
|
|
);
|
|
|
|
Route.post("/api/account/checkLogin", "UsersController.checkLogin").middleware(
|
|
"writeLog"
|
|
);
|
|
|
|
//Log
|
|
Route.get("/api/log/showLog/:name?", "LogsController.showLog").middleware(
|
|
"writeLog"
|
|
);
|
|
|
|
Route.get("/api/getAllLogDetect", "LogsController.getAllLogDetect");
|
|
|
|
//Key-Value
|
|
Route.post("/api/getKeyValue", "ValuesController.getKeyValue");
|
|
|
|
Route.post("/api/deleteValue", "ValuesController.destroy").middleware(
|
|
"writeLog"
|
|
);
|
|
|
|
Route.post("/api/editValue", "ValuesController.edit").middleware("writeLog");
|
|
|
|
Route.post("/api/addValue", "ValuesController.create").middleware("writeLog");
|
|
|
|
Route.post("/api/backupProduct", async ({ request, response }) => {
|
|
try {
|
|
const from = request.all().from || moment(Date.now()).format("YYYYMMDD");
|
|
const to = request.all().to || moment(Date.now()).format("YYYYMMDD");
|
|
const res = await axios.post(
|
|
"https://logs.danielvu.com/api/getIndexSerialNumber",
|
|
{ from: from, to: to },
|
|
{
|
|
headers: {
|
|
Authorization: request.headers().authorization?.replace(/"/g, ""),
|
|
},
|
|
}
|
|
);
|
|
|
|
res.data.map((obj, index) => {
|
|
res.data[index] = {
|
|
PID: res.data[index].PID,
|
|
SN: res.data[index].SN,
|
|
VID: res.data[index].VID,
|
|
line: res.data[index].line.join(","),
|
|
file: res.data[index].fileName,
|
|
warehouse: res.data[index].warehouse,
|
|
};
|
|
});
|
|
await Product.createMany(res.data);
|
|
// console.log(addProduct)
|
|
response.status(200).send("Add " + res.data.length + " success!");
|
|
await sendMessToZulip(
|
|
"stream",
|
|
"networkToolBot",
|
|
"Log service",
|
|
"Backup product " +
|
|
from +
|
|
" to " +
|
|
to +
|
|
" success with " +
|
|
res.data.length +
|
|
" products"
|
|
);
|
|
} catch (error) {
|
|
response.status(500).send(error);
|
|
await sendMessToZulip(
|
|
"stream",
|
|
"networkToolBot",
|
|
"Log service",
|
|
"Backup product fail. Please check!"
|
|
);
|
|
}
|
|
}).middleware("writeLog");
|
|
|
|
Route.post("/api/sendMailInforDevice", async () => {
|
|
try {
|
|
sendDeviceInfora();
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
})
|
|
.middleware("checkToken")
|
|
.middleware("writeLog");
|
|
|
|
Route.post(
|
|
"/api/private-log/getFileOnFolder",
|
|
async ({ request, response }) => {
|
|
try {
|
|
let result = [];
|
|
let path = request.all().folerPath;
|
|
return new Promise((resolve, reject) => {
|
|
fs.readdir(path, (err, entries) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
entries.forEach((entry) => {
|
|
const entryPath = path + "/" + entry;
|
|
|
|
fs.stat(entryPath, (statErr, stats) => {
|
|
if (statErr) {
|
|
reject(statErr);
|
|
return;
|
|
}
|
|
|
|
const type = stats.isFile() ? "file" : "directory";
|
|
result.push({ name: entryPath, type: type });
|
|
|
|
if (result.length === entries.length) {
|
|
resolve(result);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
Route.post("/api/private-log/readFile", async ({ request, response }) => {
|
|
try {
|
|
let result = [];
|
|
let path = request.all().filePath;
|
|
return await fs.readFileSync(path, "utf8");
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
});
|
|
|
|
Route.post("/api/extension/addressDetect", async ({ request, response }) => {
|
|
// TODO(developer): Uncomment these variables before running the sample.
|
|
const projectId = "532287737140";
|
|
const location = "us"; // Format is 'us' or 'eu'
|
|
const processorId = "64ad0cc100561909"; // Create processor before running sample
|
|
// const filePath = "https://int.ipsupply.com.au/upload/temp/packagepurchaseorder_17162360547876.jpeg";
|
|
const mimeType = "image/jpeg"; // Refer to https://cloud.google.com/document-ai/docs/file-types for supported file types
|
|
const fieldMask = ["entities"]; // Optional. The fields to return in the Document object.
|
|
const processorVersionId = "8aa31873669ac16f"; // Optional. Processor version to use
|
|
const bucketName = "get-address"; // Replace with your GCS bucket name
|
|
|
|
// Set up authentication
|
|
process.env.GOOGLE_APPLICATION_CREDENTIALS =
|
|
"strategic-block-424302-v3-54b10fc7e085.json";
|
|
|
|
async function downloadFile(url, outputLocationPath) {
|
|
const writer = fs.createWriteStream(outputLocationPath);
|
|
const response = await axios({
|
|
url,
|
|
method: "GET",
|
|
responseType: "stream",
|
|
});
|
|
response.data.pipe(writer);
|
|
return new Promise((resolve, reject) => {
|
|
writer.on("finish", resolve);
|
|
writer.on("error", reject);
|
|
});
|
|
}
|
|
|
|
async function processDocument(filePath) {
|
|
const client = new DocumentProcessorServiceClient();
|
|
|
|
let name;
|
|
if (processorVersionId) {
|
|
// The full resource name of the processor version, e.g.:
|
|
// `projects/${projectId}/locations/${location}/processors/${processorId}/processorVersions/${processorVersionId}`
|
|
name = client.processorVersionPath(
|
|
projectId,
|
|
location,
|
|
processorId,
|
|
processorVersionId
|
|
);
|
|
} else {
|
|
// The full resource name of the processor, e.g.:
|
|
// `projects/${projectId}/locations/${location}/processors/${processorId}`
|
|
name = client.processorPath(projectId, location, processorId);
|
|
}
|
|
|
|
// Download the file from URL
|
|
let imageContent;
|
|
const tempFilePath = path.join(__dirname, "images/temp.jpeg");
|
|
if (filePath.includes("http")) {
|
|
console.log("Download file");
|
|
await downloadFile(filePath, tempFilePath);
|
|
imageContent = fs.readFileSync(tempFilePath);
|
|
} else {
|
|
imageContent = fs.readFileSync(filePath);
|
|
}
|
|
// Read the file into memory
|
|
// imageContent = fs.readFileSync(filePath);
|
|
|
|
// Load binary data
|
|
const rawDocument = {
|
|
content: imageContent,
|
|
mimeType: mimeType,
|
|
};
|
|
|
|
// Optional: Additional configurations for processing.
|
|
const processOptions = {
|
|
individualPageSelector: {
|
|
pages: [1],
|
|
},
|
|
};
|
|
|
|
// Configure the process request
|
|
const request = {
|
|
name: name,
|
|
rawDocument: rawDocument,
|
|
fieldMask: fieldMask, // Join fieldMask array into a comma-separated string
|
|
processOptions: processOptions,
|
|
};
|
|
|
|
const [result] = await client.processDocument(request);
|
|
|
|
// For a full list of `Document` object attributes, reference this page:
|
|
// https://cloud.google.com/document-ai/docs/reference/rest/v1/Document
|
|
const document = result.document;
|
|
|
|
// Read the text recognition output from the processor
|
|
console.log("The document contains the following text:");
|
|
return extractAndPrintData(document.entities);
|
|
}
|
|
|
|
// Function to extract and print data
|
|
function extractAndPrintData(response) {
|
|
let result = "";
|
|
response.forEach((item) => {
|
|
const type = item.type;
|
|
const mentionText = item.mentionText;
|
|
result += `${type} : ${mentionText}\n\n`;
|
|
});
|
|
return result;
|
|
}
|
|
const { filePath } = request.all();;
|
|
console.log(filePath)
|
|
try {
|
|
await processDocument(filePath).then((result) => {
|
|
console.log(result)
|
|
response.status(200).send(JSON.stringify(result));
|
|
}).catch((e) => console.log(e));
|
|
} catch (error) {
|
|
response.status(500).send(`Error: ${error.message}`);
|
|
}
|
|
});
|
|
|
|
Route.post("/api/find-value", async ({ request, response }) => {
|
|
try {
|
|
const { value } = request.all();
|
|
|
|
console.log(value);
|
|
const { stdout, stderr } = await exec(`grep -nrE "${value}" /home/logs`, {
|
|
maxBuffer: 1024 * 500,
|
|
});
|
|
|
|
// response.status(200).send("sdjkghs");
|
|
response.status(200).send(JSON.stringify(stdout));
|
|
} catch (error) {
|
|
console.error(error);
|
|
response.status(500).send(`Error: ${error.message}`);
|
|
}
|
|
});
|
|
|
|
Route.post("/api/test", async () => {
|
|
try {
|
|
const logs = await Cache.get("logs");
|
|
|
|
if (logs) {
|
|
return { type: "cache", data: logs };
|
|
} else {
|
|
let data = await LogReport.all();
|
|
Cache.set("logs", data, 120);
|
|
return { type: "no cache", data: data };
|
|
}
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
});
|
|
|
|
|
|
Route.post('/api/wiki/page/insert', async ({ request, response }) => {
|
|
try {
|
|
const { title, data: dataPayload, pid, vid, sn, lineNumber, license } = request.all()
|
|
if (!title || !dataPayload) {
|
|
return response.status(422).send({ error: `'title' & 'text' is required` })
|
|
}
|
|
|
|
let text = ""
|
|
if (typeof dataPayload === "string")
|
|
text = dataPayload.replace(/\\n/g, '\n')
|
|
else if (Array.isArray(dataPayload)) {
|
|
const dataPlatform = dataPayload?.find(el => el.command?.trim() === "show platform")
|
|
const DPELP = dataPlatform && !dataPlatform?.output?.includes("Incomplete") ? true : false
|
|
text = `<pre>
|
|
Line ${lineNumber ?? ""}:
|
|
PID: ${pid ?? ""}
|
|
VID: ${vid ?? ""}
|
|
SN: ${sn ?? ""}
|
|
Tested mode: ${DPELP ? "DPELP" : "DPEL"}
|
|
License: ${license ?? ""}
|
|
</pre>
|
|
`
|
|
}
|
|
|
|
// ============ LOGIN LẤY COOKIE ============
|
|
const loginTokenRes = await fetch(
|
|
'https://jv.ipsupply.com.au/wiki/api.php?action=query&meta=tokens&type=login&format=json'
|
|
)
|
|
const loginToken = (await loginTokenRes.json())?.query?.tokens?.logintoken
|
|
if (!loginToken) return response.status(500).send('Không lấy được loginToken')
|
|
|
|
// gửi login
|
|
const bodyLogin = new URLSearchParams()
|
|
bodyLogin.append('username', 'Ips')
|
|
bodyLogin.append('password', 'Work1234%')
|
|
bodyLogin.append('logintoken', loginToken)
|
|
bodyLogin.append('format', 'json')
|
|
bodyLogin.append("loginreturnurl", "https://jv.ipsupply.com.au/wiki")
|
|
|
|
const loginRes = await fetch(
|
|
'https://jv.ipsupply.com.au/wiki/api.php?action=clientlogin',
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded', cookie: loginTokenRes.headers.get('set-cookie') ?? '' },
|
|
body: bodyLogin,
|
|
}
|
|
)
|
|
const loginCookies = loginRes.headers.raw()['set-cookie']?.join('; ') ?? ''
|
|
const loginData = await loginRes.json()
|
|
if (loginData?.clientlogin?.status !== 'PASS') {
|
|
return response.status(500).send(loginData)
|
|
}
|
|
|
|
// ============ LẤY CSRF TOKEN ============
|
|
const csrfRes = await fetch(
|
|
'https://jv.ipsupply.com.au/wiki/api.php?action=query&meta=tokens&type=csrf&format=json',
|
|
{
|
|
headers: { cookie: loginCookies },
|
|
}
|
|
)
|
|
const csrfToken = (await csrfRes.json())?.query?.tokens?.csrftoken
|
|
if (!csrfToken) return response.status(500).send('Không lấy được CSRF token')
|
|
|
|
// ============ LẤY PAGE CŨ ============
|
|
const pageRes = await fetch(
|
|
`https://jv.ipsupply.com.au/wiki/api.php?action=query&prop=revisions&rvprop=content&format=json&titles=${encodeURIComponent(title)}`,
|
|
{
|
|
headers: { cookie: loginCookies },
|
|
}
|
|
)
|
|
const pageJson = await pageRes.json()
|
|
const pageId = Object.keys(pageJson.query.pages)[0]
|
|
const oldContent = pageJson.query.pages[pageId]?.revisions?.[0]?.['*'] ?? ''
|
|
|
|
// nối nội dung mới vào
|
|
const now = new Date();
|
|
const lineDate = `${String(now.getDate()).padStart(2, "0")}/${String(now.getMonth() + 1).padStart(2, "0")}/${now.getFullYear()}`;
|
|
const scopeDate = `-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*<br/>ngay ${lineDate}<br/>-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*<br/><br/>`
|
|
const newText = `${scopeDate}${text}\n\n${checkDateWiki(oldContent) ? oldContent.replace(scopeDate, "") : oldContent}`
|
|
|
|
// ============ EDIT PAGE ============
|
|
const formData = new FormData()
|
|
formData.append('text', newText)
|
|
formData.append('token', csrfToken)
|
|
|
|
const editRes = await fetch(
|
|
`https://jv.ipsupply.com.au/wiki/api.php?action=edit&format=json&title=${encodeURIComponent(title)}`,
|
|
{
|
|
method: 'POST',
|
|
headers: { cookie: loginCookies },
|
|
body: formData,
|
|
}
|
|
)
|
|
const editResult = await editRes.json()
|
|
return response.status(200).send(editResult)
|
|
} catch (err) {
|
|
console.error(err)
|
|
return response.status(500).send(err)
|
|
}
|
|
})
|
|
|
|
Route.post("/api/po-email/packing-slip", async ({ request, response }) => {
|
|
try {
|
|
const { data } = request.all()
|
|
const PDFDocument = require('pdfkit')
|
|
const getStream = require('stream-buffers')
|
|
|
|
function generatePackingSlip(inputData) {
|
|
|
|
function loadFont(name) {
|
|
return path.join(__dirname, 'fonts', name + ".ttf")
|
|
}
|
|
|
|
const doc = new PDFDocument({
|
|
size: 'A4',
|
|
margins: { top: 50, bottom: 50, left: 50, right: 50 }
|
|
})
|
|
const stream = new getStream.WritableStreamBuffer()
|
|
|
|
// Layout parameters
|
|
const leftMargin = 50
|
|
const pageWidth = 500
|
|
const rightColumnX = 420
|
|
let y = doc.y
|
|
|
|
doc.pipe(stream)
|
|
|
|
// ===== LOGO (LEFT COLUMN) =====
|
|
const logoPath = '/home/Log_service/start/images/IP-Supply-Logo.png'
|
|
try {
|
|
doc.image(logoPath, leftMargin, y, { width: 180 })
|
|
} catch (err) {
|
|
console.warn('⚠️ Logo not found:', logoPath)
|
|
}
|
|
|
|
// ===== HEADER (RIGHT COLUMN) =====
|
|
doc.font(loadFont('calibri-bold')).fontSize(18).fillColor('#396190')
|
|
.text('IP Supply Pty Ltd', rightColumnX, y, { width: 195, align: 'left' })
|
|
|
|
doc.fillColor('black').font(loadFont('calibri-regular')).fontSize(10)
|
|
doc.text('Unit 8, 4A Bachell Ave', rightColumnX, doc.y, { width: 195, align: 'left' })
|
|
doc.text('Lidcombe NSW 2141', rightColumnX, doc.y, { width: 195, align: 'left' })
|
|
doc.text('Australia', rightColumnX, doc.y, { width: 195, align: 'left' })
|
|
|
|
doc.moveDown(1.5)
|
|
|
|
// ===== CONTACT INFO (TWO COLUMNS) =====
|
|
const contactY = doc.y
|
|
|
|
// Left column - ABN, ACN
|
|
doc.font(loadFont('calibri-bold')).fontSize(10)
|
|
doc.text('ABN: ', leftMargin, contactY, { continued: true })
|
|
doc.font(loadFont('calibri-regular')).text('66 145 174 358')
|
|
|
|
doc.font(loadFont('calibri-bold'))
|
|
doc.text('ACN: ', leftMargin, doc.y, { continued: true })
|
|
doc.font(loadFont('calibri-regular')).text('145 174 358')
|
|
|
|
// Right column - Phone, Mobile, Email
|
|
doc.font(loadFont('calibri-bold'))
|
|
doc.text('Ph: ', rightColumnX, contactY, { continued: true })
|
|
doc.font(loadFont('calibri-regular')).text('02 8061 6886')
|
|
|
|
doc.font(loadFont('calibri-bold'))
|
|
doc.text('M: ', rightColumnX, doc.y, { continued: true })
|
|
doc.font(loadFont('calibri-regular')).text('04 8881 6886')
|
|
|
|
doc.font(loadFont('calibri-bold'))
|
|
doc.text('E: ', rightColumnX, doc.y, { continued: true })
|
|
doc.font(loadFont('calibri-regular')).text('sales@ipsupply.com.au')
|
|
|
|
doc.moveDown(2)
|
|
|
|
// ===== "NOT CONFIRM TO" RIBBON (if needed) =====
|
|
// if (inputData.confirmStatus === "NOT CONFIRM TO") {
|
|
// doc.save()
|
|
// doc.fontSize(10).fillColor('white').font(loadFont('calibri-bold')
|
|
// const ribbonText = 'NOT CONFIRM TO'
|
|
// const ribbonWidth = doc.widthOfString(ribbonText) + 20
|
|
// doc.rotate(45, { origin: [pageWidth + leftMargin - 60, 60] })
|
|
// doc.rect(pageWidth + leftMargin - 80, 50, ribbonWidth, 20)
|
|
// .fillAndStroke('#d80000', '#a00000')
|
|
// doc.fillColor('white')
|
|
// .text(ribbonText, pageWidth + leftMargin - 70, 55)
|
|
// doc.restore()
|
|
// }
|
|
|
|
// ===== TITLE =====
|
|
doc.fontSize(25).font(loadFont('calibri-bold')).fillColor('#396190')
|
|
.text('PACKING SLIP', leftMargin, doc.y, { width: pageWidth, align: 'center' })
|
|
doc.fillColor('black')
|
|
doc.moveDown(0.5)
|
|
|
|
// ===== SHIP TO & PO (TWO COLUMNS) =====
|
|
const detailsY = doc.y
|
|
|
|
// Left column - Ship To
|
|
doc.fontSize(13).font(loadFont('calibri-bold'))
|
|
.text('Ship To', leftMargin, detailsY)
|
|
|
|
doc.fontSize(11).font(loadFont('calibri-regular'))
|
|
if (inputData.shipTo.name) {
|
|
doc.text(inputData.shipTo.name, leftMargin, doc.y, { width: 280 })
|
|
}
|
|
if (inputData.shipTo.address) {
|
|
doc.text(inputData.shipTo.address, leftMargin, doc.y, { width: 280 })
|
|
}
|
|
|
|
if (inputData.shipTo.attn) {
|
|
doc.text(`Attn: ${inputData.shipTo.attn}`, leftMargin, doc.y, { width: 280 })
|
|
}
|
|
|
|
if (inputData.shipTo.phone) {
|
|
doc.text(`Phone: ${inputData.shipTo.phone}`, leftMargin, doc.y, { width: 280 })
|
|
}
|
|
|
|
// Right column - PO
|
|
doc.font(loadFont('calibri-bold')).fontSize(11)
|
|
.text('PO: ', rightColumnX, detailsY, { continued: true })
|
|
doc.font(loadFont('calibri-regular'))
|
|
.text(inputData.po || '', { width: 195 })
|
|
|
|
|
|
// Nối tất cả value thành một chuỗi, ngăn cách bằng xuống dòng
|
|
const joined = Object.values(inputData.shipTo).join('\n');
|
|
|
|
// Tách theo \n và đếm số dòng (bỏ qua dòng rỗng)
|
|
const lines = joined.split('\n').filter(line => line.trim() !== '');
|
|
const lineCount = lines.length;
|
|
|
|
doc.moveDown(lineCount + 1)
|
|
|
|
// ===== TABLE HEADER =====
|
|
const tableY = doc.y
|
|
const qtyColX = leftMargin
|
|
const descColX = leftMargin + 50
|
|
|
|
// Header background
|
|
doc.rect(leftMargin, tableY, pageWidth, 20)
|
|
.fillAndStroke('#396290', '#396290')
|
|
|
|
// Header text
|
|
doc.fillColor('white').font(loadFont('calibri-bold')).fontSize(11)
|
|
doc.text('Qty', qtyColX + 10, tableY + 5, { width: 40, align: 'center' })
|
|
doc.text('Description', descColX + 5, tableY + 5, { width: pageWidth - 60 })
|
|
|
|
doc.fillColor('black')
|
|
// doc.moveDown(0.5)
|
|
|
|
// ===== TABLE ROWS =====
|
|
inputData.items.forEach((item) => {
|
|
const rowStartY = doc.y
|
|
|
|
// Calculate description height
|
|
let descText = ''
|
|
// if (item.name) descText += item.name + ' '
|
|
if (item.sku) descText += item.sku
|
|
if (item.SN && item.SN.join(', ') !== "") {
|
|
descText += `\nSN: ${item.SN.join(', ')}`
|
|
}
|
|
|
|
// Measure text height
|
|
const textHeight = doc.heightOfString(descText.trim(), {
|
|
width: pageWidth - 60,
|
|
lineBreak: true
|
|
})
|
|
|
|
const rowHeight = Math.max(25, textHeight + 10)
|
|
|
|
// Draw cell borders
|
|
doc.strokeColor('#396290').lineWidth(0.5)
|
|
|
|
// Qty cell
|
|
doc.rect(qtyColX, rowStartY, 50, rowHeight).stroke()
|
|
doc.fontSize(11).font(loadFont('calibri-regular'))
|
|
.text(item.qty.toString(), qtyColX, rowStartY + 8, { width: 50, align: 'center' })
|
|
|
|
// Description cell
|
|
doc.rect(descColX, rowStartY, pageWidth - 50, rowHeight).stroke()
|
|
|
|
doc.text(descText.trim(), descColX + 5, rowStartY + 5, {
|
|
width: pageWidth - 60,
|
|
lineBreak: true
|
|
})
|
|
|
|
doc.y = rowStartY + rowHeight
|
|
})
|
|
|
|
// ===== FOOTER =====
|
|
doc.moveDown(1)
|
|
doc.fontSize(10).font(loadFont('calibri-bold'))
|
|
.text('Thank you for your purchase.', leftMargin, doc.y, { width: pageWidth })
|
|
|
|
doc.end()
|
|
return stream
|
|
}
|
|
|
|
const pdfStream = generatePackingSlip(data)
|
|
|
|
await new Promise(resolve => pdfStream.on('finish', resolve))
|
|
const buffer = pdfStream.getContents()
|
|
|
|
const fileName = `${data.po || 'PackingSlip'}_PackingSlip.pdf`
|
|
const filePath = `/tmp/${fileName}`
|
|
fs.writeFileSync(filePath, buffer)
|
|
|
|
response.header('Content-Type', 'application/pdf')
|
|
response.header('Content-Disposition', `attachment; filename="${fileName}"`)
|
|
return response.download(filePath)
|
|
|
|
} catch (err) {
|
|
console.error(err)
|
|
return response.status(500).json({
|
|
status: 'error',
|
|
message: err.message,
|
|
})
|
|
}
|
|
}) |