fix(work-orders): sync wizard refs in effects and exhaust duplicate WO lookup

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Arthur Bassi 2026-08-11 11:05:10 -03:00
parent db0b87eb8f
commit c6c11b9f04
3 changed files with 50 additions and 11 deletions

View file

@ -48,8 +48,14 @@ export function useNewWoWizardState({
const [isCheckingDuplicate, setIsCheckingDuplicate] = useState(false); const [isCheckingDuplicate, setIsCheckingDuplicate] = useState(false);
const openRef = useRef(open); const openRef = useRef(open);
const draftRef = useRef(draft); const draftRef = useRef(draft);
openRef.current = open;
draftRef.current = draft; useEffect(() => {
openRef.current = open;
}, [open]);
useEffect(() => {
draftRef.current = draft;
}, [draft]);
const { data: apiLocations = [] } = useWorkOrderLocations(); const { data: apiLocations = [] } = useWorkOrderLocations();
const { data: apiProblems = [] } = useDropdownOptionsByCategory("Problem"); const { data: apiProblems = [] } = useDropdownOptionsByCategory("Problem");

View file

@ -193,20 +193,26 @@ export const workOrdersApi = {
findByWoNumber: async (woNumber: string): Promise<WorkOrderTableRow | undefined> => { findByWoNumber: async (woNumber: string): Promise<WorkOrderTableRow | undefined> => {
const normalized = woNumber.trim(); const normalized = woNumber.trim();
// Skip empty / very short queries — fuzzy board/search would page endlessly for 1–2 chars. // board/search contract: only send search when length >= 2.
const minLength = 3; const minLength = 2;
const maxPages = 5; // Safety cap — if hit before exhausting results, fail closed (do not treat as unique).
const safetyMaxPages = 50;
if (normalized.length < minLength) { if (normalized.length < minLength) {
return undefined; return undefined;
} }
// `board/search` is fuzzy and paginated (0-based); scan a bounded number of pages for an exact WO#. // `board/search` is fuzzy and paginated (0-based); exhaust pages for an exact WO#.
const pageSize = 100; const pageSize = 100;
let page = 0; let page = 0;
let scanned = 0; let scanned = 0;
let totalCount = Number.POSITIVE_INFINITY; let totalCount = Number.POSITIVE_INFINITY;
while (scanned < totalCount && page < maxPages) { while (scanned < totalCount) {
if (page >= safetyMaxPages) {
throw new Error(
"Unable to verify the work order number: search result set exceeded the safety page limit.",
);
}
const result = await workOrdersApi.searchBoard({ const result = await workOrdersApi.searchBoard({
search: normalized, search: normalized,
page, page,

View file

@ -364,13 +364,13 @@ describe("workOrdersApi.findByWoNumber", () => {
expect(result).toMatchObject({ id: 999, woNumber: "10000009999" }); expect(result).toMatchObject({ id: 999, woNumber: "10000009999" });
}); });
it("skips remote search for short WO numbers", async () => { it("skips remote search for single-character WO numbers", async () => {
const result = await workOrdersApi.findByWoNumber("12"); const result = await workOrdersApi.findByWoNumber("1");
expect(apiGet).not.toHaveBeenCalled(); expect(apiGet).not.toHaveBeenCalled();
expect(result).toBeUndefined(); expect(result).toBeUndefined();
}); });
it("caps paging when scanning fuzzy board search results", async () => { it("throws when the safety page limit is hit before exhausting results", async () => {
apiGet.mockResolvedValue({ apiGet.mockResolvedValue({
items: Array.from({ length: 100 }, (_, index) => ({ items: Array.from({ length: 100 }, (_, index) => ({
id: index + 1, id: index + 1,
@ -382,9 +382,36 @@ describe("workOrdersApi.findByWoNumber", () => {
pageSize: 100, pageSize: 100,
}); });
await expect(workOrdersApi.findByWoNumber("10000005555")).rejects.toThrow(/safety page limit/i);
expect(apiGet).toHaveBeenCalledTimes(50);
});
it("returns undefined after exhausting all fuzzy search pages", async () => {
apiGet
.mockResolvedValueOnce({
items: Array.from({ length: 100 }, (_, index) => ({
id: index + 1,
woNumber: `fuzzy-match-${index}`,
lifecycleStatusLabel: "Incomplete",
})),
totalCount: 150,
page: 0,
pageSize: 100,
})
.mockResolvedValueOnce({
items: Array.from({ length: 50 }, (_, index) => ({
id: index + 101,
woNumber: `fuzzy-match-${index + 100}`,
lifecycleStatusLabel: "Incomplete",
})),
totalCount: 150,
page: 1,
pageSize: 100,
});
const result = await workOrdersApi.findByWoNumber("10000005555"); const result = await workOrdersApi.findByWoNumber("10000005555");
expect(apiGet).toHaveBeenCalledTimes(5); expect(apiGet).toHaveBeenCalledTimes(2);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
}); });
}); });