From 25d779c8388ebe9ee780b8af45dcf6ee95a02eac Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:27:55 +0000 Subject: [PATCH 1/4] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 40 ++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index ca77f3e..3bf7d90 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -1206,17 +1206,49 @@ function ImportPage() { return data.id; }; + // Pre-load existing invoices by number so we can defer client resolution + // when the invoice already exists with a real client_id (don't clobber + // it with a placeholder). + const invNumbers = mapped.map((r) => String(r.invoice_number ?? "")).filter(Boolean); + const existingInvClient = new Map(); // invoice_number -> client_id + for (let k = 0; k < invNumbers.length; k += 500) { + const slice = invNumbers.slice(k, k + 500); + const { data } = await supabase + .from("invoices") + .select("invoice_number, client_id") + .in("invoice_number", slice); + for (const row of (data ?? []) as { invoice_number: string; client_id: string }[]) { + if (row.client_id) existingInvClient.set(row.invoice_number, row.client_id); + } + } + const resolved: Row[] = []; for (const row of mapped) { let clientId: string | null = (row.client_id as string | null) ?? null; if (!clientId) { const cExt = row._clientext ? String(row._clientext).trim() : null; const cName = row._clientname ? String(row._clientname).trim() : null; - const nameGuess = cName || cExt || "Imported (no client)"; - clientId = await ensureArchivedClient(nameGuess, cExt); - if (!clientId) { bumpSkip("auto-create client failed"); continue; } + // If this invoice already exists with a real client, inherit it + // instead of falling back to a placeholder — prevents wiping good + // client links during a re-import of the invoices CSV. + const invNum = row.invoice_number ? String(row.invoice_number) : ""; + const existingClient = invNum ? existingInvClient.get(invNum) : null; + if (existingClient) { + clientId = existingClient; + } else if (cName || cExt) { + // Only auto-create a placeholder when the CSV actually had a + // client hint we couldn't resolve. Pure-blank rows are left + // unset so the merge step skips client_id entirely. + const nameGuess = cName || cExt!; + clientId = await ensureArchivedClient(nameGuess, cExt); + if (!clientId) { bumpSkip("auto-create client failed"); continue; } + } else { + // No client info at all — leave client_id unset. The merge step + // will skip it (preserving any existing value); inserts will + // route through the catch-all placeholder below. + } } - row.client_id = clientId; + if (clientId) row.client_id = clientId; // If the invoice mentions a case but we don't have it, create an // archived placeholder case so the reference is preserved. From f52822022e32c08b78b2d5154e99260f09f4a7c7 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:28:09 +0000 Subject: [PATCH 2/4] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index 3bf7d90..f1e3e47 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -1255,7 +1255,7 @@ function ImportPage() { let caseId: string | null = (row.case_id as string | null) ?? null; const caseExt = row._caseext ? String(row._caseext).trim() : null; const caseNum = row._casenum ? String(row._casenum).trim() : null; - if (!caseId && (caseExt || caseNum)) { + if (!caseId && clientId && (caseExt || caseNum)) { const titleGuess = caseNum || caseExt || "Imported case"; const numberGuess = caseNum || caseExt || `IMPORTED-${Date.now()}-${Math.floor(Math.random() * 10000)}`; caseId = await ensureArchivedCase(clientId, titleGuess, numberGuess, caseExt); From 0498fbe62e9286c7b67d1cd616e40292f53787aa Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:28:26 +0000 Subject: [PATCH 3/4] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index f1e3e47..224ab6e 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -1372,6 +1372,16 @@ function ImportPage() { } } + // Inserts must have a non-null client_id; fall back to placeholder + // ONLY for genuinely new invoices that never matched anything. + let placeholderClient: string | null = null; + const insertsNeedingClient = toInsertInv.filter((r) => !r.client_id); + if (insertsNeedingClient.length > 0) { + placeholderClient = await ensureArchivedClient("Imported (no client)", "imported-no-client"); + if (placeholderClient) { + for (const r of insertsNeedingClient) r.client_id = placeholderClient; + } + } for (let k = 0; k < toInsertInv.length; k += chunkSize) { const slice = toInsertInv.slice(k, k + chunkSize); const { data, error } = await supabase.from("invoices").insert(slice as any).select("*"); From 7d305d09e945cecd59aff74be3cf72c82f91db01 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:28:42 +0000 Subject: [PATCH 4/4] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index 224ab6e..9b013e3 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -1374,10 +1374,24 @@ function ImportPage() { // Inserts must have a non-null client_id; fall back to placeholder // ONLY for genuinely new invoices that never matched anything. - let placeholderClient: string | null = null; const insertsNeedingClient = toInsertInv.filter((r) => !r.client_id); if (insertsNeedingClient.length > 0) { - placeholderClient = await ensureArchivedClient("Imported (no client)", "imported-no-client"); + const { data: ph } = await supabase + .from("clients") + .upsert( + { + name: "Imported (no client)", + external_id: "imported-no-client", + client_type: "hoa", + archived_at: new Date().toISOString(), + notes: "Auto-created from invoice import (no matching client).", + created_by: user.id, + } as any, + { onConflict: "external_id" }, + ) + .select("id") + .single(); + const placeholderClient = (ph?.id as string | undefined) ?? null; if (placeholderClient) { for (const r of insertsNeedingClient) r.client_id = placeholderClient; }