Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-19 21:29:28 +00:00
co-authored by renee-png
parent 945c6d8ae2
commit 4b67b5123b
+95
View File
@@ -1085,6 +1085,101 @@ function ImportPage() {
mapped.push(...resolved);
}
// Async pre-insert for invoices: if the row references an unknown client,
// auto-create an archived client from the row hints. If it also references
// an unknown case, auto-create an archived case under that client so the
// case is recorded (but clearly archived) rather than silently dropped.
if (cfg.table === "invoices") {
const ensureArchivedClient = async (name: string, extId?: string | null): Promise<string | null> => {
const key = name.toLowerCase().trim();
const existing = (extId && ctx.clientByExt.get(String(extId))) || ctx.clientByName.get(key);
if (existing) return existing;
const { data, error } = await supabase
.from("clients")
.insert({
name,
external_id: extId || null,
client_type: "hoa",
archived_at: new Date().toISOString(),
notes: "Auto-created from invoice import (no matching client).",
created_by: user.id,
} as any)
.select("id")
.single();
if (error || !data) {
errors.push(`Auto-create client "${name}": ${error?.message ?? "unknown"}`);
return null;
}
ctx.clientByName.set(key, data.id);
if (extId) ctx.clientByExt.set(String(extId), data.id);
return data.id;
};
const ensureArchivedCase = async (
clientId: string,
title: string,
caseNumber: string,
extId?: string | null,
): Promise<string | null> => {
const existing =
(extId && ctx.caseByExt.get(String(extId))) ||
ctx.caseByNumber.get(caseNumber) ||
ctx.caseByTitle.get(title.toLowerCase().trim());
if (existing) return existing;
const { data, error } = await supabase
.from("cases")
.insert({
client_id: clientId,
case_number: caseNumber,
title,
external_id: extId || null,
status: "closed",
archived_at: new Date().toISOString(),
description: "Auto-created from invoice import (no matching case).",
created_by: user.id,
} as any)
.select("id")
.single();
if (error || !data) {
errors.push(`Auto-create case "${title}": ${error?.message ?? "unknown"}`);
return null;
}
ctx.caseByNumber.set(caseNumber, data.id);
ctx.caseByTitle.set(title.toLowerCase().trim(), data.id);
if (extId) ctx.caseByExt.set(String(extId), data.id);
return data.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; }
}
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.
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)) {
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);
}
if (caseId) row.case_id = caseId;
// Clean hint columns
delete row._clientext; delete row._clientname; delete row._caseext; delete row._casenum;
resolved.push(row);
}
mapped.length = 0;
mapped.push(...resolved);
}
if (mapped.length === 0) {
setResults((r) => ({ ...r, [cfg.key]: { total: rawRows.length, inserted: 0, skipped, errors: ["No valid rows after mapping. Check headers/required fields."], skipReasons } }));
return;