diff --git a/src/app/api/invoicing/delivery-note/route.test.ts b/src/app/api/invoicing/delivery-note/route.test.ts index acd4d14..f31ad34 100644 --- a/src/app/api/invoicing/delivery-note/route.test.ts +++ b/src/app/api/invoicing/delivery-note/route.test.ts @@ -256,6 +256,30 @@ describe("POST /api/invoicing/delivery-note", () => { expect(payload.order).toBeNull(); }); + it("requests expand.order.* in the fields projection so PocketBase doesn't strip the expand data", async () => { + // Root cause of a real bug (DN26090702 rendered "(no company)" / + // "(no customer)" / "(no order number)"): PocketBase silently + // drops `expand` from the response when a `fields` projection is + // present, UNLESS the expand sub-paths are explicitly listed in + // that same `fields` string (see item-intel-service.ts / + // visits-service.ts for the established pattern). The route + // requests `expand: "order.company,order.customer"` but the + // `fields` projection added for `exclude_from_invoice` didn't + // list any `expand.*` paths, so PB returned every item with + // `expand: undefined` even though the relations were populated. + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-07-14T13:00:00.000Z")); + + await POST(makeRequest({ orderItemIds: ["row-1", "row-2"] }) as any); + + const [[opts]] = getFullList.mock.calls; + const fields = (opts as { fields?: string })?.fields ?? ""; + expect(fields).toContain("expand.order.order_number"); + expect(fields).toContain("expand.order.notes"); + expect(fields).toContain("expand.order.expand.company.company"); + expect(fields).toContain("expand.order.expand.customer.customer"); + }); + it("populates customer/company/order on the header when all items share them", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-07-14T13:00:00.000Z")); diff --git a/src/app/api/invoicing/delivery-note/route.ts b/src/app/api/invoicing/delivery-note/route.ts index af1e674..5dbeb34 100644 --- a/src/app/api/invoicing/delivery-note/route.ts +++ b/src/app/api/invoicing/delivery-note/route.ts @@ -225,9 +225,21 @@ export async function POST(request: NextRequest) { // line list. Without it the field comes back undefined and every // extra silently lands on the customer delivery note — the same // load-bearing-projection concern documented for INVOICING_FIELDS. + // + // The `expand.order.*` / `expand.order.expand.*` entries are + // load-bearing too: PocketBase silently drops `expand` from the + // response whenever a `fields` projection is present, unless the + // expand sub-paths are explicitly listed inside that same + // `fields` string (same pattern as item-intel-service.ts / + // visits-service.ts). Without them, `order?.order_number`, + // `order?.expand?.company?.company`, and + // `order?.expand?.customer?.customer` all come back undefined + // even though `expand: "order.company,order.customer"` below + // asked PB to populate them — the PDF then renders "(no company)" + // / "(no customer)" / "(no order number)". const items = await pb.collection("order_items").getFullList({ filter: orderItemIds.map((id) => `id = "${id}"`).join(" || "), - fields: "id,description,delivery_note_number,order_item_details,order_item_attachements,quantity,order_qty,qty_collected,exclude_from_invoice", + fields: "id,description,delivery_note_number,order_item_details,order_item_attachements,quantity,order_qty,qty_collected,exclude_from_invoice,expand.order.id,expand.order.order_number,expand.order.notes,expand.order.expand.company.id,expand.order.expand.company.company,expand.order.expand.customer.id,expand.order.expand.customer.customer", expand: "purchase_order.supplier,order.company,order.customer", }); if (items.length === 0) {