refactor(orders): hoist WINDOW_DAYS, add 60-day boundary tests, clarify comment

This commit is contained in:
Dave 2026-08-30 22:20:29 +02:00
parent a6d88cef1c
commit d32d71f55b
2 changed files with 72 additions and 6 deletions

View file

@ -307,6 +307,65 @@ describe("suggestDeliveryNoteForOrder", () => {
expect(result.best).toBeNull();
});
it("matches a delivery note at exactly 60 days old (boundary positive control)", async () => {
// 60 days before "now" 2026-08-30 = 2026-07-01
const orderItem = { description: "M16x50 Bolt", quantity: 300 };
const dnItem = { description: "M16x50 Bolt", quantity: 300, order: "other-order" };
mockAdminPb(makePb({
[ORDERS]: {
getOne: vi.fn().mockResolvedValue({ id: "ord1", company: "co1" }),
update: vi.fn().mockResolvedValue({}),
},
[ORDER_ITEMS]: {
getFullList: vi.fn()
.mockResolvedValueOnce([orderItem])
.mockResolvedValueOnce([dnItem]),
},
[DELIVERY_NOTES]: {
getFullList: vi.fn().mockResolvedValue([
{
id: "dn-edge-in",
delivery_note_number: "DN-EDGE-IN",
company: "co1",
status: "completed",
created: "2026-07-01 12:00:00.000Z", // exactly 60 days back
order: "other-order",
},
]),
},
}));
const result = await suggestDeliveryNoteForOrder("ord1");
expect(result.best?.deliveryNoteId).toBe("dn-edge-in");
});
it("does not match a delivery note just past the 60-day window (61 days old)", async () => {
// 61 days before "now" 2026-08-30 = 2026-06-30
const orderItem = { description: "M16x50 Bolt", quantity: 300 };
const dnItem = { description: "M16x50 Bolt", quantity: 300, order: "other-order" };
mockAdminPb(makePb({
[ORDERS]: {
getOne: vi.fn().mockResolvedValue({ id: "ord1", company: "co1" }),
update: vi.fn().mockResolvedValue({}),
},
[ORDER_ITEMS]: {
getFullList: vi.fn()
.mockResolvedValueOnce([orderItem])
.mockResolvedValueOnce([dnItem]),
},
[DELIVERY_NOTES]: {
getFullList: vi.fn().mockResolvedValue([]), // 61 days = filtered out by cutoff
},
}));
const result = await suggestDeliveryNoteForOrder("ord1");
expect(result.best).toBeNull();
});
});
/* ============================================================================

View file

@ -34,11 +34,18 @@ const DELIVERY_NOTES_COLLECTION = "delivery_notes";
const QUOTES_COLLECTION = "quotes";
const QUOTE_ITEMS_COLLECTION = "quote_items";
/** Maximum age (in days) of a delivery-note candidate considered for
* repeat matching. Beyond this window, even an exact-line match is
* treated as a coincidence, not a repeat order. */
const WINDOW_DAYS = 60;
// Per 2026-08-30 business decision: match against a DN of ANY status,
// including `cancelled` — the prior exclusion missed real repeat orders
// where the original delivery's DN had been cancelled for reasons
// unrelated to whether goods went out. A 60-day candidate window
// (computed below) bounds the search space instead.
// including `cancelled` (previously excluded along with a hardcoded
// allowlist of "active" statuses — see git history) — the prior
// exclusion missed real repeat orders where the original delivery's DN
// had been cancelled for reasons unrelated to whether goods went out.
// A 60-day candidate window (computed below) bounds the search space
// instead.
function esc(s: string): string {
return (s || "").replace(/"/g, '\\"');
@ -94,8 +101,8 @@ export async function suggestDeliveryNoteForOrder(
quantity: Number(r.quantity) || 0,
}));
const WINDOW_DAYS = 60;
const cutoff = new Date(Date.now() - WINDOW_DAYS * 24 * 60 * 60 * 1000);
const WINDOW_MS = WINDOW_DAYS * 24 * 60 * 60 * 1000;
const cutoff = new Date(Date.now() - WINDOW_MS);
const cutoffStr = cutoff.toISOString().replace("T", " ");
const candidates = await pb