From 36fb3bbfb44bff83d780f94fcfda8c955f36f892 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:11:26 +0000 Subject: [PATCH 1/3] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.$invoiceId.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/routes/invoices.$invoiceId.tsx b/src/routes/invoices.$invoiceId.tsx index 43af859..357f412 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(); } From d5abd69f24dd67a35c6b9ce3f60028d10069cad7 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:11:37 +0000 Subject: [PATCH 2/3] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.$invoiceId.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/routes/invoices.$invoiceId.tsx b/src/routes/invoices.$invoiceId.tsx index 357f412..95fab9b 100644 --- a/src/routes/invoices.$invoiceId.tsx +++ b/src/routes/invoices.$invoiceId.tsx @@ -317,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"); From 7517828bfa677e305718e9d19d90b3d749dcbcac Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:12:15 +0000 Subject: [PATCH 3/3] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.$invoiceId.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/invoices.$invoiceId.tsx b/src/routes/invoices.$invoiceId.tsx index 95fab9b..7241cd9 100644 --- a/src/routes/invoices.$invoiceId.tsx +++ b/src/routes/invoices.$invoiceId.tsx @@ -573,9 +573,9 @@ function InvoiceDetail() { - {canEdit && isDraft && ( + {canEdit && ( )}