import { describe, expect, it } from "vitest"; import type { WorkOrderDetail } from "@/domain/work-orders/types/work-order-detail"; import type { WoClosabilityInput } from "@/domain/work-orders/utils/wo-closability"; import { detailToClosabilityInput, formatClosabilityMissingTitle, getClosabilityGaps, isRealWoNumber, wizardDraftToClosabilityInput, } from "@/domain/work-orders/utils/wo-closability"; import { EMPTY_WIZARD_DRAFT } from "@/domain/work-orders/types/work-order-wizard"; function makeInput(overrides: Partial = {}): WoClosabilityInput { return { dispatcherId: "u1", dispatcherName: "Alice", company: "ACME", tech: "Bob", completedDate: "2026-07-15", woNumber: "20260623001", pm: "HVAC", docStatus: "Yes", mediaCount: 2, type: "PM", ...overrides, }; } describe("isRealWoNumber", () => { it("rejects empty and SH-prefixed placeholders", () => { expect(isRealWoNumber("")).toBe(false); expect(isRealWoNumber(" ")).toBe(false); expect(isRealWoNumber("SH-123")).toBe(false); expect(isRealWoNumber("sh999")).toBe(false); }); it("accepts non-SH numbers", () => { expect(isRealWoNumber("20260623001")).toBe(true); expect(isRealWoNumber(" 42 ")).toBe(true); }); }); describe("getClosabilityGaps", () => { it("returns empty when all requirements are met", () => { expect(getClosabilityGaps(makeInput())).toEqual([]); }); it("lists universal gaps", () => { expect( getClosabilityGaps( makeInput({ dispatcherId: "", dispatcherName: "", company: "", tech: "", completedDate: "", woNumber: "SH-1", pm: "", }), ), ).toEqual(["Dispatcher", "Company", "Completed Date", "WO number", "Service"]); }); it("treats dispatcherName alone as sufficient", () => { expect(getClosabilityGaps(makeInput({ dispatcherId: "", dispatcherName: "Alice" }))).toEqual( [], ); }); it("allows company without technician (SH-183)", () => { const gaps = getClosabilityGaps(makeInput({ company: "ACME", tech: "" })); expect(gaps).toEqual([]); expect(gaps).not.toContain("Technician"); expect(gaps).not.toContain("Company"); }); it("blocks missing company even when technician is set", () => { expect(getClosabilityGaps(makeInput({ company: "", tech: "Bob" }))).toEqual(["Company"]); expect(getClosabilityGaps(makeInput({ company: " ", tech: "Bob" }))).toEqual(["Company"]); }); it("requires completion document for PM and Overdue", () => { expect(getClosabilityGaps(makeInput({ type: "PM", docStatus: "No" }))).toEqual([ "Completion document", ]); expect(getClosabilityGaps(makeInput({ type: "Overdue", docStatus: "" }))).toEqual([ "Completion document", ]); expect(getClosabilityGaps(makeInput({ type: "PM", docStatus: "NN" }))).toEqual([]); expect(getClosabilityGaps(makeInput({ type: "Overdue", docStatus: "Yes" }))).toEqual([]); }); it("requires at least one photo for Emergency and Reactive", () => { expect( getClosabilityGaps(makeInput({ type: "Emergency", mediaCount: 0, docStatus: "No" })), ).toEqual(["At least 1 photo"]); expect(getClosabilityGaps(makeInput({ type: "Reactive", mediaCount: 0 }))).toEqual([ "At least 1 photo", ]); expect( getClosabilityGaps(makeInput({ type: "Emergency", mediaCount: 1, docStatus: "No" })), ).toEqual([]); }); it("blocks missing service on every type", () => { expect(getClosabilityGaps(makeInput({ type: "Reactive", pm: "", mediaCount: 1 }))).toEqual([ "Service", ]); expect(getClosabilityGaps(makeInput({ type: "PM", pm: " ", docStatus: "Yes" }))).toEqual([ "Service", ]); }); }); describe("formatClosabilityMissingTitle", () => { it("joins gaps for tooltip copy", () => { expect(formatClosabilityMissingTitle(["Dispatcher", "Service"])).toBe( "Missing: Dispatcher, Service", ); }); }); describe("wizardDraftToClosabilityInput", () => { it("maps vendorName to company and leaves tech empty", () => { const input = wizardDraftToClosabilityInput({ ...EMPTY_WIZARD_DRAFT, assigneeId: "u1", assigneeName: "Alice", vendorName: "ACME", woNumber: "20260623001", pm: "HVAC", type: "PM", }); expect(input).toEqual({ dispatcherId: "u1", dispatcherName: "Alice", company: "ACME", tech: "", completedDate: "", woNumber: "20260623001", pm: "HVAC", docStatus: "", mediaCount: 0, type: "PM", }); expect(getClosabilityGaps(input)).toContain("Completed Date"); expect(getClosabilityGaps(input)).toContain("Completion document"); expect(getClosabilityGaps(input)).not.toContain("Company"); expect(getClosabilityGaps(input)).not.toContain("Technician"); }); it("blocks Completed for a sparse create draft", () => { const gaps = getClosabilityGaps(wizardDraftToClosabilityInput(EMPTY_WIZARD_DRAFT)); expect(gaps.length).toBeGreaterThan(0); expect(gaps).toEqual( expect.arrayContaining(["Dispatcher", "Company", "Completed Date", "WO number", "Service"]), ); expect(gaps).not.toContain("Technician"); }); }); describe("detailToClosabilityInput", () => { 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; const input = detailToClosabilityInput(detail); expect(input.dispatcherName).toBe("Alice"); expect(input.company).toBe("ACME Tech"); expect(input.tech).toBe(""); expect(input.completedDate).toBe("2026-07-15"); expect(input.woNumber).toBe("20260623001"); 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"); }); });