mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-03 16:43:23 +00:00
Merge pull request #289 from Sea-Haven-Industries/fix/ab/sh-357-incomplete-empty-schedule
Some checks are pending
Frontend checks / static (push) Waiting to run
Frontend checks / build (push) Waiting to run
Frontend checks / unit (push) Waiting to run
Frontend checks / Visual regression (push) Waiting to run
Frontend checks / browser-smoke (push) Waiting to run
Frontend checks / governance (push) Waiting to run
Frontend checks / ci-complete (push) Blocked by required conditions
Deploy Web / Resolve target (push) Waiting to run
Deploy Web / Deploy SPA to (push) Blocked by required conditions
Some checks are pending
Frontend checks / static (push) Waiting to run
Frontend checks / build (push) Waiting to run
Frontend checks / unit (push) Waiting to run
Frontend checks / Visual regression (push) Waiting to run
Frontend checks / browser-smoke (push) Waiting to run
Frontend checks / governance (push) Waiting to run
Frontend checks / ci-complete (push) Blocked by required conditions
Deploy Web / Resolve target (push) Waiting to run
Deploy Web / Deploy SPA to (push) Blocked by required conditions
SH-357: disable Incomplete creation without Schedule On
This commit is contained in:
commit
84cfc90282
16 changed files with 63 additions and 38 deletions
|
|
@ -181,8 +181,9 @@ test.describe("existing work order status menu", () => {
|
|||
.click();
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await page.getByRole("button", { name: "Incomplete", exact: true }).last().click();
|
||||
await page.getByRole("button", { name: "Pending", exact: true }).last().click();
|
||||
const menu = page.locator(".MuiPopover-paper").last();
|
||||
await expect(menu.getByRole("button", { name: "Incomplete", exact: true })).toBeDisabled();
|
||||
await expect(menu.getByRole("button", { name: "Pending", exact: true })).toBeEnabled();
|
||||
await menu.getByRole("button", { name: "Pending", exact: true }).click();
|
||||
await expect(
|
||||
|
|
@ -228,8 +229,9 @@ test.describe("existing work order status menu", () => {
|
|||
.click();
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await page.getByRole("button", { name: "Incomplete", exact: true }).last().click();
|
||||
await page.getByRole("button", { name: "Pending", exact: true }).last().click();
|
||||
const menu = page.locator(".MuiPopover-paper").last();
|
||||
await expect(menu.getByRole("button", { name: "Incomplete", exact: true })).toBeDisabled();
|
||||
await menu.getByRole("button", { name: "Pending", exact: true }).click();
|
||||
await expect(
|
||||
page.getByRole("dialog").getByRole("button", { name: "Pending", exact: true }),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ function resolveInlineDraftSchedule(draft: WorkOrderTableDraft) {
|
|||
return {
|
||||
scheduleWeekOnly,
|
||||
status: resolveCreationStatus({
|
||||
status: draft.status || "Incomplete",
|
||||
status: draft.status || "Pending",
|
||||
scheduledOn: draft.scheduledOn,
|
||||
scheduleWeekOnly,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ export function WoTableInlineRowCells({
|
|||
dispatcherId={draft.dispatcherId}
|
||||
users={users}
|
||||
onSave={(id) => {
|
||||
onPatch({ dispatcherId: id, status: draft.status || "Incomplete" });
|
||||
onPatch({ dispatcherId: id, status: draft.status || "Pending" });
|
||||
}}
|
||||
/>
|
||||
</InlineFieldCell>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ export function useNewWoWizardDraft(open: boolean) {
|
|||
const [step, setStep] = useState(1);
|
||||
const [draft, setDraft] = useState<WorkOrderWizardDraft>({
|
||||
...EMPTY_WIZARD_DRAFT,
|
||||
status: "Incomplete",
|
||||
});
|
||||
const [svcOpen, setSvcOpen] = useState(false);
|
||||
const [svcSearch, setSvcSearch] = useState("");
|
||||
|
|
@ -18,7 +17,7 @@ export function useNewWoWizardDraft(open: boolean) {
|
|||
useEffect(() => {
|
||||
if (open) {
|
||||
setStep(1);
|
||||
setDraft({ ...EMPTY_WIZARD_DRAFT, status: "Incomplete" });
|
||||
setDraft({ ...EMPTY_WIZARD_DRAFT });
|
||||
setSvcOpen(false);
|
||||
setSvcSearch("");
|
||||
setShowErrors(false);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ export function useNewWoWizardState({
|
|||
const [showErrors, setShowErrors] = useState(false);
|
||||
const [draft, setDraft] = useState<WorkOrderWizardDraft>({
|
||||
...EMPTY_WIZARD_DRAFT,
|
||||
status: "Incomplete",
|
||||
});
|
||||
const [pocAutoFilledFor, setPocAutoFilledFor] = useState("");
|
||||
|
||||
|
|
@ -83,7 +82,7 @@ export function useNewWoWizardState({
|
|||
if (open) {
|
||||
setStep(1);
|
||||
setShowErrors(false);
|
||||
setDraft({ ...EMPTY_WIZARD_DRAFT, status: "Incomplete" });
|
||||
setDraft({ ...EMPTY_WIZARD_DRAFT });
|
||||
setPocAutoFilledFor("");
|
||||
setDuplicateRow(null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,6 @@ export function buildWizardSidebarSummaryRows(draft: WorkOrderWizardDraft): [str
|
|||
["Technician", draft.vendorName || "—"],
|
||||
["Company", draft.vendorName || "—"],
|
||||
["Appt time", draft.apptStart ? formatApptRange(draft.apptStart, draft.apptEnd) : "—"],
|
||||
["Status", draft.status || "Incomplete"],
|
||||
["Status", draft.status || "Pending"],
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import { ColorDot } from "@/app/(protected)/workorders/_components/wizard/wizard
|
|||
|
||||
export function WizardSidebarSummary({ draft }: { draft: WorkOrderWizardDraft }) {
|
||||
const summary = buildWizardSidebarSummaryRows(draft);
|
||||
const statusLabel = draft.status || "Incomplete";
|
||||
const statusLabel = draft.status || "Pending";
|
||||
const statusValue = statusLabel as WizardWOStatus;
|
||||
const statusDotColor = WIZARD_STATUS_DOTS[statusValue] ?? WIZARD_STATUS_DOTS.Incomplete;
|
||||
const statusDotColor = WIZARD_STATUS_DOTS[statusValue] ?? WIZARD_STATUS_DOTS.Pending;
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "auto", paddingTop: 44 }}>
|
||||
|
|
|
|||
|
|
@ -194,8 +194,8 @@ export function WizardStepVendorTime({
|
|||
draft.technicianAssigned === false
|
||||
? undefined
|
||||
: vendors.find((v) => String(v.id) === draft.vendorId);
|
||||
const statusValue = (draft.status || "Incomplete") as WizardWOStatus;
|
||||
const statusDotColor = WIZARD_STATUS_DOTS[statusValue] ?? WIZARD_STATUS_DOTS.Incomplete;
|
||||
const statusValue = (draft.status || "Pending") as WizardWOStatus;
|
||||
const statusDotColor = WIZARD_STATUS_DOTS[statusValue] ?? WIZARD_STATUS_DOTS.Pending;
|
||||
const selectedDistance = formatVendorDistance(selectedVendor?.distanceMiles);
|
||||
const closabilityGaps = getClosabilityGaps(wizardDraftToClosabilityInput(draft));
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export function useWorkOrdersTableUiState(weekMonday: string) {
|
|||
setDraft({
|
||||
...EMPTY_TABLE_DRAFT,
|
||||
scheduledOn,
|
||||
status: scheduledOn ? "Scheduled" : "Incomplete",
|
||||
status: scheduledOn ? "Scheduled" : "Pending",
|
||||
});
|
||||
},
|
||||
[weekMonday],
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@ import {
|
|||
WORK_ORDER_TYPE_API,
|
||||
} from "@/domain/work-orders/types/work-order-board";
|
||||
|
||||
import type {
|
||||
WizardWOStatus,
|
||||
WOType,
|
||||
WorkOrderWizardDraft,
|
||||
} from "@/domain/work-orders/types/work-order-wizard";
|
||||
import type { WOType, WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard";
|
||||
|
||||
import { fmtDateRange } from "@/domain/work-orders/utils/wizard-date-utils";
|
||||
|
||||
|
|
@ -20,6 +16,7 @@ import { assignWizardScheduleFields } from "@/domain/work-orders/mappers/assign-
|
|||
import { assignWizardVendorApptFields } from "@/domain/work-orders/mappers/assign-wizard-vendor-appt-fields";
|
||||
import { assignWizardServicePocFields } from "@/domain/work-orders/mappers/assign-wizard-service-poc-fields";
|
||||
import { resolveIsAddOnForCreate } from "@/domain/work-orders/utils/wo-addon-cutoff";
|
||||
import { resolveCreationStatus } from "@/domain/work-orders/utils/wo-lifecycle-transitions";
|
||||
|
||||
const WO_TYPE_TO_API: Record<WOType, WorkOrderTypeApi> = {
|
||||
PM: WORK_ORDER_TYPE_API.PM,
|
||||
|
|
@ -114,7 +111,7 @@ export function mapWizardDraftToBoardCreatePayload(
|
|||
};
|
||||
|
||||
if (draft.status) {
|
||||
payload.lifecycleStatus = LIFECYCLE_STATUS_API[draft.status as WizardWOStatus];
|
||||
payload.lifecycleStatus = LIFECYCLE_STATUS_API[resolveCreationStatus(draft)];
|
||||
}
|
||||
|
||||
if (draft.woNumber.trim()) {
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ export const EMPTY_TABLE_DRAFT: WorkOrderTableDraft = {
|
|||
techPhone: "",
|
||||
apptStart: "",
|
||||
apptEnd: "",
|
||||
status: "Incomplete",
|
||||
status: "Pending",
|
||||
pocName: "",
|
||||
pocPhone: "",
|
||||
pocNotes: "",
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export const EMPTY_WIZARD_DRAFT: WorkOrderWizardDraft = {
|
|||
vendorNotes: "",
|
||||
apptStart: "",
|
||||
apptEnd: "",
|
||||
status: "Incomplete",
|
||||
status: "Pending",
|
||||
pocName: "",
|
||||
pocPhone: "",
|
||||
pocNotes: "",
|
||||
|
|
|
|||
|
|
@ -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 INCOMPLETE_NEEDS_DATE_MSG =
|
||||
"Set a Schedule On date before marking this work order as Incomplete.";
|
||||
export const PENDING_NEEDS_NO_DATE_MSG =
|
||||
"Clear the Schedule On date before marking this work order as Pending.";
|
||||
|
||||
|
|
@ -134,6 +136,9 @@ export function getStatusOptionState(
|
|||
status: WizardWOStatus,
|
||||
closabilityGaps: string[],
|
||||
): StatusOptionState {
|
||||
if (status === "Incomplete" && !hasConcreteScheduleDate(row)) {
|
||||
return { disabled: true, title: INCOMPLETE_NEEDS_DATE_MSG };
|
||||
}
|
||||
if (status === "Pending" && hasConcreteScheduleDate(row)) {
|
||||
return { disabled: true, title: PENDING_NEEDS_NO_DATE_MSG };
|
||||
}
|
||||
|
|
@ -157,7 +162,7 @@ export function maybeAutoSchedulePatch(
|
|||
if (hasDate && isPreScheduleStatus(row.status ?? "")) {
|
||||
return { status: "Scheduled" };
|
||||
}
|
||||
if (!hasDate && row.status === "Scheduled") {
|
||||
if (!hasDate && (row.status === "Scheduled" || row.status === "Incomplete")) {
|
||||
return { status: "Pending" };
|
||||
}
|
||||
return {};
|
||||
|
|
@ -211,5 +216,8 @@ export function resolveCreationStatus(row: ScheduleDateInput & { status: string
|
|||
if (autoStatus === "Scheduled" || autoStatus === "Pending") {
|
||||
return autoStatus;
|
||||
}
|
||||
return (row.status || "Incomplete") as WizardWOStatus;
|
||||
if (!row.status || (row.status === "Incomplete" && !hasConcreteScheduleDate(row))) {
|
||||
return "Pending";
|
||||
}
|
||||
return row.status as WizardWOStatus;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ describe("workOrdersApi.createFromBoard", () => {
|
|||
expect(apiPost).toHaveBeenCalledWith(API_PATHS.workOrder.boardCreate, {
|
||||
workOrderType: 2,
|
||||
siteCode: "BK5",
|
||||
lifecycleStatus: 1,
|
||||
lifecycleStatus: 2,
|
||||
woNumber: "12345",
|
||||
isAddOn: false,
|
||||
locationId: 9,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
|
|||
|
||||
isAddOn: false,
|
||||
|
||||
lifecycleStatus: 1,
|
||||
lifecycleStatus: 2,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -162,6 +162,7 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
|
|||
type: "PM",
|
||||
siteCode: "BK5",
|
||||
status: "Scheduled",
|
||||
scheduledOn: "2026-06-01",
|
||||
});
|
||||
|
||||
expect(payload.lifecycleStatus).toBe(3);
|
||||
|
|
@ -283,7 +284,7 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
|
|||
expect(payload.scheduledDate).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps Incomplete on week-only create and omits scheduledDate", () => {
|
||||
it("normalizes Incomplete to Pending on week-only create and omits scheduledDate", () => {
|
||||
const payload = mapWizardDraftToBoardCreatePayload({
|
||||
...EMPTY_WIZARD_DRAFT,
|
||||
type: "PM",
|
||||
|
|
@ -293,7 +294,7 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
|
|||
targetWeek: "2026-06-22",
|
||||
});
|
||||
|
||||
expect(payload.lifecycleStatus).toBe(LIFECYCLE_STATUS_API.Incomplete);
|
||||
expect(payload.lifecycleStatus).toBe(LIFECYCLE_STATUS_API.Pending);
|
||||
expect(payload.scheduleWeekOnly).toBe(true);
|
||||
expect(payload.targetWeek).toBe("2026-06-22");
|
||||
expect(payload.scheduledDate).toBeUndefined();
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
COMPLETED_DOC_BLOCK_MSG,
|
||||
COMPLETED_MEDIA_BLOCK_MSG,
|
||||
CREATION_INLINE_STATUSES,
|
||||
INCOMPLETE_NEEDS_DATE_MSG,
|
||||
PENDING_NEEDS_NO_DATE_MSG,
|
||||
SCHEDULED_NEEDS_DATE_MSG,
|
||||
formatAapCompletedBlockTitle,
|
||||
|
|
@ -59,7 +60,17 @@ describe("wo-lifecycle-transitions", () => {
|
|||
expect(hasConcreteScheduleDate({ scheduledOn: "2026-06-01" })).toBe(true);
|
||||
});
|
||||
|
||||
it("disables Scheduled without a concrete date", () => {
|
||||
it("disables Incomplete and Scheduled without a concrete date", () => {
|
||||
expect(getStatusOptionState({ scheduledOn: "" }, "Incomplete", [])).toEqual({
|
||||
disabled: true,
|
||||
title: INCOMPLETE_NEEDS_DATE_MSG,
|
||||
});
|
||||
expect(
|
||||
getStatusOptionState({ scheduledOn: "2026-06-01", scheduleWeekOnly: true }, "Incomplete", []),
|
||||
).toEqual({ disabled: true, title: INCOMPLETE_NEEDS_DATE_MSG });
|
||||
expect(getStatusOptionState({ scheduledOn: "2026-06-01" }, "Incomplete", [])).toEqual({
|
||||
disabled: false,
|
||||
});
|
||||
expect(getStatusOptionState({ scheduledOn: "" }, "Scheduled", [])).toEqual({
|
||||
disabled: true,
|
||||
title: SCHEDULED_NEEDS_DATE_MSG,
|
||||
|
|
@ -102,14 +113,15 @@ describe("wo-lifecycle-transitions", () => {
|
|||
).toEqual({ status: "Scheduled" });
|
||||
});
|
||||
|
||||
it("reverts Scheduled without a concrete date to Pending", () => {
|
||||
expect(
|
||||
maybeAutoSchedulePatch({
|
||||
status: "Scheduled",
|
||||
scheduledOn: "",
|
||||
scheduleWeekOnly: false,
|
||||
}),
|
||||
).toEqual({ status: "Pending" });
|
||||
it("reverts Scheduled or Incomplete without a concrete date to Pending", () => {
|
||||
for (const status of ["Scheduled", "Incomplete"]) {
|
||||
expect(maybeAutoSchedulePatch({ status, scheduledOn: "", scheduleWeekOnly: false })).toEqual({
|
||||
status: "Pending",
|
||||
});
|
||||
expect(
|
||||
maybeAutoSchedulePatch({ status, scheduledOn: "2026-06-01", scheduleWeekOnly: true }),
|
||||
).toEqual({ status: "Pending" });
|
||||
}
|
||||
});
|
||||
|
||||
it("does not auto-change In Progress when a date exists", () => {
|
||||
|
|
@ -180,7 +192,7 @@ describe("wo-lifecycle-transitions", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("still demotes Scheduled when the schedule is cleared", () => {
|
||||
it("demotes Scheduled or Incomplete when the schedule is cleared", () => {
|
||||
const row = {
|
||||
status: "Scheduled",
|
||||
scheduledOn: "2026-06-01",
|
||||
|
|
@ -191,6 +203,13 @@ describe("wo-lifecycle-transitions", () => {
|
|||
scheduleWeekOnly: false,
|
||||
status: "Pending",
|
||||
});
|
||||
expect(withAutoScheduleStatus({ ...row, status: "Incomplete" }, { scheduledOn: "" })).toEqual({
|
||||
scheduledOn: "",
|
||||
status: "Pending",
|
||||
});
|
||||
expect(
|
||||
withAutoScheduleStatus({ ...row, status: "Incomplete" }, { scheduleWeekOnly: true }),
|
||||
).toEqual({ scheduleWeekOnly: true, status: "Pending" });
|
||||
});
|
||||
|
||||
it("promotes a Pending create pick to Scheduled when the draft is dated", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue