mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 00:23:13 +00:00
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>
205 lines
6.7 KiB
TypeScript
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");
|
|
});
|
|
});
|