test(orders): pin no-match _attempted flag + 60-day boundary for order duplicate matcher
This commit is contained in:
parent
f981cf96c3
commit
75b1e524ad
1 changed files with 79 additions and 1 deletions
|
|
@ -17,7 +17,11 @@ const { store, pbMock, clearStore } = vi.hoisted(() => {
|
|||
const clauses = filter.split("&&").map((c) => c.trim());
|
||||
return items.filter((item) =>
|
||||
clauses.every((clause) => {
|
||||
const m = clause.match(/^(\w+)\s*([!=<>]+)\s*"?([^"]+)"?$/);
|
||||
// Mock-only filter parser: accepts the four operators the
|
||||
// duplicate-matcher uses (=, !=, >=, plus the lex variants
|
||||
// <, <=, >). Anything else silently passes through so a
|
||||
// typo in a clause doesn't 500 the test.
|
||||
const m = clause.match(/^(\w+)\s*([!=]+|>=|<=|>|<)\s*"?([^"]+)"?$/);
|
||||
if (!m) return true;
|
||||
const [, field, op, raw] = m;
|
||||
const value = raw.replace(/^"|"$/g, "");
|
||||
|
|
@ -204,6 +208,14 @@ describe("suggestOrderDuplicate", () => {
|
|||
const result = await suggestOrderDuplicate("order-mine");
|
||||
|
||||
expect(result.duplicateOrderId).toBeNull();
|
||||
// Symmetric contract: no-match branch still stamps the
|
||||
// _matching_attempted flag so a UI / external scan can distinguish
|
||||
// "we tried and found nothing" from "we never looked".
|
||||
const updated = await pbMock.collection("orders").getOne("order-mine");
|
||||
expect(updated.duplicate_order_matching_attempted).toBe(true);
|
||||
expect(updated.duplicate_order_match_method).toBe("no_match");
|
||||
expect(updated.duplicate_order_match_confidence).toBe(0);
|
||||
expect(updated.potential_duplicate_order).toBeFalsy();
|
||||
});
|
||||
|
||||
it("does not flag when the matching order is older than 60 days", async () => {
|
||||
|
|
@ -221,6 +233,11 @@ describe("suggestOrderDuplicate", () => {
|
|||
const result = await suggestOrderDuplicate("order-mine");
|
||||
|
||||
expect(result.duplicateOrderId).toBeNull();
|
||||
const updated = await pbMock.collection("orders").getOne("order-mine");
|
||||
expect(updated.duplicate_order_matching_attempted).toBe(true);
|
||||
expect(updated.duplicate_order_match_method).toBe("no_match");
|
||||
expect(updated.duplicate_order_match_confidence).toBe(0);
|
||||
expect(updated.potential_duplicate_order).toBeFalsy();
|
||||
});
|
||||
|
||||
it("does not flag when item quantities differ", async () => {
|
||||
|
|
@ -238,6 +255,11 @@ describe("suggestOrderDuplicate", () => {
|
|||
const result = await suggestOrderDuplicate("order-b");
|
||||
|
||||
expect(result.duplicateOrderId).toBeNull();
|
||||
const updated = await pbMock.collection("orders").getOne("order-b");
|
||||
expect(updated.duplicate_order_matching_attempted).toBe(true);
|
||||
expect(updated.duplicate_order_match_method).toBe("no_match");
|
||||
expect(updated.duplicate_order_match_confidence).toBe(0);
|
||||
expect(updated.potential_duplicate_order).toBeFalsy();
|
||||
});
|
||||
|
||||
it("never matches an order against itself", async () => {
|
||||
|
|
@ -250,5 +272,61 @@ describe("suggestOrderDuplicate", () => {
|
|||
const result = await suggestOrderDuplicate("order-solo");
|
||||
|
||||
expect(result.duplicateOrderId).toBeNull();
|
||||
const updated = await pbMock.collection("orders").getOne("order-solo");
|
||||
expect(updated.duplicate_order_matching_attempted).toBe(true);
|
||||
expect(updated.duplicate_order_match_method).toBe("no_match");
|
||||
expect(updated.duplicate_order_match_confidence).toBe(0);
|
||||
expect(updated.potential_duplicate_order).toBeFalsy();
|
||||
});
|
||||
|
||||
it("does not flag when the matching order is just past the 60-day window", async () => {
|
||||
// 2026-08-30T12:00:00Z - 61d = 2026-06-30T12:00:00Z, just one day
|
||||
// outside the inclusive `created >= cutoff` window. This pins
|
||||
// the boundary actually being enforced — without it, a bug that
|
||||
// drops the `>=` clause would still pass the 90-day-old test.
|
||||
await seedOrderWithItems(
|
||||
"order-edge",
|
||||
{ order_number: "ORD-A", company: "comp-1", created: "2026-06-30 12:00:00.000Z" },
|
||||
[{ description: "M16x50 Bolt", quantity: 300 }],
|
||||
);
|
||||
await seedOrderWithItems(
|
||||
"order-mine",
|
||||
{ order_number: "ORD-B", company: "comp-1", created: "2026-08-30 00:00:00.000Z" },
|
||||
[{ description: "M16x50 Bolt", quantity: 300 }],
|
||||
);
|
||||
|
||||
const result = await suggestOrderDuplicate("order-mine");
|
||||
|
||||
expect(result.duplicateOrderId).toBeNull();
|
||||
const updated = await pbMock.collection("orders").getOne("order-mine");
|
||||
expect(updated.duplicate_order_matching_attempted).toBe(true);
|
||||
expect(updated.duplicate_order_match_method).toBe("no_match");
|
||||
expect(updated.potential_duplicate_order).toBeFalsy();
|
||||
});
|
||||
|
||||
it("flags when the matching order is within the 60-day window (boundary positive control)", async () => {
|
||||
// 2026-08-30T12:00:00Z - 60d = 2026-07-01T12:00:00Z. The candidate
|
||||
// lands exactly on the inclusive boundary of `created >= cutoff`,
|
||||
// so it must be matched. Pairs with the just-past-the-window
|
||||
// test above to pin the boundary from both sides.
|
||||
await seedOrderWithItems(
|
||||
"order-edge-in",
|
||||
{ order_number: "ORD-A", company: "comp-1", created: "2026-07-01 12:00:00.000Z" },
|
||||
[{ description: "M16x50 Bolt", quantity: 300 }],
|
||||
);
|
||||
await seedOrderWithItems(
|
||||
"order-mine",
|
||||
{ order_number: "ORD-B", company: "comp-1", created: "2026-08-30 00:00:00.000Z" },
|
||||
[{ description: "M16x50 Bolt", quantity: 300 }],
|
||||
);
|
||||
|
||||
const result = await suggestOrderDuplicate("order-mine");
|
||||
|
||||
expect(result.duplicateOrderId).toBe("order-edge-in");
|
||||
const updated = await pbMock.collection("orders").getOne("order-mine");
|
||||
expect(updated.duplicate_order_matching_attempted).toBe(true);
|
||||
expect(updated.duplicate_order_match_method).toBe("exact_items_same_company");
|
||||
expect(updated.duplicate_order_match_confidence).toBe(1);
|
||||
expect(updated.potential_duplicate_order).toBe("order-edge-in");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue