From 4e4e7328b3f36d0d1ce8407ec2fee6e2ac0d58c7 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 4 Oct 2026 17:00:30 +0200 Subject: [PATCH] fix(delivery-note): signed copy invisible, and a bigger QR in the corner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TWO bugs, one of which was hiding the other. 1. "No signed copy on file" for a note that had just been signed, scanned and uploaded. The upload was never the problem — the READ path was. `delivery_notes.signed_copy` is a PB file field with `maxSelect: 1`: we WRITE it as an array (`signed_copy: [file]`), but PocketBase RETURNS A BARE STRING when exactly one file is attached. Verified on the live record after a real upload: typeof : string Array.isArray : false value : "image_ar52u2hjfq.jpg" value[0] : "i" <-- indexing yields a CHARACTER Every read site assumed `string[]`, and not one of them threw: - signed-copy GET: `Array.isArray(x) ? x[0] : null` returned null for notes that demonstrably had a copy — the reported error - delivery-note detail tooltip: `signed_copy[0]` rendered the first LETTER of the filename - delivery-notes-service: treated every signed copy as absent, so the quote went out with the UNSIGNED pdf attached - deliveries-service: reported an empty filename - deliveries page: `.join(", ")` on a string — this one THREW, on a page people open daily Adds `src/lib/file-fields.ts` (firstFileName / fileNames / hasFile) and routes all of them through it. The type was the root of the wrong assumptions, so it is now `string | string[]` with a comment pointing at the helper. 2. QR 28pt -> 52pt, moved to the top-right corner. Measured by rasterising the real PDF at 200 dpi and decoding: it now reads at 1.24 px/module, where the old size needed ~2.5 — roughly double the distance and angle tolerance. It could not simply go at top:40: the logo is an absolute 160x100pt block in the same corner, so the QR landed on top of it. That is not only ugly, it breaks the QR's quiet zone — logo ink inside the light margin makes the code refuse to decode. The logo now steps left (right: 112 -> 106, width 160 -> 132) to give the QR the true corner, and the company name is width-bounded ONLY when a logo is present. The Westscott letterhead has no matching .svg in public/letterhead/, so in practice the name still gets the full width and stays on one line. Nine tests pin the file-field shapes, including one that asserts the old `.join(", ")` call really does throw on a string, so the string case cannot be quietly dropped again. --- src/app/(app)/deliveries/page.tsx | 5 +- src/app/(app)/delivery-notes/[id]/page.tsx | 7 +- .../delivery-notes/[id]/signed-copy/route.ts | 10 ++- src/lib/file-fields.test.ts | 86 +++++++++++++++++++ src/lib/file-fields.ts | 62 +++++++++++++ src/lib/pdf/delivery-note-document.tsx | 59 +++++++++---- src/lib/server/deliveries-service.ts | 3 +- src/lib/server/delivery-notes-service.ts | 8 +- src/lib/types.ts | 15 +++- 9 files changed, 227 insertions(+), 28 deletions(-) create mode 100644 src/lib/file-fields.test.ts create mode 100644 src/lib/file-fields.ts diff --git a/src/app/(app)/deliveries/page.tsx b/src/app/(app)/deliveries/page.tsx index d1f541f..06e984f 100644 --- a/src/app/(app)/deliveries/page.tsx +++ b/src/app/(app)/deliveries/page.tsx @@ -119,6 +119,7 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { cn } from "@/lib/utils"; +import { fileNames, hasFile } from "@/lib/file-fields"; // ──────────────────────────────────────────────────────────────────── // Wire types — match the JSON shape returned by /api/v1/deliveries @@ -1807,12 +1808,12 @@ function DeliveryNoteRowComponent({ ) : ( Counter pickup )} - {dn.signed_copy && dn.signed_copy.length > 0 && ( + {hasFile(dn.signed_copy) && ( <> · signed copy diff --git a/src/app/(app)/delivery-notes/[id]/page.tsx b/src/app/(app)/delivery-notes/[id]/page.tsx index af19152..23159be 100644 --- a/src/app/(app)/delivery-notes/[id]/page.tsx +++ b/src/app/(app)/delivery-notes/[id]/page.tsx @@ -61,6 +61,7 @@ import type { DeliveryNoteDetail } from "@/lib/server/delivery-notes-service"; import { AuditTimeline } from "@/components/audit-timeline"; import getPocketBase from "@/lib/pocketbase"; import { cn } from "@/lib/utils"; +import { fileNames, hasFile } from "@/lib/file-fields"; import type { DeliveryNote, DeliveryNoteStatus, @@ -1472,7 +1473,7 @@ export default function DeliveryNoteDetailPage({ params }: PageProps) { of the unsigned PDF. */}