mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 23:53:21 +00:00
fix(work-orders): validate inline technician phone before roster save
The inline technician rows on the wizard's Vendor & time step had no client-side phone check, so a number that does not normalize to ten digits failed the roster PATCH and blocked the whole Create with the raw backend message in a toast. Validate each staged phone with the same isValidNorthAmericanPhone predicate the vendor roster schema uses, so the inline path matches the modal path. A blank phone stays valid.
This commit is contained in:
parent
712d8e0dc6
commit
0b229fd054
2 changed files with 50 additions and 0 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { toast } from "react-toastify";
|
||||
import { useAddTechniciansToCompany } from "@/domain/vendors/use-cases/use-add-technicians-to-company";
|
||||
import { isValidNorthAmericanPhone } from "@/domain/vendors/schemas/vendor-roster-schema";
|
||||
import type { VendorDropdownItem } from "@/domain/work-orders/types/work-order";
|
||||
import type {
|
||||
WizardNewTechnician,
|
||||
|
|
@ -56,6 +57,13 @@ export function useWizardCreateWithTechnicians({
|
|||
createMutation.mutate(draft);
|
||||
return;
|
||||
}
|
||||
// Mirror the vendor roster schema so an unparseable phone is caught here with a
|
||||
// clear message instead of failing the roster PATCH and blocking Create with the
|
||||
// raw backend sentence. A blank phone stays valid, matching the modal path.
|
||||
if (pending.some((technician) => !isValidNorthAmericanPhone(technician.phone))) {
|
||||
toast.error("Enter a 10-digit phone number for each new technician.");
|
||||
return;
|
||||
}
|
||||
const anchor = vendors.find((vendor) => vendor.companyName === company);
|
||||
if (!anchor) {
|
||||
toast.error("Select an existing vendor company before adding technicians.");
|
||||
|
|
|
|||
|
|
@ -196,6 +196,48 @@ describe("useWizardCreateWithTechnicians (SH-364)", () => {
|
|||
expect(createMutation.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedPatch));
|
||||
});
|
||||
|
||||
it("blocks Create and skips the roster save when a staged phone is not a valid number", async () => {
|
||||
const createMutation = { mutate: vi.fn(), isPending: false };
|
||||
const { result } = renderHook(() =>
|
||||
useWizardCreateWithTechnicians({ vendors, patchDraft: vi.fn(), createMutation }),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
result.current.mutate(
|
||||
draftWith({
|
||||
vendorCompany: "Vinewood LLC",
|
||||
newTechnicians: [{ name: "Jordan Lee", phone: "(314) 555-01" }],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(mutateAsync).not.toHaveBeenCalled();
|
||||
expect(createMutation.mutate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows a staged technician with a blank phone, matching the modal path", async () => {
|
||||
mutateAsync.mockResolvedValue([{ ...vendors[0], id: 99, contactName: "Jordan Lee" }]);
|
||||
const createMutation = { mutate: vi.fn(), isPending: false };
|
||||
const { result } = renderHook(() =>
|
||||
useWizardCreateWithTechnicians({ vendors, patchDraft: vi.fn(), createMutation }),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
result.current.mutate(
|
||||
draftWith({
|
||||
vendorCompany: "Vinewood LLC",
|
||||
newTechnicians: [{ name: "Jordan Lee", phone: "" }],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(mutateAsync).toHaveBeenCalledWith({
|
||||
anchorVendorId: 41,
|
||||
technicians: [{ contactName: "Jordan Lee", phone: "", tradeSpecialties: "HVAC" }],
|
||||
});
|
||||
expect(createMutation.mutate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not create the work order when the technician save fails", async () => {
|
||||
mutateAsync.mockRejectedValue(new Error("boom"));
|
||||
const createMutation = { mutate: vi.fn(), isPending: false };
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue