fix(dn): a combined line saves even when the supplier rule cannot be remembered

Co-Authored-By: MiniMax (Pinchy)
This commit is contained in:
DEls 2026-10-26 19:15:44 +02:00
parent 3ca3021d33
commit 9db3b2fb6c
2 changed files with 38 additions and 10 deletions

View file

@ -5,6 +5,8 @@ import type { LineEdit } from "./line-rows";
const MESH_ITEM = "meshitem1234567";
const BAILING_ITEM = "bailingitem12345";
/** An order line the slip sells in metres. */
const KERB_ITEM = "kerbitem12345678";
/** An order line from another purchase order. */
const FOREIGN_ITEM = "foreignitem12345";
@ -15,6 +17,7 @@ const context = (): PreviewContext => ({
po: { id: "poabcdefghijklm", purchase_order_number: "MEGCPO9448", total_value: 11748.7 },
poItems: [
{ id: MESH_ITEM, description: "MESH DIAMOND 600 x 2.5 ROLL", unit: "m", qty: 2, rate: 1124.35, disc_percent: 0, vat_percent: 15 },
{ id: KERB_ITEM, description: "KERB 150", unit: "m", qty: 100, rate: 150, disc_percent: 0, vat_percent: 15 },
{ id: BAILING_ITEM, description: "1.6mm Bailing Wire", unit: "ea", qty: 100, rate: 95, disc_percent: 0, vat_percent: 15 },
],
lines: [
@ -85,8 +88,25 @@ describe("saveLineEdits", () => {
expect(rules.some((rule) => rule.receiving_match?.factor === 30)).toBe(true);
});
it("saves nothing when one edit in the batch fails", () => {
it("keeps a combined line even when the supplier rule cannot be remembered", () => {
const c = context();
// Two kerb lines on one order line, sold in metres: there is no single
// product to scope a supplier rule to, so the rule is skipped — but the
// edit itself has to stand.
const result = saveLineEdits(c, preview(c), [edit({
lines: [1, 2],
poItemId: KERB_ITEM,
slipQty: 4,
slipUnit: "ea",
factor: 15,
reason: "One kerb is 15 m.",
})], "operator");
expect(result.remembered).toBeNull();
expect(result.preview.allocations.some((allocation) =>
allocation.purchaseOrderItemId === KERB_ITEM && allocation.sourceLineIds.length === 2)).toBe(true);
});
it("saves nothing when one edit in the batch fails", () => { const c = context();
const before = preview(c);
const untouched = structuredClone(before);
expect(() => saveLineEdits(c, before, [

View file

@ -101,15 +101,23 @@ export function saveLineEdits(
const item = edit.poItemId ? working.poItems.find((candidate) => candidate.id === edit.poItemId) : null;
const targetUnit = item && typeof item.unit === "string" ? item.unit : null;
if (targetUnit && edit.factor !== null && edit.factor !== 1 && edit.slipUnit.trim().toLowerCase() !== targetUnit.trim().toLowerCase()) {
const guidance = rememberConnection(
{ ...working, supplier: { ...working.supplier, reconciliation_rules: remembered ?? working.supplier.reconciliation_rules } },
current,
current.allocations.findIndex((allocation) => sameLines(allocation.sourceLineIds, sourceLineIds)),
actor,
);
remembered = guidance;
working = { ...working, supplier: { ...working.supplier, reconciliation_rules: guidance } };
current = { ...current, snapshotHash: snapshotHash(working) };
try {
const guidance = rememberConnection(
{ ...working, supplier: { ...working.supplier, reconciliation_rules: remembered ?? working.supplier.reconciliation_rules } },
current,
current.allocations.findIndex((allocation) => sameLines(allocation.sourceLineIds, sourceLineIds)),
actor,
);
remembered = guidance;
working = { ...working, supplier: { ...working.supplier, reconciliation_rules: guidance } };
current = { ...current, snapshotHash: snapshotHash(working) };
} catch (error) {
// A rule that cannot be written must not lose the edit: a combined
// line has no single product to scope the rule to. The line still
// books and the office still sees it — the supplier rule is simply
// not remembered this time.
if (!(error instanceof Error) || !/can be remembered/i.test(error.message)) throw error;
}
}
}