mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
fix(work-orders): use completedDate in CompDoc without lifecycle Completed
This commit is contained in:
parent
e1daf94be2
commit
5ea79ce8d1
12 changed files with 260 additions and 55 deletions
|
|
@ -4,7 +4,6 @@ import { dialogBlurBackdropSlotProps } from "@/components/ui/dialog-blur-backdro
|
||||||
import { Text } from "@/components/ui/text";
|
import { Text } from "@/components/ui/text";
|
||||||
import type { CompDocData, CompDocSaveStatus } from "@/domain/work-orders/types/completion-doc";
|
import type { CompDocData, CompDocSaveStatus } from "@/domain/work-orders/types/completion-doc";
|
||||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||||
import type { WizardWOStatus } from "@/domain/work-orders/types/work-order-wizard";
|
|
||||||
import { CompDocDialogFooter } from "./comp-doc-dialog-footer";
|
import { CompDocDialogFooter } from "./comp-doc-dialog-footer";
|
||||||
import { CompDocDialogHeader } from "./comp-doc-dialog-header";
|
import { CompDocDialogHeader } from "./comp-doc-dialog-header";
|
||||||
import { CompDocDialogResourceLinks } from "./comp-doc-dialog-resource-links";
|
import { CompDocDialogResourceLinks } from "./comp-doc-dialog-resource-links";
|
||||||
|
|
@ -18,7 +17,6 @@ type CompDocDialogProps = {
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
data?: CompDocData;
|
data?: CompDocData;
|
||||||
onSave: (data: CompDocData, status: CompDocSaveStatus) => void;
|
onSave: (data: CompDocData, status: CompDocSaveStatus) => void;
|
||||||
onStatusChange?: (status: WizardWOStatus) => void;
|
|
||||||
onDateCompletedChange?: (date: string) => void;
|
onDateCompletedChange?: (date: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -28,7 +26,6 @@ export function CompDocDialog({
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
data,
|
data,
|
||||||
onSave,
|
onSave,
|
||||||
onStatusChange,
|
|
||||||
onDateCompletedChange,
|
onDateCompletedChange,
|
||||||
}: CompDocDialogProps) {
|
}: CompDocDialogProps) {
|
||||||
const dialog = useCompDocDialog({
|
const dialog = useCompDocDialog({
|
||||||
|
|
@ -36,7 +33,6 @@ export function CompDocDialog({
|
||||||
open,
|
open,
|
||||||
data,
|
data,
|
||||||
onSave,
|
onSave,
|
||||||
onStatusChange,
|
|
||||||
onDateCompletedChange,
|
onDateCompletedChange,
|
||||||
});
|
});
|
||||||
const handleClose = dialog.isUploading ? undefined : () => onOpenChange(false);
|
const handleClose = dialog.isUploading ? undefined : () => onOpenChange(false);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import type {
|
||||||
DocState,
|
DocState,
|
||||||
} from "@/domain/work-orders/types/completion-doc";
|
} from "@/domain/work-orders/types/completion-doc";
|
||||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||||
import type { WizardWOStatus } from "@/domain/work-orders/types/work-order-wizard";
|
|
||||||
import { useWorkOrderBoardDetail } from "@/domain/work-orders/use-cases/use-work-order-detail";
|
import { useWorkOrderBoardDetail } from "@/domain/work-orders/use-cases/use-work-order-detail";
|
||||||
import { useUploadCompletionDoc } from "@/domain/work-orders/use-cases/use-work-order-mutations";
|
import { useUploadCompletionDoc } from "@/domain/work-orders/use-cases/use-work-order-mutations";
|
||||||
import { resolveCompDocSite } from "@/domain/work-orders/utils/completion-doc-site";
|
import { resolveCompDocSite } from "@/domain/work-orders/utils/completion-doc-site";
|
||||||
|
|
@ -25,7 +24,6 @@ type UseCompDocDialogArgs = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
data?: CompDocData;
|
data?: CompDocData;
|
||||||
onSave: (data: CompDocData, status: CompDocSaveStatus) => void;
|
onSave: (data: CompDocData, status: CompDocSaveStatus) => void;
|
||||||
onStatusChange?: (status: WizardWOStatus) => void;
|
|
||||||
onDateCompletedChange?: (date: string) => void;
|
onDateCompletedChange?: (date: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -61,7 +59,6 @@ export function useCompDocDialog({
|
||||||
open,
|
open,
|
||||||
data,
|
data,
|
||||||
onSave,
|
onSave,
|
||||||
onStatusChange,
|
|
||||||
onDateCompletedChange,
|
onDateCompletedChange,
|
||||||
}: UseCompDocDialogArgs) {
|
}: UseCompDocDialogArgs) {
|
||||||
const locId = String(row.locationId || "");
|
const locId = String(row.locationId || "");
|
||||||
|
|
@ -90,12 +87,12 @@ export function useCompDocDialog({
|
||||||
setNotes(data?.notes ?? "");
|
setNotes(data?.notes ?? "");
|
||||||
setDocState(docStateOf(completion?.docStatus || info.docStatus));
|
setDocState(docStateOf(completion?.docStatus || info.docStatus));
|
||||||
setGeneratedAt(data?.generatedAt ?? "");
|
setGeneratedAt(data?.generatedAt ?? "");
|
||||||
setEditDateCompleted(info.scheduledOn || "");
|
setEditDateCompleted(info.completedDate || "");
|
||||||
}, [
|
}, [
|
||||||
open,
|
open,
|
||||||
info.id,
|
info.id,
|
||||||
info.docStatus,
|
info.docStatus,
|
||||||
info.scheduledOn,
|
info.completedDate,
|
||||||
completion?.docStatus,
|
completion?.docStatus,
|
||||||
data?.notes,
|
data?.notes,
|
||||||
data?.generatedAt,
|
data?.generatedAt,
|
||||||
|
|
@ -122,7 +119,7 @@ export function useCompDocDialog({
|
||||||
};
|
};
|
||||||
|
|
||||||
const syncDateIfChanged = () => {
|
const syncDateIfChanged = () => {
|
||||||
if (editDateCompleted !== info.scheduledOn) {
|
if (editDateCompleted !== (info.completedDate || "")) {
|
||||||
onDateCompletedChange?.(editDateCompleted);
|
onDateCompletedChange?.(editDateCompleted);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -149,7 +146,6 @@ export function useCompDocDialog({
|
||||||
setDocState("generated");
|
setDocState("generated");
|
||||||
persist({ generatedAt: ts }, "Yes");
|
persist({ generatedAt: ts }, "Yes");
|
||||||
syncDateIfChanged();
|
syncDateIfChanged();
|
||||||
onStatusChange?.("Completed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { handleDownloadPdf, handleUploadPdf } = useCompDocPdfActions({
|
const { handleDownloadPdf, handleUploadPdf } = useCompDocPdfActions({
|
||||||
|
|
|
||||||
|
|
@ -148,8 +148,9 @@ export default function WorkOrdersListPage() {
|
||||||
}}
|
}}
|
||||||
data={page.completionDocs[String(activeDocRow.id)]}
|
data={page.completionDocs[String(activeDocRow.id)]}
|
||||||
onSave={(data, status) => page.saveCompletionDoc(activeDocRow, data, status)}
|
onSave={(data, status) => page.saveCompletionDoc(activeDocRow, data, status)}
|
||||||
onStatusChange={(status) => page.patchField(activeDocRow.id, { status })}
|
onDateCompletedChange={(date) =>
|
||||||
onDateCompletedChange={(date) => page.patchField(activeDocRow.id, { scheduledOn: date })}
|
page.patchField(activeDocRow.id, { completedDate: date })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,7 @@ export function mapWorkOrderTableRow(raw: unknown): WorkOrderTableRow {
|
||||||
dispatcherId,
|
dispatcherId,
|
||||||
dispatcherName,
|
dispatcherName,
|
||||||
scheduledOn,
|
scheduledOn,
|
||||||
|
completedDate: toDateInputValue(readString(item, "completedDate", "CompletedDate")),
|
||||||
targetWeek: toDateInputValue(readString(item, "targetWeek", "TargetWeek")),
|
targetWeek: toDateInputValue(readString(item, "targetWeek", "TargetWeek")),
|
||||||
dayGroup,
|
dayGroup,
|
||||||
dayLabel,
|
dayLabel,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ export interface WorkOrderTableRow {
|
||||||
dispatcherId: string;
|
dispatcherId: string;
|
||||||
dispatcherName: string;
|
dispatcherName: string;
|
||||||
scheduledOn: string;
|
scheduledOn: string;
|
||||||
|
/** Work completed date (API CompletedDate); distinct from scheduledOn. */
|
||||||
|
completedDate: string;
|
||||||
targetWeek: string;
|
targetWeek: string;
|
||||||
dayGroup: string;
|
dayGroup: string;
|
||||||
dayLabel: string;
|
dayLabel: string;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@ import {
|
||||||
expandTablePatchToBoardFieldOps,
|
expandTablePatchToBoardFieldOps,
|
||||||
isLocalOnlyTablePatch,
|
isLocalOnlyTablePatch,
|
||||||
} from "@/domain/work-orders/mappers/work-order-board-patch-mapper";
|
} from "@/domain/work-orders/mappers/work-order-board-patch-mapper";
|
||||||
import { mapWorkOrderTableRow } from "@/domain/work-orders/mappers/work-order-mapper";
|
import {
|
||||||
|
mapWorkOrderDetailToFormValues,
|
||||||
|
mapWorkOrderTableRow,
|
||||||
|
} from "@/domain/work-orders/mappers/work-order-mapper";
|
||||||
import type {
|
import type {
|
||||||
WorkOrderTablePatch,
|
WorkOrderTablePatch,
|
||||||
WorkOrderTableRow,
|
WorkOrderTableRow,
|
||||||
|
|
@ -28,6 +31,96 @@ export type WorkOrderTablePatchHandlers = {
|
||||||
getRow?: (id: string | number) => WorkOrderTableRow | undefined;
|
getRow?: (id: string | number) => WorkOrderTableRow | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PatchResult =
|
||||||
|
{ kind: "local"; patch: WorkOrderTablePatch } | { kind: "board"; row: WorkOrderTableRow };
|
||||||
|
|
||||||
|
async function persistCompletedDate(id: string | number, completedDate: string): Promise<void> {
|
||||||
|
const detail = await workOrdersApi.getById(id);
|
||||||
|
const values = mapWorkOrderDetailToFormValues(detail);
|
||||||
|
await workOrdersApi.update(id, { ...values, completedDate });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyBoardFieldOps(
|
||||||
|
id: string | number,
|
||||||
|
patch: WorkOrderTablePatch,
|
||||||
|
getRow: ((id: string | number) => WorkOrderTableRow | undefined) | undefined,
|
||||||
|
): Promise<WorkOrderTableRow> {
|
||||||
|
const ops = expandTablePatchToBoardFieldOps(patch);
|
||||||
|
if (ops.length === 0) {
|
||||||
|
throw new BoardPatchValidationError(
|
||||||
|
"NoPersistableFields",
|
||||||
|
"These changes cannot be saved on the board. Refresh and edit a supported field.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = getRow?.(id);
|
||||||
|
if (!current?.rowVersion) {
|
||||||
|
throw new Error("Missing work order version for board patch. Refresh and retry.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let latest = current;
|
||||||
|
let completedOps = 0;
|
||||||
|
for (const op of ops) {
|
||||||
|
try {
|
||||||
|
latest = await workOrdersApi.patchBoardField(id, buildBoardPatchPayload(op, latest));
|
||||||
|
completedOps += 1;
|
||||||
|
} catch (error) {
|
||||||
|
if (completedOps > 0) {
|
||||||
|
const field = op.field;
|
||||||
|
const partial = new Error(
|
||||||
|
`Schedule update partially saved (${completedOps}/${ops.length} fields). Failed on "${field}": ${
|
||||||
|
error instanceof Error ? error.message : "Unknown error"
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
(partial as Error & { partialRow?: WorkOrderTableRow }).partialRow = latest;
|
||||||
|
throw partial;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return latest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** CompDoc completedDate uses EditWorkorder; board fields use PATCH …/board. */
|
||||||
|
async function runWorkOrderTablePatch(
|
||||||
|
id: string | number,
|
||||||
|
patch: WorkOrderTablePatch,
|
||||||
|
getRow: ((id: string | number) => WorkOrderTableRow | undefined) | undefined,
|
||||||
|
): Promise<PatchResult> {
|
||||||
|
if (isLocalOnlyTablePatch(patch)) {
|
||||||
|
return { kind: "local", patch };
|
||||||
|
}
|
||||||
|
|
||||||
|
const completedDate = patch.completedDate;
|
||||||
|
const boardPatch: WorkOrderTablePatch = { ...patch };
|
||||||
|
delete boardPatch.completedDate;
|
||||||
|
|
||||||
|
let boardRow: WorkOrderTableRow | undefined;
|
||||||
|
const hasBoardKeys = Object.keys(boardPatch).length > 0;
|
||||||
|
if (hasBoardKeys && !isLocalOnlyTablePatch(boardPatch)) {
|
||||||
|
boardRow = await applyBoardFieldOps(id, boardPatch, getRow);
|
||||||
|
} else if (!hasBoardKeys && completedDate === undefined) {
|
||||||
|
throw new BoardPatchValidationError(
|
||||||
|
"NoPersistableFields",
|
||||||
|
"These changes cannot be saved on the board. Refresh and edit a supported field.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completedDate !== undefined) {
|
||||||
|
await persistCompletedDate(id, completedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boardRow) {
|
||||||
|
return {
|
||||||
|
kind: "board",
|
||||||
|
row: completedDate !== undefined ? { ...boardRow, completedDate } : boardRow,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { kind: "local", patch };
|
||||||
|
}
|
||||||
|
|
||||||
export function useWorkOrderTableMutations(
|
export function useWorkOrderTableMutations(
|
||||||
onPatchOrHandlers:
|
onPatchOrHandlers:
|
||||||
((id: string | number, patch: WorkOrderTablePatch) => void) | WorkOrderTablePatchHandlers,
|
((id: string | number, patch: WorkOrderTablePatch) => void) | WorkOrderTablePatchHandlers,
|
||||||
|
|
@ -50,47 +143,8 @@ export function useWorkOrderTableMutations(
|
||||||
};
|
};
|
||||||
|
|
||||||
const patchMutation = useMutation({
|
const patchMutation = useMutation({
|
||||||
mutationFn: async ({ id, patch }: { id: string | number; patch: WorkOrderTablePatch }) => {
|
mutationFn: ({ id, patch }: { id: string | number; patch: WorkOrderTablePatch }) =>
|
||||||
if (isLocalOnlyTablePatch(patch)) {
|
runWorkOrderTablePatch(id, patch, getRow),
|
||||||
return { kind: "local" as const, patch };
|
|
||||||
}
|
|
||||||
|
|
||||||
const ops = expandTablePatchToBoardFieldOps(patch);
|
|
||||||
if (ops.length === 0) {
|
|
||||||
throw new BoardPatchValidationError(
|
|
||||||
"NoPersistableFields",
|
|
||||||
"These changes cannot be saved on the board. Refresh and edit a supported field.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const current = getRow?.(id);
|
|
||||||
if (!current?.rowVersion) {
|
|
||||||
throw new Error("Missing work order version for board patch. Refresh and retry.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let latest = current;
|
|
||||||
let completedOps = 0;
|
|
||||||
for (const op of ops) {
|
|
||||||
try {
|
|
||||||
latest = await workOrdersApi.patchBoardField(id, buildBoardPatchPayload(op, latest));
|
|
||||||
completedOps += 1;
|
|
||||||
} catch (error) {
|
|
||||||
if (completedOps > 0) {
|
|
||||||
const field = op.field;
|
|
||||||
const partial = new Error(
|
|
||||||
`Schedule update partially saved (${completedOps}/${ops.length} fields). Failed on "${field}": ${
|
|
||||||
error instanceof Error ? error.message : "Unknown error"
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
(partial as Error & { partialRow?: WorkOrderTableRow }).partialRow = latest;
|
|
||||||
throw partial;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { kind: "board" as const, row: latest };
|
|
||||||
},
|
|
||||||
onMutate: async ({ id, patch }) => {
|
onMutate: async ({ id, patch }) => {
|
||||||
onPatch(id, patch);
|
onPatch(id, patch);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { CompDocDialog } from "@/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog";
|
||||||
|
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||||
|
|
||||||
|
vi.mock("@/domain/locations/use-cases/use-location-detail", () => ({
|
||||||
|
useLocationDetail: () => ({ data: undefined }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/domain/work-orders/use-cases/use-work-order-detail", () => ({
|
||||||
|
useWorkOrderBoardDetail: () => ({ data: undefined, isLoading: false }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/domain/work-orders/use-cases/use-work-order-mutations", () => ({
|
||||||
|
useUploadCompletionDoc: () => ({ mutate: vi.fn(), isPending: false }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("react-toastify", () => ({
|
||||||
|
toast: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SCHEDULED_ON = "2026-06-01";
|
||||||
|
|
||||||
|
function baseRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow {
|
||||||
|
return {
|
||||||
|
id: 42,
|
||||||
|
woNumber: "WO-42",
|
||||||
|
site: "SEA1",
|
||||||
|
type: "PM",
|
||||||
|
dispatcherId: "u1",
|
||||||
|
dispatcherName: "Dispatcher",
|
||||||
|
scheduledOn: SCHEDULED_ON,
|
||||||
|
completedDate: "",
|
||||||
|
targetWeek: "",
|
||||||
|
dayGroup: "",
|
||||||
|
dayLabel: "",
|
||||||
|
location: "Seattle",
|
||||||
|
locationId: "mock-loc-1",
|
||||||
|
pm: "HVAC",
|
||||||
|
serviceNotes: "",
|
||||||
|
extraServices: [],
|
||||||
|
vendorId: "",
|
||||||
|
company: "Vendor Co",
|
||||||
|
tech: "Tech One",
|
||||||
|
techPhone: "",
|
||||||
|
apptStart: "",
|
||||||
|
apptEnd: "",
|
||||||
|
apptTime: "",
|
||||||
|
status: "Scheduled",
|
||||||
|
docStatus: "No",
|
||||||
|
pocName: "Amazon Rep",
|
||||||
|
pocPhone: "",
|
||||||
|
pocNotes: "",
|
||||||
|
dueDate: "",
|
||||||
|
rescheduleCount: 0,
|
||||||
|
carriedOver: 0,
|
||||||
|
originalWeek: "",
|
||||||
|
originalDate: "",
|
||||||
|
isPastDue: false,
|
||||||
|
scheduleWeekOnly: false,
|
||||||
|
mediaCount: 0,
|
||||||
|
title: "PM — SEA1",
|
||||||
|
priority: "Sev 3",
|
||||||
|
primaryDispatchId: null,
|
||||||
|
rowVersion: "",
|
||||||
|
dispatchRowVersion: "",
|
||||||
|
flagColor: null,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("CompDocDialog Save Draft / Generate completion contract", () => {
|
||||||
|
const onSave = vi.fn();
|
||||||
|
const onDateCompletedChange = vi.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
onSave.mockReset();
|
||||||
|
onDateCompletedChange.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderDialog(row: WorkOrderTableRow) {
|
||||||
|
// Mirrors board wiring: only completedDate / docStatus — never scheduledOn or lifecycle.
|
||||||
|
const patches: Array<Partial<WorkOrderTableRow>> = [];
|
||||||
|
const client = new QueryClient({
|
||||||
|
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<QueryClientProvider client={client}>
|
||||||
|
<CompDocDialog
|
||||||
|
row={row}
|
||||||
|
open
|
||||||
|
onOpenChange={vi.fn()}
|
||||||
|
onSave={(data, status) => {
|
||||||
|
onSave(data, status);
|
||||||
|
patches.push({ docStatus: status });
|
||||||
|
}}
|
||||||
|
onDateCompletedChange={(date) => {
|
||||||
|
onDateCompletedChange(date);
|
||||||
|
patches.push({ completedDate: date });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</QueryClientProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
return { patches };
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeDateCompleted(value: string) {
|
||||||
|
const dateInput = document.querySelector('input[type="date"]');
|
||||||
|
expect(dateInput).not.toBeNull();
|
||||||
|
fireEvent.change(dateInput!, { target: { value } });
|
||||||
|
}
|
||||||
|
|
||||||
|
it("Save draft emits completedDate without writing scheduledOn or lifecycle Completed", () => {
|
||||||
|
const row = baseRow();
|
||||||
|
const { patches } = renderDialog(row);
|
||||||
|
|
||||||
|
changeDateCompleted("2026-07-15");
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /save draft/i }));
|
||||||
|
|
||||||
|
expect(onDateCompletedChange).toHaveBeenCalledWith("2026-07-15");
|
||||||
|
expect(onSave).toHaveBeenCalledWith(expect.any(Object), "No");
|
||||||
|
expect(patches).toEqual([{ docStatus: "No" }, { completedDate: "2026-07-15" }]);
|
||||||
|
expect(patches.some((patch) => "scheduledOn" in patch)).toBe(false);
|
||||||
|
expect(patches.some((patch) => patch.status === "Completed")).toBe(false);
|
||||||
|
expect(row.scheduledOn).toBe(SCHEDULED_ON);
|
||||||
|
expect(row.status).toBe("Scheduled");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Generate emits completedDate and doc Yes without auto-setting lifecycle Completed", () => {
|
||||||
|
const row = baseRow({ completedDate: "2026-07-01" });
|
||||||
|
const { patches } = renderDialog(row);
|
||||||
|
|
||||||
|
changeDateCompleted("2026-07-20");
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /^generate$/i }));
|
||||||
|
|
||||||
|
expect(onDateCompletedChange).toHaveBeenCalledWith("2026-07-20");
|
||||||
|
expect(onSave).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ generatedAt: expect.any(String) }),
|
||||||
|
"Yes",
|
||||||
|
);
|
||||||
|
expect(patches).toEqual([{ docStatus: "Yes" }, { completedDate: "2026-07-20" }]);
|
||||||
|
expect(patches.some((patch) => "scheduledOn" in patch)).toBe(false);
|
||||||
|
expect(patches.some((patch) => patch.status === "Completed")).toBe(false);
|
||||||
|
expect(row.scheduledOn).toBe(SCHEDULED_ON);
|
||||||
|
expect(row.status).toBe("Scheduled");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -55,6 +55,7 @@ function baseRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow
|
||||||
dispatcherId: "u1",
|
dispatcherId: "u1",
|
||||||
dispatcherName: "Dispatcher",
|
dispatcherName: "Dispatcher",
|
||||||
scheduledOn: "2026-07-20",
|
scheduledOn: "2026-07-20",
|
||||||
|
completedDate: "",
|
||||||
targetWeek: "",
|
targetWeek: "",
|
||||||
dayGroup: "07/20",
|
dayGroup: "07/20",
|
||||||
dayLabel: "Mon",
|
dayLabel: "Mon",
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ const canceledRow: WorkOrderTableRow = {
|
||||||
dispatcherId: "u1",
|
dispatcherId: "u1",
|
||||||
dispatcherName: "Test Dispatcher",
|
dispatcherName: "Test Dispatcher",
|
||||||
scheduledOn: "2026-07-20",
|
scheduledOn: "2026-07-20",
|
||||||
|
completedDate: "",
|
||||||
targetWeek: "2026-07-20",
|
targetWeek: "2026-07-20",
|
||||||
dayGroup: "2026-07-20",
|
dayGroup: "2026-07-20",
|
||||||
dayLabel: "Mon",
|
dayLabel: "Mon",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ function baseRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow
|
||||||
dispatcherId: "10",
|
dispatcherId: "10",
|
||||||
dispatcherName: "Alex",
|
dispatcherName: "Alex",
|
||||||
scheduledOn: "2026-07-16",
|
scheduledOn: "2026-07-16",
|
||||||
|
completedDate: "",
|
||||||
targetWeek: "",
|
targetWeek: "",
|
||||||
dayGroup: "2026-07-16",
|
dayGroup: "2026-07-16",
|
||||||
dayLabel: "Thu",
|
dayLabel: "Thu",
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ function row(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow {
|
||||||
dispatcherId: "10",
|
dispatcherId: "10",
|
||||||
dispatcherName: "Alex",
|
dispatcherName: "Alex",
|
||||||
scheduledOn: "2026-07-16",
|
scheduledOn: "2026-07-16",
|
||||||
|
completedDate: "",
|
||||||
targetWeek: "",
|
targetWeek: "",
|
||||||
dayGroup: "2026-07-16",
|
dayGroup: "2026-07-16",
|
||||||
dayLabel: "Thu",
|
dayLabel: "Thu",
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ function makeRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow
|
||||||
dispatcherId: "u1",
|
dispatcherId: "u1",
|
||||||
dispatcherName: "Alice",
|
dispatcherName: "Alice",
|
||||||
scheduledOn: "2026-06-23",
|
scheduledOn: "2026-06-23",
|
||||||
|
completedDate: "",
|
||||||
targetWeek: "",
|
targetWeek: "",
|
||||||
dayGroup: "06/23",
|
dayGroup: "06/23",
|
||||||
dayLabel: "TUE · 06/23",
|
dayLabel: "TUE · 06/23",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue