This commit is contained in:
BIN
app/assets/fonts/NotoSansCJKsc-Regular.otf
Normal file
BIN
app/assets/fonts/NotoSansCJKsc-Regular.otf
Normal file
Binary file not shown.
@@ -5,6 +5,7 @@ const path = require("path");
|
|||||||
const moment = require("moment-timezone");
|
const moment = require("moment-timezone");
|
||||||
const archiver = require("archiver");
|
const archiver = require("archiver");
|
||||||
const { PDFDocument } = require("pdf-lib");
|
const { PDFDocument } = require("pdf-lib");
|
||||||
|
const fontkit = require("@pdf-lib/fontkit");
|
||||||
const Report = db.report;
|
const Report = db.report;
|
||||||
const RoutePath = db.route_path;
|
const RoutePath = db.route_path;
|
||||||
const Employee = db.employee;
|
const Employee = db.employee;
|
||||||
@@ -44,6 +45,26 @@ const findTemplatePathBySite = (site) => {
|
|||||||
return candidatePaths.find((candidate) => fs.existsSync(candidate)) || "";
|
return candidatePaths.find((candidate) => fs.existsSync(candidate)) || "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findUnicodeFontPath = (site) => {
|
||||||
|
const safeSite = [1, 2, 3].includes(Number(site)) ? Number(site) : 1;
|
||||||
|
const fileName = "NotoSansCJKsc-Regular.otf";
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const candidatePaths = [
|
||||||
|
path.join(ROOT_DIR, "app", "assets", "fonts", fileName),
|
||||||
|
path.join(ROOT_DIR, "app", "views", "upload", fileName),
|
||||||
|
path.join(ROOT_DIR, "client", "build", "upload", fileName),
|
||||||
|
path.join(ROOT_DIR, "client", "public", "upload", fileName),
|
||||||
|
path.join(cwd, "app", "assets", "fonts", fileName),
|
||||||
|
path.join(cwd, "app", "views", "upload", fileName),
|
||||||
|
path.join(cwd, "client", "build", "upload", fileName),
|
||||||
|
path.join(cwd, "client", "public", "upload", fileName),
|
||||||
|
path.join("/www/wwwroot/upload", fileName),
|
||||||
|
path.join(`/www/wwwroot/worldshine${safeSite}`, "app", "assets", "fonts", fileName),
|
||||||
|
path.join(`/www/wwwroot/worldshine${safeSite}-tspt`, "app", "assets", "fonts", fileName)
|
||||||
|
];
|
||||||
|
return candidatePaths.find((candidate) => fs.existsSync(candidate)) || "";
|
||||||
|
};
|
||||||
|
|
||||||
const formatUtcToLocalHm = (dateLike) => {
|
const formatUtcToLocalHm = (dateLike) => {
|
||||||
if (!dateLike) return "";
|
if (!dateLike) return "";
|
||||||
const parsed = moment.utc(dateLike);
|
const parsed = moment.utc(dateLike);
|
||||||
@@ -74,8 +95,10 @@ const findOutboundStatusByCustomerId = (outboundCustomerStatuses, customerId) =>
|
|||||||
return outboundCustomerStatuses.find((item) => toObjectIdText(item?.customer_id) === targetId) || {};
|
return outboundCustomerStatuses.find((item) => toObjectIdText(item?.customer_id) === targetId) || {};
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, vehiclesMap, outboundCustomerStatuses) => {
|
const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, vehiclesMap, outboundCustomerStatuses, unicodeFontBytes) => {
|
||||||
const pdfDoc = await PDFDocument.load(templateBytes);
|
const pdfDoc = await PDFDocument.load(templateBytes);
|
||||||
|
pdfDoc.registerFontkit(fontkit);
|
||||||
|
const unicodeFont = await pdfDoc.embedFont(unicodeFontBytes, { subset: true });
|
||||||
const form = pdfDoc.getForm();
|
const form = pdfDoc.getForm();
|
||||||
const routeName = route?.name || "";
|
const routeName = route?.name || "";
|
||||||
const scheduleDate = route?.schedule_date || "";
|
const scheduleDate = route?.schedule_date || "";
|
||||||
@@ -107,11 +130,11 @@ const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, veh
|
|||||||
const enterCenterTime = formatUtcToLocalHm(customer?.customer_enter_center_time);
|
const enterCenterTime = formatUtcToLocalHm(customer?.customer_enter_center_time);
|
||||||
if (enterCenterTime) {
|
if (enterCenterTime) {
|
||||||
safeSetField(form, `arrive_${row}`, enterCenterTime);
|
safeSetField(form, `arrive_${row}`, enterCenterTime);
|
||||||
safeSetField(form, `y_${row}`, "Y");
|
safeSetField(form, `y_${row}`, "✓");
|
||||||
} else if (customer?.customer_route_status === "inCenter") {
|
} else if (customer?.customer_route_status === "inCenter") {
|
||||||
safeSetField(form, `y_${row}`, "Y");
|
safeSetField(form, `y_${row}`, "✓");
|
||||||
} else {
|
} else {
|
||||||
safeSetField(form, `n_${row}`, "N");
|
safeSetField(form, `n_${row}`, "✕");
|
||||||
}
|
}
|
||||||
|
|
||||||
const outboundStatus = findOutboundStatusByCustomerId(outboundCustomerStatuses, customer?.customer_id);
|
const outboundStatus = findOutboundStatusByCustomerId(outboundCustomerStatuses, customer?.customer_id);
|
||||||
@@ -121,6 +144,8 @@ const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, veh
|
|||||||
if (dropoffTime) safeSetField(form, `drop_${row}`, dropoffTime);
|
if (dropoffTime) safeSetField(form, `drop_${row}`, dropoffTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Rebuild form appearances with a Unicode font so UTF-8 (e.g., Chinese) renders correctly.
|
||||||
|
form.updateFieldAppearances(unicodeFont);
|
||||||
form.flatten();
|
form.flatten();
|
||||||
return pdfDoc.save();
|
return pdfDoc.save();
|
||||||
};
|
};
|
||||||
@@ -248,9 +273,13 @@ exports.exportRouteReportZip = async (req, res) => {
|
|||||||
|
|
||||||
const site = splitSite.findSiteNumber(req);
|
const site = splitSite.findSiteNumber(req);
|
||||||
const templatePath = findTemplatePathBySite(site);
|
const templatePath = findTemplatePathBySite(site);
|
||||||
|
const unicodeFontPath = findUnicodeFontPath(site);
|
||||||
if (!templatePath) {
|
if (!templatePath) {
|
||||||
return res.status(500).send({ message: `Missing PDF template for site ${site}.` });
|
return res.status(500).send({ message: `Missing PDF template for site ${site}.` });
|
||||||
}
|
}
|
||||||
|
if (!unicodeFontPath) {
|
||||||
|
return res.status(500).send({ message: "Missing Unicode font file: NotoSansCJKsc-Regular.otf" });
|
||||||
|
}
|
||||||
|
|
||||||
const [routes, drivers, vehicles] = await Promise.all([
|
const [routes, drivers, vehicles] = await Promise.all([
|
||||||
RoutePath.find(splitSite.splitSiteGet(req, { schedule_date: date, status: { $ne: "disabled" } })),
|
RoutePath.find(splitSite.splitSiteGet(req, { schedule_date: date, status: { $ne: "disabled" } })),
|
||||||
@@ -278,6 +307,7 @@ exports.exportRouteReportZip = async (req, res) => {
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
const templateBytes = fs.readFileSync(templatePath);
|
const templateBytes = fs.readFileSync(templatePath);
|
||||||
|
const unicodeFontBytes = fs.readFileSync(unicodeFontPath);
|
||||||
|
|
||||||
const filenameDate = (date || "").replace(/\//g, "-");
|
const filenameDate = (date || "").replace(/\//g, "-");
|
||||||
const zipName = `route_report_${filenameDate || "date"}.zip`;
|
const zipName = `route_report_${filenameDate || "date"}.zip`;
|
||||||
@@ -303,7 +333,8 @@ exports.exportRouteReportZip = async (req, res) => {
|
|||||||
i + 1,
|
i + 1,
|
||||||
driversMap,
|
driversMap,
|
||||||
vehiclesMap,
|
vehiclesMap,
|
||||||
outboundCustomerStatuses
|
outboundCustomerStatuses,
|
||||||
|
unicodeFontBytes
|
||||||
);
|
);
|
||||||
const base = sanitizeFileName(route?.name || `route_${i + 1}`) || `route_${i + 1}`;
|
const base = sanitizeFileName(route?.name || `route_${i + 1}`) || `route_${i + 1}`;
|
||||||
const existingCount = filenameCounter.get(base) || 0;
|
const existingCount = filenameCounter.get(base) || 0;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ const rootDir = path.resolve(__dirname, "..");
|
|||||||
const sourceDir = path.join(rootDir, "public", "upload");
|
const sourceDir = path.join(rootDir, "public", "upload");
|
||||||
const targetDir = path.join(rootDir, "build", "upload");
|
const targetDir = path.join(rootDir, "build", "upload");
|
||||||
const templates = ["pdf_templete1.pdf", "pdf_templete2.pdf", "pdf_templete3.pdf"];
|
const templates = ["pdf_templete1.pdf", "pdf_templete2.pdf", "pdf_templete3.pdf"];
|
||||||
|
const unicodeFontFile = "NotoSansCJKsc-Regular.otf";
|
||||||
|
const appFontSourcePath = path.join(rootDir, "..", "app", "assets", "fonts", unicodeFontFile);
|
||||||
|
|
||||||
if (!fs.existsSync(targetDir)) {
|
if (!fs.existsSync(targetDir)) {
|
||||||
fs.mkdirSync(targetDir, { recursive: true });
|
fs.mkdirSync(targetDir, { recursive: true });
|
||||||
@@ -19,4 +21,8 @@ templates.forEach((templateName) => {
|
|||||||
fs.copyFileSync(sourcePath, targetPath);
|
fs.copyFileSync(sourcePath, targetPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Route report PDF templates copied to build/upload.");
|
if (fs.existsSync(appFontSourcePath)) {
|
||||||
|
fs.copyFileSync(appFontSourcePath, path.join(targetDir, unicodeFontFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Route report PDF templates and Unicode font copied to build/upload.");
|
||||||
|
|||||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
@@ -733,6 +734,21 @@
|
|||||||
"sparse-bitfield": "^3.0.3"
|
"sparse-bitfield": "^3.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@pdf-lib/fontkit": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@pdf-lib/fontkit/-/fontkit-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-KjMd7grNapIWS/Dm0gvfHEilSyAmeLvrEGVcqLGi0VYebuqqzTbgF29efCx7tvx+IEbG3zQciRSWl3GkUSvjZg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pako": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@pdf-lib/fontkit/node_modules/pako": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||||
|
"license": "(MIT AND Zlib)"
|
||||||
|
},
|
||||||
"node_modules/@pdf-lib/standard-fonts": {
|
"node_modules/@pdf-lib/standard-fonts": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"author": "yangli",
|
"author": "yangli",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
|
|||||||
Reference in New Issue
Block a user