fix(work-orders): enforce single-service wizard create per SH-118

Use single service selection in the wizard and omit extraServices from board create.
This commit is contained in:
arthur.bassi 2026-08-11 17:42:48 -03:00
parent 8bfa600301
commit 64c485025b
4 changed files with 52 additions and 63 deletions

View file

@ -28,18 +28,14 @@ export function WizardStepLocationServiceSelect({
onUpdate,
onPatch,
}: WizardStepLocationServiceSelectProps) {
const svcSel = [draft.pm, ...draft.extraServices].filter((p, i, a) => !!p && a.indexOf(p) === i);
const clearVendorIfPrimaryChanged = (nextPm: string): Partial<WorkOrderWizardDraft> =>
nextPm !== draft.pm ? { vendorId: "", vendorName: "", techPhone: "" } : {};
const toggleSvc = (p: string) => {
const next = svcSel.includes(p) ? svcSel.filter((x) => x !== p) : [...svcSel, p];
const nextPm = next[0] ?? "";
const selectSvc = (p: string) => {
onPatch({
pm: nextPm,
extraServices: next.slice(1),
...clearVendorIfPrimaryChanged(nextPm),
pm: p,
extraServices: [],
...clearVendorIfPrimaryChanged(p),
});
};
@ -63,13 +59,7 @@ export function WizardStepLocationServiceSelect({
),
};
})}
onPick={(p) =>
onPatch({
pm: p,
extraServices: draft.extraServices.filter((x) => x !== p),
...clearVendorIfPrimaryChanged(p),
})
}
onPick={selectSvc}
/>
{!svcOpen ? (
<button
@ -95,8 +85,8 @@ export function WizardStepLocationServiceSelect({
>
<WizardServiceNotesFields
services={services}
sel={svcSel}
onToggle={toggleSvc}
sel={draft.pm ? [draft.pm] : []}
onToggle={selectSvc}
note={draft.pmNote}
setNote={(v) => onUpdate("pmNote", v)}
search={svcSearch}

View file

@ -7,42 +7,7 @@ function trimOrUndefined(value: string | undefined): string | undefined {
return trimmed ? trimmed : undefined;
}
function normalizeExtraServices(
primary: string | undefined,
extras: string[],
): string[] | undefined {
if (extras.length === 0) {
return undefined;
}
const seen = new Set<string>();
const primaryKey = primary?.toLowerCase() ?? "";
const out: string[] = [];
for (const raw of extras) {
const trimmed = raw.trim();
if (!trimmed) {
continue;
}
const key = trimmed.toLowerCase();
if (key === primaryKey || seen.has(key)) {
continue;
}
seen.add(key);
out.push(trimmed);
}
return out.length > 0 ? out : undefined;
}
/** Mutates `payload` with primary/extra services, service notes, POC, tech phone, and vendor notes. */
/** Mutates `payload` with primary service, service notes, POC, tech phone, and vendor notes. */
export function assignWizardServicePocFields(
payload: WorkOrderBoardCreatePayload,
draft: WorkOrderWizardDraft,
@ -53,12 +18,6 @@ export function assignWizardServicePocFields(
payload.primaryService = primaryService;
}
const extraServices = normalizeExtraServices(primaryService, draft.extraServices);
if (extraServices) {
payload.extraServices = extraServices;
}
const serviceNotes = trimOrUndefined(draft.pmNote);
if (serviceNotes) {

View file

@ -39,10 +39,50 @@ describe("WizardStepLocationServiceSelect — vendor clear on service change", (
expect(onPatch).toHaveBeenCalledWith(
expect.objectContaining({
pm: "Plumbing",
extraServices: [],
vendorId: "",
vendorName: "",
techPhone: "",
}),
);
});
it("replaces the selected service in Service & Notes without persisting extraServices", () => {
const onPatch = vi.fn();
const draft: WorkOrderWizardDraft = {
...EMPTY_WIZARD_DRAFT,
pm: "HVAC",
extraServices: ["Plumbing"],
vendorId: "9",
vendorName: "Old Vendor",
techPhone: "555-0100",
};
render(
<WizardStepLocationServiceSelect
draft={draft}
services={[
{ id: "1", value: "HVAC" },
{ id: "2", value: "Plumbing" },
{ id: "3", value: "Electrical" },
]}
svcOpen={true}
onSvcOpenChange={vi.fn()}
svcSearch=""
onSvcSearchChange={vi.fn()}
onUpdate={vi.fn()}
onPatch={onPatch}
/>,
);
fireEvent.click(screen.getByRole("button", { name: "Electrical" }));
expect(onPatch).toHaveBeenCalledWith({
pm: "Electrical",
extraServices: [],
vendorId: "",
vendorName: "",
techPhone: "",
});
});
});

View file

@ -122,8 +122,6 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
primaryService: "HVAC PM",
extraServices: ["Filter change", "Coil clean"],
serviceNotes: "Check unit on roof",
pocName: "Jane Site Lead",
@ -141,6 +139,8 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
expect(payload.trade).toBeUndefined();
expect(payload.extraServices).toBeUndefined();
expect(payload.description).toBeUndefined();
expect(payload.scheduleWeekOnly).toBeUndefined();
@ -294,7 +294,7 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
expect(payload.vendorNotes).toBe("Also not in description");
});
it("maps service notes and extras as structured fields instead of description", () => {
it("maps service notes as structured fields and omits extraServices from wizard create", () => {
const payload = mapWizardDraftToBoardCreatePayload({
...EMPTY_WIZARD_DRAFT,
@ -311,7 +311,7 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
expect(payload.primaryService).toBe("HVAC PM");
expect(payload.extraServices).toEqual(["Filter change", "Coil clean"]);
expect(payload.extraServices).toBeUndefined();
expect(payload.serviceNotes).toBe("Check unit on roof");