diff --git a/src/routes/invoices.$invoiceId.tsx b/src/routes/invoices.$invoiceId.tsx index 43af859..7241cd9 100644 --- a/src/routes/invoices.$invoiceId.tsx +++ b/src/routes/invoices.$invoiceId.tsx @@ -131,6 +131,26 @@ function InvoiceDetail() { const balance = Number(invoice.total) - Number(invoice.amount_paid); const setStatus = async (status: string) => { + if (status === "void") { + if (!confirm("Void this invoice? Totals will be zeroed out and time/expenses will be unbilled.")) return; + // Unlink time/expenses so they go back to unbilled + await supabase.from("time_entries").update({ invoice_id: null }).eq("invoice_id", invoiceId); + await supabase.from("expenses").update({ invoice_id: null }).eq("invoice_id", invoiceId); + // Zero out line item amounts/rates so the invoice reads as $0 + await supabase + .from("invoice_line_items") + .update({ amount: 0, rate: 0, quantity: 0 }) + .eq("invoice_id", invoiceId); + // Zero invoice totals + set status + const { error } = await supabase + .from("invoices") + .update({ status: "void" as any, subtotal: 0, tax: 0, total: 0 }) + .eq("id", invoiceId); + if (error) return toast.error(error.message); + toast.success("Invoice voided and zeroed out"); + load(); + return; + } const { error } = await supabase.from("invoices").update({ status: status as any }).eq("id", invoiceId); if (error) toast.error(error.message); else { toast.success(`Marked ${status}`); load(); } @@ -297,10 +317,16 @@ function InvoiceDetail() { }; const deleteInvoice = async () => { - if (!confirm("Delete this draft invoice and unbill its time/expenses?")) return; + const msg = isDraft + ? "Delete this draft invoice and unbill its time/expenses?" + : `Permanently delete invoice ${invoice.invoice_number}? This will remove all payments and unbill its time/expenses. This cannot be undone.`; + if (!confirm(msg)) return; // Unlink time/expenses await supabase.from("time_entries").update({ invoice_id: null }).eq("invoice_id", invoiceId); await supabase.from("expenses").update({ invoice_id: null }).eq("invoice_id", invoiceId); + // Remove payments first (no cascade guarantee) + await supabase.from("invoice_payments").delete().eq("invoice_id", invoiceId); + await supabase.from("invoice_line_items").delete().eq("invoice_id", invoiceId); const { error } = await supabase.from("invoices").delete().eq("id", invoiceId); if (error) return toast.error(error.message); toast.success("Invoice deleted"); @@ -547,9 +573,9 @@ function InvoiceDetail() { - {canEdit && isDraft && ( + {canEdit && ( )}