From e3b8360a68a24d5daca93dd7f03c2ec4eaf8eae0 Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Wed, 19 Aug 2026 15:04:50 -0300 Subject: [PATCH] fix(work-orders): treat Canceled and Refused dispatches as inactive (SH-183) Backend returns Cancelled, Canceled, and Refused; only Cancelled was skipped, so Complete stayed enabled with no live vendor. --- .../work-orders/utils/wo-closability.ts | 11 ++++-- .../work-orders/utils/wo-closability.test.ts | 36 +++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/domain/work-orders/utils/wo-closability.ts b/src/domain/work-orders/utils/wo-closability.ts index ab9f9494..822f23d5 100644 --- a/src/domain/work-orders/utils/wo-closability.ts +++ b/src/domain/work-orders/utils/wo-closability.ts @@ -157,12 +157,19 @@ export function wizardDraftToClosabilityInput(draft: WorkOrderWizardDraft): WoCl }; } -/** Current vendor assignment — cancelled dispatches do not count as company (SH-183). */ +/** Backend terminal-cancel spellings; dispatch status is a raw API string. */ +const INACTIVE_DISPATCH_STATUSES = new Set(["cancelled", "canceled", "refused"]); + +function isInactiveDispatchStatus(status: string | undefined): boolean { + return INACTIVE_DISPATCH_STATUSES.has((status ?? "").trim().toLowerCase()); +} + +/** Current vendor assignment — cancelled/refused dispatches do not count as company (SH-183). */ function currentAssignedDispatch( dispatches: WorkOrderDispatchSummary[], ): WorkOrderDispatchSummary | undefined { return dispatches.find((dispatch) => { - if (dispatch.status === "Cancelled") { + if (isInactiveDispatchStatus(dispatch.status)) { return false; } const vendorId = String(dispatch.vendorId ?? "").trim(); diff --git a/src/test/domain/work-orders/utils/wo-closability.test.ts b/src/test/domain/work-orders/utils/wo-closability.test.ts index 9aa2c15e..c50b27fb 100644 --- a/src/test/domain/work-orders/utils/wo-closability.test.ts +++ b/src/test/domain/work-orders/utils/wo-closability.test.ts @@ -327,8 +327,30 @@ describe("detailToClosabilityInput", () => { expect(getClosabilityGaps(input)).toEqual([]); }); - it("ignores cancelled dispatches when resolving assigned company (SH-183)", () => { - const cancelledOnly = { + it.each(["Cancelled", "Canceled", "Refused", "CANCELLED", " canceled "])( + "ignores inactive dispatch status %s when resolving assigned company (SH-183)", + (status) => { + const inactiveOnly = { + assignedTo: "Alice", + completedDate: "2026-07-15", + workOrderNumber: "20260623001", + internalWONumber: "SH-1", + problem: "HVAC PM", + source: "procurement-integration", + type: "", + docStatus: "", + mediaCount: 0, + dispatches: [{ vendorName: "Old Co", vendorId: 45, status }], + } as WorkOrderDetail; + + expect(detailToClosabilityInput(inactiveOnly).vendorId).toBe(""); + expect(detailToClosabilityInput(inactiveOnly).company).toBe(""); + expect(getClosabilityGaps(detailToClosabilityInput(inactiveOnly))).toContain("Company"); + }, + ); + + it("uses the active dispatch after an inactive cancelled spelling (SH-183)", () => { + const supersededThenCurrent = { assignedTo: "Alice", completedDate: "2026-07-15", workOrderNumber: "20260623001", @@ -338,16 +360,8 @@ describe("detailToClosabilityInput", () => { type: "", docStatus: "", mediaCount: 0, - dispatches: [{ vendorName: "", vendorId: 45, status: "Cancelled" }], - } as WorkOrderDetail; - - expect(detailToClosabilityInput(cancelledOnly).vendorId).toBe(""); - expect(getClosabilityGaps(detailToClosabilityInput(cancelledOnly))).toContain("Company"); - - const supersededThenCurrent = { - ...cancelledOnly, dispatches: [ - { vendorName: "Old Co", vendorId: 1, status: "Cancelled" }, + { vendorName: "Old Co", vendorId: 1, status: "Canceled" }, { vendorName: "ACME Tech", vendorId: 45, status: "Sent" }, ], } as WorkOrderDetail;