From 7bcd92f9991fd6c73381ede074550831151c616e Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 17 Oct 2026 01:37:29 +0200 Subject: [PATCH] fix(collection-ai): findings fallback tests, rounding Co-Authored-By: Claude Opus 5.5 --- src/lib/server/collection-ai-findings.test.ts | 53 +++++++++++++++---- src/lib/server/collection-ai-findings.ts | 4 +- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/lib/server/collection-ai-findings.test.ts b/src/lib/server/collection-ai-findings.test.ts index f4b5596..243e60b 100644 --- a/src/lib/server/collection-ai-findings.test.ts +++ b/src/lib/server/collection-ai-findings.test.ts @@ -182,15 +182,50 @@ describe("buildDocFindings", () => { { doc_line_no: 2, po_line_id: null }, ], }); - expect(out.map((s) => s.split(" ")[0])).toEqual([ - "Document", // covers - "Alpha", // not collected - "Document", // no PO line - "Beta", // collected not on doc - "Supplier", - "Alpha:", // to follow - "Alpha:", // qty - "No", + expect(out).toEqual([ + "Document 5028256 covers 2 POs: PO1, PO2.", + "Alpha × 40 is on the document but not marked collected.", + 'Document line "Orphan" doesn\'t match any PO line.', + "Beta was marked collected but isn't on the document.", + "Supplier on the document (Other Co) differs from the PO supplier (Acme).", + "Alpha: supplier shows nothing to follow, but 4 are still outstanding.", + "Alpha: order line says 40 but the PO quantity is 4.", + "No PO number on the document matched; checked against PO1.", ]); }); + + const toFollow = (po: Partial) => + buildDocFindings({ + ...base, + read: read([dl({ line_no: 1, quantity: 30, to_follow: 0 })]), + poLines: [pl(po)], + pairs: [{ doc_line_no: 1, po_line_id: "p1" }], + }); + + it("to-follow uses quantity when order_qty is 0", () => { + expect(toFollow({ order_qty: 0, quantity: 40, qty_collected: 30 })).toEqual([ + "M8x25 stainless steel: supplier shows nothing to follow, but 10 are still outstanding.", + ]); + }); + + it("to-follow does not fire when fallback ordered is fully collected", () => { + expect(toFollow({ order_qty: 0, quantity: 30, qty_collected: 30 })).toEqual([]); + }); + + it("rounds float noise in outstanding quantity", () => { + expect(toFollow({ order_qty: 0, quantity: 0.3, qty_collected: 0.1 })).toEqual([ + "M8x25 stainless steel: supplier shows nothing to follow, but 0.2 are still outstanding.", + ]); + }); + + it("prints a single-spaced placeholder for a null document quantity", () => { + expect( + buildDocFindings({ + ...base, + read: read([dl({ line_no: 1, quantity: null })]), + poLines: [pl({ collected: false, qty_collected: 0 })], + pairs: [{ doc_line_no: 1, po_line_id: "p1" }], + }), + ).toEqual(["M8x25 stainless steel × ? is on the document but not marked collected."]); + }); }); diff --git a/src/lib/server/collection-ai-findings.ts b/src/lib/server/collection-ai-findings.ts index 06af087..4898353 100644 --- a/src/lib/server/collection-ai-findings.ts +++ b/src/lib/server/collection-ai-findings.ts @@ -13,9 +13,9 @@ export type PoLineLite = { export type LinePair = { doc_line_no: number; po_line_id: string | null }; -/** 40 not 40.0; other values as-is. */ +/** 40 not 40.0; at most 3 decimals so float noise never prints. */ function fmt(n: number): string { - return String(n); + return String(Math.round(n * 1000) / 1000); } /** Ordered = order_qty, falling back to quantity when order_qty is 0. */