fix(work-orders): map detail problem and source into closability

detailToClosabilityInput now uses WorkOrderDetail.problem as pm and source as WO type so Done is not permanently blocked by a false Service gap.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
arthur.bassi 2026-08-10 19:11:35 -03:00
parent 55449a9e31
commit 8cd6d92ba7
2 changed files with 39 additions and 8 deletions

View file

@ -1,6 +1,10 @@
import type { WorkOrderDetail } from "@/domain/work-orders/types/work-order-detail"; import type { WorkOrderDetail } from "@/domain/work-orders/types/work-order-detail";
import type { CompletionDocStatus } from "@/domain/work-orders/types/work-order-table-row"; import type { CompletionDocStatus } from "@/domain/work-orders/types/work-order-table-row";
import type { WOType, WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard"; import {
WO_TYPES,
type WOType,
type WorkOrderWizardDraft,
} from "@/domain/work-orders/types/work-order-wizard";
/** Fields required to evaluate whether a WO may be marked Completed. */ /** Fields required to evaluate whether a WO may be marked Completed. */
export type WoClosabilityInput = { export type WoClosabilityInput = {
@ -106,9 +110,15 @@ export function wizardDraftToClosabilityInput(draft: WorkOrderWizardDraft): WoCl
}; };
} }
function detailSourceToWoType(source: string): WOType | "" {
return (WO_TYPES as readonly string[]).includes(source) ? (source as WOType) : "";
}
/** /**
* Map standalone detail DTO onto closability. Fields the detail route does not * Map standalone detail DTO onto closability. `problem` is the same service
* expose (service, docStatus, media, type) stay empty so Completed fails closed. * field the board maps into `pm`; `source` carries wizard WO type when set.
* docStatus/media stay empty (detail route does not expose them) so those
* type-specific gates still fail closed.
*/ */
export function detailToClosabilityInput(wo: WorkOrderDetail): WoClosabilityInput { export function detailToClosabilityInput(wo: WorkOrderDetail): WoClosabilityInput {
const vendorName = wo.dispatches.find((dispatch) => const vendorName = wo.dispatches.find((dispatch) =>
@ -121,9 +131,9 @@ export function detailToClosabilityInput(wo: WorkOrderDetail): WoClosabilityInpu
tech: "", tech: "",
completedDate: wo.completedDate, completedDate: wo.completedDate,
woNumber: wo.workOrderNumber || wo.internalWONumber, woNumber: wo.workOrderNumber || wo.internalWONumber,
pm: "", pm: wo.problem,
docStatus: "", docStatus: "",
mediaCount: 0, mediaCount: 0,
type: "", type: detailSourceToWoType(wo.source),
}; };
} }

View file

@ -161,12 +161,14 @@ describe("wizardDraftToClosabilityInput", () => {
}); });
describe("detailToClosabilityInput", () => { describe("detailToClosabilityInput", () => {
it("maps vendorName to company and fails closed on missing service/media fields", () => { it("maps problem/source and vendorName so Done is not permanently blocked", () => {
const detail = { const detail = {
assignedTo: "Alice", assignedTo: "Alice",
completedDate: "2026-07-15", completedDate: "2026-07-15",
workOrderNumber: "20260623001", workOrderNumber: "20260623001",
internalWONumber: "SH-1", internalWONumber: "SH-1",
problem: "HVAC PM",
source: "Add-On",
dispatches: [{ vendorName: "ACME Tech" }], dispatches: [{ vendorName: "ACME Tech" }],
} as WorkOrderDetail; } as WorkOrderDetail;
@ -176,9 +178,28 @@ describe("detailToClosabilityInput", () => {
expect(input.tech).toBe(""); expect(input.tech).toBe("");
expect(input.completedDate).toBe("2026-07-15"); expect(input.completedDate).toBe("2026-07-15");
expect(input.woNumber).toBe("20260623001"); expect(input.woNumber).toBe("20260623001");
expect(input.pm).toBe(""); expect(input.pm).toBe("HVAC PM");
expect(getClosabilityGaps(input)).toContain("Service"); expect(input.type).toBe("Add-On");
expect(getClosabilityGaps(input)).not.toContain("Service");
expect(getClosabilityGaps(input)).not.toContain("Company"); expect(getClosabilityGaps(input)).not.toContain("Company");
expect(getClosabilityGaps(input)).not.toContain("Technician"); expect(getClosabilityGaps(input)).not.toContain("Technician");
expect(getClosabilityGaps(input)).toEqual([]);
});
it("fails closed on missing service when problem is empty", () => {
const detail = {
assignedTo: "Alice",
completedDate: "2026-07-15",
workOrderNumber: "20260623001",
internalWONumber: "SH-1",
problem: "",
source: "Reactive",
dispatches: [{ vendorName: "ACME Tech" }],
} as WorkOrderDetail;
const input = detailToClosabilityInput(detail);
expect(input.pm).toBe("");
expect(input.type).toBe("Reactive");
expect(getClosabilityGaps(input)).toContain("Service");
}); });
}); });