Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
a0e98d6a82
commit
665dbfb9c7
@@ -569,7 +569,7 @@ const IMPORTERS: ImporterConfig[] = [
|
||||
{
|
||||
key: "invoices",
|
||||
label: "Invoices → Invoices",
|
||||
description: "Invoice headers. Matched against existing invoices by invoice number (preferred) or external ID — when a match exists, only header fields (status, dates, notes) are updated and totals are left intact (totals = sum of attached time/expense line items). Non-matching invoices are inserted as new. Import time entries and expenses FIRST so their invoices are auto-created as drafts; this CSV then promotes them to sent/paid/etc.",
|
||||
description: "Invoice headers. Matched against existing invoices by invoice number (preferred) or external ID — when a match exists, only header fields (status, dates, notes) are updated and totals are left intact (totals = sum of attached time/expense line items). Non-matching invoices are inserted as new. Rows that don't link to an ACTIVE case or client are SKIPPED (no placeholder client is created). Import time entries and expenses FIRST so their invoices are auto-created as drafts; this CSV then promotes them to sent/paid/etc.",
|
||||
table: "invoices",
|
||||
conflict: "external_id",
|
||||
// Only invoice_number is strictly required at the row level. client_id is
|
||||
@@ -598,6 +598,13 @@ const IMPORTERS: ImporterConfig[] = [
|
||||
transform: (r, ctx) => {
|
||||
r.client_id = (r._clientext && ctx.clientByExt.get(String(r._clientext))) || (r._clientname && ctx.clientByName.get(String(r._clientname).toLowerCase())) || null;
|
||||
r.case_id = (r._caseext && ctx.caseByExt.get(String(r._caseext))) || (r._casenum && ctx.caseByNumber.get(String(r._casenum))) || null;
|
||||
// Only accept ACTIVE (non-archived, non-closed) cases. Drop inactive case refs.
|
||||
if (r.case_id && !ctx.activeCaseIds.has(r.case_id)) {
|
||||
r.case_id = null;
|
||||
}
|
||||
// Skip rows that don't link to either an active case or a real client.
|
||||
// No placeholder client is created — these invoices are dropped.
|
||||
if (!r.client_id && !r.case_id) return null;
|
||||
// Auto-generate invoice number if missing (uses external_id or timestamp)
|
||||
if (!r.invoice_number || !String(r.invoice_number).trim()) {
|
||||
r.invoice_number = r.external_id
|
||||
@@ -1372,32 +1379,32 @@ function ImportPage() {
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts must have a non-null client_id; fall back to placeholder
|
||||
// ONLY for genuinely new invoices that never matched anything.
|
||||
const insertsNeedingClient = toInsertInv.filter((r) => !r.client_id);
|
||||
if (insertsNeedingClient.length > 0) {
|
||||
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;
|
||||
// Skip new invoices without a real client. If only case_id is present,
|
||||
// derive client_id from the case. Rows that still have no client are
|
||||
// dropped (no placeholder is created).
|
||||
const needClient = toInsertInv.filter((r) => !r.client_id && r.case_id);
|
||||
if (needClient.length > 0) {
|
||||
const caseIds = Array.from(new Set(needClient.map((r) => String(r.case_id))));
|
||||
const { data: caseRows } = await supabase
|
||||
.from("cases")
|
||||
.select("id, client_id")
|
||||
.in("id", caseIds);
|
||||
const clientByCase = new Map<string, string>();
|
||||
for (const c of (caseRows ?? []) as { id: string; client_id: string | null }[]) {
|
||||
if (c.client_id) clientByCase.set(c.id, c.client_id);
|
||||
}
|
||||
for (const r of needClient) {
|
||||
const cid = clientByCase.get(String(r.case_id));
|
||||
if (cid) r.client_id = cid;
|
||||
}
|
||||
}
|
||||
for (let k = 0; k < toInsertInv.length; k += chunkSize) {
|
||||
const slice = toInsertInv.slice(k, k + chunkSize);
|
||||
const droppedNoClient = toInsertInv.filter((r) => !r.client_id).length;
|
||||
if (droppedNoClient > 0) {
|
||||
bumpSkip("no active case/client");
|
||||
}
|
||||
const insertable = toInsertInv.filter((r) => !!r.client_id);
|
||||
for (let k = 0; k < insertable.length; k += chunkSize) {
|
||||
const slice = insertable.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) {
|
||||
|
||||
Reference in New Issue
Block a user