shoc-frontend-new/src/test/domain/work-orders/utils/wo-closability.test.ts
arthur.bassi 8cd6d92ba7 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>
2026-08-10 19:11:35 -03:00

205 lines
6.7 KiB
TypeScript

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> = {}): 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");
});
});