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:50:44 +00:00
co-authored by renee-png
parent 7899a3070f
commit 17def7c43f
+34 -12
View File
@@ -555,28 +555,50 @@ const IMPORTERS: ImporterConfig[] = [
description: "Invoice headers (line items not imported). If the client isn't recognized, an archived client is auto-created. If the case isn't recognized, an archived placeholder case is auto-created so the reference is preserved.",
table: "invoices",
conflict: "external_id",
required: ["invoice_number", "client_id"],
// Only invoice_number is strictly required at the row level. client_id is
// deferred — the async step will look it up or auto-create an archived
// client. If invoice_number is also missing we auto-generate one.
required: ["invoice_number"],
aliases: {
id: "external_id", invoiceid: "external_id", externalid: "external_id",
number: "invoice_number", invoicenumber: "invoice_number",
companyid: "_clientext", clientid: "_clientext", company: "_clientname",
caseid: "_caseext", casenumber: "_casenum",
issuedate: "issue_date", date: "issue_date",
number: "invoice_number", invoicenumber: "invoice_number", invoiceno: "invoice_number", invno: "invoice_number",
companyid: "_clientext", clientid: "_clientext", company: "_clientname", clientname: "_clientname", client: "_clientname",
caseid: "_caseext", matterid: "_caseext", casenumber: "_casenum", matternumber: "_casenum",
issuedate: "issue_date", date: "issue_date", invoicedate: "issue_date", billdate: "issue_date",
duedate: "due_date",
status: "status",
subtotal: "subtotal", tax: "tax", total: "total", amount: "total",
amountpaid: "amount_paid", paid: "amount_paid",
notes: "notes", memo: "notes",
subtotal: "subtotal", tax: "tax", total: "total", amount: "total", invoicetotal: "total", grandtotal: "total",
amountpaid: "amount_paid", paid: "amount_paid", paidamount: "amount_paid",
notes: "notes", memo: "notes", description: "notes",
},
numeric: ["subtotal", "tax", "total", "amount_paid"],
dateCols: ["issue_date", "due_date"],
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;
// Keep hints — async pre-insert step will auto-create an archived client
// (and archived case) when no match is found.
const s = String(r.status ?? "").toLowerCase();
r.status = ["draft", "sent", "paid", "overdue", "void"].includes(s) ? s : "draft";
// 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
? `IMP-${String(r.external_id)}`
: `IMP-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
}
// Broader status normalization — many legacy systems use Open/Outstanding/Partial/etc.
const s = String(r.status ?? "").toLowerCase().trim();
const statusMap: Record<string, string> = {
"": "draft",
draft: "draft",
open: "sent", outstanding: "sent", unpaid: "sent", sent: "sent", issued: "sent", billed: "sent", pending: "sent",
paid: "paid", closed: "paid", complete: "paid", completed: "paid", settled: "paid",
overdue: "overdue", late: "overdue", pastdue: "overdue", "past due": "overdue",
void: "void", voided: "void", cancelled: "void", canceled: "void",
partial: "sent", partiallypaid: "sent", "partially paid": "sent",
};
r.status = statusMap[s] ?? "draft";
// Ensure totals have safe defaults so non-null numeric columns don't break
if (r.total == null) r.total = (r.subtotal ?? 0) + (r.tax ?? 0);
if (r.subtotal == null) r.subtotal = r.total ?? 0;
if (r.tax == null) r.tax = 0;
if (r.amount_paid == null) r.amount_paid = 0;
return r;
},
},