mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 06:53:12 +00:00
fix(workorders): preserve partial POC edits
This commit is contained in:
parent
5b0c758a9f
commit
3b1da21abb
8 changed files with 59 additions and 18 deletions
|
|
@ -370,7 +370,7 @@ export const workOrdersApi = {
|
||||||
payload,
|
payload,
|
||||||
),
|
),
|
||||||
|
|
||||||
/** SH-379: manual POC override — same row/error contract as the board patch. */
|
/** Manual POC override — same row/error contract as the board patch. */
|
||||||
updatePoc: async (
|
updatePoc: async (
|
||||||
id: string | number,
|
id: string | number,
|
||||||
payload: WorkOrderPocPatchPayload,
|
payload: WorkOrderPocPatchPayload,
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,12 @@ export function isLocalOnlyTablePatch(patch: WorkOrderTablePatch): boolean {
|
||||||
return keys.length > 0 && keys.every((key) => LOCAL_ONLY_KEYS.has(key));
|
return keys.length > 0 && keys.every((key) => LOCAL_ONLY_KEYS.has(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** SH-379: composite manual POC override op routed to PATCH …/poc. */
|
/** Composite manual POC override op routed to PATCH …/poc. */
|
||||||
export type BoardPocOp = {
|
export type BoardPocOp = {
|
||||||
kind: "poc";
|
kind: "poc";
|
||||||
pocName: string;
|
pocName?: string;
|
||||||
pocPhone: string;
|
pocPhone?: string;
|
||||||
pocNotes: string;
|
pocNotes?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BoardOp = BoardFieldOp | BoardPocOp;
|
export type BoardOp = BoardFieldOp | BoardPocOp;
|
||||||
|
|
@ -70,9 +70,9 @@ function expandPocOp(patch: WorkOrderTablePatch): BoardPocOp[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
kind: "poc",
|
kind: "poc",
|
||||||
pocName: patch.pocName ?? "",
|
pocName: patch.pocName,
|
||||||
pocPhone: patch.pocPhone ?? "",
|
pocPhone: patch.pocPhone,
|
||||||
pocNotes: patch.pocNotes ?? "",
|
pocNotes: patch.pocNotes,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ export interface WorkOrderBoardPatchPayload {
|
||||||
primaryDispatchId?: number | null;
|
primaryDispatchId?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Payload for PATCH /workorders/{id}/poc (SH-379 manual POC override). */
|
/** Payload for PATCH /workorders/{id}/poc (manual POC override). */
|
||||||
export interface WorkOrderPocPatchPayload {
|
export interface WorkOrderPocPatchPayload {
|
||||||
pocName: string;
|
pocName: string;
|
||||||
pocPhone: string;
|
pocPhone: string;
|
||||||
|
|
|
||||||
|
|
@ -54,19 +54,23 @@ export type WorkOrderTablePatchHandlers = {
|
||||||
type PatchResult =
|
type PatchResult =
|
||||||
{ kind: "local"; patch: WorkOrderTablePatch } | { kind: "board"; row: WorkOrderTableRow };
|
{ kind: "local"; patch: WorkOrderTablePatch } | { kind: "board"; row: WorkOrderTableRow };
|
||||||
|
|
||||||
|
function isPocOp(op: BoardOp): op is Extract<BoardOp, { kind: "poc" }> {
|
||||||
|
return "kind" in op && op.kind === "poc";
|
||||||
|
}
|
||||||
|
|
||||||
async function applyBoardOp(
|
async function applyBoardOp(
|
||||||
id: string | number,
|
id: string | number,
|
||||||
op: BoardOp,
|
op: BoardOp,
|
||||||
row: WorkOrderTableRow,
|
row: WorkOrderTableRow,
|
||||||
): Promise<WorkOrderTableRow> {
|
): Promise<WorkOrderTableRow> {
|
||||||
if ("pocName" in op) {
|
if (isPocOp(op)) {
|
||||||
if (!row.rowVersion) {
|
if (!row.rowVersion) {
|
||||||
throw new Error("workOrderVersion is required for board patch");
|
throw new Error("workOrderVersion is required for board patch");
|
||||||
}
|
}
|
||||||
return workOrdersApi.updatePoc(id, {
|
return workOrdersApi.updatePoc(id, {
|
||||||
pocName: op.pocName,
|
pocName: op.pocName ?? row.pocName,
|
||||||
pocPhone: op.pocPhone,
|
pocPhone: op.pocPhone ?? row.pocPhone,
|
||||||
pocNotes: op.pocNotes,
|
pocNotes: op.pocNotes ?? row.pocNotes,
|
||||||
workOrderVersion: row.rowVersion,
|
workOrderVersion: row.rowVersion,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
/**
|
/**
|
||||||
* Fields compared for dirty state and included in Save patch.
|
* Fields compared for dirty state and included in Save patch.
|
||||||
* Only keys that map to board PATCH (or required companions like dispatcherName).
|
* Only keys that map to board PATCH (or required companions like dispatcherName).
|
||||||
* Primary POC scalars persist via the dedicated POC endpoint (SH-379);
|
* Primary POC scalars persist via the dedicated POC endpoint;
|
||||||
* tech phone is not board-editable — exclude so Save cannot no-op.
|
* tech phone is not board-editable — exclude so Save cannot no-op.
|
||||||
* additionalContacts is board-editable (SH-218 PATCH field).
|
* additionalContacts is board-editable (SH-218 PATCH field).
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ describe("isLocalOnlyTablePatch", () => {
|
||||||
expect(isLocalOnlyTablePatch({ scheduleEnd: "2026-06-12", schedMode: "range" })).toBe(true);
|
expect(isLocalOnlyTablePatch({ scheduleEnd: "2026-06-12", schedMode: "range" })).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not treat POC edits as local-only (SH-379)", () => {
|
it("does not treat POC edits as local-only", () => {
|
||||||
expect(isLocalOnlyTablePatch({ pocName: "Pat", pocPhone: "111", pocNotes: "call" })).toBe(
|
expect(isLocalOnlyTablePatch({ pocName: "Pat", pocPhone: "111", pocNotes: "call" })).toBe(
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
@ -202,7 +202,7 @@ describe("expandTablePatchToBoardFieldOps", () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("emits one composite POC op before scalar ops (SH-379)", () => {
|
it("emits one composite POC op before scalar ops", () => {
|
||||||
expect(
|
expect(
|
||||||
expandTablePatchToBoardFieldOps({
|
expandTablePatchToBoardFieldOps({
|
||||||
pocName: "Pat",
|
pocName: "Pat",
|
||||||
|
|
@ -213,7 +213,7 @@ describe("expandTablePatchToBoardFieldOps", () => {
|
||||||
{
|
{
|
||||||
kind: "poc",
|
kind: "poc",
|
||||||
pocName: "Pat",
|
pocName: "Pat",
|
||||||
pocPhone: "",
|
pocPhone: undefined,
|
||||||
pocNotes: "call first",
|
pocNotes: "call first",
|
||||||
},
|
},
|
||||||
{ field: "docStatus", value: "Yes" },
|
{ field: "docStatus", value: "Yes" },
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,14 @@ import type {
|
||||||
|
|
||||||
const getById = vi.fn();
|
const getById = vi.fn();
|
||||||
const update = vi.fn();
|
const update = vi.fn();
|
||||||
|
const updatePoc = vi.fn();
|
||||||
const patchBoardField = vi.fn();
|
const patchBoardField = vi.fn();
|
||||||
|
|
||||||
vi.mock("@/domain/work-orders/api/work-orders-api", () => ({
|
vi.mock("@/domain/work-orders/api/work-orders-api", () => ({
|
||||||
workOrdersApi: {
|
workOrdersApi: {
|
||||||
getById: (...args: unknown[]) => getById(...args),
|
getById: (...args: unknown[]) => getById(...args),
|
||||||
update: (...args: unknown[]) => update(...args),
|
update: (...args: unknown[]) => update(...args),
|
||||||
|
updatePoc: (...args: unknown[]) => updatePoc(...args),
|
||||||
patchBoardField: (...args: unknown[]) => patchBoardField(...args),
|
patchBoardField: (...args: unknown[]) => patchBoardField(...args),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
@ -121,6 +123,7 @@ describe("useWorkOrderTableMutations completion-doc persistence", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getById.mockReset();
|
getById.mockReset();
|
||||||
update.mockReset();
|
update.mockReset();
|
||||||
|
updatePoc.mockReset();
|
||||||
patchBoardField.mockReset();
|
patchBoardField.mockReset();
|
||||||
serverState = { ...BASE_SERVER };
|
serverState = { ...BASE_SERVER };
|
||||||
rowState = { ...BASE_ROW };
|
rowState = { ...BASE_ROW };
|
||||||
|
|
@ -829,6 +832,40 @@ describe("useWorkOrderTableMutations type and severity", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("useWorkOrderTableMutations POC persistence", () => {
|
||||||
|
it("fills omitted POC fields from the current row before the full replacement", async () => {
|
||||||
|
const currentRow = {
|
||||||
|
...BASE_ROW,
|
||||||
|
pocName: "Alex",
|
||||||
|
pocPhone: "555-0000",
|
||||||
|
pocNotes: "Gate",
|
||||||
|
};
|
||||||
|
updatePoc.mockResolvedValue({ ...currentRow, pocName: "Jordan", rowVersion: "wo-v2" });
|
||||||
|
|
||||||
|
const { result } = renderHook(
|
||||||
|
() =>
|
||||||
|
useWorkOrderTableMutations({
|
||||||
|
onPatch: vi.fn(),
|
||||||
|
clearPatch: () => undefined,
|
||||||
|
getRow: () => currentRow,
|
||||||
|
}),
|
||||||
|
{ wrapper: makeWrapper() },
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.patchField(42, { pocName: "Jordan" });
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => expect(updatePoc).toHaveBeenCalled());
|
||||||
|
expect(updatePoc).toHaveBeenCalledWith(42, {
|
||||||
|
pocName: "Jordan",
|
||||||
|
pocPhone: "555-0000",
|
||||||
|
pocNotes: "Gate",
|
||||||
|
workOrderVersion: "AAAAAAA=",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("useWorkOrderTableMutations audit cache refresh", () => {
|
describe("useWorkOrderTableMutations audit cache refresh", () => {
|
||||||
it("invalidates work-order queries after a successful status transition", async () => {
|
it("invalidates work-order queries after a successful status transition", async () => {
|
||||||
const client = new QueryClient({
|
const client = new QueryClient({
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ describe("slide-over-draft", () => {
|
||||||
expect(buildSlideOverPatch(vendorDraft, row)).toEqual({ vendorId: "v2" });
|
expect(buildSlideOverPatch(vendorDraft, row)).toEqual({ vendorId: "v2" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tracks POC edits for dirty state and patch (SH-379)", () => {
|
it("tracks POC edits for dirty state and patch", () => {
|
||||||
const row = baseRow();
|
const row = baseRow();
|
||||||
const draft = baseRow({ pocName: "Jordan", pocPhone: "555-0100" });
|
const draft = baseRow({ pocName: "Jordan", pocPhone: "555-0100" });
|
||||||
expect(isSlideOverDraftDirty(draft, row)).toBe(true);
|
expect(isSlideOverDraftDirty(draft, row)).toBe(true);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue