const { splitSite } = require("../middlewares"); const db = require("../models"); const fs = require("fs"); const path = require("path"); const moment = require("moment-timezone"); const archiver = require("archiver"); const { PDFDocument } = require("pdf-lib"); const Report = db.report; const RoutePath = db.route_path; const Employee = db.employee; const Vehicle = db.vehicle; const ROOT_DIR = path.resolve(__dirname, "../.."); const TARGET_TIMEZONE = "America/New_York"; const sanitizeFileName = (name) => (name || "route") .toString() .replace(/[\\/:*?"<>|]/g, "_") .trim(); const toObjectIdText = (value) => { if (!value) return ""; if (typeof value === "string") return value; if (typeof value === "object" && value._id) return `${value._id}`; return `${value}`; }; const findTemplatePathBySite = (site) => { const safeSite = [1, 2, 3].includes(Number(site)) ? Number(site) : 1; const fileName = `pdf_templete${safeSite}.pdf`; const candidatePaths = [ path.join(ROOT_DIR, "app", "views", "upload", fileName), path.join(ROOT_DIR, "client", "build", "upload", fileName), path.join(ROOT_DIR, "client", "public", "upload", fileName) ]; return candidatePaths.find((candidate) => fs.existsSync(candidate)) || ""; }; const formatUtcToLocalHm = (dateLike) => { if (!dateLike) return ""; const parsed = moment.utc(dateLike); if (!parsed.isValid()) return ""; return parsed.tz(TARGET_TIMEZONE).format("HH:mm"); }; const safeSetField = (form, fieldName, value) => { if (!fieldName || value === undefined || value === null) return; const text = `${value}`; try { const field = form.getTextField(fieldName); field.setText(text); return; } catch (_textErr) {} try { const checkbox = form.getCheckBox(fieldName); if (text.trim()) { checkbox.check(); } else { checkbox.uncheck(); } } catch (_checkboxErr) {} }; const findOutboundStatusByCustomerId = (outboundCustomerStatuses, customerId) => { const targetId = toObjectIdText(customerId); return outboundCustomerStatuses.find((item) => toObjectIdText(item?.customer_id) === targetId) || {}; }; const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, vehiclesMap, outboundCustomerStatuses) => { const pdfDoc = await PDFDocument.load(templateBytes); const form = pdfDoc.getForm(); const routeName = route?.name || ""; const scheduleDate = route?.schedule_date || ""; const driverId = toObjectIdText(route?.driver); const vehicleId = toObjectIdText(route?.vehicle); const driver = driversMap.get(driverId); const vehicle = vehiclesMap.get(vehicleId); const driverName = driver ? `${driver.name || ""}${driver.name_cn ? driver.name_cn : ""}` : ""; safeSetField(form, "route", routeName); safeSetField(form, "driver", driverName); safeSetField(form, "date", scheduleDate); safeSetField(form, "vehicle", vehicle?.vehicle_number || ""); safeSetField(form, "seq_num", seqNum); const customers = Array.isArray(route?.route_customer_list) ? route.route_customer_list : []; customers.forEach((customer, index) => { const row = index + 1; safeSetField(form, `name_${row}`, customer?.customer_name || ""); safeSetField(form, `addr_${row}`, customer?.customer_address || ""); safeSetField(form, `phone_${row}`, customer?.customer_phone || ""); safeSetField(form, `note_${row}`, customer?.customer_note || ""); const pickupTime = formatUtcToLocalHm(customer?.customer_pickup_time); if (pickupTime) safeSetField(form, `pick_${row}`, pickupTime); const enterCenterTime = formatUtcToLocalHm(customer?.customer_enter_center_time); if (enterCenterTime) { safeSetField(form, `arrive_${row}`, enterCenterTime); safeSetField(form, `y_${row}`, "✓"); } else if (customer?.customer_route_status === "inCenter") { safeSetField(form, `y_${row}`, "✓"); } else { safeSetField(form, `n_${row}`, "✕"); } const outboundStatus = findOutboundStatusByCustomerId(outboundCustomerStatuses, customer?.customer_id); const leaveCenterTime = formatUtcToLocalHm(outboundStatus?.customer_leave_center_time); const dropoffTime = formatUtcToLocalHm(outboundStatus?.customer_dropoff_time); if (leaveCenterTime) safeSetField(form, `departure_${row}`, leaveCenterTime); if (dropoffTime) safeSetField(form, `drop_${row}`, dropoffTime); }); form.flatten(); return pdfDoc.save(); }; exports.createReport = (req, res) => { // Validate request if (!req.body.data) { res.status(400).send({ message: "Content can not be empty!" }); return; } const site = splitSite.findSiteNumber(req); // Create a Report const report = new Report({ date: req.body.date || '', type: req.body.type || '', route_id: req.body.route_id || '', driver_name: req.body.driver_name || '', route_name: req.body.route_name || '', data: req.body.data || [], head: req.body.head || [], chinese_head: req.body.chinese_head || [], checklist_result: req.body.checklist_result || [], vehicle_number: req.body.vehicle_number || null, site }); // Save Report in the database report .save(report) .then(data => { res.send(data); }) .catch(err => { res.status(500).send({ message: err.message || "Some error occurred while creating the Report." }); }); }; // Retrieve all Reports from the database. exports.getAllReports = (req, res) => { var condition = {}; condition = splitSite.splitSiteGet(req, condition); Report.find(condition) .then(data => { res.send(data); }) .catch(err => { res.status(500).send({ message: err.message || "Some error occurred while retrieving reports." }); }); }; // Retrieve all Active Reports By Date and Type (Admin Reports). exports.getReportsByDateAndType = (req, res) => { const params = req.query; const date = params?.date; const type = params?.type; var condition = { date, type }; condition = splitSite.splitSiteGet(req, condition); Report.find(condition) .then(data => { res.send(data); }) .catch(err => { res.status(500).send({ message: err.message || "Some error occurred while retrieving Reports with Date and Type." }); }); }; // Retrieve reports By RouteId and Type (Senior Route report) exports.getReportsByRouteIdAndType = (req, res) => { const params = req.query; const route_id = params?.route_id; const type = params?.type; var condition = { route_id, type }; condition = splitSite.splitSiteGet(req, condition); Report.find(condition) .then(data => { res.send(data); }) .catch(err => { res.status(500).send({ message: err.message || "Some error occurred while retrieving Reports with Date and Type." }); }); }; // Get One Report by Id exports.getReport = (req, res) => { }; // Update a Report by the id in the request exports.updateReport = (req, res) => { if (!req.body) { return res.status(400).send({ message: "Data to update can not be empty!" }); } const id = req.params.id; Report.findByIdAndUpdate(id, req.body, { useFindAndModify: false }) .then(data => { if (!data) { res.status(404).send({ message: `Cannot update Report with id=${id}. Maybe Report was not found!` }); } else res.send({ success: true, message: "Report was updated successfully." }); }) .catch(err => { res.status(500).send({ success: false, message: "Error updating Report with id=" + id }); }); }; exports.exportRouteReportZip = async (req, res) => { try { const date = req.query?.date; if (!date) { return res.status(400).send({ message: "date query is required." }); } const site = splitSite.findSiteNumber(req); const templatePath = findTemplatePathBySite(site); if (!templatePath) { return res.status(500).send({ message: `Missing PDF template for site ${site}.` }); } const [routes, drivers, vehicles] = await Promise.all([ RoutePath.find(splitSite.splitSiteGet(req, { schedule_date: date, status: { $ne: "disabled" } })), Employee.find(splitSite.splitSiteGet(req, {})), Vehicle.find(splitSite.splitSiteGet(req, { status: "active" })) ]); const inboundRoutes = (routes || []).filter((route) => route?.type === "inbound"); const outboundRoutes = (routes || []).filter((route) => route?.type === "outbound"); const outboundCustomerStatuses = outboundRoutes.reduce((acc, route) => { const list = Array.isArray(route?.route_customer_list) ? route.route_customer_list : []; return acc.concat(list); }, []); const driversMap = new Map( (drivers || []).map((driver) => [ toObjectIdText(driver?._id || driver?.id), driver ]) ); const vehiclesMap = new Map( (vehicles || []).map((vehicle) => [ toObjectIdText(vehicle?._id || vehicle?.id), vehicle ]) ); const templateBytes = fs.readFileSync(templatePath); const filenameDate = (date || "").replace(/\//g, "-"); const zipName = `route_report_${filenameDate || "date"}.zip`; res.setHeader("Content-Type", "application/zip"); res.setHeader("Content-Disposition", `attachment; filename="${zipName}"`); const archive = archiver("zip", { zlib: { level: 9 } }); archive.on("error", (err) => { if (!res.headersSent) { res.status(500).send({ message: err.message || "Failed to generate route report zip." }); } else { res.end(); } }); archive.pipe(res); const filenameCounter = new Map(); for (let i = 0; i < inboundRoutes.length; i += 1) { const route = inboundRoutes[i]; const pdfBytes = await buildRoutePdfBuffer( templateBytes, route, i + 1, driversMap, vehiclesMap, outboundCustomerStatuses ); const base = sanitizeFileName(route?.name || `route_${i + 1}`) || `route_${i + 1}`; const existingCount = filenameCounter.get(base) || 0; filenameCounter.set(base, existingCount + 1); const finalName = existingCount ? `${base}_${existingCount + 1}.pdf` : `${base}.pdf`; archive.append(Buffer.from(pdfBytes), { name: finalName }); } archive.finalize(); } catch (err) { res.status(500).send({ message: err.message || "Failed to export route reports." }); } };