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:02:06 +00:00
co-authored by renee-png
parent f11a2858b4
commit 67fbbfd161
+30 -21
View File
@@ -1020,40 +1020,44 @@ function ImportPage() {
if (extId) ctx.caseByExt.set(String(extId), data.id);
return data.id;
};
// Look up an existing invoice by number for this client, or create a
// new "sent" invoice with that exact number to bucket imported items.
const invoiceByClientAndNumber = new Map<string, string>(); // `${clientId}::${number}` -> invoiceId
const ensureNamedInvoice = async (
clientId: string,
caseId: string,
// Look up an existing invoice by number (globally, not per-client) and
// create a draft invoice with status='draft' if none exists. Time/expense
// import drives invoice creation; the invoices CSV will later update
// header fields but never replace these auto-created draft invoices.
const invoiceByNumber = new Map<string, string>(); // invoice_number -> invoiceId
const ensureDraftInvoice = async (
invoiceNumber: string,
clientId: string | null,
caseId: string | null,
): Promise<string | null> => {
const cacheKey = `${clientId}::${invoiceNumber}`;
const cached = invoiceByClientAndNumber.get(cacheKey);
const cached = invoiceByNumber.get(invoiceNumber);
if (cached) return cached;
// First try existing invoice with this number for this client
const { data: existing } = await supabase
.from("invoices")
.select("id")
.eq("client_id", clientId)
.eq("invoice_number", invoiceNumber)
.maybeSingle();
if (existing?.id) {
invoiceByClientAndNumber.set(cacheKey, existing.id);
invoiceByNumber.set(invoiceNumber, existing.id);
return existing.id;
}
if (!clientId) {
// invoices.client_id is NOT NULL — fall back to "Imported (no client)"
clientId = await ensureArchivedClient("Imported (no client)", "imported-no-client");
if (!clientId) return null;
}
const { data, error } = await supabase
.from("invoices")
.insert({
client_id: clientId,
case_id: caseId,
invoice_number: invoiceNumber,
status: "sent",
status: "draft",
issue_date: new Date().toISOString().slice(0, 10),
subtotal: 0,
tax: 0,
total: 0,
notes: "Auto-created from import to hold items linked to this invoice number.",
notes: "Auto-created from time/expense import — header will be updated when invoices CSV is uploaded.",
created_by: user.id,
} as any)
.select("id")
@@ -1062,7 +1066,7 @@ function ImportPage() {
errors.push(`Auto-create invoice "${invoiceNumber}": ${error?.message ?? "unknown"}`);
return null;
}
invoiceByClientAndNumber.set(cacheKey, data.id);
invoiceByNumber.set(invoiceNumber, data.id);
return data.id;
};
@@ -1098,7 +1102,7 @@ function ImportPage() {
delete row._caseext; delete row._casenum; delete row._casename;
row.case_id = caseId;
// Look up client_id for the case to attach a placeholder voided invoice
// Look up client_id for the case so a draft invoice can be created
if (!clientId) {
clientId = caseClientCache.get(caseId) ?? null;
if (!clientId) {
@@ -1112,15 +1116,20 @@ function ImportPage() {
}
}
// Link to a real invoice ONLY when the source row provided an
// invoice number. Otherwise leave invoice_id null so the user can
// bill these items normally later.
// If the source row gave an invoice number, ensure a draft invoice
// exists and link this row. Mark billed=true so it's tracked as
// already on an invoice. Otherwise leave invoice_id null and
// billed=false — these are open charges awaiting invoicing.
const invNumber = row._invoice ? String(row._invoice).trim() : "";
delete row._invoice;
if (invNumber && clientId && !row.invoice_id) {
const invId = await ensureNamedInvoice(clientId, caseId, invNumber);
if (invId) row.invoice_id = invId;
if (invNumber && !row.invoice_id) {
const invId = await ensureDraftInvoice(invNumber, clientId, caseId);
if (invId) {
row.invoice_id = invId;
row.billed = true;
}
}
if (row.billed == null) row.billed = !!row.invoice_id;
resolved.push(row);
}