From 712d8e0dc6123865dde9510b70c80e7da29db5b7 Mon Sep 17 00:00:00 2001 From: Codex Review Integration Date: Wed, 16 Sep 2026 16:50:26 -0300 Subject: [PATCH] fix(work-orders): clear stale technician drafts on selection changes --- e2e/work-orders/inline-technician.spec.ts | 2 +- .../use-wizard-create-with-technicians.ts | 4 +- .../wizard-step-location-service-select.tsx | 4 +- .../wizard/wizard-vendor-company-fields.tsx | 6 +- .../wizard/wizard-vendor-new-technicians.tsx | 2 +- .../use-add-technicians-to-company.ts | 2 +- .../work-orders/types/work-order-wizard.ts | 4 +- .../wizard-service-clears-vendor.test.tsx | 71 +++++++++++++++++++ .../wizard-vendor-inline-technician.test.tsx | 53 ++++++++++++++ 9 files changed, 137 insertions(+), 11 deletions(-) diff --git a/e2e/work-orders/inline-technician.spec.ts b/e2e/work-orders/inline-technician.spec.ts index b2dee631..bcac5250 100644 --- a/e2e/work-orders/inline-technician.spec.ts +++ b/e2e/work-orders/inline-technician.spec.ts @@ -1,6 +1,6 @@ import { expect, test, type Page, type Request, type Route } from "@playwright/test"; -// SH-364 / SH-365 / SH-366: technicians registered inline from work-order entry points +// Technicians registered inline from work-order entry points // become real vendor records only on save, through the additive vendor-company roster // PATCH (never a whole-vendor POST that restates or blanks company contact fields). diff --git a/src/app/(protected)/workorders/_components/wizard/use-wizard-create-with-technicians.ts b/src/app/(protected)/workorders/_components/wizard/use-wizard-create-with-technicians.ts index 29d874e6..fe0f1950 100644 --- a/src/app/(protected)/workorders/_components/wizard/use-wizard-create-with-technicians.ts +++ b/src/app/(protected)/workorders/_components/wizard/use-wizard-create-with-technicians.ts @@ -38,9 +38,9 @@ export function buildCreatedTechniciansPatch( } /** - * SH-364: persists technicians registered inline on the Vendor & time step under the + * Persists technicians registered inline on the Vendor & time step under the * selected company, then creates the work order. Nothing is written before Create, so - * cancelling the wizard leaves no stray vendor records (SH-366). + * cancelling the wizard leaves no stray vendor records. */ export function useWizardCreateWithTechnicians({ vendors, diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service-select.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service-select.tsx index bfefcf5d..c9b2c41a 100644 --- a/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service-select.tsx +++ b/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service-select.tsx @@ -29,7 +29,9 @@ export function WizardStepLocationServiceSelect({ onPatch, }: WizardStepLocationServiceSelectProps) { const clearVendorIfPrimaryChanged = (nextPm: string): Partial => - nextPm !== draft.pm ? { vendorId: "", vendorName: "", techPhone: "" } : {}; + nextPm !== draft.pm + ? { vendorId: "", vendorName: "", techPhone: "", vendorCompany: "", newTechnicians: [] } + : {}; const selectSvc = (p: string) => { onPatch({ diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-vendor-company-fields.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-vendor-company-fields.tsx index 1859fa0d..24cef2a4 100644 --- a/src/app/(protected)/workorders/_components/wizard/wizard-vendor-company-fields.tsx +++ b/src/app/(protected)/workorders/_components/wizard/wizard-vendor-company-fields.tsx @@ -34,7 +34,7 @@ function TechnicianAvatar({ vendor }: { vendor: VendorDropdownItem }) { ); } -/** Company-first vendor selection with inline technician registration (SH-364 prototype parity). */ +/** Company-first vendor selection with inline technician registration (prototype parity). */ export function WizardVendorCompanyFields({ draft, vendors, @@ -79,8 +79,8 @@ export function WizardVendorCompanyFields({ onPick={(nextCompany) => onPatch({ vendorCompany: nextCompany, - ...(selectedVendor != null && selectedVendor.companyName !== nextCompany - ? { vendorId: "", vendorName: "", techPhone: "" } + ...(nextCompany !== company + ? { vendorId: "", vendorName: "", techPhone: "", newTechnicians: [] } : {}), }) } diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-vendor-new-technicians.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-vendor-new-technicians.tsx index 7e68e39b..4496a6cb 100644 --- a/src/app/(protected)/workorders/_components/wizard/wizard-vendor-new-technicians.tsx +++ b/src/app/(protected)/workorders/_components/wizard/wizard-vendor-new-technicians.tsx @@ -12,7 +12,7 @@ type WizardVendorNewTechniciansProps = { }; /** - * SH-364: inline technician registration on the wizard's Vendor & time step. + * Inline technician registration on the wizard's Vendor & time step. * Rows stay local to the draft; they are persisted under the selected company only * when the work order is created, so cancelling the wizard never creates a vendor. */ diff --git a/src/domain/vendors/use-cases/use-add-technicians-to-company.ts b/src/domain/vendors/use-cases/use-add-technicians-to-company.ts index 477cc0cb..7085d2d8 100644 --- a/src/domain/vendors/use-cases/use-add-technicians-to-company.ts +++ b/src/domain/vendors/use-cases/use-add-technicians-to-company.ts @@ -31,7 +31,7 @@ function takeAddedTechnician( /** * Adds technicians to an existing vendor company through the additive roster PATCH. * - * SH-365: work-order entry points used to POST a whole new vendor with blank company + * Work-order entry points used to POST a whole new vendor with blank company * fields, which the create validator rejects (company phone/email) and which would * overwrite company data. The additive path only appends technicians, keeps the * company record untouched, and returns the persisted technician ids. diff --git a/src/domain/work-orders/types/work-order-wizard.ts b/src/domain/work-orders/types/work-order-wizard.ts index e93cbd6a..dc657c92 100644 --- a/src/domain/work-orders/types/work-order-wizard.ts +++ b/src/domain/work-orders/types/work-order-wizard.ts @@ -50,9 +50,9 @@ export interface WorkOrderWizardDraft { avetaRequired: boolean; vendorId: string; vendorName: string; - /** Company picked on the Vendor & time step; scopes the technician list (SH-364). */ + /** Company picked on the Vendor & time step; scopes the technician list. */ vendorCompany?: string; - /** Technicians registered inline; persisted under `vendorCompany` only on create (SH-364). */ + /** Technicians registered inline; persisted under `vendorCompany` only on create. */ newTechnicians?: WizardNewTechnician[]; techPhone: string; vendorNotes: string; diff --git a/src/test/app/(protected)/workorders/wizard-service-clears-vendor.test.tsx b/src/test/app/(protected)/workorders/wizard-service-clears-vendor.test.tsx index 3812fb63..c227d9d0 100644 --- a/src/test/app/(protected)/workorders/wizard-service-clears-vendor.test.tsx +++ b/src/test/app/(protected)/workorders/wizard-service-clears-vendor.test.tsx @@ -43,6 +43,8 @@ describe("WizardStepLocationServiceSelect — vendor clear on service change", ( vendorId: "", vendorName: "", techPhone: "", + vendorCompany: "", + newTechnicians: [], }), ); }); @@ -83,6 +85,75 @@ describe("WizardStepLocationServiceSelect — vendor clear on service change", ( vendorId: "", vendorName: "", techPhone: "", + vendorCompany: "", + newTechnicians: [], }); }); + + it("clears the vendor company and staged inline technicians when the service changes", () => { + const onPatch = vi.fn(); + const draft: WorkOrderWizardDraft = { + ...EMPTY_WIZARD_DRAFT, + pm: "HVAC", + vendorCompany: "Vinewood LLC", + newTechnicians: [{ name: "Jordan Lee", phone: "(314) 555-0199" }], + }; + + render( + , + ); + + fireEvent.click(screen.getByRole("button", { name: "HVAC" })); + fireEvent.click(screen.getByRole("button", { name: "Plumbing" })); + + expect(onPatch).toHaveBeenCalledWith({ + pm: "Plumbing", + extraServices: [], + vendorId: "", + vendorName: "", + techPhone: "", + vendorCompany: "", + newTechnicians: [], + }); + }); + + it("keeps vendor company and staged technicians when the service is unchanged", () => { + const onPatch = vi.fn(); + const draft: WorkOrderWizardDraft = { + ...EMPTY_WIZARD_DRAFT, + pm: "HVAC", + vendorCompany: "Vinewood LLC", + newTechnicians: [{ name: "Jordan Lee", phone: "(314) 555-0199" }], + }; + + render( + , + ); + + fireEvent.click(screen.getByRole("button", { name: "HVAC" })); + fireEvent.click(screen.getByRole("button", { name: "HVAC" })); + + expect(onPatch).toHaveBeenCalledWith({ pm: "HVAC", extraServices: [] }); + }); }); diff --git a/src/test/app/(protected)/workorders/wizard-vendor-inline-technician.test.tsx b/src/test/app/(protected)/workorders/wizard-vendor-inline-technician.test.tsx index ca694615..96d7b612 100644 --- a/src/test/app/(protected)/workorders/wizard-vendor-inline-technician.test.tsx +++ b/src/test/app/(protected)/workorders/wizard-vendor-inline-technician.test.tsx @@ -87,6 +87,59 @@ describe("WizardStepVendorTime inline technician (SH-364)", () => { expect(screen.getByRole("button", { name: "Adam Whyte" })).toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Casey Pipe" })).not.toBeInTheDocument(); }); + + it("clears the picked vendor and staged technicians when the company changes", () => { + const onPatch = vi.fn(); + render( + , + ); + + fireEvent.click(screen.getByRole("button", { name: "Vinewood LLC" })); + fireEvent.click(screen.getByRole("button", { name: "Gateway Plumbing" })); + + expect(onPatch).toHaveBeenCalledWith({ + vendorCompany: "Gateway Plumbing", + vendorId: "", + vendorName: "", + techPhone: "", + newTechnicians: [], + }); + }); + + it("preserves the picked vendor and staged technicians when the same company is re-picked", () => { + const onPatch = vi.fn(); + render( + , + ); + + fireEvent.click(screen.getByRole("button", { name: "Vinewood LLC" })); + const companyOptions = screen.getAllByRole("button", { name: "Vinewood LLC" }); + fireEvent.click(companyOptions[companyOptions.length - 1]); + + expect(onPatch).toHaveBeenCalledWith({ vendorCompany: "Vinewood LLC" }); + }); }); describe("useWizardCreateWithTechnicians (SH-364)", () => {