fixes
Build and Push Docker Image / build (push) Successful in 1m6s

This commit is contained in:
jason
2026-04-21 20:59:55 -05:00
parent bb452a59ae
commit bc3b78aa33
17 changed files with 534 additions and 40 deletions
+45 -5
View File
@@ -20,7 +20,9 @@ const MARGIN = 48; // 2/3"
export interface OperationCardData {
project: { code: string; name: string };
assembly: { code: string; name: string };
/** `qty` is the number of assemblies of this kind in the project. */
assembly: { code: string; name: string; qty: number };
/** `qty` is the per-assembly part count (so total parts = assembly.qty × part.qty). */
part: { code: string; name: string; material: string | null; qty: number };
operation: {
id: string;
@@ -59,7 +61,7 @@ export interface PurchaseOrderPdfData {
export interface PartCoverData {
project: { code: string; name: string };
assembly: { code: string; name: string };
assembly: { code: string; name: string; qty: number };
part: {
code: string;
name: string;
@@ -98,10 +100,21 @@ export async function renderPurchaseOrder(data: PurchaseOrderPdfData): Promise<U
return doc.save();
}
/** Entry point: cover sheet + every operation card, all in one PDF. */
/** Entry point: cover sheet + every operation card, all in one PDF.
*
* If `drawingPdfBytes` is provided (raw bytes of the part's PDF drawing),
* those pages are inlined right after the cover sheet so the printed stack
* is: cover → drawing(s) → op 1 → op 2 … Operators see the drawing on the
* same sheet they're holding while running the part — no separate print.
*
* Assembly-level drawings can be appended too (`assemblyDrawingPdfBytes`),
* rendered before the part drawing.
*/
export async function renderPartTravelers(payload: {
cover: PartCoverData;
cards: OperationCardData[];
drawingPdfBytes?: Uint8Array | null;
assemblyDrawingPdfBytes?: Uint8Array | null;
}): Promise<Uint8Array> {
const doc = await PDFDocument.create();
const fonts = await embedFonts(doc);
@@ -109,6 +122,16 @@ export async function renderPartTravelers(payload: {
const coverPage = doc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
await drawCoverSheet(doc, coverPage, fonts, payload.cover);
// Inline the assembly-level drawing first, then the part drawing. Both are
// optional. We swallow per-PDF errors so a corrupt drawing doesn't block
// the op cards from printing.
if (payload.assemblyDrawingPdfBytes) {
await appendPdfPages(doc, payload.assemblyDrawingPdfBytes, "assembly drawing");
}
if (payload.drawingPdfBytes) {
await appendPdfPages(doc, payload.drawingPdfBytes, "part drawing");
}
for (const card of payload.cards) {
const page = doc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
await drawOperationCard(doc, page, fonts, card);
@@ -117,6 +140,19 @@ export async function renderPartTravelers(payload: {
return doc.save();
}
// Load an external PDF's pages and copy them into `doc`. Best-effort: if the
// upstream PDF is unreadable we log to stderr (server-side) and skip; the
// caller's traveler PDF is still produced.
async function appendPdfPages(doc: PDFDocument, bytes: Uint8Array, label: string): Promise<void> {
try {
const src = await PDFDocument.load(bytes, { ignoreEncryption: true });
const pages = await doc.copyPages(src, src.getPageIndices());
for (const p of pages) doc.addPage(p);
} catch (err) {
console.warn(`[travelers.pdf] skipped ${label}: ${(err as Error).message}`);
}
}
// ---------------------------------------------------------------------------
// Layout helpers
// ---------------------------------------------------------------------------
@@ -258,10 +294,11 @@ async function drawOperationCard(
drawText(page, line, { x: MARGIN, y: cursor.top, font: fonts.bold, size: 22 });
}
cursor.top -= 14;
const totalUnits = data.assembly.qty * data.part.qty;
const partMeta = [
`Part ${data.part.code}`,
data.part.material ? `${data.part.material}` : null,
`qty ${data.part.qty}`,
`${data.part.qty}/assembly × ${data.assembly.qty} assy = ${totalUnits} to produce`,
]
.filter(Boolean)
.join(" · ");
@@ -471,9 +508,12 @@ async function drawCoverSheet(
});
cursor.top -= 18;
const totalUnits = data.assembly.qty * data.part.qty;
const meta = [
data.part.material ? `Material: ${data.part.material}` : null,
`Quantity: ${data.part.qty}`,
`Per-assembly qty: ${data.part.qty}`,
`Assemblies: ${data.assembly.qty}`,
`Total to produce: ${totalUnits}`,
`Project: ${data.project.name}`,
`Assembly: ${data.assembly.name}`,
].filter(Boolean) as string[];