From 4a7da7cbd5589eef1963244474c4c64fff2c3c2f Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Wed, 30 Sep 2026 17:51:31 -0300 Subject: [PATCH] fix(workorders): retain compatible inactive services --- .../tabs/slide-over-info-tab-edit-view.tsx | 4 +- .../list/table/save-existing-row-type.ts | 4 +- .../utils/services-for-work-order-type.ts | 7 +- .../workorders/save-existing-row-type.test.ts | 28 ++++++-- ...tab-edit-view-additional-contacts.test.tsx | 70 +++++++++++++++++-- 5 files changed, 99 insertions(+), 14 deletions(-) diff --git a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-edit-view.tsx b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-edit-view.tsx index da8b4264..2a6afbb3 100644 --- a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-edit-view.tsx +++ b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-edit-view.tsx @@ -17,7 +17,7 @@ import type { import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; import type { WizardWOStatus } from "@/domain/work-orders/types/work-order-wizard"; import type { Service } from "@/domain/services/types/service"; -import { isServiceEligibleForWorkOrderType } from "@/domain/services/utils/services-for-work-order-type"; +import { serviceSupportsWorkOrderType } from "@/domain/services/utils/services-for-work-order-type"; import { serviceIdentityPatch } from "@/domain/work-orders/utils/service-identity-patch"; import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils"; import { toVendorTablePatch } from "@/domain/work-orders/utils/vendor-assignment-patch"; @@ -180,7 +180,7 @@ export function SlideOverInfoTabEditView({ onDraftChange( draft.serviceId != null && selectedService != null && - !isServiceEligibleForWorkOrderType(selectedService, type) + !serviceSupportsWorkOrderType(selectedService, type) ? { type, severity, serviceId: null, pm: "" } : { type, severity }, ); diff --git a/src/app/(protected)/workorders/_components/list/table/save-existing-row-type.ts b/src/app/(protected)/workorders/_components/list/table/save-existing-row-type.ts index bc32d6df..6e5e0bf7 100644 --- a/src/app/(protected)/workorders/_components/list/table/save-existing-row-type.ts +++ b/src/app/(protected)/workorders/_components/list/table/save-existing-row-type.ts @@ -6,7 +6,7 @@ import type { } from "@/domain/work-orders/types/work-order-table-row"; import type { WOType } from "@/domain/work-orders/types/work-order-wizard"; import type { Service } from "@/domain/services/types/service"; -import { isServiceEligibleForWorkOrderType } from "@/domain/services/utils/services-for-work-order-type"; +import { serviceSupportsWorkOrderType } from "@/domain/services/utils/services-for-work-order-type"; import { formatSeverityEscalationToast, shouldAnnounceSeverityEscalation, @@ -31,7 +31,7 @@ export function saveExistingRowType( const clearService = row.serviceId != null && selectedService != null && - !isServiceEligibleForWorkOrderType(selectedService, type); + !serviceSupportsWorkOrderType(selectedService, type); const patch = clearService ? { type, severity, serviceId: null, pm: "" } : { type, severity }; if (!shouldAnnounceSeverityEscalation(row.severity, severity)) { onPatch(row.id, patch); diff --git a/src/domain/services/utils/services-for-work-order-type.ts b/src/domain/services/utils/services-for-work-order-type.ts index c11641cf..6ba6096a 100644 --- a/src/domain/services/utils/services-for-work-order-type.ts +++ b/src/domain/services/utils/services-for-work-order-type.ts @@ -10,5 +10,10 @@ export function servicesForWorkOrderType(services: Service[], type: WOType | "") } export function isServiceEligibleForWorkOrderType(service: Service, type: WOType | ""): boolean { - return service.isActive && service.supportedWorkOrderTypes.includes(serviceWorkOrderType(type)); + return service.isActive && serviceSupportsWorkOrderType(service, type); +} + +/** Whether a service definition supports a WO type, regardless of active assignment status. */ +export function serviceSupportsWorkOrderType(service: Service, type: WOType | ""): boolean { + return service.supportedWorkOrderTypes.includes(serviceWorkOrderType(type)); } diff --git a/src/test/app/(protected)/workorders/save-existing-row-type.test.ts b/src/test/app/(protected)/workorders/save-existing-row-type.test.ts index 7754c9cf..11809966 100644 --- a/src/test/app/(protected)/workorders/save-existing-row-type.test.ts +++ b/src/test/app/(protected)/workorders/save-existing-row-type.test.ts @@ -78,16 +78,36 @@ describe("saveExistingRowType", () => { }, ]; - saveExistingRowType(onPatch, { ...row, serviceId: 701 }, "Reactive", null, services); + saveExistingRowType(onPatch, { ...row, serviceId: 701 }, "Emergency", null, services); expect(onPatch).toHaveBeenCalledWith(7, { - type: "Reactive", + type: "Emergency", severity: null, serviceId: null, pm: "", }); }); + it("preserves a known inactive historical service that supports the new type", () => { + const onPatch = vi.fn(); + const services: Service[] = [ + { + id: 702, + name: "Retired Multi-type Service", + trade: "Electrical", + iconKey: "wrench", + requiresCompletionDocument: false, + completionDocTemplate: null, + isActive: false, + supportedWorkOrderTypes: ["PM", "Reactive"], + }, + ]; + + saveExistingRowType(onPatch, { ...row, serviceId: 702 }, "Reactive", null, services); + + expect(onPatch).toHaveBeenCalledWith(7, { type: "Reactive", severity: null }); + }); + it("clears a known inactive historical service before type change", () => { const onPatch = vi.fn(); const services: Service[] = [ @@ -103,10 +123,10 @@ describe("saveExistingRowType", () => { }, ]; - saveExistingRowType(onPatch, { ...row, serviceId: 701 }, "Reactive", null, services); + saveExistingRowType(onPatch, { ...row, serviceId: 701 }, "Emergency", null, services); expect(onPatch).toHaveBeenCalledWith(7, { - type: "Reactive", + type: "Emergency", severity: null, serviceId: null, pm: "", diff --git a/src/test/app/(protected)/workorders/slide-over-info-tab-edit-view-additional-contacts.test.tsx b/src/test/app/(protected)/workorders/slide-over-info-tab-edit-view-additional-contacts.test.tsx index 19b7be33..8457d0ac 100644 --- a/src/test/app/(protected)/workorders/slide-over-info-tab-edit-view-additional-contacts.test.tsx +++ b/src/test/app/(protected)/workorders/slide-over-info-tab-edit-view-additional-contacts.test.tsx @@ -50,10 +50,19 @@ vi.mock( }), ); vi.mock("@/app/(protected)/workorders/_components/list/table/cells/type-cell", () => ({ - TypeCell: ({ onSave }: { onSave: (type: "Reactive", severity: number) => void }) => ( - + TypeCell: ({ + onSave, + }: { + onSave: (type: "Reactive" | "Emergency", severity: number) => void; + }) => ( + <> + + + ), })); vi.mock("@/app/(protected)/workorders/_components/list/table/cells/assigned-to-cell", () => ({ @@ -227,12 +236,63 @@ describe("SlideOverInfoTabEditView additional contacts", () => { } renderWithProviders(, { withAuth: false }); - fireEvent.click(screen.getByRole("button", { name: "Change type to Reactive" })); + fireEvent.click(screen.getByRole("button", { name: "Change type to Emergency" })); fireEvent.click(screen.getByRole("button", { name: "Save changes" })); expect(onSave).toHaveBeenCalledWith([ { field: "severity", value: "2" }, { field: "serviceId", value: "" }, + { field: "workOrderType", value: "Emergency" }, + ]); + }); + + it("preserves a compatible inactive service during a slide-over type change", () => { + const baseline = baseRow({ type: "PM", pm: "Historical service", serviceId: 702 }); + const onSave = vi.fn(); + const compatibleInactiveService = { + id: 702, + name: "Historical service", + trade: "Electrical", + iconKey: "wrench", + requiresCompletionDocument: false, + completionDocTemplate: null, + isActive: false, + supportedWorkOrderTypes: ["PM" as const, "Reactive" as const], + }; + + function SlideOverSaveHarness() { + const [draft, setDraft] = useState(baseline); + return ( + <> + setDraft((current) => ({ ...current, ...patch }))} + /> + + + ); + } + + renderWithProviders(, { withAuth: false }); + fireEvent.click(screen.getByRole("button", { name: "Change type to Reactive" })); + fireEvent.click(screen.getByRole("button", { name: "Save changes" })); + + expect(onSave).toHaveBeenCalledWith([ + { field: "severity", value: "2" }, { field: "workOrderType", value: "Reactive" }, ]); });