Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-18 01:08:34 +00:00
co-authored by renee-png
parent a583e788ba
commit 6fa8e0fb63
@@ -41,6 +41,7 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName
const [taxPct, setTaxPct] = useState("0");
const [dueDays, setDueDays] = useState("30");
const [notes, setNotes] = useState("");
const [isRetainer, setIsRetainer] = useState(false);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
@@ -104,9 +105,53 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName
const submit = async () => {
if (!user?.id) return;
if (selectedIds.length === 0) return toast.error("Select at least one case");
if (selectedIds.length === 0 && !isRetainer) return toast.error("Select at least one case");
setSaving(true);
try {
if (isRetainer) {
// Retainer invoices have no time/expenses — create a single line item for the deposit.
const yr = new Date().getFullYear();
const num = `INV-${yr}-${Math.floor(1000 + Math.random() * 9000)}`;
const due = new Date();
due.setDate(due.getDate() + (Number(dueDays) || 30));
const amt = Math.max(0, Number((document.getElementById("retainer-amount") as HTMLInputElement)?.value) || 0);
if (amt <= 0) {
setSaving(false);
return toast.error("Enter a retainer amount");
}
const tax = +(amt * (Number(taxPct) || 0) / 100).toFixed(2);
const { data: inv, error: ie } = await supabase
.from("invoices")
.insert({
invoice_number: num,
client_id: clientId,
status: "draft",
issue_date: new Date().toISOString().slice(0, 10),
due_date: due.toISOString().slice(0, 10),
subtotal: amt,
tax,
total: +(amt + tax).toFixed(2),
notes: notes || "Retainer deposit",
is_retainer: true,
created_by: user.id,
})
.select("id, invoice_number")
.single();
if (ie || !inv) throw ie ?? new Error("Failed to create");
await supabase.from("invoice_line_items").insert({
invoice_id: inv.id,
kind: "manual",
description: "Retainer deposit into trust account",
quantity: 1,
rate: amt,
amount: amt,
sort_order: 0,
});
toast.success(`Retainer invoice ${inv.invoice_number} created`);
onOpenChange(false);
navigate({ to: "/invoices/$invoiceId", params: { invoiceId: inv.id } });
return;
}
const { invoiceId, invoiceNumber } = await generateInvoiceForClient({
clientId,
caseIds: selectedIds,