diff --git a/erpnext/public/js/sales_order_proforma.js b/erpnext/public/js/sales_order_proforma.js index afd36f248e9..3e4677e4f57 100644 --- a/erpnext/public/js/sales_order_proforma.js +++ b/erpnext/public/js/sales_order_proforma.js @@ -189,7 +189,8 @@ Object.assign(erpnext.proforma, { const list = new frappe.ui.EmbeddedList({ wrapper, doctype: "Proforma Invoice", - filters: { sales_order: frm.doc.name, docstatus: 1 }, + // Include cancelled (docstatus 2) so voided proformas stay visible for audit. + filters: { sales_order: frm.doc.name, docstatus: ["in", [1, 2]] }, fields: ["name", "proforma_date", "grand_total", "status", "proforma_pdf", "sent_on", "currency"], order_by: "creation desc", empty_message: __("No proforma invoices yet."), @@ -218,7 +219,7 @@ Object.assign(erpnext.proforma, { label: __("Status"), type: "badge", fieldname: "status", - color: (row) => (row.status === "Issued" ? "green" : "gray"), + color: (row) => (row.status === "Cancelled" ? "red" : "green"), }, { type: "actions", diff --git a/erpnext/selling/doctype/proforma_invoice/proforma_invoice.py b/erpnext/selling/doctype/proforma_invoice/proforma_invoice.py index 8d60aa89fe9..9569db2d5c1 100644 --- a/erpnext/selling/doctype/proforma_invoice/proforma_invoice.py +++ b/erpnext/selling/doctype/proforma_invoice/proforma_invoice.py @@ -52,7 +52,7 @@ class ProformaInvoice(Document): self.generate_and_attach_pdf() def on_cancel(self) -> None: - self.status = "Cancelled" + self.db_set("status", "Cancelled") def set_total_qty(self) -> None: self.total_qty = sum(flt(item.qty) for item in self.items) diff --git a/erpnext/selling/doctype/proforma_invoice/test_proforma_invoice.py b/erpnext/selling/doctype/proforma_invoice/test_proforma_invoice.py index ae148a2cec0..c48de3f041c 100644 --- a/erpnext/selling/doctype/proforma_invoice/test_proforma_invoice.py +++ b/erpnext/selling/doctype/proforma_invoice/test_proforma_invoice.py @@ -80,6 +80,18 @@ class TestProformaInvoice(ERPNextTestSuite): self.assertEqual(flt(item.amount), 250) self.assertEqual(flt(proforma.grand_total), 250) + def test_cancelled_proforma_keeps_pdf(self): + """Cancelling voids the proforma but keeps its PDF and status for the audit trail.""" + sales_order = make_sales_order(qty=10) + proforma = self.create_proforma(sales_order, [(sales_order.items[0].name, 4)]) + pdf = proforma.proforma_pdf + self.assertTrue(pdf) + + proforma.cancel() + proforma.reload() + self.assertEqual(proforma.status, "Cancelled") + self.assertEqual(proforma.proforma_pdf, pdf) + def test_feature_toggle_is_enforced(self): sales_order = make_sales_order(qty=10) frappe.db.set_single_value("Selling Settings", "enable_proforma_invoice", 0)