import { describe, it, expect, beforeEach, vi } from "vitest"; /** * Tests for the server-side delivery-notes service. * * Mocks PocketBase with an in-memory store. The service is the * only thing under test — the route layer is verified separately * by integration / E2E. */ // In-memory store keyed by collection name. We use this to // simulate PB across `getOne`, `getList`, `getFullList`, `create`, // `update` calls without standing up a real PB instance. const { store, getOrCreate, where, pbMock, clearStore } = vi.hoisted(() => { const store: Record>> = {}; const getOrCreate = (collection: string) => { if (!store[collection]) store[collection] = new Map(); return store[collection]; }; const where = (collection: string, filter: string) => { const map = getOrCreate(collection); const items = Array.from(map.values()); if (!filter || !filter.trim()) return items; const clauses = filter.split("&&").map((c) => c.trim()); return items.filter((item) => clauses.every((clause) => { // PB supports two operators we use in this service: // = — exact match (with quoted RHS) // ~ — contains (substring match) // Both are joined with a quoted RHS. Handle both. const eq = clause.match(/^(\w+)\s*=\s*"([^"]+)"$/); if (eq) { const [, field, raw] = eq; return String(item[field] ?? "") === raw; } const contains = clause.match(/^(\w+)\s*~\s*"([^"]+)"$/); if (contains) { const [, field, raw] = contains; return String(item[field] ?? "").includes(raw); } return true; }), ); }; const clearStore = () => { for (const k of Object.keys(store)) delete store[k]; }; const pbMock = { collection: (name: string) => ({ async getOne(id: string) { const item = getOrCreate(name).get(id); if (!item) { const err = new Error("not found") as Error & { status: number }; err.status = 404; throw err; } return item; }, async getList(page = 1, perPage = 25, opts: { filter?: string; sort?: string } = {}) { let items = where(name, opts.filter || ""); // Honour PB's `-field` / `field` sort syntax. The real // service uses `sort: "-delivery_note_number"` to find // the most-recent DN — without sorting here, the test // mock returns insertion order and the counter never // advances. if (opts.sort) { const desc = opts.sort.startsWith("-"); const field = desc ? opts.sort.slice(1) : opts.sort; items = [...items].sort((a, b) => { const av = String(a[field] ?? ""); const bv = String(b[field] ?? ""); if (av === bv) return 0; return desc ? (av < bv ? 1 : -1) : av < bv ? -1 : 1; }); } const start = (page - 1) * perPage; return { items: items.slice(start, start + perPage), totalItems: items.length, page, perPage, }; }, async getFullList(opts: { filter?: string } = {}) { return where(name, opts.filter || ""); }, async create(data: Record) { const id = `mock-${Math.random().toString(36).slice(2, 10)}`; const item = { id, ...data }; getOrCreate(name).set(id, item); return item; }, async update(id: string, data: Record) { const item = getOrCreate(name).get(id); if (!item) { const err = new Error("not found") as Error & { status: number }; err.status = 404; throw err; } Object.assign(item, data); return item; }, }), }; return { store, getOrCreate, where, pbMock, clearStore }; }); vi.mock("@/lib/server/pocketbase-admin", () => ({ getAdminPocketBase: vi.fn().mockResolvedValue(pbMock), })); import { createQuoteFromDeliveryNote, setDeliveryNoteCustomerPo, setDeliveryNoteOrder, markDeliveryNoteInvoiced, markDeliveryNoteCompletedByInvoice, createAdHocDeliveryNote, generateDeliveryNoteNumberServer, getDeliveryNoteDetail, } from "./delivery-notes-service"; /** Seed a delivery note into the mock store. Returns its id. */ function seedDeliveryNote(overrides: Partial> = {}): string { const id = "dn-1"; getOrCreate("delivery_notes").set(id, { id, delivery_note_number: "DN26071401", status: "draft", customer: "customer-1", company: "company-1", order: "order-1", notes: "leave at back door", customer_po: "", delivery_date: "", created_by: "user-1", ...overrides, }); return id; } /** Seed an order_item into the mock store, stamped with the given * delivery_note_number. Returns its id. */ function seedOrderItem( deliveryNoteNumber: string, overrides: Partial> = {}, ): string { const id = `oi-${Math.random().toString(36).slice(2, 8)}`; getOrCreate("order_items").set(id, { id, order: "order-1", delivery_note_number: deliveryNoteNumber, description: "10x M8 bolts", quantity: 10, cost_price: 100, selling_price: 150, supplier: "supplier-1", sku: "M8-50", pricing_source: "catalogue", item_status: "delivery_note", notes: "", ...overrides, }); return id; } beforeEach(() => { clearStore(); }); describe("createQuoteFromDeliveryNote", () => { it("creates a quote with status=delivery_note + one quote_item per order_item, and flips the note to sent", async () => { // Seed with NO notes — the service falls back to the default // description containing the DN number, which is what we // want to assert. const dnId = seedDeliveryNote({ notes: "" }); seedOrderItem("DN26071401"); seedOrderItem("DN26071401", { description: "20x M10 nuts", quantity: 20 }); const result = await createQuoteFromDeliveryNote(dnId); expect(result.id).toBeTruthy(); expect(result.quote_number).toMatch(/^Q\d{6}\d{3}$/); // Quote header is persisted with the right shape. const quotes = Array.from(getOrCreate("quotes").values()); expect(quotes).toHaveLength(1); const q = quotes[0] as Record; expect(q.status).toBe("delivery_note"); expect(q.delivery_note).toBe(dnId); expect(q.customer).toBe("customer-1"); expect(q.company).toBe("company-1"); expect(q.validity_days).toBe(30); expect(q.description).toContain("DN26071401"); // v2.1 fix: the RFQ number is the delivery note number, so // the operator sees the DN ref on the quote's RFQ line and // knows instantly "this came from a delivery note, not // from a customer email." expect(q.rfq_number).toBe("DN26071401"); // One quote_item per order_item. const quoteItems = Array.from(getOrCreate("quote_items").values()); expect(quoteItems).toHaveLength(2); const byDescription = new Map( quoteItems.map((qi) => [ (qi as Record).description, qi as Record, ]), ); expect(byDescription.get("10x M8 bolts")?.selling_price).toBe(150); expect(byDescription.get("10x M8 bolts")?.pricing_source).toBe("catalogue"); expect(byDescription.get("20x M10 nuts")?.quantity).toBe(20); // Delivery note status flipped to "sent". const dn = getOrCreate("delivery_notes").get(dnId) as Record; expect(dn.status).toBe("sent"); }); it("stamps pricing_source=manual when the order_item has no selling_price", async () => { const dnId = seedDeliveryNote(); seedOrderItem("DN26071401", { selling_price: null }); await createQuoteFromDeliveryNote(dnId); const qi = Array.from(getOrCreate("quote_items").values())[0] as Record; // The service falls back to 0 — operator will fill in the // real price on the quote detail page. expect(qi.selling_price).toBe(0); expect(qi.pricing_source).toBe("manual"); }); it("throws when the delivery note is not in draft status", async () => { const dnId = seedDeliveryNote({ status: "sent" }); seedOrderItem("DN26071401"); await expect(createQuoteFromDeliveryNote(dnId)).rejects.toThrow( /must be in "draft" status/, ); }); it("throws when a delivery-note quote already exists for this note", async () => { const dnId = seedDeliveryNote(); seedOrderItem("DN26071401"); // Pre-seed an existing delivery-note quote linked to this note. getOrCreate("quotes").set("existing-quote", { id: "existing-quote", quote_number: "Q202607001", status: "delivery_note", delivery_note: dnId, }); await expect(createQuoteFromDeliveryNote(dnId)).rejects.toThrow( /already exists/, ); }); it("throws when the delivery note has no order_items", async () => { const dnId = seedDeliveryNote(); // No seedOrderItem call → the items lookup returns []. await expect(createQuoteFromDeliveryNote(dnId)).rejects.toThrow( /No order items found/, ); }); }); describe("setDeliveryNoteCustomerPo", () => { it("stamps the PO and flips the note from sent → po_received", async () => { const dnId = seedDeliveryNote({ status: "sent" }); const updated = await setDeliveryNoteCustomerPo(dnId, "PO-2026-1234"); expect(updated.customer_po).toBe("PO-2026-1234"); expect(updated.status).toBe("po_received"); }); it("trims whitespace before saving", async () => { const dnId = seedDeliveryNote({ status: "sent" }); const updated = await setDeliveryNoteCustomerPo(dnId, " PO-2026-1234 "); expect(updated.customer_po).toBe("PO-2026-1234"); }); it("rejects empty PO", async () => { const dnId = seedDeliveryNote({ status: "sent" }); await expect(setDeliveryNoteCustomerPo(dnId, "")).rejects.toThrow( /cannot be empty/i, ); await expect(setDeliveryNoteCustomerPo(dnId, " ")).rejects.toThrow( /cannot be empty/i, ); }); it("allows re-setting the PO on a po_received note (typo correction)", async () => { const dnId = seedDeliveryNote({ status: "po_received", customer_po: "PO-2026-OLD", }); const updated = await setDeliveryNoteCustomerPo(dnId, "PO-2026-NEW"); expect(updated.customer_po).toBe("PO-2026-NEW"); // Status unchanged — we only stamp, don't transition again. expect(updated.status).toBe("po_received"); }); it("rejects setting PO on a draft note (must send first)", async () => { const dnId = seedDeliveryNote({ status: "draft" }); await expect(setDeliveryNoteCustomerPo(dnId, "PO-1")).rejects.toThrow( /must be sent first/, ); }); }); describe("markDeliveryNoteInvoiced", () => { it("flips order_items to ready_invoice and the note to invoiced", async () => { const dnId = seedDeliveryNote({ status: "po_received" }); seedOrderItem("DN26071401"); seedOrderItem("DN26071401"); const updated = await markDeliveryNoteInvoiced(dnId); expect(updated.status).toBe("invoiced"); // Both order_items flipped to ready_invoice. const items = Array.from(getOrCreate("order_items").values()) as Array< Record >; expect(items).toHaveLength(2); for (const it of items) { expect(it.item_status).toBe("ready_invoice"); } }); it("rejects when the note is not in po_received", async () => { const dnId = seedDeliveryNote({ status: "draft" }); await expect(markDeliveryNoteInvoiced(dnId)).rejects.toThrow( /must be in "po_received" status/, ); }); it("rejects when the note has no order_items", async () => { const dnId = seedDeliveryNote({ status: "po_received" }); await expect(markDeliveryNoteInvoiced(dnId)).rejects.toThrow( /No order items found/, ); }); }); describe("markDeliveryNoteCompletedByInvoice", () => { it("flips status to completed and appends a QuickBooks banner to notes", async () => { const dnId = seedDeliveryNote({ status: "invoiced", notes: "leave at back door", }); const updated = await markDeliveryNoteCompletedByInvoice(dnId, { invoiceNumber: "QB-INV-1234", sentByName: "David Els", sentAtIso: "2026-08-16T12:34:56.000Z", }); expect(updated.status).toBe("completed"); // Existing notes preserved, banner appended on a new line. expect(updated.notes).toBe( "leave at back door\nAuto-closed via QuickBooks invoice QB-INV-1234 by David Els on 2026-08-16T12:34:56.000Z", ); }); it("works when notes is empty (banner is the first content)", async () => { const dnId = seedDeliveryNote({ status: "invoiced", notes: "" }); const updated = await markDeliveryNoteCompletedByInvoice(dnId, { invoiceNumber: "QB-INV-9999", sentByName: "Laura", sentAtIso: "2026-08-16T08:00:00.000Z", }); expect(updated.status).toBe("completed"); expect(updated.notes).toBe( "Auto-closed via QuickBooks invoice QB-INV-9999 by Laura on 2026-08-16T08:00:00.000Z", ); }); it("omits the 'by ' segment when sentByName is empty", async () => { const dnId = seedDeliveryNote({ status: "invoiced", notes: "" }); const updated = await markDeliveryNoteCompletedByInvoice(dnId, { invoiceNumber: "QB-INV-7", sentByName: " ", sentAtIso: "2026-08-16T08:00:00.000Z", }); expect(updated.notes).toBe( "Auto-closed via QuickBooks invoice QB-INV-7 on 2026-08-16T08:00:00.000Z", ); }); it("rejects when the note is completed or cancelled (terminal statuses)", async () => { const completedDn = seedDeliveryNote({ status: "completed" }); await expect( markDeliveryNoteCompletedByInvoice(completedDn, { invoiceNumber: "QB-1", }), ).rejects.toThrow(/in "completed" status cannot be auto-closed/); const cancelledDn = seedDeliveryNote({ status: "cancelled" }); await expect( markDeliveryNoteCompletedByInvoice(cancelledDn, { invoiceNumber: "QB-1", }), ).rejects.toThrow(/in "cancelled" status cannot be auto-closed/); }); it("accepts any non-terminal status and flips to completed", async () => { // The QB "invoice + close" button is now shown on every active // DN. Verify each non-terminal status is accepted. Each iteration // re-seeds dn-1 with the target status (the seed helper always // uses the same id), then runs the service. const active = ["draft", "sent", "po_received", "invoiced"] as const; for (const status of active) { const dnId = seedDeliveryNote({ status, notes: "", }); const updated = await markDeliveryNoteCompletedByInvoice(dnId, { invoiceNumber: `QB-${status}`, sentByName: "David", sentAtIso: "2026-08-16T12:00:00.000Z", }); expect(updated.status).toBe("completed"); expect(updated.notes).toContain(`QB-${status}`); } }); it("rejects when invoiceNumber is empty", async () => { const dnId = seedDeliveryNote({ status: "invoiced" }); await expect( markDeliveryNoteCompletedByInvoice(dnId, { invoiceNumber: " " }), ).rejects.toThrow(/invoiceNumber is required/); }); it("does not touch order_items (mirrors markDeliveryNoteComplete semantics)", async () => { // The DN's items were already flipped to ready_invoice by // markDeliveryNoteInvoiced, then n8n wrote them to "invoiced" // when the QB invoice was created. Auto-close should not // re-flip them — that's the same contract as the manual // "Mark complete" button. const dnId = seedDeliveryNote({ status: "invoiced" }); const itemId = seedOrderItem("DN26071401", { item_status: "invoiced" }); await markDeliveryNoteCompletedByInvoice(dnId, { invoiceNumber: "QB-INV-1", sentByName: "David", sentAtIso: "2026-08-16T12:00:00.000Z", }); const item = getOrCreate("order_items").get(itemId) as Record; expect(item.item_status).toBe("invoiced"); }); }); describe("generateDeliveryNoteNumberServer", () => { it("generates a DN{YY}{MM}{DD}{NN} number for the current day", async () => { const n = await generateDeliveryNoteNumberServer(); const now = new Date(); const yy = String(now.getFullYear()).slice(-2); const mm = String(now.getMonth() + 1).padStart(2, "0"); const dd = String(now.getDate()).padStart(2, "0"); expect(n).toBe(`DN${yy}${mm}${dd}01`); }); it("picks up from the highest existing DN number on order_items", async () => { // Pre-seed an order_item with a high DN number. The service // reads order_items.delivery_note_number (sorted -number) to // find the latest, so the next call must return that + 1. // // (We don't test the "increments on each consecutive call" // behavior here because the in-memory mock doesn't persist // the generated number back to order_items — in real PB the // delivery-note create flow writes the new number to every // item before the next call sees it, but the mock can't // simulate that without intercepting the generator. The // production behavior is covered by the format test above // + the create-flow tests in delivery-notes-service.) const now = new Date(); const yy = String(now.getFullYear()).slice(-2); const mm = String(now.getMonth() + 1).padStart(2, "0"); const dd = String(now.getDate()).padStart(2, "0"); const prefix = `DN${yy}${mm}${dd}`; seedOrderItem("XXX-OLD", { delivery_note_number: `${prefix}05` }); const next = await generateDeliveryNoteNumberServer(); expect(next).toBe(`${prefix}06`); }); }); describe("createAdHocDeliveryNote", () => { it("creates a delivery_notes header + one order_item per line (no parent order)", async () => { const result = await createAdHocDeliveryNote({ customer: "customer-1", company: "company-1", notes: "customer paid cash", createdBy: "user-1", items: [ { description: "10x M8 bolts", quantity: 10, cost_price: 100, }, { description: "20x M10 nuts", quantity: 20, selling_price: 8, }, ], }); expect(result.id).toBeTruthy(); expect(result.delivery_note_number).toMatch(/^DN\d{6}01$/); // Header persisted with the right shape. const dnotes = Array.from(getOrCreate("delivery_notes").values()); expect(dnotes).toHaveLength(1); const dn = dnotes[0] as Record; expect(dn.delivery_note_number).toBe(result.delivery_note_number); expect(dn.customer).toBe("customer-1"); expect(dn.company).toBe("company-1"); expect(dn.order).toBeNull(); expect(dn.status).toBe("draft"); expect(dn.notes).toBe("customer paid cash"); expect(dn.created_by).toBe("user-1"); // One order_item per input line, all with `order: null` and // the delivery note number stamped on. const items = Array.from(getOrCreate("order_items").values()) as Array< Record >; expect(items).toHaveLength(2); const byDescription = new Map( items.map((it) => [it.description, it]), ); const bolts = byDescription.get("10x M8 bolts"); const nuts = byDescription.get("20x M10 nuts"); expect(bolts?.order).toBeNull(); expect(bolts?.delivery_note_number).toBe(result.delivery_note_number); expect(bolts?.item_status).toBe("delivery_note"); expect(bolts?.quantity).toBe(10); expect(bolts?.cost_price).toBe(100); expect(nuts?.quantity).toBe(20); expect(nuts?.selling_price).toBe(8); }); it("rejects when customer is missing", async () => { await expect( createAdHocDeliveryNote({ customer: "", items: [{ description: "x", quantity: 1 }], }), ).rejects.toThrow(/Customer is required/); }); it("rejects when items is empty", async () => { await expect( createAdHocDeliveryNote({ customer: "customer-1", items: [], }), ).rejects.toThrow(/At least one item/); }); it("rejects items with empty description or non-positive quantity", async () => { await expect( createAdHocDeliveryNote({ customer: "customer-1", items: [{ description: "", quantity: 1 }], }), ).rejects.toThrow(/missing a description/); await expect( createAdHocDeliveryNote({ customer: "customer-1", items: [{ description: "ok", quantity: 0 }], }), ).rejects.toThrow(/invalid quantity/); }); }); // ────────────────────────────────────────────────────────────────────── // setDeliveryNoteOrder (Hermes unblock) // ────────────────────────────────────────────────────────────────────── describe("setDeliveryNoteOrder", () => { it("links a delivery note to an order, returns {deliveryNote, previous}", async () => { const dnId = seedDeliveryNote({ status: "draft", order: "" }); getOrCreate("orders").set("order-2", { id: "order-2", order_number: "ORD-2", }); const result = await setDeliveryNoteOrder({ deliveryNoteId: dnId, orderId: "order-2", }); expect(result.previous).toEqual({ order: "" }); expect(result.deliveryNote.id).toBe(dnId); expect(result.deliveryNote.order).toBe("order-2"); // The store reflects the write. const stored = getOrCreate("delivery_notes").get(dnId); expect(stored?.order).toBe("order-2"); }); it("refuses 409 delivery_note_locked when DN is invoiced", async () => { const dnId = seedDeliveryNote({ status: "invoiced", order: "" }); getOrCreate("orders").set("order-2", { id: "order-2", order_number: "ORD-2" }); try { await setDeliveryNoteOrder({ deliveryNoteId: dnId, orderId: "order-2" }); expect.fail("should have thrown"); } catch (err) { expect((err as Error & { status?: number }).status).toBe(409); const data = (err as Error & { data?: { code?: string; status?: string } }).data; expect(data?.code).toBe("delivery_note_locked"); expect(data?.status).toBe("invoiced"); } // Order must not have changed. expect(getOrCreate("delivery_notes").get(dnId)?.order).toBe(""); }); it("refuses 409 delivery_note_locked when DN is completed", async () => { const dnId = seedDeliveryNote({ status: "completed", order: "" }); getOrCreate("orders").set("order-2", { id: "order-2", order_number: "ORD-2" }); try { await setDeliveryNoteOrder({ deliveryNoteId: dnId, orderId: "order-2" }); expect.fail("should have thrown"); } catch (err) { expect((err as Error & { status?: number }).status).toBe(409); const data = (err as Error & { data?: { code?: string; status?: string } }).data; expect(data?.code).toBe("delivery_note_locked"); expect(data?.status).toBe("completed"); } }); it("404 when the delivery note doesn't exist", async () => { getOrCreate("orders").set("order-2", { id: "order-2", order_number: "ORD-2" }); await expect( setDeliveryNoteOrder({ deliveryNoteId: "missing", orderId: "order-2" }), ).rejects.toMatchObject({ status: 404 }); }); it("404 when the order doesn't exist", async () => { const dnId = seedDeliveryNote({ status: "draft", order: "" }); await expect( setDeliveryNoteOrder({ deliveryNoteId: dnId, orderId: "missing" }), ).rejects.toMatchObject({ status: 404 }); }); it("is idempotent when the DN already points at the order (no-op + no audit stamp)", async () => { const dnId = seedDeliveryNote({ status: "draft", order: "order-1", notes: "original dn note", }); getOrCreate("orders").set("order-1", { id: "order-1", order_number: "ORD-1" }); const result = await setDeliveryNoteOrder({ deliveryNoteId: dnId, orderId: "order-1", actor: { id: "agent-1", name: "Hermes" }, }); expect(result.previous).toEqual({ order: "order-1" }); expect(result.deliveryNote.order).toBe("order-1"); // Notes must NOT be touched on idempotent no-op. expect(getOrCreate("delivery_notes").get(dnId)?.notes).toBe("original dn note"); }); it("stamps actor into notes when provided", async () => { const dnId = seedDeliveryNote({ status: "draft", order: "", notes: "existing dn note", }); getOrCreate("orders").set("order-2", { id: "order-2", order_number: "ORD-2" }); await setDeliveryNoteOrder({ deliveryNoteId: dnId, orderId: "order-2", actor: { id: "agent-1", name: "Hermes" }, }); const stored = getOrCreate("delivery_notes").get(dnId); expect(stored?.order).toBe("order-2"); expect(String(stored?.notes ?? "")).toMatch(/existing dn note/); expect(String(stored?.notes ?? "")).toMatch(/Hermes linked this delivery note to order order-2/); }); // Regression coverage for the delivery-notes backfill (2026-09-07): // DNs created while the /api/invoicing/delivery-note `fields`+`expand` // bug was live (see route.ts) got saved with customer/company/order // all blank. Re-linking the order via this setter should also // backfill customer/company from that order, since they're supposed // to mirror it — see the docstring above `setDeliveryNoteOrder`. it("backfills customer/company from the order when the DN's own values are blank", async () => { const dnId = seedDeliveryNote({ status: "draft", order: "", customer: "", company: "" }); getOrCreate("orders").set("order-2", { id: "order-2", order_number: "ORD-2", customer: "customer-9", company: "company-9", }); const result = await setDeliveryNoteOrder({ deliveryNoteId: dnId, orderId: "order-2", }); expect(result.deliveryNote.customer).toBe("customer-9"); expect(result.deliveryNote.company).toBe("company-9"); const stored = getOrCreate("delivery_notes").get(dnId); expect(stored?.customer).toBe("customer-9"); expect(stored?.company).toBe("company-9"); }); it("backfills customer/company even when the order is already linked (idempotent-on-order path)", async () => { // A DN can already point at the right order (e.g. re-running this // setter a second time) yet still be missing customer/company — // exactly the shape the fields+expand regression left behind. // The idempotent-on-order branch must still apply the backfill. const dnId = seedDeliveryNote({ status: "draft", order: "order-2", customer: "", company: "" }); getOrCreate("orders").set("order-2", { id: "order-2", order_number: "ORD-2", customer: "customer-9", company: "company-9", }); const result = await setDeliveryNoteOrder({ deliveryNoteId: dnId, orderId: "order-2", }); expect(result.previous).toEqual({ order: "order-2" }); expect(result.deliveryNote.customer).toBe("customer-9"); expect(result.deliveryNote.company).toBe("company-9"); }); it("does not overwrite a customer/company the DN already has", async () => { const dnId = seedDeliveryNote({ status: "draft", order: "", customer: "customer-1", company: "company-1", }); getOrCreate("orders").set("order-2", { id: "order-2", order_number: "ORD-2", customer: "customer-9", company: "company-9", }); await setDeliveryNoteOrder({ deliveryNoteId: dnId, orderId: "order-2" }); const stored = getOrCreate("delivery_notes").get(dnId); expect(stored?.customer).toBe("customer-1"); expect(stored?.company).toBe("company-1"); }); }); describe("getDeliveryNoteDetail", () => { it("returns the note, its lines by number and the linked quote", async () => { const dnId = seedDeliveryNote(); seedOrderItem("DN26071401"); seedOrderItem("DN26071401"); seedOrderItem("DN-OTHER"); getOrCreate("quotes").set("q-dn", { id: "q-dn", delivery_note: dnId, created: "2026-07-14" }); const detail = await getDeliveryNoteDetail(dnId); expect(detail.deliveryNote.delivery_note_number).toBe("DN26071401"); expect(detail.items).toHaveLength(2); expect(detail.quote?.id).toBe("q-dn"); }); it("404s for a missing note", async () => { await expect(getDeliveryNoteDetail("missing")).rejects.toMatchObject({ status: 404 }); }); });