mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
fix(work-orders): check duplicate numbers globally
This commit is contained in:
parent
41dd7d0957
commit
6ac1b5cd42
4 changed files with 126 additions and 15 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { isWoWizardMockEnabled } from "@/domain/work-orders/mocks/is-wo-wizard-mock-enabled";
|
||||
import {
|
||||
getMockWoWizardLocationDetail,
|
||||
|
|
@ -21,6 +22,7 @@ import {
|
|||
} from "@/domain/work-orders/use-cases/use-work-order-detail";
|
||||
import { useCreateWorkOrderFromWizard } from "@/domain/work-orders/use-cases/use-create-work-order-from-wizard";
|
||||
import { findDuplicateWoNumberRow } from "@/domain/work-orders/utils/find-duplicate-wo-number-row";
|
||||
import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api";
|
||||
|
||||
type UseNewWoWizardStateArgs = {
|
||||
open: boolean;
|
||||
|
|
@ -43,6 +45,7 @@ export function useNewWoWizardState({
|
|||
});
|
||||
const [pocAutoFilledFor, setPocAutoFilledFor] = useState("");
|
||||
const [duplicateRow, setDuplicateRow] = useState<WorkOrderTableRow | null>(null);
|
||||
const [isCheckingDuplicate, setIsCheckingDuplicate] = useState(false);
|
||||
|
||||
const { data: apiLocations = [] } = useWorkOrderLocations();
|
||||
const { data: apiProblems = [] } = useDropdownOptionsByCategory("Problem");
|
||||
|
|
@ -110,22 +113,40 @@ export function useNewWoWizardState({
|
|||
}
|
||||
};
|
||||
|
||||
const resolveDuplicate = (woNumber: string) => findDuplicateWoNumberRow(woNumber, existingRows);
|
||||
const resolveDuplicate = async (woNumber: string) => {
|
||||
const localDuplicate = findDuplicateWoNumberRow(woNumber, existingRows);
|
||||
if (localDuplicate) {
|
||||
return localDuplicate;
|
||||
}
|
||||
|
||||
// The loaded board rows are only an immediate UX hint; the unfiltered API lookup is authoritative.
|
||||
return workOrdersApi.findByWoNumber(woNumber);
|
||||
};
|
||||
|
||||
const handleDuplicateFound = (woNumber: string) => {
|
||||
const duplicate = resolveDuplicate(woNumber);
|
||||
if (duplicate) {
|
||||
setDuplicateRow(duplicate);
|
||||
}
|
||||
void resolveDuplicate(woNumber).then((duplicate) => {
|
||||
if (duplicate && woNumber === draft.woNumber) {
|
||||
setDuplicateRow(duplicate);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreate = () => {
|
||||
const duplicate = resolveDuplicate(draft.woNumber);
|
||||
if (duplicate) {
|
||||
setDuplicateRow(duplicate);
|
||||
return;
|
||||
}
|
||||
createMutation.mutate(draft);
|
||||
void (async () => {
|
||||
setIsCheckingDuplicate(true);
|
||||
try {
|
||||
const duplicate = await resolveDuplicate(draft.woNumber);
|
||||
if (duplicate) {
|
||||
setDuplicateRow(duplicate);
|
||||
return;
|
||||
}
|
||||
createMutation.mutate(draft);
|
||||
} catch {
|
||||
toast.error("Unable to verify the work order number. Please try again.");
|
||||
} finally {
|
||||
setIsCheckingDuplicate(false);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
@ -143,6 +164,6 @@ export function useNewWoWizardState({
|
|||
patchDraft,
|
||||
handleDuplicateFound,
|
||||
handleCreate,
|
||||
isCreating: createMutation.isPending,
|
||||
isCreating: createMutation.isPending || isCheckingDuplicate,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,6 +191,22 @@ export const workOrdersApi = {
|
|||
};
|
||||
},
|
||||
|
||||
findByWoNumber: async (woNumber: string): Promise<WorkOrderTableRow | undefined> => {
|
||||
const normalized = woNumber.trim();
|
||||
if (normalized === "") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// `board/search` without date or board filters searches the entire work-order collection.
|
||||
const result = await workOrdersApi.searchBoard({
|
||||
search: normalized,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
return result.items.find((row) => row.woNumber === normalized);
|
||||
},
|
||||
|
||||
getListByLocation: async (
|
||||
locationId: string | number,
|
||||
search = "",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { fireEvent, screen } from "@testing-library/react";
|
||||
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { NewWoWizard } from "@/app/(protected)/workorders/_components/wizard/new-wo-wizard";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { renderWithProviders } from "@/test/test-utils";
|
||||
|
||||
const existingRow = { id: "wo-99", woNumber: "12345678901" } as WorkOrderTableRow;
|
||||
const findByWoNumber = vi.fn();
|
||||
|
||||
vi.mock("@/domain/work-orders/use-cases/use-create-work-order-from-wizard", () => ({
|
||||
useCreateWorkOrderFromWizard: () => ({
|
||||
|
|
@ -26,8 +27,14 @@ vi.mock("@/domain/settings/dropdown-options/use-cases/use-dropdown-options-by-ca
|
|||
useDropdownOptionsByCategory: () => ({ data: [] }),
|
||||
}));
|
||||
|
||||
vi.mock("@/domain/work-orders/api/work-orders-api", () => ({
|
||||
workOrdersApi: {
|
||||
findByWoNumber: (...args: unknown[]) => findByWoNumber(...args),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("NewWoWizard duplicate wo number", () => {
|
||||
it("shows the duplicate dialog when the wizard wo number matches an existing row", () => {
|
||||
it("shows the duplicate dialog when the wizard wo number matches an existing row", async () => {
|
||||
renderWithProviders(
|
||||
<NewWoWizard
|
||||
open
|
||||
|
|
@ -44,7 +51,38 @@ describe("NewWoWizard duplicate wo number", () => {
|
|||
});
|
||||
fireEvent.blur(woNumberInput);
|
||||
|
||||
expect(screen.getByText("Duplicate Work Order Number")).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Duplicate Work Order Number")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(/is already assigned to another work order/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("finds a duplicate outside the loaded board rows", async () => {
|
||||
const offPageRow = {
|
||||
id: "wo-off-page",
|
||||
woNumber: "98765432109",
|
||||
} as WorkOrderTableRow;
|
||||
findByWoNumber.mockResolvedValueOnce(offPageRow);
|
||||
|
||||
renderWithProviders(
|
||||
<NewWoWizard
|
||||
open
|
||||
onOpenChange={vi.fn()}
|
||||
users={[]}
|
||||
existingRows={[]}
|
||||
onViewExistingWo={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const woNumberInput = screen.getByPlaceholderText("e.g. 24818");
|
||||
fireEvent.change(woNumberInput, {
|
||||
target: { value: "98765432109" },
|
||||
});
|
||||
fireEvent.blur(woNumberInput);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Duplicate Work Order Number")).toBeInTheDocument();
|
||||
});
|
||||
expect(findByWoNumber).toHaveBeenCalledWith("98765432109");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -291,6 +291,42 @@ describe("workOrdersApi.searchBoard", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("workOrdersApi.findByWoNumber", () => {
|
||||
beforeEach(() => {
|
||||
apiGet.mockReset();
|
||||
apiGet.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 505,
|
||||
woNumber: "10000001235",
|
||||
lifecycleStatusLabel: "Incomplete",
|
||||
},
|
||||
],
|
||||
totalCount: 1,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
});
|
||||
|
||||
it("searches globally without the active board filters", async () => {
|
||||
const result = await workOrdersApi.findByWoNumber("10000001235");
|
||||
|
||||
expect(apiGet).toHaveBeenCalledWith(
|
||||
API_PATHS.workOrder.boardSearch,
|
||||
expect.objectContaining({
|
||||
searchParams: expect.any(URLSearchParams),
|
||||
}),
|
||||
);
|
||||
const searchParams = apiGet.mock.calls[0]?.[1]?.searchParams as URLSearchParams;
|
||||
expect(Object.fromEntries(searchParams)).toEqual({
|
||||
search: "10000001235",
|
||||
page: "1",
|
||||
pageSize: "20",
|
||||
});
|
||||
expect(result).toMatchObject({ id: 505, woNumber: "10000001235" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("workOrdersApi.patchBoardField", () => {
|
||||
beforeEach(() => {
|
||||
apiPatchFn.mockReset();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue