diff --git a/src/pages/accounting/AccountingBillsPage.tsx b/src/pages/accounting/AccountingBillsPage.tsx index e3e3f77..e3913b7 100644 --- a/src/pages/accounting/AccountingBillsPage.tsx +++ b/src/pages/accounting/AccountingBillsPage.tsx @@ -46,6 +46,7 @@ export default function AccountingBillsPage() { const parseFn = parseBill; const [open, setOpen] = useState(false); const [editId, setEditId] = useState(null); + const [editBill, setEditBill] = useState(null); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [autoOnly, setAutoOnly] = useState(false); @@ -130,7 +131,7 @@ export default function AccountingBillsPage() { const total = +(subtotal + tax).toFixed(2); const resetForm = () => { - setEditId(null); + setEditId(null); setEditBill(null); setVendorId(""); setNumber(""); setDueDate(""); setTaxPct(0); setNotes(""); setItems([{ description: "", quantity: 1, rate: 0, account_id: null }]); setFile(null); setFilePreview(null); setUploadedUrl(null); @@ -139,6 +140,7 @@ export default function AccountingBillsPage() { const openEdit = async (b: any) => { setEditId(b.id); + setEditBill(b); // The dropdown is keyed by public vendor id; map the stored accounting // vendor back to its source public vendor when one exists. let pubVendorId = ""; @@ -261,6 +263,7 @@ export default function AccountingBillsPage() { const save = async (keepOpen = false) => { if (!number.trim()) return toast.error("Bill number required"); if (!vendorId) return toast.error("Select a vendor (Pay to) before saving"); + try { let attachmentUrl = uploadedUrl; if (file && !attachmentUrl) attachmentUrl = await uploadFileObj(file); @@ -291,8 +294,27 @@ export default function AccountingBillsPage() { }).eq("id", editId); if (error) return toast.error(error.message); await accounting.from("bill_items").delete().eq("bill_id", editId); - await accounting.from("bill_items").insert(itemRows(editId)); - toast.success("Bill updated"); + const { error: biErr } = await accounting.from("bill_items").insert(itemRows(editId)); + if (biErr) return toast.error(biErr.message); + + // Keep an already-paid bill's payment in sync with the edited amount. When + // the total changed and the bill has a single linked bill payment, update + // that payment (bank transaction + check) and the paid amount to match. + const wasPaid = Number(editBill?.paid_amount ?? 0) > 0; + const amountChanged = Math.abs(Number(editBill?.total ?? 0) - total) > 0.005; + if (wasPaid && amountChanged) { + const { data: pays } = await accounting.from("transactions").select("id").eq("bill_id", editId); + if ((pays?.length ?? 0) === 1) { + await accounting.from("transactions").update({ amount: total }).eq("bill_id", editId); + await accounting.from("checks").update({ amount: total }).eq("source_bill_id", editId); + await accounting.from("bills").update({ paid_amount: total, status: total > 0 ? "paid" : "open" }).eq("id", editId); + toast.success("Bill and payment updated"); + } else { + toast.warning("Bill updated — it has split/partial payments, so review the payment(s) manually."); + } + } else { + toast.success("Bill updated"); + } } else { const { data: bill, error } = await accounting.from("bills").insert({ company_id: cid, vendor_id: acctVendorId, number, @@ -308,6 +330,11 @@ export default function AccountingBillsPage() { resetForm(); if (!keepOpen) setOpen(false); qc.invalidateQueries({ queryKey: ["bills", cid] }); + qc.invalidateQueries({ queryKey: ["transactions", cid] }); + qc.invalidateQueries({ queryKey: ["accounts", cid] }); + } catch (e: any) { + toast.error(e?.message ?? "Failed to save bill"); + } }; // ── Read-only bill detail view ──