From 47cdca55c26644a8e3b7b7e9b4cb1098e5dcbbc5 Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Fri, 2 Oct 2026 00:11:11 -0300 Subject: [PATCH] fix(workorders): sync the whole saved row into the panel detail Copying only the version left the cached detail with pre-save fields, so a queued second panel save could send them with a fresh version and silently revert what the first save wrote (e.g. POC name). --- .../use-work-order-table-mutations.ts | 15 ++++--- ...rk-order-table-mutations-off-list.test.tsx | 42 +++++++++++++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/domain/work-orders/use-cases/use-work-order-table-mutations.ts b/src/domain/work-orders/use-cases/use-work-order-table-mutations.ts index 75d3313a..98fe8fd7 100644 --- a/src/domain/work-orders/use-cases/use-work-order-table-mutations.ts +++ b/src/domain/work-orders/use-cases/use-work-order-table-mutations.ts @@ -134,20 +134,19 @@ function readDetailRow( return queryClient.getQueryData(queryKeys.workOrders.boardDetail(id))?.info; } -/** Carry a saved row's version into the open panel's detail before the next queued patch runs. */ -function syncDetailRowVersion( - queryClient: QueryClient, - id: string | number, - row: WorkOrderTableRow, -) { +/** Carry a saved row into the open panel's detail before the next queued patch runs. */ +function syncDetailRow(queryClient: QueryClient, id: string | number, row: WorkOrderTableRow) { queryClient.setQueryData(queryKeys.workOrders.boardDetail(id), (detail) => detail && row.rowVersion ? { ...detail, + // The whole saved row, not just its version: a queued patch built from fresh + // version + stale fields would silently revert what the previous save wrote. info: { ...detail.info, - rowVersion: row.rowVersion, + ...row, dispatchRowVersion: row.dispatchRowVersion || detail.info.dispatchRowVersion, + mediaCount: detail.info.mediaCount, }, } : detail, @@ -166,7 +165,7 @@ function recordSavedRow( row: WorkOrderTableRow, ) { savedRows.set(String(id), row); - syncDetailRowVersion(queryClient, id, row); + syncDetailRow(queryClient, id, row); } async function runPatchAndSyncDetail( diff --git a/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-off-list.test.tsx b/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-off-list.test.tsx index 8c3c8d55..d2a42d08 100644 --- a/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-off-list.test.tsx +++ b/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-off-list.test.tsx @@ -7,10 +7,12 @@ import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-ta import { queryKeys } from "@/infra/query-key/query-key"; const patchBoardField = vi.fn(); +const updatePoc = vi.fn(); vi.mock("@/domain/work-orders/api/work-orders-api", () => ({ workOrdersApi: { patchBoardField: (...args: unknown[]) => patchBoardField(...args), + updatePoc: (...args: unknown[]) => updatePoc(...args), }, })); @@ -123,6 +125,46 @@ describe("useWorkOrderTableMutations for a work order outside the loaded list", ).toBe("saved-2"); }); + it("keeps the first save's fields when a queued second panel save patches the same group", async () => { + const client = makeClient(); + client.setQueryData(queryKeys.workOrders.boardDetail(9), { + info: { ...DETAIL_ROW, pocName: "Old POC", pocPhone: "111", pocNotes: "" }, + } as WorkOrderBoardDetail); + let version = 0; + updatePoc.mockImplementation( + async (_id: unknown, payload: { pocName: string; pocPhone: string; pocNotes: string }) => { + version += 1; + return { ...DETAIL_ROW, ...payload, rowVersion: `saved-${version}` }; + }, + ); + const onSecondSuccess = vi.fn(); + const { result } = renderHook( + () => + useWorkOrderTableMutations({ + onPatch: vi.fn(), + clearPatch: vi.fn(), + getRow: () => undefined, + }), + { wrapper: makeWrapper(client) }, + ); + + act(() => { + result.current.patchField(9, { pocName: "New POC" }, { fallbackRow: DETAIL_ROW }); + result.current.patchField( + 9, + { pocPhone: "555" }, + { fallbackRow: DETAIL_ROW, onSuccess: onSecondSuccess }, + ); + }); + + await waitFor(() => expect(onSecondSuccess).toHaveBeenCalled()); + expect(updatePoc.mock.calls[1][1]).toMatchObject({ + pocName: "New POC", + pocPhone: "555", + workOrderVersion: "saved-1", + }); + }); + it("chains the saved version when the panel's detail query never cached data", async () => { let version = 0; patchBoardField.mockImplementation(async () => {