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 { 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. */
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
* expose (service, docStatus, media, type) stay empty so Completed fails closed.
* Map standalone detail DTO onto closability. `problem` is the same service
* 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 {
const vendorName = wo.dispatches.find((dispatch) =>
@ -121,9 +131,9 @@ export function detailToClosabilityInput(wo: WorkOrderDetail): WoClosabilityInpu
tech: "",
completedDate: wo.completedDate,
woNumber: wo.workOrderNumber || wo.internalWONumber,
pm: "",
pm: wo.problem,
docStatus: "",
mediaCount: 0,
type: "",
type: detailSourceToWoType(wo.source),
};
}

View file

@ -161,12 +161,14 @@ describe("wizardDraftToClosabilityInput", () => {
});
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 = {
assignedTo: "Alice",
completedDate: "2026-07-15",
workOrderNumber: "20260623001",
internalWONumber: "SH-1",
problem: "HVAC PM",
source: "Add-On",
dispatches: [{ vendorName: "ACME Tech" }],
} as WorkOrderDetail;
@ -176,9 +178,28 @@ describe("detailToClosabilityInput", () => {
expect(input.tech).toBe("");
expect(input.completedDate).toBe("2026-07-15");
expect(input.woNumber).toBe("20260623001");
expect(input.pm).toBe("");
expect(getClosabilityGaps(input)).toContain("Service");
expect(input.pm).toBe("HVAC PM");
expect(input.type).toBe("Add-On");
expect(getClosabilityGaps(input)).not.toContain("Service");
expect(getClosabilityGaps(input)).not.toContain("Company");
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");
});
});