mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
fix(work-orders): clear stale technician drafts on selection changes
This commit is contained in:
parent
ee95847354
commit
712d8e0dc6
9 changed files with 137 additions and 11 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
import { expect, test, type Page, type Request, type Route } from "@playwright/test";
|
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
|
// 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).
|
// PATCH (never a whole-vendor POST that restates or blanks company contact fields).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
* 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({
|
export function useWizardCreateWithTechnicians({
|
||||||
vendors,
|
vendors,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,9 @@ export function WizardStepLocationServiceSelect({
|
||||||
onPatch,
|
onPatch,
|
||||||
}: WizardStepLocationServiceSelectProps) {
|
}: WizardStepLocationServiceSelectProps) {
|
||||||
const clearVendorIfPrimaryChanged = (nextPm: string): Partial<WorkOrderWizardDraft> =>
|
const clearVendorIfPrimaryChanged = (nextPm: string): Partial<WorkOrderWizardDraft> =>
|
||||||
nextPm !== draft.pm ? { vendorId: "", vendorName: "", techPhone: "" } : {};
|
nextPm !== draft.pm
|
||||||
|
? { vendorId: "", vendorName: "", techPhone: "", vendorCompany: "", newTechnicians: [] }
|
||||||
|
: {};
|
||||||
|
|
||||||
const selectSvc = (p: string) => {
|
const selectSvc = (p: string) => {
|
||||||
onPatch({
|
onPatch({
|
||||||
|
|
|
||||||
|
|
@ -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({
|
export function WizardVendorCompanyFields({
|
||||||
draft,
|
draft,
|
||||||
vendors,
|
vendors,
|
||||||
|
|
@ -79,8 +79,8 @@ export function WizardVendorCompanyFields({
|
||||||
onPick={(nextCompany) =>
|
onPick={(nextCompany) =>
|
||||||
onPatch({
|
onPatch({
|
||||||
vendorCompany: nextCompany,
|
vendorCompany: nextCompany,
|
||||||
...(selectedVendor != null && selectedVendor.companyName !== nextCompany
|
...(nextCompany !== company
|
||||||
? { vendorId: "", vendorName: "", techPhone: "" }
|
? { vendorId: "", vendorName: "", techPhone: "", newTechnicians: [] }
|
||||||
: {}),
|
: {}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
* 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.
|
* when the work order is created, so cancelling the wizard never creates a vendor.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ function takeAddedTechnician(
|
||||||
/**
|
/**
|
||||||
* Adds technicians to an existing vendor company through the additive roster PATCH.
|
* 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
|
* fields, which the create validator rejects (company phone/email) and which would
|
||||||
* overwrite company data. The additive path only appends technicians, keeps the
|
* overwrite company data. The additive path only appends technicians, keeps the
|
||||||
* company record untouched, and returns the persisted technician ids.
|
* company record untouched, and returns the persisted technician ids.
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,9 @@ export interface WorkOrderWizardDraft {
|
||||||
avetaRequired: boolean;
|
avetaRequired: boolean;
|
||||||
vendorId: string;
|
vendorId: string;
|
||||||
vendorName: 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;
|
vendorCompany?: string;
|
||||||
/** Technicians registered inline; persisted under `vendorCompany` only on create (SH-364). */
|
/** Technicians registered inline; persisted under `vendorCompany` only on create. */
|
||||||
newTechnicians?: WizardNewTechnician[];
|
newTechnicians?: WizardNewTechnician[];
|
||||||
techPhone: string;
|
techPhone: string;
|
||||||
vendorNotes: string;
|
vendorNotes: string;
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ describe("WizardStepLocationServiceSelect — vendor clear on service change", (
|
||||||
vendorId: "",
|
vendorId: "",
|
||||||
vendorName: "",
|
vendorName: "",
|
||||||
techPhone: "",
|
techPhone: "",
|
||||||
|
vendorCompany: "",
|
||||||
|
newTechnicians: [],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -83,6 +85,75 @@ describe("WizardStepLocationServiceSelect — vendor clear on service change", (
|
||||||
vendorId: "",
|
vendorId: "",
|
||||||
vendorName: "",
|
vendorName: "",
|
||||||
techPhone: "",
|
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(
|
||||||
|
<WizardStepLocationServiceSelect
|
||||||
|
draft={draft}
|
||||||
|
services={[
|
||||||
|
{ id: "1", value: "HVAC" },
|
||||||
|
{ id: "2", value: "Plumbing" },
|
||||||
|
]}
|
||||||
|
svcOpen={false}
|
||||||
|
onSvcOpenChange={vi.fn()}
|
||||||
|
svcSearch=""
|
||||||
|
onSvcSearchChange={vi.fn()}
|
||||||
|
onUpdate={vi.fn()}
|
||||||
|
onPatch={onPatch}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
<WizardStepLocationServiceSelect
|
||||||
|
draft={draft}
|
||||||
|
services={[{ id: "1", value: "HVAC" }]}
|
||||||
|
svcOpen={false}
|
||||||
|
onSvcOpenChange={vi.fn()}
|
||||||
|
svcSearch=""
|
||||||
|
onSvcSearchChange={vi.fn()}
|
||||||
|
onUpdate={vi.fn()}
|
||||||
|
onPatch={onPatch}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "HVAC" }));
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "HVAC" }));
|
||||||
|
|
||||||
|
expect(onPatch).toHaveBeenCalledWith({ pm: "HVAC", extraServices: [] });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,59 @@ describe("WizardStepVendorTime inline technician (SH-364)", () => {
|
||||||
expect(screen.getByRole("button", { name: "Adam Whyte" })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: "Adam Whyte" })).toBeInTheDocument();
|
||||||
expect(screen.queryByRole("button", { name: "Casey Pipe" })).not.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(
|
||||||
|
<WizardStepVendorTime
|
||||||
|
draft={draftWith({
|
||||||
|
vendorCompany: "Vinewood LLC",
|
||||||
|
vendorId: "41",
|
||||||
|
vendorName: "Vinewood LLC",
|
||||||
|
techPhone: "(314) 555-0100",
|
||||||
|
newTechnicians: [{ name: "Jordan Lee", phone: "(314) 555-0199" }],
|
||||||
|
})}
|
||||||
|
vendors={vendors}
|
||||||
|
onUpdate={vi.fn()}
|
||||||
|
onPatch={onPatch}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
<WizardStepVendorTime
|
||||||
|
draft={draftWith({
|
||||||
|
vendorCompany: "Vinewood LLC",
|
||||||
|
vendorId: "41",
|
||||||
|
vendorName: "Vinewood LLC",
|
||||||
|
techPhone: "(314) 555-0100",
|
||||||
|
newTechnicians: [{ name: "Jordan Lee", phone: "(314) 555-0199" }],
|
||||||
|
})}
|
||||||
|
vendors={vendors}
|
||||||
|
onUpdate={vi.fn()}
|
||||||
|
onPatch={onPatch}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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)", () => {
|
describe("useWizardCreateWithTechnicians (SH-364)", () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue