diff --git a/e2e/work-orders/pending-status-menu.spec.ts b/e2e/work-orders/pending-status-menu.spec.ts index 3f2307be..43c86efa 100644 --- a/e2e/work-orders/pending-status-menu.spec.ts +++ b/e2e/work-orders/pending-status-menu.spec.ts @@ -247,9 +247,18 @@ test.describe("existing work order status menu", () => { await page.getByRole("button", { name: "Continue" }).click(); await page.getByRole("button", { name: "Continue" }).click(); - await expect( - page.getByRole("dialog").getByRole("button", { name: "Scheduled", exact: true }), - ).toBeVisible(); + const wizardDialog = page.getByRole("dialog"); + const scheduledStatus = wizardDialog.getByRole("button", { + name: "Scheduled", + exact: true, + }); + await expect(scheduledStatus).toBeVisible(); + await scheduledStatus.click(); + const statusMenu = page.locator(".MuiPopover-paper").last(); + await expect(statusMenu.getByRole("button", { name: "Pending", exact: true })).toBeDisabled(); + await statusMenu.getByRole("button", { name: "Scheduled", exact: true }).click(); + await expect(scheduledStatus).toBeVisible(); + await page.getByRole("button", { name: "Create work order" }).click(); await expect(page.getByRole("heading", { name: "Type & schedule" })).toBeHidden(); await expect diff --git a/src/domain/work-orders/utils/wo-lifecycle-transitions.ts b/src/domain/work-orders/utils/wo-lifecycle-transitions.ts index e0351242..bbef896a 100644 --- a/src/domain/work-orders/utils/wo-lifecycle-transitions.ts +++ b/src/domain/work-orders/utils/wo-lifecycle-transitions.ts @@ -38,6 +38,8 @@ export const CREATION_INLINE_STATUSES: readonly WizardWOStatus[] = [ export const SCHEDULED_NEEDS_DATE_MSG = "Set a Schedule On date before marking this work order as Scheduled."; +export const PENDING_NEEDS_NO_DATE_MSG = + "Clear the Schedule On date before marking this work order as Pending."; export const PAST_DUE_BADGE_TOOLTIP = "This WO missed its service window. Reschedule to unlock status updates."; @@ -132,6 +134,9 @@ export function getStatusOptionState( status: WizardWOStatus, closabilityGaps: string[], ): StatusOptionState { + if (status === "Pending" && hasConcreteScheduleDate(row)) { + return { disabled: true, title: PENDING_NEEDS_NO_DATE_MSG }; + } if (status === "Scheduled" && !hasConcreteScheduleDate(row)) { return { disabled: true, title: SCHEDULED_NEEDS_DATE_MSG }; } diff --git a/src/test/app/(protected)/workorders/status-cell-closability.test.tsx b/src/test/app/(protected)/workorders/status-cell-closability.test.tsx index 77922f0d..be9a17a6 100644 --- a/src/test/app/(protected)/workorders/status-cell-closability.test.tsx +++ b/src/test/app/(protected)/workorders/status-cell-closability.test.tsx @@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest"; import { StatusCell } from "@/app/(protected)/workorders/_components/list/table/cells/status-cell"; import type { WizardWOStatus } from "@/domain/work-orders/types/work-order-wizard"; import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; +import { CREATION_INLINE_STATUSES } from "@/domain/work-orders/utils/wo-lifecycle-transitions"; const incompleteRow = { id: "wo-1", @@ -111,4 +112,23 @@ describe("StatusCell closability gate", () => { expect(screen.getByRole("button", { name: /^pending$/i })).toBeTruthy(); expect(screen.queryByRole("button", { name: /^en route$/i })).toBeNull(); }); + + it("disables Pending for inline creation when Schedule On has a concrete date", () => { + render( + , + ); + + fireEvent.click(screen.getByRole("button", { name: /scheduled/i })); + const pending = screen.getByRole("button", { name: /^pending$/i }); + expect(pending).toBeDisabled(); + expect(pending).toHaveAttribute( + "title", + "Clear the Schedule On date before marking this work order as Pending.", + ); + }); }); diff --git a/src/test/app/(protected)/workorders/wizard-step-vendor-time-closability.test.tsx b/src/test/app/(protected)/workorders/wizard-step-vendor-time-closability.test.tsx index 9280a735..265597e0 100644 --- a/src/test/app/(protected)/workorders/wizard-step-vendor-time-closability.test.tsx +++ b/src/test/app/(protected)/workorders/wizard-step-vendor-time-closability.test.tsx @@ -77,7 +77,7 @@ describe("WizardStepVendorTime creation statuses (AAP)", () => { expect(onPatch).toHaveBeenCalledWith({ status: "Pending" }); }); - it("promotes Pending to Scheduled when picked on a dated draft", () => { + it("disables Pending when Schedule On has a concrete date", () => { const onPatch = vi.fn(); render( { ); fireEvent.click(screen.getByRole("button", { name: /scheduled/i })); - fireEvent.click(screen.getByRole("button", { name: /^pending$/i })); - expect(onPatch).toHaveBeenCalledWith({ status: "Scheduled" }); + const pending = screen.getByRole("button", { name: /^pending$/i }); + expect(pending).toBeDisabled(); + expect(pending).toHaveAttribute( + "title", + "Clear the Schedule On date before marking this work order as Pending.", + ); + fireEvent.click(pending); + expect(onPatch).not.toHaveBeenCalled(); }); }); diff --git a/src/test/domain/work-orders/utils/wo-lifecycle-transitions.test.ts b/src/test/domain/work-orders/utils/wo-lifecycle-transitions.test.ts index b22e4357..5d0b4d77 100644 --- a/src/test/domain/work-orders/utils/wo-lifecycle-transitions.test.ts +++ b/src/test/domain/work-orders/utils/wo-lifecycle-transitions.test.ts @@ -6,6 +6,7 @@ import { COMPLETED_DOC_BLOCK_MSG, COMPLETED_MEDIA_BLOCK_MSG, CREATION_INLINE_STATUSES, + PENDING_NEEDS_NO_DATE_MSG, SCHEDULED_NEEDS_DATE_MSG, formatAapCompletedBlockTitle, getInlineStatusTransitions, @@ -65,6 +66,25 @@ describe("wo-lifecycle-transitions", () => { }); }); + it("disables Pending only when a concrete schedule date exists", () => { + expect(getStatusOptionState({ scheduledOn: "2026-06-01" }, "Pending", [])).toEqual({ + disabled: true, + title: PENDING_NEEDS_NO_DATE_MSG, + }); + expect( + getStatusOptionState({ scheduledOn: "", scheduleWeekOnly: false }, "Pending", []), + ).toEqual({ + disabled: false, + }); + expect( + getStatusOptionState( + { scheduledOn: "2026-06-01", scheduleWeekOnly: true, schedMode: "week" }, + "Pending", + [], + ), + ).toEqual({ disabled: false }); + }); + it("promotes Incomplete/Pending when a concrete date exists", () => { expect( maybeAutoSchedulePatch({