fix(invoicing): restore expand data on delivery-note PDF generation

PocketBase silently drops `expand` from the response once a `fields`
projection is present, unless the expand sub-paths are explicitly
listed in that same `fields` string. The `exclude_from_invoice`
projection added on 2026-08-30 didn't list any `expand.*` paths, so
order/company/customer all rendered blank on the delivery note PDF
(e.g. DN26090702).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Dave 2026-09-07 10:22:17 +02:00
parent 95d93791f9
commit 6dc7204477
2 changed files with 37 additions and 1 deletions

View file

@ -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"));

View file

@ -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<OrderItemRecord>({
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) {