mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 03:02:03 +00:00
fix(workorders): chain saved versions when the panel detail never loaded
Keep the last saved row per work order so a queued panel save does not depend on the board-detail cache, and pass the completion-doc row as a fallback so saving a completion document works after the order leaves the list.
This commit is contained in:
parent
522fea4db7
commit
32b4ea0efa
4 changed files with 62 additions and 13 deletions
|
|
@ -157,7 +157,7 @@ export function useWorkOrdersListPage() {
|
||||||
generatedAt: data.generatedAt,
|
generatedAt: data.generatedAt,
|
||||||
};
|
};
|
||||||
setCompletionDocs((prev) => ({ ...prev, [String(row.id)]: nextData }));
|
setCompletionDocs((prev) => ({ ...prev, [String(row.id)]: nextData }));
|
||||||
const saved = await patchFieldAsync(row.id, { docStatus: status });
|
const saved = await patchFieldAsync(row.id, { docStatus: status }, undefined, row);
|
||||||
if (saved.kind !== "board" || !shouldCompleteAfterCompletionDoc(status, saved.row)) {
|
if (saved.kind !== "board" || !shouldCompleteAfterCompletionDoc(status, saved.row)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,19 +154,34 @@ function syncDetailRowVersion(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SavedRowSync = {
|
||||||
|
queryClient: QueryClient;
|
||||||
|
/** Last server row each patch returned, for panels whose detail query never cached data. */
|
||||||
|
savedRows: Map<string, WorkOrderTableRow>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function recordSavedRow(
|
||||||
|
{ queryClient, savedRows }: SavedRowSync,
|
||||||
|
id: string | number,
|
||||||
|
row: WorkOrderTableRow,
|
||||||
|
) {
|
||||||
|
savedRows.set(String(id), row);
|
||||||
|
syncDetailRowVersion(queryClient, id, row);
|
||||||
|
}
|
||||||
|
|
||||||
async function runPatchAndSyncDetail(
|
async function runPatchAndSyncDetail(
|
||||||
queryClient: QueryClient,
|
sync: SavedRowSync,
|
||||||
id: string | number,
|
id: string | number,
|
||||||
patch: WorkOrderTablePatch,
|
patch: WorkOrderTablePatch,
|
||||||
readBase: (id: string | number) => WorkOrderTableRow | undefined,
|
readBase: (id: string | number) => WorkOrderTableRow | undefined,
|
||||||
): Promise<PatchResult> {
|
): Promise<PatchResult> {
|
||||||
try {
|
try {
|
||||||
const result = await runWorkOrderTablePatch(id, patch, readBase);
|
const result = await runWorkOrderTablePatch(id, patch, readBase);
|
||||||
if (result.kind === "board") syncDetailRowVersion(queryClient, id, result.row);
|
if (result.kind === "board") recordSavedRow(sync, id, result.row);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof BoardPatchPartialSaveError) {
|
if (error instanceof BoardPatchPartialSaveError) {
|
||||||
syncDetailRowVersion(queryClient, id, error.partialRow);
|
recordSavedRow(sync, id, error.partialRow);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -189,6 +204,7 @@ export function useWorkOrderTableMutations(
|
||||||
const { onPatch, clearPatch, getRow } = handlers;
|
const { onPatch, clearPatch, getRow } = handlers;
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const enqueuePatch = useMemo(() => createWorkOrderPatchQueue(), []);
|
const enqueuePatch = useMemo(() => createWorkOrderPatchQueue(), []);
|
||||||
|
const savedRows = useMemo(() => new Map<string, WorkOrderTableRow>(), []);
|
||||||
|
|
||||||
const invalidate = () => {
|
const invalidate = () => {
|
||||||
void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all });
|
void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all });
|
||||||
|
|
@ -212,12 +228,16 @@ export function useWorkOrderTableMutations(
|
||||||
}) =>
|
}) =>
|
||||||
enqueuePatch(id, () =>
|
enqueuePatch(id, () =>
|
||||||
runPatchAndSyncDetail(
|
runPatchAndSyncDetail(
|
||||||
queryClient,
|
{ queryClient, savedRows },
|
||||||
id,
|
id,
|
||||||
persistPatch,
|
persistPatch,
|
||||||
baseRow
|
baseRow
|
||||||
? () => baseRow
|
? () => baseRow
|
||||||
: (rowId) => getRow?.(rowId) ?? readDetailRow(queryClient, rowId) ?? fallbackRow,
|
: (rowId) =>
|
||||||
|
getRow?.(rowId) ??
|
||||||
|
readDetailRow(queryClient, rowId) ??
|
||||||
|
savedRows.get(String(rowId)) ??
|
||||||
|
fallbackRow,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onMutate: async ({ id, persistPatch }) => {
|
onMutate: async ({ id, persistPatch }) => {
|
||||||
|
|
@ -248,9 +268,6 @@ export function useWorkOrderTableMutations(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const resolvePatch = (id: string | number, patch: WorkOrderTablePatch) =>
|
|
||||||
mergeAutoSchedulePatch(getRow?.(id), patch);
|
|
||||||
|
|
||||||
const patchField: WorkOrderTablePatchFn = (id, patch, options) => {
|
const patchField: WorkOrderTablePatchFn = (id, patch, options) => {
|
||||||
const fallbackRow = options?.fallbackRow;
|
const fallbackRow = options?.fallbackRow;
|
||||||
const persistPatch = mergeAutoSchedulePatch(getRow?.(id) ?? fallbackRow, patch);
|
const persistPatch = mergeAutoSchedulePatch(getRow?.(id) ?? fallbackRow, patch);
|
||||||
|
|
@ -264,9 +281,10 @@ export function useWorkOrderTableMutations(
|
||||||
id: string | number,
|
id: string | number,
|
||||||
patch: WorkOrderTablePatch,
|
patch: WorkOrderTablePatch,
|
||||||
baseRow?: WorkOrderTableRow,
|
baseRow?: WorkOrderTableRow,
|
||||||
|
fallbackRow?: WorkOrderTableRow,
|
||||||
) => {
|
) => {
|
||||||
const persistPatch = baseRow ? mergeAutoSchedulePatch(baseRow, patch) : resolvePatch(id, patch);
|
const persistPatch = mergeAutoSchedulePatch(baseRow ?? getRow?.(id) ?? fallbackRow, patch);
|
||||||
return patchMutation.mutateAsync({ id, patch, persistPatch, baseRow });
|
return patchMutation.mutateAsync({ id, patch, persistPatch, baseRow, fallbackRow });
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -131,10 +131,11 @@ describe("useWorkOrdersListPage completion document (SH-341)", () => {
|
||||||
row: { ...saved, status: "Completed" },
|
row: { ...saved, status: "Completed" },
|
||||||
});
|
});
|
||||||
const { result } = renderHook(() => useWorkOrdersListPage());
|
const { result } = renderHook(() => useWorkOrdersListPage());
|
||||||
|
const docRow = closableRow();
|
||||||
|
|
||||||
await result.current.saveCompletionDoc(closableRow(), { notes: "", generatedAt: "now" }, "Yes");
|
await result.current.saveCompletionDoc(docRow, { notes: "", generatedAt: "now" }, "Yes");
|
||||||
|
|
||||||
expect(patchFieldAsync).toHaveBeenNthCalledWith(1, 77, { docStatus: "Yes" });
|
expect(patchFieldAsync).toHaveBeenNthCalledWith(1, 77, { docStatus: "Yes" }, undefined, docRow);
|
||||||
expect(patchFieldAsync).toHaveBeenNthCalledWith(2, 77, { status: "Completed" }, saved);
|
expect(patchFieldAsync).toHaveBeenNthCalledWith(2, 77, { status: "Completed" }, saved);
|
||||||
expect(patchField).not.toHaveBeenCalled();
|
expect(patchField).not.toHaveBeenCalled();
|
||||||
expect(result.current.confirmComplete).toBeNull();
|
expect(result.current.confirmComplete).toBeNull();
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,36 @@ describe("useWorkOrderTableMutations for a work order outside the loaded list",
|
||||||
).toBe("saved-2");
|
).toBe("saved-2");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("chains the saved version when the panel's detail query never cached data", async () => {
|
||||||
|
let version = 0;
|
||||||
|
patchBoardField.mockImplementation(async () => {
|
||||||
|
version += 1;
|
||||||
|
return { ...DETAIL_ROW, rowVersion: `saved-${version}` };
|
||||||
|
});
|
||||||
|
const onSecondSuccess = vi.fn();
|
||||||
|
const { result } = renderHook(
|
||||||
|
() =>
|
||||||
|
useWorkOrderTableMutations({
|
||||||
|
onPatch: vi.fn(),
|
||||||
|
clearPatch: vi.fn(),
|
||||||
|
getRow: () => undefined,
|
||||||
|
}),
|
||||||
|
{ wrapper: makeWrapper() },
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.patchField(9, { dueDate: "2026-10-09" }, { fallbackRow: DETAIL_ROW });
|
||||||
|
result.current.patchField(
|
||||||
|
9,
|
||||||
|
{ dueDate: "2026-10-16" },
|
||||||
|
{ fallbackRow: DETAIL_ROW, onSuccess: onSecondSuccess },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => expect(onSecondSuccess).toHaveBeenCalled());
|
||||||
|
expect(patchBoardField.mock.calls[1][1]).toMatchObject({ workOrderVersion: "saved-1" });
|
||||||
|
});
|
||||||
|
|
||||||
it("prefers the table row when it still holds the work order", async () => {
|
it("prefers the table row when it still holds the work order", async () => {
|
||||||
const tableRow = { ...DETAIL_ROW, rowVersion: "table-version" };
|
const tableRow = { ...DETAIL_ROW, rowVersion: "table-version" };
|
||||||
const onSuccess = vi.fn();
|
const onSuccess = vi.fn();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue