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 openRef = useRef(open);
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: apiProblems = [] } = useDropdownOptionsByCategory("Problem");

View file

@ -193,20 +193,26 @@ export const workOrdersApi = {
findByWoNumber: async (woNumber: string): Promise<WorkOrderTableRow | undefined> => {
const normalized = woNumber.trim();
// Skip empty / very short queries — fuzzy board/search would page endlessly for 1–2 chars.
const minLength = 3;
const maxPages = 5;
// board/search contract: only send search when length >= 2.
const minLength = 2;
// Safety cap — if hit before exhausting results, fail closed (do not treat as unique).
const safetyMaxPages = 50;
if (normalized.length < minLength) {
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;
let page = 0;
let scanned = 0;
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({
search: normalized,
page,

View file

@ -364,13 +364,13 @@ describe("workOrdersApi.findByWoNumber", () => {
expect(result).toMatchObject({ id: 999, woNumber: "10000009999" });
});
it("skips remote search for short WO numbers", async () => {
const result = await workOrdersApi.findByWoNumber("12");
it("skips remote search for single-character WO numbers", async () => {
const result = await workOrdersApi.findByWoNumber("1");
expect(apiGet).not.toHaveBeenCalled();
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({
items: Array.from({ length: 100 }, (_, index) => ({
id: index + 1,
@ -382,9 +382,36 @@ describe("workOrdersApi.findByWoNumber", () => {
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");
expect(apiGet).toHaveBeenCalledTimes(5);
expect(apiGet).toHaveBeenCalledTimes(2);
expect(result).toBeUndefined();
});
});