{WO_TYPES.map((t) => (
diff --git a/src/app/(protected)/workorders/_components/list/table/save-existing-row-type.ts b/src/app/(protected)/workorders/_components/list/table/save-existing-row-type.ts
new file mode 100644
index 00000000..6af21133
--- /dev/null
+++ b/src/app/(protected)/workorders/_components/list/table/save-existing-row-type.ts
@@ -0,0 +1,33 @@
+import { toast } from "react-toastify";
+import type { WorkOrderSeverity } from "@/domain/work-orders/types/work-order-severity";
+import type {
+ WorkOrderTablePatch,
+ WorkOrderTableRow,
+} from "@/domain/work-orders/types/work-order-table-row";
+import type { WOType } from "@/domain/work-orders/types/work-order-wizard";
+import {
+ formatSeverityEscalationToast,
+ shouldAnnounceSeverityEscalation,
+} from "@/domain/work-orders/utils/severity-escalation";
+
+type PersistPatchFn = (
+ id: string | number,
+ patch: WorkOrderTablePatch,
+ options?: { onSuccess?: () => void },
+) => void;
+
+export function saveExistingRowType(
+ onPatch: PersistPatchFn,
+ row: Pick
,
+ type: WOType,
+ severity: WorkOrderSeverity | null,
+) {
+ const patch = { type, severity };
+ if (!shouldAnnounceSeverityEscalation(row.severity, severity)) {
+ onPatch(row.id, patch);
+ return;
+ }
+ onPatch(row.id, patch, {
+ onSuccess: () => toast.warn(formatSeverityEscalationToast(row.woNumber, severity)),
+ });
+}
diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx
index 8d04a227..90b16533 100644
--- a/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx
+++ b/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx
@@ -2,6 +2,8 @@ import type { CSSProperties } from "react";
import type { LocationOption, WorkOrderUser } from "@/domain/work-orders/types/work-order";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import { applyAssignedToTableSave } from "@/domain/work-orders/utils/work-order-feedback-toasts";
+import { isWorkOrderCompletionSurfaceReadOnly } from "@/domain/work-orders/utils/work-order-lock";
+import { saveExistingRowType } from "./save-existing-row-type";
import { AssignedToCell } from "./cells/assigned-to-cell";
import { DateCell } from "./cells/date-cell";
import { DueDateCell } from "./cells/due-date-cell";
@@ -103,7 +105,8 @@ export function WoTableRowCells({
value={row.type}
severity={row.severity}
q={search}
- onSave={(t, sev) => onPatchRow({ type: t, severity: sev })}
+ viewOnly={isWorkOrderCompletionSurfaceReadOnly(row.status)}
+ onSave={(t, sev) => saveExistingRowType(handlers.onPatch, row, t, sev)}
/>
string | null;
+function formatSeverityAuditLabel(raw: string): string {
+ const n = Number(raw.trim());
+ if (n === 1 || n === 2 || n === 3 || n === 4 || n === 5) {
+ return SEVERITY_LEVELS[n].label;
+ }
+ const sevMatch = raw.trim().match(/^sev\s*([1-5])$/i);
+ if (sevMatch) {
+ return `SEV ${sevMatch[1]}`;
+ }
+ return raw;
+}
+
+function tryTypeRule({ fieldKey, actionKey, newValue }: AuditActionRuleContext): string | null {
+ const matches =
+ fieldKey === "type" ||
+ fieldKey === "workordertype" ||
+ fieldKey === "wotype" ||
+ actionKey === "typechanged" ||
+ actionKey.includes("workordertype");
+ if (!matches || !newValue.text) return null;
+
+ return `Changed Type to ${wrapAuditValue(newValue)}`;
+}
+
+function trySeverityRule({ fieldKey, actionKey, newValue }: AuditActionRuleContext): string | null {
+ const matches = fieldKey.includes("severity") || actionKey.includes("severity");
+ if (!matches || !newValue.text) return null;
+
+ const labeled = { text: formatSeverityAuditLabel(newValue.text), isQuotedList: false };
+ return `Changed Severity to ${wrapAuditValue(labeled)}`;
+}
+
function tryStatusRule({
fieldKey,
actionKey,
@@ -127,6 +160,8 @@ function tryAssignedToRule({
/** Applied in order; the first non-null result wins. */
export const BOARD_AUDIT_ACTION_RULES: AuditActionRule[] = [
tryStatusRule,
+ tryTypeRule,
+ trySeverityRule,
tryApptRule,
tryScheduleRule,
tryCarriedOverRule,
diff --git a/src/domain/work-orders/utils/severity-escalation.ts b/src/domain/work-orders/utils/severity-escalation.ts
new file mode 100644
index 00000000..6bf4f243
--- /dev/null
+++ b/src/domain/work-orders/utils/severity-escalation.ts
@@ -0,0 +1,20 @@
+import {
+ SEVERITY_LEVELS,
+ type WorkOrderSeverity,
+} from "@/domain/work-orders/types/work-order-severity";
+
+function isCriticalSeverity(severity: WorkOrderSeverity | null | undefined): severity is 1 | 2 {
+ return severity === 1 || severity === 2;
+}
+
+/** AAP: toast only when severity newly enters SEV 1/2 — not on re-save of the same value. */
+export function shouldAnnounceSeverityEscalation(
+ previous: WorkOrderSeverity | null | undefined,
+ next: WorkOrderSeverity | null | undefined,
+): next is 1 | 2 {
+ return isCriticalSeverity(next) && !isCriticalSeverity(previous);
+}
+
+export function formatSeverityEscalationToast(woNumber: string, severity: 1 | 2): string {
+ return `WO #${woNumber} escalated to ${SEVERITY_LEVELS[severity].label}.`;
+}
diff --git a/src/test/app/(protected)/workorders/save-existing-row-type.test.ts b/src/test/app/(protected)/workorders/save-existing-row-type.test.ts
new file mode 100644
index 00000000..0ca3f3c9
--- /dev/null
+++ b/src/test/app/(protected)/workorders/save-existing-row-type.test.ts
@@ -0,0 +1,54 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const toastWarn = vi.fn();
+
+vi.mock("react-toastify", () => ({
+ toast: { warn: (...args: unknown[]) => toastWarn(...args) },
+}));
+
+import { saveExistingRowType } from "@/app/(protected)/workorders/_components/list/table/save-existing-row-type";
+
+const row = { id: 7, woNumber: "00000024965", severity: 3 as const };
+
+describe("saveExistingRowType", () => {
+ beforeEach(() => {
+ toastWarn.mockReset();
+ });
+
+ it("warns when severity newly enters SEV 1 only after persist onSuccess", () => {
+ const onPatch = vi.fn();
+
+ saveExistingRowType(onPatch, row, "Emergency", 1);
+
+ expect(toastWarn).not.toHaveBeenCalled();
+ expect(onPatch).toHaveBeenCalledWith(
+ 7,
+ { type: "Emergency", severity: 1 },
+ expect.objectContaining({ onSuccess: expect.any(Function) }),
+ );
+
+ const { onSuccess } = onPatch.mock.calls[0]![2] as { onSuccess: () => void };
+ onSuccess();
+ expect(toastWarn).toHaveBeenCalledWith("WO #00000024965 escalated to SEV 1.");
+ });
+
+ it("does not warn when staying on a critical severity", () => {
+ const onPatch = vi.fn();
+
+ saveExistingRowType(onPatch, { ...row, severity: 1 }, "Emergency", 1);
+
+ expect(toastWarn).not.toHaveBeenCalled();
+ expect(onPatch).toHaveBeenCalledWith(7, { type: "Emergency", severity: 1 });
+ expect(onPatch.mock.calls[0]![2]).toBeUndefined();
+ });
+
+ it("does not warn when clearing severity on a PM correction", () => {
+ const onPatch = vi.fn();
+
+ saveExistingRowType(onPatch, { ...row, severity: 2 }, "PM", null);
+
+ expect(toastWarn).not.toHaveBeenCalled();
+ expect(onPatch).toHaveBeenCalledWith(7, { type: "PM", severity: null });
+ expect(onPatch.mock.calls[0]![2]).toBeUndefined();
+ });
+});
diff --git a/src/test/app/(protected)/workorders/type-cell.test.tsx b/src/test/app/(protected)/workorders/type-cell.test.tsx
new file mode 100644
index 00000000..f7844681
--- /dev/null
+++ b/src/test/app/(protected)/workorders/type-cell.test.tsx
@@ -0,0 +1,113 @@
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+import { TypeCell } from "@/app/(protected)/workorders/_components/list/table/cells/type-cell";
+
+describe("TypeCell", () => {
+ it("shows the AAP empty placeholder", () => {
+ render();
+ expect(screen.getByRole("button", { name: "Select type of WO" })).toBeInTheDocument();
+ });
+
+ it("lists PM, Reactive, Emergency, and Overdue in the popover", async () => {
+ const user = userEvent.setup();
+ render();
+
+ await user.click(screen.getByRole("button", { name: "Type of WO, PM" }));
+
+ expect(screen.getByRole("button", { name: /^Reactive$/ })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /^Emergency$/ })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /^Overdue$/ })).toBeInTheDocument();
+ });
+
+ it("saves PM from the popover without opening the modal", async () => {
+ const onSave = vi.fn();
+ const user = userEvent.setup();
+ render();
+
+ await user.click(screen.getByRole("button", { name: "Type of WO, Emergency" }));
+ await user.click(screen.getByRole("button", { name: /^PM$/ }));
+
+ expect(onSave).toHaveBeenCalledWith("PM", null);
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ });
+
+ it("opens Type & severity when changing PM to Emergency and saves after SEV", async () => {
+ const onSave = vi.fn();
+ const user = userEvent.setup();
+ render();
+
+ await user.click(screen.getByRole("button", { name: "Type of WO, PM" }));
+ await user.click(screen.getByRole("button", { name: /^Emergency$/ }));
+
+ expect(onSave).not.toHaveBeenCalled();
+ expect(screen.getByRole("dialog", { name: /Type & severity/i })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: "Close" })).toBeInTheDocument();
+
+ await user.click(screen.getByRole("button", { name: /SEV 1/ }));
+ await user.click(screen.getByRole("button", { name: /^Save$/ }));
+
+ expect(onSave).toHaveBeenCalledWith("Emergency", 1);
+ });
+
+ it("does not save when Cancel is clicked on the modal", async () => {
+ const onSave = vi.fn();
+ const user = userEvent.setup();
+ render();
+
+ await user.click(screen.getByRole("button", { name: "Type of WO, PM" }));
+ await user.click(screen.getByRole("button", { name: /^Emergency$/ }));
+ await user.click(screen.getByRole("button", { name: /^Cancel$/ }));
+
+ expect(onSave).not.toHaveBeenCalled();
+ await waitFor(() => {
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ });
+ });
+
+ it("clears previous severity when switching Emergency to Reactive", async () => {
+ const onSave = vi.fn();
+ const user = userEvent.setup();
+ render();
+
+ await user.click(screen.getByRole("button", { name: "Type of WO, Emergency" }));
+ await user.click(screen.getByRole("button", { name: /^Reactive$/ }));
+
+ expect(onSave).not.toHaveBeenCalled();
+ const save = screen.getByRole("button", { name: /^Save$/ });
+ expect(save).toBeDisabled();
+
+ await user.click(screen.getByRole("button", { name: /SEV 4/ }));
+ await user.click(save);
+
+ expect(onSave).toHaveBeenCalledWith("Reactive", 4);
+ });
+
+ it("does not open the picker or severity modal when viewOnly", async () => {
+ const onSave = vi.fn();
+ const user = userEvent.setup();
+ render();
+
+ expect(screen.queryByRole("button", { name: "Type of WO, Emergency" })).not.toBeInTheDocument();
+ expect(screen.getByText("Emergency")).toBeInTheDocument();
+ expect(screen.getByText("SEV 2")).toBeInTheDocument();
+
+ await user.click(screen.getByText("Emergency"));
+ await user.click(screen.getByText("SEV 2"));
+
+ expect(screen.queryByRole("button", { name: /^Reactive$/ })).not.toBeInTheDocument();
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ expect(onSave).not.toHaveBeenCalled();
+ });
+
+ it("opens the modal from the severity badge", async () => {
+ const onSave = vi.fn();
+ const user = userEvent.setup();
+ render();
+
+ await user.click(screen.getByText("SEV 2"));
+
+ expect(screen.getByRole("dialog", { name: /Type & severity/i })).toBeInTheDocument();
+ expect(onSave).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/test/app/(protected)/workorders/wo-table-row-cells-type-toast.test.tsx b/src/test/app/(protected)/workorders/wo-table-row-cells-type-toast.test.tsx
new file mode 100644
index 00000000..b98743e2
--- /dev/null
+++ b/src/test/app/(protected)/workorders/wo-table-row-cells-type-toast.test.tsx
@@ -0,0 +1,69 @@
+import { fireEvent, screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+import { WoTableRowCells } from "@/app/(protected)/workorders/_components/list/table/wo-table-row-cells";
+import type { WoTableRowHandlers } from "@/app/(protected)/workorders/_components/list/table/wo-table-row";
+import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
+import { renderWithProviders } from "@/test/test-utils";
+
+vi.mock("@/app/(protected)/workorders/_components/list/table/cells/site-cell", () => ({
+ SiteCell: () => null,
+}));
+vi.mock(
+ "@/app/(protected)/workorders/_components/list/table/cells/editable-wo-number-cell",
+ () => ({
+ EditableWONumberCell: () => null,
+ }),
+);
+vi.mock("@/app/(protected)/workorders/_components/list/table/cells/type-cell", () => ({
+ TypeCell: ({ onSave }: { onSave: (type: string, severity: number) => void }) => (
+
+ ),
+}));
+vi.mock("@/app/(protected)/workorders/_components/list/table/cells/date-cell", () => ({
+ DateCell: () => null,
+}));
+vi.mock("@/app/(protected)/workorders/_components/list/table/cells/due-date-cell", () => ({
+ DueDateCell: () => null,
+}));
+vi.mock("@/app/(protected)/workorders/_components/list/table/cells/assigned-to-cell", () => ({
+ AssignedToCell: () => null,
+}));
+
+const row = { id: 7, woNumber: "00000024965", severity: 3 } as WorkOrderTableRow;
+
+describe("WoTableRowCells type persist", () => {
+ it("forwards type save through patch onSuccess instead of onPatchRow", () => {
+ const onPatch = vi.fn();
+ const onPatchRow = vi.fn();
+ const handlers = { onPatch } as unknown as WoTableRowHandlers;
+
+ renderWithProviders(
+ ,
+ { withAuth: false },
+ );
+
+ fireEvent.click(screen.getByRole("button", { name: "persist-type" }));
+
+ expect(onPatchRow).not.toHaveBeenCalled();
+ expect(onPatch).toHaveBeenCalledWith(
+ 7,
+ { type: "Emergency", severity: 1 },
+ expect.objectContaining({ onSuccess: expect.any(Function) }),
+ );
+ });
+});
diff --git a/src/test/app/(protected)/workorders/wo-table-row-completed-lock.test.tsx b/src/test/app/(protected)/workorders/wo-table-row-completed-lock.test.tsx
index 3bbed85f..1ce5a477 100644
--- a/src/test/app/(protected)/workorders/wo-table-row-completed-lock.test.tsx
+++ b/src/test/app/(protected)/workorders/wo-table-row-completed-lock.test.tsx
@@ -134,4 +134,56 @@ describe("WoTableRow completed selective lock", () => {
expect(screen.queryByRole("button", { name: /in progress/i })).not.toBeInTheDocument();
expect(handlers.onPatch).not.toHaveBeenCalled();
});
+
+ it("does not expose the type picker on a Completed row", () => {
+ const row = makeRow();
+ renderWithProviders(
+ ,
+ { withAuth: false },
+ );
+
+ expect(screen.queryByRole("button", { name: "Type of WO, PM" })).not.toBeInTheDocument();
+ expect(screen.getByText("PM")).toBeInTheDocument();
+ });
+
+ it("does not expose the type picker on a Canceled row", () => {
+ const row = makeRow({ status: "Canceled" });
+ renderWithProviders(
+ ,
+ { withAuth: false },
+ );
+
+ expect(screen.queryByRole("button", { name: "Type of WO, PM" })).not.toBeInTheDocument();
+ expect(screen.getByText("PM")).toBeInTheDocument();
+ });
});
diff --git a/src/test/domain/work-orders/use-cases/use-work-order-table-mutations.test.tsx b/src/test/domain/work-orders/use-cases/use-work-order-table-mutations.test.tsx
index 514f8991..d3e1095d 100644
--- a/src/test/domain/work-orders/use-cases/use-work-order-table-mutations.test.tsx
+++ b/src/test/domain/work-orders/use-cases/use-work-order-table-mutations.test.tsx
@@ -23,6 +23,7 @@ vi.mock("react-toastify", () => ({
toast: { error: vi.fn(), success: vi.fn(), warning: vi.fn() },
}));
+import { BoardPatchConflictError } from "@/domain/work-orders/errors/board-patch-errors";
import { mapWorkOrderTableRow } from "@/domain/work-orders/mappers/work-order-table-row-mapper";
import {
applySchedulePatch,
@@ -692,6 +693,113 @@ describe("useWorkOrderTableMutations apptTime clear", () => {
});
});
+describe("useWorkOrderTableMutations type and severity", () => {
+ let rowState: WorkOrderTableRow;
+
+ beforeEach(() => {
+ getById.mockReset();
+ update.mockReset();
+ patchBoardField.mockReset();
+ rowState = { ...BASE_ROW, type: "PM", severity: null };
+ });
+
+ it("patches severity before workOrderType and merges the API row", async () => {
+ const fields: string[] = [];
+ patchBoardField.mockImplementation(
+ async (
+ _id: unknown,
+ payload: { field: string; value?: string | null; workOrderVersion: string },
+ ) => {
+ fields.push(payload.field);
+ if (payload.field === "severity") {
+ rowState = {
+ ...rowState,
+ severity: payload.value ? (Number(payload.value) as 1) : null,
+ rowVersion: `${payload.workOrderVersion}-sev`,
+ };
+ }
+ if (payload.field === "workOrderType") {
+ rowState = {
+ ...rowState,
+ type: (payload.value as WorkOrderTableRow["type"]) || rowState.type,
+ rowVersion: `${payload.workOrderVersion}-type`,
+ };
+ }
+ return { ...rowState };
+ },
+ );
+
+ const onPatch = vi.fn();
+ const { result } = renderHook(
+ () =>
+ useWorkOrderTableMutations({
+ onPatch,
+ clearPatch: () => undefined,
+ getRow: () => rowState,
+ }),
+ { wrapper: makeWrapper() },
+ );
+
+ act(() => {
+ result.current.patchField(42, { type: "Emergency", severity: 1 });
+ });
+
+ await waitFor(() => expect(fields).toEqual(["severity", "workOrderType"]));
+ expect(onPatch).toHaveBeenCalledWith(
+ 42,
+ expect.objectContaining({ type: "Emergency", severity: 1 }),
+ );
+ });
+
+ it("applies currentState on 409 conflict instead of clearing", async () => {
+ const currentState = { ...BASE_ROW, type: "PM" as const, severity: null };
+ patchBoardField.mockRejectedValue(new BoardPatchConflictError("stale", currentState));
+
+ const onPatch = vi.fn();
+ const clearPatch = vi.fn();
+ const { result } = renderHook(
+ () =>
+ useWorkOrderTableMutations({
+ onPatch,
+ clearPatch,
+ getRow: () => rowState,
+ }),
+ { wrapper: makeWrapper() },
+ );
+
+ act(() => {
+ result.current.patchField(42, { type: "Emergency", severity: 1 });
+ });
+
+ await waitFor(() =>
+ expect(onPatch).toHaveBeenCalledWith(42, expect.objectContaining({ type: "PM" })),
+ );
+ expect(clearPatch).not.toHaveBeenCalled();
+ });
+
+ it("rolls back with clearPatch on a generic type patch error", async () => {
+ patchBoardField.mockRejectedValue(new Error("network"));
+
+ const onPatch = vi.fn();
+ const clearPatch = vi.fn();
+ const { result } = renderHook(
+ () =>
+ useWorkOrderTableMutations({
+ onPatch,
+ clearPatch,
+ getRow: () => rowState,
+ }),
+ { wrapper: makeWrapper() },
+ );
+
+ act(() => {
+ result.current.patchField(42, { type: "Emergency", severity: 1 });
+ });
+
+ await waitFor(() => expect(clearPatch).toHaveBeenCalledWith(42));
+ });
+});
+
describe("useWorkOrderTableMutations audit cache refresh", () => {
it("invalidates work-order queries after a successful status transition", async () => {
const client = new QueryClient({
diff --git a/src/test/domain/work-orders/utils/format-board-audit-action.test.ts b/src/test/domain/work-orders/utils/format-board-audit-action.test.ts
index 3ba0ad1b..8c9ff415 100644
--- a/src/test/domain/work-orders/utils/format-board-audit-action.test.ts
+++ b/src/test/domain/work-orders/utils/format-board-audit-action.test.ts
@@ -89,6 +89,28 @@ describe("formatBoardAuditAction", () => {
).toBe("WO marked as Past Due. Schedule On has passed without completion.");
});
+ it("formats type correction as Changed Type to (AAP, new value only)", () => {
+ expect(
+ formatBoardAuditAction({
+ action: "FieldPatched",
+ fieldName: "WorkOrderType",
+ oldValue: "PM",
+ newValue: "Emergency",
+ }),
+ ).toBe('Changed Type to "Emergency"');
+ });
+
+ it("formats severity as Changed Severity to SEV N", () => {
+ expect(
+ formatBoardAuditAction({
+ action: "FieldPatched",
+ fieldName: "Severity",
+ oldValue: "3",
+ newValue: "1",
+ }),
+ ).toBe('Changed Severity to "SEV 1"');
+ });
+
it("falls back to from → to for unknown fields", () => {
expect(
formatBoardAuditAction({
diff --git a/src/test/domain/work-orders/utils/severity-escalation.test.ts b/src/test/domain/work-orders/utils/severity-escalation.test.ts
new file mode 100644
index 00000000..812e40f9
--- /dev/null
+++ b/src/test/domain/work-orders/utils/severity-escalation.test.ts
@@ -0,0 +1,32 @@
+import { describe, expect, it } from "vitest";
+import {
+ formatSeverityEscalationToast,
+ shouldAnnounceSeverityEscalation,
+} from "@/domain/work-orders/utils/severity-escalation";
+
+describe("shouldAnnounceSeverityEscalation", () => {
+ it("announces when severity newly enters SEV 1", () => {
+ expect(shouldAnnounceSeverityEscalation(3, 1)).toBe(true);
+ expect(shouldAnnounceSeverityEscalation(null, 1)).toBe(true);
+ });
+
+ it("does not announce a re-save of the same critical severity", () => {
+ expect(shouldAnnounceSeverityEscalation(1, 1)).toBe(false);
+ });
+
+ it("does not announce a de-escalation out of SEV 1/2", () => {
+ expect(shouldAnnounceSeverityEscalation(2, 3)).toBe(false);
+ });
+
+ it("does not announce SEV 2 to SEV 1 (already critical)", () => {
+ expect(shouldAnnounceSeverityEscalation(2, 1)).toBe(false);
+ });
+});
+
+describe("formatSeverityEscalationToast", () => {
+ it("matches the AAP warning copy", () => {
+ expect(formatSeverityEscalationToast("00000024965", 1)).toBe(
+ "WO #00000024965 escalated to SEV 1.",
+ );
+ });
+});
diff --git a/src/test/domain/work-orders/utils/table-filter-utils.test.ts b/src/test/domain/work-orders/utils/table-filter-utils.test.ts
index 85651d2c..b6a6c416 100644
--- a/src/test/domain/work-orders/utils/table-filter-utils.test.ts
+++ b/src/test/domain/work-orders/utils/table-filter-utils.test.ts
@@ -194,6 +194,22 @@ describe("filterByBarAndSearch", () => {
expect(ids).toEqual(["pd"]);
});
+ it("hides a row after type correction when the bar filter no longer matches", () => {
+ const afterCorrection = makeRow({
+ id: "1",
+ type: "Emergency",
+ scheduledOn: "2026-06-03",
+ });
+
+ expect(
+ filterByBarAndSearch(
+ [afterCorrection],
+ bar(weekMonday, { types: new Set(["PM"]) }),
+ "",
+ ).map((row) => row.id),
+ ).toEqual([]);
+ });
+
it("does not filter by type when the set is empty", () => {
const pm = makeRow({ id: "pm", type: "PM", scheduledOn: "2026-06-03" });
const emergency = makeRow({ id: "em", type: "Emergency", scheduledOn: "2026-06-03" });
diff --git a/src/test/domain/work-orders/utils/work-order-lock.test.ts b/src/test/domain/work-orders/utils/work-order-lock.test.ts
index f65f3a30..e8ce1f1b 100644
--- a/src/test/domain/work-orders/utils/work-order-lock.test.ts
+++ b/src/test/domain/work-orders/utils/work-order-lock.test.ts
@@ -45,5 +45,10 @@ describe("work-order-lock", () => {
expect(canApplyWorkOrderTablePatch("Completed", { site: "BK1" })).toBe(false);
expect(canApplyWorkOrderTablePatch("Scheduled", { dispatcherId: "u2" })).toBe(true);
expect(canApplyWorkOrderTablePatch("Canceled", { flagColor: "red" })).toBe(false);
+ expect(canApplyWorkOrderTablePatch("Completed", { type: "Emergency", severity: 1 })).toBe(
+ false,
+ );
+ expect(canApplyWorkOrderTablePatch("Canceled", { type: "PM" })).toBe(false);
+ expect(canApplyWorkOrderTablePatch("Scheduled", { type: "Reactive", severity: 3 })).toBe(true);
});
});
|