refactor(orders): hoist WINDOW_DAYS, add 60-day boundary tests, clarify comment
This commit is contained in:
parent
a6d88cef1c
commit
d32d71f55b
2 changed files with 72 additions and 6 deletions
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
/* ============================================================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue