diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index baa04b7..29c79f5 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -1374,6 +1374,71 @@ function ImportPage() { if (cfg.postInsert) cfg.postInsert(inserted_rows, ctx); + // For time/expense imports: create invoice_line_items for any inserted + // row that was attached to an invoice (billed=true), then recompute the + // affected invoices' subtotal/total = sum of all their lines. + if ((cfg.table === "time_entries" || cfg.table === "expenses") && inserted_rows.length > 0) { + type LinkedRow = Row & { id: string; invoice_id: string; case_id: string }; + const linked = inserted_rows.filter( + (r) => r.invoice_id && r.id && r.case_id, + ) as LinkedRow[]; + if (linked.length > 0) { + const lineItems = linked.map((r) => { + if (cfg.table === "time_entries") { + const qty = Number(r.hours ?? 0); + const rate = Number(r.hourly_rate ?? 0); + return { + invoice_id: r.invoice_id, + case_id: r.case_id, + kind: "time", + description: String(r.description ?? "Time entry"), + work_date: r.work_date ?? null, + quantity: qty, + rate, + amount: +(qty * rate).toFixed(2), + time_entry_id: r.id, + user_id: r.user_id ?? null, + }; + } + const amt = Number(r.amount ?? 0); + return { + invoice_id: r.invoice_id, + case_id: r.case_id, + kind: "expense", + description: String(r.description ?? "Expense"), + work_date: r.expense_date ?? null, + quantity: 1, + rate: amt, + amount: amt, + expense_id: r.id, + user_id: r.user_id ?? null, + }; + }); + for (let i = 0; i < lineItems.length; i += 500) { + const slice = lineItems.slice(i, i + 500); + const { error: liErr } = await supabase + .from("invoice_line_items") + .insert(slice as any); + if (liErr) errors.push(`Line items (batch ${i / 500 + 1}): ${liErr.message}`); + } + const invoiceIds = Array.from(new Set(linked.map((r) => r.invoice_id))); + for (const invId of invoiceIds) { + const { data: lines } = await supabase + .from("invoice_line_items") + .select("amount") + .eq("invoice_id", invId); + const subtotal = (lines ?? []).reduce( + (s, l) => s + Number((l as { amount: number }).amount ?? 0), + 0, + ); + await supabase + .from("invoices") + .update({ subtotal, total: subtotal } as any) + .eq("id", invId); + } + } + } + // Mirror imported "client" contacts into the clients table so they appear on the Clients page. let mirrored = 0; if (cfg.key === "contacts" && contactGroup === "client" && inserted_rows.length > 0) {