fix(work-orders): fail closed on unknown uplift status

This commit is contained in:
arthur.bassi 2026-08-04 11:42:44 -03:00
parent ccfb3d9e6d
commit a3e9a20477
3 changed files with 37 additions and 8 deletions

View file

@ -25,6 +25,7 @@ function rowClosabilityGaps(wo: WorkOrderTableRow) {
return getClosabilityGaps({
dispatcherId: wo.dispatcherId,
dispatcherName: wo.dispatcherName,
company: wo.company,
tech: wo.tech,
completedDate: wo.completedDate ?? "",
woNumber: wo.woNumber,

View file

@ -8,7 +8,9 @@ import type { WOType, WorkOrderWizardDraft } from "@/domain/work-orders/types/wo
export type WoClosabilityInput = {
dispatcherId: string;
dispatcherName: string;
/** Vendor / technician assignment on the board row. */
/** Vendor/company assignment on the board row. */
company: string;
/** Technician name is informational and does not satisfy the vendor requirement. */
tech: string;
/** Work completed date (API CompletedDate). Empty until set. */
completedDate: string;
@ -22,6 +24,7 @@ export type WoClosabilityInput = {
};
export const PENDING_UPLIFT_GAP = "Pending uplift approval";
export const UPLIFT_STATUS_UNAVAILABLE_GAP = "Uplift status unavailable";
/** Design copy when Completed is blocked solely by a pending uplift. */
export const UPLIFT_PENDING_COMPLETED_TITLE =
@ -45,6 +48,12 @@ export function hasPendingUplift(
return (row.upliftSummary?.pendingCount ?? 0) > 0;
}
function hasKnownUpliftStatus(
row: Pick<WoClosabilityInput, "upliftSummary" | "hasPendingUplift">,
): boolean {
return typeof row.hasPendingUplift === "boolean" || row.upliftSummary != null;
}
function hasDispatcher(row: Pick<WoClosabilityInput, "dispatcherId" | "dispatcherName">): boolean {
return Boolean(row.dispatcherId.trim() || row.dispatcherName.trim());
}
@ -68,8 +77,8 @@ export function getClosabilityGaps(row: WoClosabilityInput): string[] {
if (!hasDispatcher(row)) {
gaps.push("Dispatcher");
}
if (!row.tech.trim()) {
gaps.push("Technician");
if (!row.company.trim()) {
gaps.push("Company / vendor");
}
if (!row.completedDate.trim()) {
gaps.push("Completed Date");
@ -81,7 +90,9 @@ export function getClosabilityGaps(row: WoClosabilityInput): string[] {
gaps.push("Service");
}
if (hasPendingUplift(row)) {
if (!hasKnownUpliftStatus(row)) {
gaps.push(UPLIFT_STATUS_UNAVAILABLE_GAP);
} else if (hasPendingUplift(row)) {
gaps.push(PENDING_UPLIFT_GAP);
}
@ -126,6 +137,7 @@ export function wizardDraftToClosabilityInput(draft: WorkOrderWizardDraft): WoCl
return {
dispatcherId: draft.assigneeId,
dispatcherName: draft.assigneeName,
company: draft.vendorName,
tech: draft.vendorName,
completedDate: "",
woNumber: draft.woNumber,

View file

@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import type { WoClosabilityInput } from "@/domain/work-orders/utils/wo-closability";
import {
PENDING_UPLIFT_GAP,
UPLIFT_STATUS_UNAVAILABLE_GAP,
UPLIFT_PENDING_COMPLETED_TITLE,
canGenerateCompDoc,
formatClosabilityMissingTitle,
@ -14,6 +15,7 @@ function makeInput(overrides: Partial<WoClosabilityInput> = {}): WoClosabilityIn
return {
dispatcherId: "u1",
dispatcherName: "Alex",
company: "Acme HVAC",
tech: "Sam",
completedDate: "2026-07-01",
woNumber: "1001",
@ -21,8 +23,7 @@ function makeInput(overrides: Partial<WoClosabilityInput> = {}): WoClosabilityIn
docStatus: "Yes",
mediaCount: 1,
type: "PM",
upliftSummary: undefined,
hasPendingUplift: undefined,
hasPendingUplift: false,
...overrides,
};
}
@ -47,7 +48,7 @@ describe("hasPendingUplift", () => {
).toBe(true);
});
it("is false when no uplift signals are present", () => {
it("is false when uplift is explicitly not pending", () => {
expect(hasPendingUplift(makeInput())).toBe(false);
expect(
hasPendingUplift(makeInput({ upliftSummary: { hasUplift: false, pendingCount: 0 } })),
@ -60,12 +61,21 @@ describe("getClosabilityGaps", () => {
expect(getClosabilityGaps(makeInput())).toEqual([]);
});
it("accepts a company without a technician", () => {
expect(getClosabilityGaps(makeInput({ tech: "" }))).toEqual([]);
});
it("does not let a technician satisfy a missing company requirement", () => {
expect(getClosabilityGaps(makeInput({ company: "" }))).toEqual(["Company / vendor"]);
});
it("lists field gaps before uplift and document gaps", () => {
expect(
getClosabilityGaps(
makeInput({
dispatcherId: "",
dispatcherName: "",
company: " ",
tech: " ",
completedDate: "",
woNumber: "SH-1",
@ -77,7 +87,7 @@ describe("getClosabilityGaps", () => {
),
).toEqual([
"Dispatcher",
"Technician",
"Company / vendor",
"Completed Date",
"WO number",
"Service",
@ -96,6 +106,12 @@ describe("getClosabilityGaps", () => {
expect(getClosabilityGaps(makeInput({ hasPendingUplift: true }))).toEqual([PENDING_UPLIFT_GAP]);
});
it("blocks Completed when the backend does not provide uplift status", () => {
expect(
getClosabilityGaps(makeInput({ hasPendingUplift: undefined, upliftSummary: undefined })),
).toEqual([UPLIFT_STATUS_UNAVAILABLE_GAP]);
});
it("requires a photo for Emergency / Reactive", () => {
expect(
getClosabilityGaps(makeInput({ type: "Emergency", mediaCount: 0, docStatus: "No" })),