From 2d54ff12f809d789ec361854ebbd3411779834c2 Mon Sep 17 00:00:00 2001 From: Codex Review Integration Date: Wed, 16 Sep 2026 21:22:07 -0300 Subject: [PATCH] fix(work-orders): show fixed copy for edit conflict toasts (SH-375) --- .../work-orders/errors/board-patch-errors.ts | 6 +++++ .../use-work-order-table-mutations.ts | 2 +- ...work-order-table-mutations-errors.test.tsx | 26 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/domain/work-orders/errors/board-patch-errors.ts b/src/domain/work-orders/errors/board-patch-errors.ts index 1dc215f5..02af77cb 100644 --- a/src/domain/work-orders/errors/board-patch-errors.ts +++ b/src/domain/work-orders/errors/board-patch-errors.ts @@ -44,6 +44,9 @@ export function boardPatchValidationMessage(error: BoardPatchValidationError): s return KNOWN_VALIDATION_COPY.get(error.code) ?? BOARD_PATCH_FAILED_MESSAGE; } +export const BOARD_PATCH_CONFLICT_MESSAGE = + "This work order was changed by someone else. Refresh to see the latest before saving again."; + export const PARTIAL_SAVE_PREFIX = "Some changes were saved, but the rest were not."; /** Statuses whose mapped copy is fixed product text, never the response body. */ @@ -51,6 +54,9 @@ const FIXED_COPY_STATUSES = new Set([401, 403, 404, 500]); /** why: any board edit failure toast must use product copy, never backend text or field keys. */ export function boardPatchErrorMessage(error: unknown): string { + if (error instanceof BoardPatchConflictError) { + return BOARD_PATCH_CONFLICT_MESSAGE; + } if (error instanceof BoardPatchValidationError) { return boardPatchValidationMessage(error); } 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 ce60ca81..c0fbbe65 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 @@ -156,7 +156,7 @@ export function useWorkOrderTableMutations( if (error instanceof BoardPatchConflictError && error.currentState) { onPatch(id, error.currentState); - toast.error(error.message || "Conflict — refreshed with latest data"); + toast.error(boardPatchErrorMessage(error)); } else { clearPatch(id); toast.error(boardPatchErrorMessage(error)); diff --git a/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-errors.test.tsx b/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-errors.test.tsx index 3dee6976..d4105ff0 100644 --- a/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-errors.test.tsx +++ b/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-errors.test.tsx @@ -4,6 +4,7 @@ import type { ReactNode } from "react"; import { toast } from "react-toastify"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { + BOARD_PATCH_CONFLICT_MESSAGE, BOARD_PATCH_FAILED_MESSAGE, DISPATCH_REQUIRED_MESSAGE, PARTIAL_SAVE_PREFIX, @@ -142,4 +143,29 @@ describe("useWorkOrderTableMutations edit error copy", () => { expect(message).toBe(`${PARTIAL_SAVE_PREFIX} ${DISPATCH_REQUIRED_MESSAGE}`); }); + + it("shows fixed conflict copy and still applies the latest row on a 409", async () => { + const onPatch = vi.fn(); + respondWith({ + status: 409, + body: { + message: "RowVersion mismatch for workOrderId 7 (expected v1, found v3).", + currentState: { id: 7, workOrderNumber: "WO-099", rowVersion: "v3" }, + }, + }); + + const message = await patchAndGetToast({ woNumber: "WO-100" }, 1, onPatch); + + expect(message).toBe(BOARD_PATCH_CONFLICT_MESSAGE); + expect(message).not.toMatch(/RowVersion|workOrderId|v3/); + expect(onPatch).toHaveBeenLastCalledWith(7, expect.objectContaining({ rowVersion: "v3" })); + }); + + it("shows fixed conflict copy when a 409 carries no latest row", async () => { + respondWith({ status: 409, body: { message: "RowVersion mismatch for workOrderId 7." } }); + + const message = await patchAndGetToast(); + + expect(message).toBe(BOARD_PATCH_CONFLICT_MESSAGE); + }); });