fix(work-orders): ignore cancelled dispatches in detail closability (SH-183)

This commit is contained in:
Arthur Bassi 2026-08-19 12:02:17 -03:00
parent 1554cf30d0
commit a43de20ed4
2 changed files with 62 additions and 4 deletions

View file

@ -1,3 +1,4 @@
import type { WorkOrderDispatchSummary } from "@/domain/work-orders/types/dispatch";
import type { WorkOrderDetail } from "@/domain/work-orders/types/work-order-detail";
import type {
CompletionDocStatus,
@ -156,16 +157,26 @@ export function wizardDraftToClosabilityInput(draft: WorkOrderWizardDraft): WoCl
};
}
/** Current vendor assignment — cancelled dispatches do not count as company (SH-183). */
function currentAssignedDispatch(
dispatches: WorkOrderDispatchSummary[],
): WorkOrderDispatchSummary | undefined {
return dispatches.find((dispatch) => {
if (dispatch.status === "Cancelled") {
return false;
}
const vendorId = String(dispatch.vendorId ?? "").trim();
return Boolean((dispatch.vendorName ?? "").trim() || vendorId);
});
}
/**
* Map standalone detail DTO onto closability. Uses authoritative `type` / `docStatus` /
* `mediaCount` (enriched from board detail on the view route). Never treats `source`
* as WorkOrderType. `problem` is the same service field the board maps into `pm`.
*/
export function detailToClosabilityInput(wo: WorkOrderDetail): WoClosabilityInput {
const assigned = wo.dispatches.find((dispatch) => {
const vendorId = String(dispatch.vendorId ?? "").trim();
return Boolean(dispatch.vendorName.trim() || vendorId);
});
const assigned = currentAssignedDispatch(wo.dispatches);
return {
dispatcherId: "",
dispatcherName: wo.assignedTo,

View file

@ -96,6 +96,22 @@ describe("getClosabilityGaps", () => {
]);
});
it.each(["PM", "Reactive", "Emergency", "Overdue", ""] as const)(
"blocks Completed without a company for type %s (SH-183)",
(type) => {
const gaps = getClosabilityGaps(
makeInput({
company: "",
vendorId: "",
type,
docStatus: "Yes",
mediaCount: 1,
}),
);
expect(gaps).toContain("Company");
},
);
it("lists field gaps before uplift and document gaps", () => {
expect(
getClosabilityGaps(
@ -310,6 +326,37 @@ describe("detailToClosabilityInput", () => {
expect(input.vendorId).toBe("45");
expect(getClosabilityGaps(input)).toEqual([]);
});
it("ignores cancelled dispatches when resolving assigned company (SH-183)", () => {
const cancelledOnly = {
assignedTo: "Alice",
completedDate: "2026-07-15",
workOrderNumber: "20260623001",
internalWONumber: "SH-1",
problem: "HVAC PM",
source: "procurement-integration",
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: "ACME Tech", vendorId: 45, status: "Sent" },
],
} as WorkOrderDetail;
const input = detailToClosabilityInput(supersededThenCurrent);
expect(input.company).toBe("ACME Tech");
expect(input.vendorId).toBe("45");
expect(getClosabilityGaps(input)).toEqual([]);
});
});
describe("readDetailWoType", () => {