Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-19 22:03:38 +00:00
co-authored by renee-png
parent 3d92e220e0
commit 0462ffb3ec
+83
View File
@@ -1271,6 +1271,89 @@ function ImportPage() {
const chunkSize = 200;
let inserted = 0;
const inserted_rows: Row[] = [];
// INVOICES: special merge — match existing invoices by invoice_number
// (preferred) or external_id and UPDATE only header fields. Subtotal,
// total, tax, and amount_paid are left alone so totals stay = sum of
// line items already attached from the time/expense imports.
if (cfg.table === "invoices" && mapped.length > 0) {
const numbers = mapped.map((r) => String(r.invoice_number ?? "")).filter(Boolean);
const externalIds = mapped.map((r) => String(r.external_id ?? "")).filter(Boolean);
const byNumber = new Map<string, string>();
const byExt = new Map<string, string>();
for (let k = 0; k < numbers.length; k += 500) {
const slice = numbers.slice(k, k + 500);
const { data } = await supabase
.from("invoices")
.select("id, invoice_number")
.in("invoice_number", slice);
for (const row of (data ?? []) as { id: string; invoice_number: string }[]) {
byNumber.set(row.invoice_number, row.id);
}
}
for (let k = 0; k < externalIds.length; k += 500) {
const slice = externalIds.slice(k, k + 500);
const { data } = await supabase
.from("invoices")
.select("id, external_id")
.in("external_id", slice);
for (const row of (data ?? []) as { id: string; external_id: string }[]) {
byExt.set(row.external_id, row.id);
}
}
const HEADER_FIELDS = [
"status", "issue_date", "due_date", "notes",
"client_id", "case_id", "external_id", "invoice_number",
] as const;
const toUpdateInv: Array<{ id: string; payload: Row }> = [];
const toInsertInv: Row[] = [];
for (const r of mapped) {
const num = r.invoice_number ? String(r.invoice_number) : "";
const ext = r.external_id ? String(r.external_id) : "";
const matchId = (num && byNumber.get(num)) || (ext && byExt.get(ext)) || null;
if (matchId) {
const payload: Row = {};
for (const f of HEADER_FIELDS) {
if (r[f] != null && r[f] !== "") payload[f] = r[f];
}
toUpdateInv.push({ id: matchId, payload });
} else {
toInsertInv.push(r);
}
}
for (let k = 0; k < toUpdateInv.length; k += 50) {
const slice = toUpdateInv.slice(k, k + 50);
const results = await Promise.all(
slice.map(({ id, payload }) =>
supabase.from("invoices").update(payload as any).eq("id", id).select("*").single(),
),
);
for (const result of results) {
if (result.error) {
errors.push(`Invoice merge update: ${result.error.message}`);
} else if (result.data) {
inserted += 1;
inserted_rows.push(result.data as Row);
}
}
}
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("*");
if (error) errors.push(`Invoice merge insert: ${error.message}`);
else if (data) {
inserted += data.length;
inserted_rows.push(...(data as Row[]));
}
}
// Skip the generic chunk loop below for invoices.
mapped.length = 0;
}
for (let i = 0; i < mapped.length; i += chunkSize) {
const chunk = mapped.slice(i, i + chunkSize);
// Filter out rows missing the conflict column for upsert; insert plain