mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 19:22:06 +00:00
fix(workorders): preserve explicit no-technician choice
This commit is contained in:
parent
316cafccaa
commit
64c53e9da0
7 changed files with 126 additions and 3 deletions
|
|
@ -134,6 +134,19 @@ async function mockApi(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (pathname === "/api/workorders/board" && method === "POST") {
|
if (pathname === "/api/workorders/board" && method === "POST") {
|
||||||
|
const body = bodyOf(request) as { vendorId?: number; technicianAssigned?: boolean };
|
||||||
|
const vendorId = body.vendorId ?? 0;
|
||||||
|
currentBoardRows.push({
|
||||||
|
...BOARD_ROW,
|
||||||
|
id: 900,
|
||||||
|
woNumber: "SH-900",
|
||||||
|
lifecycleStatusLabel: "Incomplete",
|
||||||
|
vendorId: vendorId || null,
|
||||||
|
vendorName: vendorId > 0 ? "Vinewood LLC" : null,
|
||||||
|
technicianAssigned: body.technicianAssigned,
|
||||||
|
techName: body.technicianAssigned === true ? "Jordan Lee" : null,
|
||||||
|
techPhone: body.technicianAssigned === true ? "(314) 555-0199" : null,
|
||||||
|
});
|
||||||
return fulfillJson(route, { data: { id: 900, woNumber: "SH-900" } });
|
return fulfillJson(route, { data: { id: 900, woNumber: "SH-900" } });
|
||||||
}
|
}
|
||||||
if (/^\/api\/workorders\/\d+\/board$/u.test(pathname) && method === "PATCH") {
|
if (/^\/api\/workorders\/\d+\/board$/u.test(pathname) && method === "PATCH") {
|
||||||
|
|
@ -280,6 +293,44 @@ test.describe("Inline technician registration", () => {
|
||||||
expect(create?.body).toEqual(expect.objectContaining({ vendorId: 42 }));
|
expect(create?.body).toEqual(expect.objectContaining({ vendorId: 42 }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("wizard No technician selection overrides a staged technician through create and reopen", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const writes: Captured[] = [];
|
||||||
|
await mockApi(page, writes);
|
||||||
|
await openWizardVendorStep(page);
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "No technician" }).first().click();
|
||||||
|
await page.getByRole("button", { name: "No technician" }).last().click();
|
||||||
|
await page.getByRole("button", { name: "Create work order" }).click();
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll(() =>
|
||||||
|
writes.find(
|
||||||
|
(write) => write.pathname === "/api/workorders/board" && write.method === "POST",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toBeTruthy();
|
||||||
|
const create = writes.find(
|
||||||
|
(write) => write.pathname === "/api/workorders/board" && write.method === "POST",
|
||||||
|
);
|
||||||
|
expect(create?.body).toEqual(
|
||||||
|
expect.objectContaining({ vendorId: 41, technicianAssigned: false }),
|
||||||
|
);
|
||||||
|
expect(vendorWrites(writes)).toEqual([
|
||||||
|
expect.objectContaining({ pathname: "/api/vendor-company-roster/7" }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
const createdRow = page.locator("#wo-row-900");
|
||||||
|
await expect(createdRow.getByRole("button", { name: /Vinewood LLC/ })).toBeVisible();
|
||||||
|
await createdRow.getByRole("button", { name: /Vinewood LLC/ }).click();
|
||||||
|
const vendorDialog = page.getByRole("dialog", { name: "Vendor" });
|
||||||
|
await expect(vendorDialog.getByRole("button", { name: "No technician" })).toBeVisible();
|
||||||
|
await expect(vendorDialog.locator('input[type="tel"]')).toHaveCount(0);
|
||||||
|
await expect(vendorDialog.getByRole("button", { name: "Jordan Lee" })).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
test("company-only save returns no technician and stays empty after reopen and reload", async ({
|
test("company-only save returns no technician and stays empty after reopen and reload", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
|
||||||
|
|
@ -172,9 +172,7 @@ export function useVendorDialogForm({
|
||||||
notes,
|
notes,
|
||||||
pendingCreateCount: Object.keys(pendingCreates).length,
|
pendingCreateCount: Object.keys(pendingCreates).length,
|
||||||
},
|
},
|
||||||
baseline: {
|
baseline: buildSubmitBaseline({ ...technicianKeyArgs, techPhone }),
|
||||||
...buildSubmitBaseline({ ...technicianKeyArgs, techPhone }),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { handleSave } = useVendorDialogSave({
|
const { handleSave } = useVendorDialogSave({
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ export function buildCreatedTechniciansPatch(
|
||||||
if (
|
if (
|
||||||
created.length === 1 &&
|
created.length === 1 &&
|
||||||
only != null &&
|
only != null &&
|
||||||
|
draft.technicianSelectionCleared !== true &&
|
||||||
(draft.technicianAssigned === false || draft.vendorId === "")
|
(draft.technicianAssigned === false || draft.vendorId === "")
|
||||||
) {
|
) {
|
||||||
patch.vendorId = String(only.id);
|
patch.vendorId = String(only.id);
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ export function WizardVendorCompanyFields({
|
||||||
techPhone: "",
|
techPhone: "",
|
||||||
vendorNotes: "",
|
vendorNotes: "",
|
||||||
newTechnicians: [],
|
newTechnicians: [],
|
||||||
|
technicianSelectionCleared: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -96,6 +97,7 @@ export function WizardVendorCompanyFields({
|
||||||
vendorId: nextAnchor ? String(nextAnchor.id) : "",
|
vendorId: nextAnchor ? String(nextAnchor.id) : "",
|
||||||
vendorName: nextAnchor ? nextCompany : "",
|
vendorName: nextAnchor ? nextCompany : "",
|
||||||
technicianAssigned: false,
|
technicianAssigned: false,
|
||||||
|
technicianSelectionCleared: false,
|
||||||
techPhone: "",
|
techPhone: "",
|
||||||
vendorNotes: "",
|
vendorNotes: "",
|
||||||
newTechnicians: [],
|
newTechnicians: [],
|
||||||
|
|
@ -116,6 +118,7 @@ export function WizardVendorCompanyFields({
|
||||||
vendorId: noTechnicianVendorId,
|
vendorId: noTechnicianVendorId,
|
||||||
vendorName: companyAnchor ? company : "",
|
vendorName: companyAnchor ? company : "",
|
||||||
technicianAssigned: false,
|
technicianAssigned: false,
|
||||||
|
technicianSelectionCleared: true,
|
||||||
techPhone: "",
|
techPhone: "",
|
||||||
vendorNotes: "",
|
vendorNotes: "",
|
||||||
})
|
})
|
||||||
|
|
@ -147,6 +150,7 @@ export function WizardVendorCompanyFields({
|
||||||
vendorId: noTechnicianVendorId,
|
vendorId: noTechnicianVendorId,
|
||||||
vendorName: companyAnchor ? company : "",
|
vendorName: companyAnchor ? company : "",
|
||||||
technicianAssigned: false,
|
technicianAssigned: false,
|
||||||
|
technicianSelectionCleared: true,
|
||||||
techPhone: "",
|
techPhone: "",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
@ -157,6 +161,7 @@ export function WizardVendorCompanyFields({
|
||||||
vendorId: id,
|
vendorId: id,
|
||||||
vendorName: vendor?.companyName ?? "",
|
vendorName: vendor?.companyName ?? "",
|
||||||
technicianAssigned: true,
|
technicianAssigned: true,
|
||||||
|
technicianSelectionCleared: false,
|
||||||
techPhone: "",
|
techPhone: "",
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ export interface WorkOrderWizardDraft {
|
||||||
vendorCompany?: string;
|
vendorCompany?: string;
|
||||||
/** Explicit technician choice; null/omitted retains legacy inference. */
|
/** Explicit technician choice; null/omitted retains legacy inference. */
|
||||||
technicianAssigned?: boolean | null;
|
technicianAssigned?: boolean | null;
|
||||||
|
/** UI intent: an explicit clear overrides staged single-technician autoassignment. */
|
||||||
|
technicianSelectionCleared?: boolean;
|
||||||
/** Technicians registered inline; persisted under `vendorCompany` only on create. */
|
/** Technicians registered inline; persisted under `vendorCompany` only on create. */
|
||||||
newTechnicians?: WizardNewTechnician[];
|
newTechnicians?: WizardNewTechnician[];
|
||||||
techPhone: string;
|
techPhone: string;
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ describe("WizardStepVendorTime inline technician (SH-364)", () => {
|
||||||
vendorId: "52",
|
vendorId: "52",
|
||||||
vendorName: "Gateway Plumbing",
|
vendorName: "Gateway Plumbing",
|
||||||
technicianAssigned: false,
|
technicianAssigned: false,
|
||||||
|
technicianSelectionCleared: false,
|
||||||
techPhone: "",
|
techPhone: "",
|
||||||
vendorNotes: "",
|
vendorNotes: "",
|
||||||
newTechnicians: [],
|
newTechnicians: [],
|
||||||
|
|
@ -141,6 +142,7 @@ describe("WizardStepVendorTime inline technician (SH-364)", () => {
|
||||||
vendorId: "41",
|
vendorId: "41",
|
||||||
vendorName: "Vinewood LLC",
|
vendorName: "Vinewood LLC",
|
||||||
technicianAssigned: false,
|
technicianAssigned: false,
|
||||||
|
technicianSelectionCleared: false,
|
||||||
techPhone: "",
|
techPhone: "",
|
||||||
vendorNotes: "",
|
vendorNotes: "",
|
||||||
newTechnicians: [],
|
newTechnicians: [],
|
||||||
|
|
@ -170,10 +172,41 @@ describe("WizardStepVendorTime inline technician (SH-364)", () => {
|
||||||
vendorId: "41",
|
vendorId: "41",
|
||||||
vendorName: "Vinewood LLC",
|
vendorName: "Vinewood LLC",
|
||||||
technicianAssigned: true,
|
technicianAssigned: true,
|
||||||
|
technicianSelectionCleared: false,
|
||||||
techPhone: "",
|
techPhone: "",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("records explicit no-technician intent while leaving a staged technician untouched", async () => {
|
||||||
|
const onPatch = vi.fn();
|
||||||
|
render(
|
||||||
|
<WizardStepVendorTime
|
||||||
|
draft={draftWith({
|
||||||
|
vendorCompany: "Vinewood LLC",
|
||||||
|
vendorId: "41",
|
||||||
|
technicianAssigned: false,
|
||||||
|
newTechnicians: [{ name: "Jordan Lee", phone: "" }],
|
||||||
|
})}
|
||||||
|
vendors={vendors}
|
||||||
|
onUpdate={vi.fn()}
|
||||||
|
onPatch={onPatch}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "No technician" }));
|
||||||
|
const noTechnicianOptions = screen.getAllByRole("button", { name: "No technician" });
|
||||||
|
fireEvent.click(noTechnicianOptions[noTechnicianOptions.length - 1]!);
|
||||||
|
|
||||||
|
expect(onPatch).toHaveBeenCalledWith({
|
||||||
|
vendorId: "41",
|
||||||
|
vendorName: "Vinewood LLC",
|
||||||
|
technicianAssigned: false,
|
||||||
|
technicianSelectionCleared: true,
|
||||||
|
techPhone: "",
|
||||||
|
});
|
||||||
|
expect(screen.getByRole("group", { name: "New Technician 1" })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("retains the same company vendor id when clearing a second technician", async () => {
|
it("retains the same company vendor id when clearing a second technician", async () => {
|
||||||
const onPatch = vi.fn();
|
const onPatch = vi.fn();
|
||||||
const secondVinewoodTech = {
|
const secondVinewoodTech = {
|
||||||
|
|
@ -201,6 +234,7 @@ describe("WizardStepVendorTime inline technician (SH-364)", () => {
|
||||||
vendorId: "42",
|
vendorId: "42",
|
||||||
vendorName: "Vinewood LLC",
|
vendorName: "Vinewood LLC",
|
||||||
technicianAssigned: false,
|
technicianAssigned: false,
|
||||||
|
technicianSelectionCleared: true,
|
||||||
techPhone: "",
|
techPhone: "",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -327,6 +361,24 @@ describe("useWizardCreateWithTechnicians (SH-364)", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not autoassign a staged technician after No technician was explicitly chosen", () => {
|
||||||
|
const created = { ...vendors[0], id: 99, contactName: "Jordan Lee" };
|
||||||
|
expect(
|
||||||
|
buildCreatedTechniciansPatch(
|
||||||
|
draftWith({
|
||||||
|
vendorCompany: "Vinewood LLC",
|
||||||
|
vendorId: "41",
|
||||||
|
technicianAssigned: false,
|
||||||
|
technicianSelectionCleared: true,
|
||||||
|
newTechnicians: [{ name: "Jordan Lee", phone: "" }],
|
||||||
|
}),
|
||||||
|
"Vinewood LLC",
|
||||||
|
[{ name: "Jordan Lee", phone: "" }],
|
||||||
|
[created],
|
||||||
|
),
|
||||||
|
).toEqual({ newTechnicians: [] });
|
||||||
|
});
|
||||||
|
|
||||||
it("blocks Create and skips the roster save when a staged phone is not a valid number", async () => {
|
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 createMutation = { mutate: vi.fn(), isPending: false };
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,20 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
|
||||||
expect(payload).not.toHaveProperty("customer");
|
expect(payload).not.toHaveProperty("customer");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("omits wizard-only technician selection intent from the create payload", () => {
|
||||||
|
const payload = mapWizardDraftToBoardCreatePayload({
|
||||||
|
...EMPTY_WIZARD_DRAFT,
|
||||||
|
type: "PM",
|
||||||
|
siteCode: "BK5",
|
||||||
|
vendorId: "41",
|
||||||
|
technicianAssigned: false,
|
||||||
|
technicianSelectionCleared: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(payload).toEqual(expect.objectContaining({ vendorId: 41, technicianAssigned: false }));
|
||||||
|
expect(payload).not.toHaveProperty("technicianSelectionCleared");
|
||||||
|
});
|
||||||
|
|
||||||
it("allows create mapping without a service", () => {
|
it("allows create mapping without a service", () => {
|
||||||
const payload = mapWizardDraftToBoardCreatePayload({
|
const payload = mapWizardDraftToBoardCreatePayload({
|
||||||
...EMPTY_WIZARD_DRAFT,
|
...EMPTY_WIZARD_DRAFT,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue