This commit is contained in:
@@ -28,6 +28,7 @@ const toObjectIdText = (value) => {
|
|||||||
if (typeof value === "object" && value._id) return `${value._id}`;
|
if (typeof value === "object" && value._id) return `${value._id}`;
|
||||||
return `${value}`;
|
return `${value}`;
|
||||||
};
|
};
|
||||||
|
const normalizeCustomerName = (name) => (name || "").toString().trim().toLowerCase().replace(/\s+/g, " ");
|
||||||
|
|
||||||
const findTemplatePathBySite = (site) => {
|
const findTemplatePathBySite = (site) => {
|
||||||
const safeSite = [1, 2, 3].includes(Number(site)) ? Number(site) : 1;
|
const safeSite = [1, 2, 3].includes(Number(site)) ? Number(site) : 1;
|
||||||
@@ -180,11 +181,22 @@ const appendNoticeLine = (parts, text) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, vehiclesMap, outboundCustomerStatuses, unicodeFontBytes, allRoutes, customerNotesById, debugRows) => {
|
const collectFieldRects = (form, fieldName) => {
|
||||||
|
try {
|
||||||
|
const field = form.getTextField(fieldName);
|
||||||
|
const widgets = field?.acroField?.getWidgets?.() || [];
|
||||||
|
return widgets.map((widget) => widget.getRectangle());
|
||||||
|
} catch (_err) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, vehiclesMap, outboundCustomerStatuses, unicodeFontBytes, allRoutes, customerNotesById, customerNotesByName, debugRows) => {
|
||||||
const pdfDoc = await PDFDocument.load(templateBytes);
|
const pdfDoc = await PDFDocument.load(templateBytes);
|
||||||
pdfDoc.registerFontkit(fontkit);
|
pdfDoc.registerFontkit(fontkit);
|
||||||
const unicodeFont = await pdfDoc.embedFont(unicodeFontBytes, { subset: true });
|
const unicodeFont = await pdfDoc.embedFont(unicodeFontBytes, { subset: true });
|
||||||
const form = pdfDoc.getForm();
|
const form = pdfDoc.getForm();
|
||||||
|
const markDrawQueue = [];
|
||||||
const routeName = route?.name || "";
|
const routeName = route?.name || "";
|
||||||
const scheduleDate = route?.schedule_date || "";
|
const scheduleDate = route?.schedule_date || "";
|
||||||
const driverId = toObjectIdText(route?.driver);
|
const driverId = toObjectIdText(route?.driver);
|
||||||
@@ -209,7 +221,11 @@ const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, veh
|
|||||||
safeSetField(form, `addr_${row}`, customer?.customer_address || "");
|
safeSetField(form, `addr_${row}`, customer?.customer_address || "");
|
||||||
safeSetField(form, `phone_${row}`, customer?.customer_phone || "");
|
safeSetField(form, `phone_${row}`, customer?.customer_phone || "");
|
||||||
const customerId = toObjectIdText(customer?.customer_id);
|
const customerId = toObjectIdText(customer?.customer_id);
|
||||||
const profileNoteToDriver = (customerNotesById?.get(customerId) || "").toString().trim();
|
const profileNoteToDriver = (
|
||||||
|
customerNotesById?.get(customerId) ||
|
||||||
|
customerNotesByName?.get(normalizeCustomerName(customer?.customer_name || "")) ||
|
||||||
|
""
|
||||||
|
).toString().trim();
|
||||||
const customerNoteText = profileNoteToDriver;
|
const customerNoteText = profileNoteToDriver;
|
||||||
safeSetField(form, `note_${row}`, customerNoteText);
|
safeSetField(form, `note_${row}`, customerNoteText);
|
||||||
|
|
||||||
@@ -236,16 +252,22 @@ const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, veh
|
|||||||
nValue = "";
|
nValue = "";
|
||||||
safeSetField(form, `y_${row}`, yValue, { fontSize: 12, center: true, blackText: true });
|
safeSetField(form, `y_${row}`, yValue, { fontSize: 12, center: true, blackText: true });
|
||||||
safeSetField(form, `n_${row}`, nValue, { fontSize: 12, center: true, blackText: true });
|
safeSetField(form, `n_${row}`, nValue, { fontSize: 12, center: true, blackText: true });
|
||||||
|
const yRects = collectFieldRects(form, `y_${row}`);
|
||||||
|
yRects.forEach((rect) => markDrawQueue.push({ rect, text: "Y" }));
|
||||||
} else if (hasInCenterStatusByCandidate(customer, relativeRouteCustomer, customerInOtherRoute)) {
|
} else if (hasInCenterStatusByCandidate(customer, relativeRouteCustomer, customerInOtherRoute)) {
|
||||||
yValue = "Y";
|
yValue = "Y";
|
||||||
nValue = "";
|
nValue = "";
|
||||||
safeSetField(form, `y_${row}`, yValue, { fontSize: 12, center: true, blackText: true });
|
safeSetField(form, `y_${row}`, yValue, { fontSize: 12, center: true, blackText: true });
|
||||||
safeSetField(form, `n_${row}`, nValue, { fontSize: 12, center: true, blackText: true });
|
safeSetField(form, `n_${row}`, nValue, { fontSize: 12, center: true, blackText: true });
|
||||||
|
const yRects = collectFieldRects(form, `y_${row}`);
|
||||||
|
yRects.forEach((rect) => markDrawQueue.push({ rect, text: "Y" }));
|
||||||
} else {
|
} else {
|
||||||
yValue = "";
|
yValue = "";
|
||||||
nValue = "N";
|
nValue = "N";
|
||||||
safeSetField(form, `y_${row}`, yValue, { fontSize: 12, center: true, blackText: true });
|
safeSetField(form, `y_${row}`, yValue, { fontSize: 12, center: true, blackText: true });
|
||||||
safeSetField(form, `n_${row}`, nValue, { fontSize: 12, center: true, blackText: true });
|
safeSetField(form, `n_${row}`, nValue, { fontSize: 12, center: true, blackText: true });
|
||||||
|
const nRects = collectFieldRects(form, `n_${row}`);
|
||||||
|
nRects.forEach((rect) => markDrawQueue.push({ rect, text: "N" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const outboundStatus = findOutboundStatusByCustomerId(outboundCustomerStatuses, customer?.customer_id);
|
const outboundStatus = findOutboundStatusByCustomerId(outboundCustomerStatuses, customer?.customer_id);
|
||||||
@@ -328,6 +350,14 @@ const buildRoutePdfBuffer = async (templateBytes, route, seqNum, driversMap, veh
|
|||||||
// Rebuild form appearances with a Unicode font so UTF-8 (e.g., Chinese) renders correctly.
|
// Rebuild form appearances with a Unicode font so UTF-8 (e.g., Chinese) renders correctly.
|
||||||
form.updateFieldAppearances(unicodeFont);
|
form.updateFieldAppearances(unicodeFont);
|
||||||
form.flatten();
|
form.flatten();
|
||||||
|
const firstPage = pdfDoc.getPage(0);
|
||||||
|
markDrawQueue.forEach(({ rect, text }) => {
|
||||||
|
const fontSize = Math.min(Math.max(rect.height * 0.72, 9), 14);
|
||||||
|
const textWidth = unicodeFont.widthOfTextAtSize(text, fontSize);
|
||||||
|
const x = rect.x + Math.max((rect.width - textWidth) / 2, 1);
|
||||||
|
const y = rect.y + Math.max((rect.height - fontSize) / 2, 1);
|
||||||
|
firstPage.drawText(text, { x, y, size: fontSize, font: unicodeFont, color: rgb(0, 0, 0) });
|
||||||
|
});
|
||||||
return pdfDoc.save();
|
return pdfDoc.save();
|
||||||
};
|
};
|
||||||
exports.createReport = (req, res) => {
|
exports.createReport = (req, res) => {
|
||||||
@@ -494,6 +524,17 @@ exports.exportRouteReportZip = async (req, res) => {
|
|||||||
(customer?.notes_for_driver || "").toString().trim()
|
(customer?.notes_for_driver || "").toString().trim()
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
const customerNotesByName = new Map();
|
||||||
|
(customers || []).forEach((customer) => {
|
||||||
|
const note = (customer?.notes_for_driver || "").toString().trim();
|
||||||
|
if (!note) return;
|
||||||
|
const key = normalizeCustomerName(customer?.name || customer?.name_cn || `${customer?.firstname || ""} ${customer?.lastname || ""}`);
|
||||||
|
if (!key) return;
|
||||||
|
const previous = customerNotesByName.get(key);
|
||||||
|
if (!previous || (customer?.status || "").toString().toLowerCase() === "active") {
|
||||||
|
customerNotesByName.set(key, note);
|
||||||
|
}
|
||||||
|
});
|
||||||
const getRouteVehicleNumber = (route) => {
|
const getRouteVehicleNumber = (route) => {
|
||||||
const vehicleId = toObjectIdText(route?.vehicle);
|
const vehicleId = toObjectIdText(route?.vehicle);
|
||||||
const vehicleNumberRaw = vehiclesMap.get(vehicleId)?.vehicle_number;
|
const vehicleNumberRaw = vehiclesMap.get(vehicleId)?.vehicle_number;
|
||||||
@@ -544,6 +585,7 @@ exports.exportRouteReportZip = async (req, res) => {
|
|||||||
unicodeFontBytes,
|
unicodeFontBytes,
|
||||||
routes,
|
routes,
|
||||||
customerNotesById,
|
customerNotesById,
|
||||||
|
customerNotesByName,
|
||||||
debugMode ? debugRows : null
|
debugMode ? debugRows : null
|
||||||
);
|
);
|
||||||
const base = sanitizeFileName(route?.name || `route_${i + 1}`) || `route_${i + 1}`;
|
const base = sanitizeFileName(route?.name || `route_${i + 1}`) || `route_${i + 1}`;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const RouteReportWithSignature = () => {
|
|||||||
|
|
||||||
const [directorSignature, setDirectorSignature] = useState(undefined);
|
const [directorSignature, setDirectorSignature] = useState(undefined);
|
||||||
const [customerMetaById, setCustomerMetaById] = useState(new Map());
|
const [customerMetaById, setCustomerMetaById] = useState(new Map());
|
||||||
|
const [customerMetaByName, setCustomerMetaByName] = useState(new Map());
|
||||||
|
|
||||||
const site = EventsService.site;
|
const site = EventsService.site;
|
||||||
|
|
||||||
@@ -82,17 +83,28 @@ const RouteReportWithSignature = () => {
|
|||||||
CustomerService.getAllCustomers()
|
CustomerService.getAllCustomers()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const nextMap = new Map();
|
const nextMap = new Map();
|
||||||
|
const nextByNameMap = new Map();
|
||||||
(res?.data || []).forEach((customer) => {
|
(res?.data || []).forEach((customer) => {
|
||||||
nextMap.set(customer?.id, {
|
const meta = {
|
||||||
program_type: customer?.program_type || '',
|
program_type: customer?.program_type || '',
|
||||||
pay_source: customer?.pay_source || '',
|
pay_source: customer?.pay_source || '',
|
||||||
notes_for_driver: customer?.notes_for_driver || ''
|
notes_for_driver: customer?.notes_for_driver || ''
|
||||||
});
|
};
|
||||||
|
nextMap.set(customer?.id, meta);
|
||||||
|
const nameKey = (customer?.name || '').toString().trim().toLowerCase();
|
||||||
|
if (nameKey) {
|
||||||
|
const previous = nextByNameMap.get(nameKey);
|
||||||
|
if (!previous || (customer?.status || '').toString().toLowerCase() === 'active') {
|
||||||
|
nextByNameMap.set(nameKey, meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
setCustomerMetaById(nextMap);
|
setCustomerMetaById(nextMap);
|
||||||
|
setCustomerMetaByName(nextByNameMap);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setCustomerMetaById(new Map());
|
setCustomerMetaById(new Map());
|
||||||
|
setCustomerMetaByName(new Map());
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
const normalizeChecklistText = (value = '') =>
|
const normalizeChecklistText = (value = '') =>
|
||||||
@@ -187,6 +199,9 @@ const RouteReportWithSignature = () => {
|
|||||||
const getRouteNoteToDriverText = (customerId, ...customerCandidates) => {
|
const getRouteNoteToDriverText = (customerId, ...customerCandidates) => {
|
||||||
const profileNote = (customerMetaById.get(customerId)?.notes_for_driver || '').toString().trim();
|
const profileNote = (customerMetaById.get(customerId)?.notes_for_driver || '').toString().trim();
|
||||||
if (profileNote) return profileNote;
|
if (profileNote) return profileNote;
|
||||||
|
const firstCustomerName = (customerCandidates?.[0]?.customer_name || '').toString().trim().toLowerCase();
|
||||||
|
const profileNoteByName = (customerMetaByName.get(firstCustomerName)?.notes_for_driver || '').toString().trim();
|
||||||
|
if (profileNoteByName) return profileNoteByName;
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user