- WO Number
+ WO Number
onUpdate(
"woNumber",
- e.target.value.replace(/\D/g, "").slice(0, WIZARD_WO_NUMBER_MAX_LENGTH),
+ e.target.value.replace(/[^a-zA-Z0-9]/g, "").slice(0, WIZARD_WO_NUMBER_MAX_LENGTH),
)
}
onBlur={() => onDuplicateWoNumber?.(draft.woNumber)}
- placeholder="Enter WO number"
+ placeholder="Leave blank for SH provisional number"
maxLength={WIZARD_WO_NUMBER_MAX_LENGTH}
className={DLG_INPUT_CLS}
style={DLG_INPUT_STYLE}
@@ -300,7 +290,7 @@ export function WizardStepTypeSchedule({
when={Boolean(woNumberError)}
sx={{ fontSize: 11, marginTop: "6px" }}
>
- {draft.woNumber.trim() ? "WO number must be up to 11 digits" : "WO number is required"}
+ WO number must be alphanumeric.
diff --git a/src/domain/work-orders/types/work-order-wizard.ts b/src/domain/work-orders/types/work-order-wizard.ts
index d7ccdb40..f13b387e 100644
--- a/src/domain/work-orders/types/work-order-wizard.ts
+++ b/src/domain/work-orders/types/work-order-wizard.ts
@@ -103,14 +103,15 @@ export const EMPTY_WIZARD_DRAFT: WorkOrderWizardDraft = {
isAddOnOverride: false,
};
-export const WIZARD_WO_NUMBER_MAX_LENGTH = 11;
+export const WIZARD_WO_NUMBER_MAX_LENGTH = 20;
-/** Create requires a WO number of 1–11 digits. */
+/** Blank is valid (backend generates an SH provisional number). Otherwise alphanumeric, max 20. */
export function isWizardWoNumberValid(woNumber: string): boolean {
const trimmed = woNumber.trim();
- return (
- trimmed.length > 0 && trimmed.length <= WIZARD_WO_NUMBER_MAX_LENGTH && /^\d+$/.test(trimmed)
- );
+ if (!trimmed) {
+ return true;
+ }
+ return trimmed.length <= WIZARD_WO_NUMBER_MAX_LENGTH && /^[a-z0-9]+$/i.test(trimmed);
}
/** Type is optional at creation; empty draft type defaults to PM. */
@@ -186,9 +187,6 @@ export const TIME_SLOTS: string[] = (() => {
})();
export function canContinueWizardStep1(draft: WorkOrderWizardDraft): boolean {
- if (!draft.type) {
- return false;
- }
if (!isWizardWoNumberValid(draft.woNumber)) {
return false;
}
@@ -202,14 +200,8 @@ export function wizardStep1ContinueHint(draft: WorkOrderWizardDraft): string | n
if (canContinueWizardStep1(draft)) {
return null;
}
- if (!draft.type) {
- return "Select a work order type to continue";
- }
- if (!draft.woNumber.trim()) {
- return "Enter a WO number to continue";
- }
if (!isWizardWoNumberValid(draft.woNumber)) {
- return "WO number must be up to 11 digits";
+ return "WO number must be alphanumeric";
}
return "Select severity to continue";
}
diff --git a/src/test/app/(protected)/workorders/new-wo-wizard-duplicate.test.tsx b/src/test/app/(protected)/workorders/new-wo-wizard-duplicate.test.tsx
index fd6b1212..02a1fa6c 100644
--- a/src/test/app/(protected)/workorders/new-wo-wizard-duplicate.test.tsx
+++ b/src/test/app/(protected)/workorders/new-wo-wizard-duplicate.test.tsx
@@ -48,7 +48,7 @@ describe("NewWoWizard duplicate wo number", () => {
/>,
);
- const woNumberInput = screen.getByPlaceholderText("Enter WO number");
+ const woNumberInput = screen.getByPlaceholderText("Leave blank for SH provisional number");
fireEvent.change(woNumberInput, {
target: { value: "12345678901" },
});
@@ -77,7 +77,7 @@ describe("NewWoWizard duplicate wo number", () => {
/>,
);
- const woNumberInput = screen.getByPlaceholderText("Enter WO number");
+ const woNumberInput = screen.getByPlaceholderText("Leave blank for SH provisional number");
fireEvent.change(woNumberInput, {
target: { value: "98765432109" },
});
diff --git a/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx b/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx
index 3ec402b5..e273324c 100644
--- a/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx
+++ b/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx
@@ -227,16 +227,9 @@ describe("WorkOrdersListPage board-core public interface", () => {
expect(screen.getByRole("dialog")).toBeInTheDocument();
expect(screen.getByRole("heading", { name: /type & schedule/i })).toBeInTheDocument();
- expect(screen.getByPlaceholderText(/enter wo number/i)).toBeInTheDocument();
- fireEvent.click(screen.getByRole("button", { name: /continue/i }));
- expect(screen.getByText(/type of wo is required/i)).toBeInTheDocument();
- expect(screen.getByText(/wo number is required/i)).toBeInTheDocument();
- expect(screen.getByRole("heading", { name: /type & schedule/i })).toBeInTheDocument();
-
- fireEvent.click(screen.getByRole("button", { name: /scheduled preventive maintenance/i }));
- fireEvent.change(screen.getByPlaceholderText(/enter wo number/i), {
- target: { value: "12345678901" },
- });
+ expect(
+ screen.getByPlaceholderText(/leave blank for sh provisional number/i),
+ ).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: /continue/i }));
expect(screen.getByRole("heading", { name: /location & service/i })).toBeInTheDocument();
diff --git a/src/test/domain/work-orders/mappers/wizard-draft-mapper.test.ts b/src/test/domain/work-orders/mappers/wizard-draft-mapper.test.ts
index cbcf9be7..b1124749 100644
--- a/src/test/domain/work-orders/mappers/wizard-draft-mapper.test.ts
+++ b/src/test/domain/work-orders/mappers/wizard-draft-mapper.test.ts
@@ -67,21 +67,21 @@ describe("mapWizardDraftToFormValues", () => {
});
describe("wizard step validation", () => {
- it("blocks step 1 without a woNumber", () => {
- expect(canContinueWizardStep1(EMPTY_WIZARD_DRAFT)).toBe(false);
- expect(isWizardStepValid(1, EMPTY_WIZARD_DRAFT)).toBe(false);
+ it("allows step 1 with a blank woNumber for backend SH generation", () => {
+ expect(canContinueWizardStep1(EMPTY_WIZARD_DRAFT)).toBe(true);
+ expect(isWizardStepValid(1, EMPTY_WIZARD_DRAFT)).toBe(true);
});
- it("blocks step 1 without type even when woNumber is set", () => {
+ it("allows step 1 without type when woNumber is set", () => {
const draft = { ...EMPTY_WIZARD_DRAFT, woNumber: "12345" };
- expect(canContinueWizardStep1(draft)).toBe(false);
- expect(isWizardStepValid(1, draft)).toBe(false);
+ expect(canContinueWizardStep1(draft)).toBe(true);
+ expect(isWizardStepValid(1, draft)).toBe(true);
});
- it("blocks step 1 without woNumber even when type is set", () => {
+ it("allows step 1 without woNumber when type is set", () => {
const draft = { ...EMPTY_WIZARD_DRAFT, type: "PM" as const };
- expect(canContinueWizardStep1(draft)).toBe(false);
- expect(isWizardStepValid(1, draft)).toBe(false);
+ expect(canContinueWizardStep1(draft)).toBe(true);
+ expect(isWizardStepValid(1, draft)).toBe(true);
});
it("allows step 1 when type and woNumber are set (schedule optional)", () => {
@@ -194,56 +194,44 @@ describe("wizard step validation", () => {
expect(isWizardStepValid(2, draft)).toBe(true);
});
- it("blocks SH-prefixed woNumber on create", () => {
+ it("allows provisional SH-prefixed woNumber", () => {
const draft = {
...EMPTY_WIZARD_DRAFT,
type: "PM" as const,
woNumber: "SH12345",
};
- expect(canContinueWizardStep1(draft)).toBe(false);
+ expect(isWizardWoNumberValid(draft.woNumber)).toBe(true);
+ expect(canContinueWizardStep1(draft)).toBe(true);
});
- it("rejects blank woNumber", () => {
- const draft = {
- ...EMPTY_WIZARD_DRAFT,
- type: "PM" as const,
- woNumber: "",
- };
- expect(isWizardWoNumberValid(draft.woNumber)).toBe(false);
- expect(canContinueWizardStep1(draft)).toBe(false);
- });
-
- it("rejects whitespace-only woNumber", () => {
+ it("accepts whitespace-only woNumber for backend SH generation", () => {
const draft = {
...EMPTY_WIZARD_DRAFT,
type: "PM" as const,
woNumber: " ",
};
- expect(isWizardWoNumberValid(draft.woNumber)).toBe(false);
- expect(canContinueWizardStep1(draft)).toBe(false);
+ expect(isWizardWoNumberValid(draft.woNumber)).toBe(true);
+ expect(canContinueWizardStep1(draft)).toBe(true);
});
- it("rejects non-digit woNumber", () => {
+ it("rejects non-alphanumeric woNumber", () => {
expect(isWizardWoNumberValid("!!!")).toBe(false);
expect(canContinueWizardStep1({ ...EMPTY_WIZARD_DRAFT, type: "PM", woNumber: "!!!" })).toBe(
false,
);
});
- it("rejects woNumber longer than 11 digits", () => {
- expect(isWizardWoNumberValid("123456789012")).toBe(false);
+ it("accepts alphanumeric woNumber", () => {
+ expect(isWizardWoNumberValid("ABC123")).toBe(true);
});
- it("accepts an 11-digit woNumber", () => {
- expect(isWizardWoNumberValid("12345678901")).toBe(true);
- });
-
- it("hints type then WO number when step 1 is incomplete", () => {
- expect(wizardStep1ContinueHint(EMPTY_WIZARD_DRAFT)).toBe(
- "Select a work order type to continue",
+ it("hints alphanumeric WO number or severity when step 1 is incomplete", () => {
+ expect(wizardStep1ContinueHint(EMPTY_WIZARD_DRAFT)).toBeNull();
+ expect(wizardStep1ContinueHint({ ...EMPTY_WIZARD_DRAFT, type: "PM", woNumber: "!!!" })).toBe(
+ "WO number must be alphanumeric",
);
- expect(wizardStep1ContinueHint({ ...EMPTY_WIZARD_DRAFT, type: "PM" })).toBe(
- "Enter a WO number to continue",
+ expect(wizardStep1ContinueHint({ ...EMPTY_WIZARD_DRAFT, type: "Emergency" })).toBe(
+ "Select severity to continue",
);
});
});