From 8f9db3c72d3245d9c10efee867494bbb3bba928b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:11:25 +0530 Subject: [PATCH] Avoid status updation for purchase invoice from paid to unpaid by issuing a paid debit note against it (backport #54382) (#55576) Co-authored-by: Shllokkk <140623894+Shllokkk@users.noreply.github.com> --- .../purchase_invoice/purchase_invoice.js | 19 +++++++++++ .../purchase_invoice/purchase_invoice.py | 34 ++++++++++++++++--- erpnext/controllers/accounts_controller.py | 2 +- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 9b0cf76aee5..68dc8d26d1b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -591,6 +591,25 @@ frappe.ui.form.on("Purchase Invoice", { }; }); + frm.set_query("write_off_account", function (doc) { + return { + filters: { + report_type: "Profit and Loss", + is_group: 0, + company: doc.company, + }, + }; + }); + + frm.set_query("write_off_cost_center", function (doc) { + return { + filters: { + is_group: 0, + company: doc.company, + }, + }; + }); + frm.fields_dict["items"].grid.get_field("deferred_expense_account").get_query = function (doc) { return { filters: { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index a7ef9f1414d..5323c93eaca 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -290,6 +290,7 @@ class PurchaseInvoice(BuyingController): self.validate_expense_account() self.set_against_expense_account() self.validate_write_off_account() + self.validate_write_off_cost_center() self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount") self.set_status() self.validate_purchase_receipt_if_update_stock() @@ -659,6 +660,27 @@ class PurchaseInvoice(BuyingController): if self.write_off_amount and not self.write_off_account: throw(_("Please enter Write Off Account")) + if not self.write_off_account: + return + + doc = frappe.db.get_value( + "Account", self.write_off_account, ["report_type", "is_group", "company"], as_dict=True + ) + + if not doc or doc.report_type != "Profit and Loss" or doc.is_group or doc.company != self.company: + throw(_("Please enter a valid Write Off Account")) + + def validate_write_off_cost_center(self): + if not self.write_off_cost_center: + return + + doc = frappe.db.get_value( + "Cost Center", self.write_off_cost_center, ["is_group", "company"], as_dict=True + ) + + if not doc or doc.is_group or doc.company != self.company: + throw(_("Please enter a valid Write Off Cost Center")) + def check_prev_docstatus(self): for d in self.get("items"): if d.purchase_order: @@ -739,6 +761,7 @@ class PurchaseInvoice(BuyingController): def validate_for_repost(self): self.validate_write_off_account() + self.validate_write_off_cost_center() self.validate_expense_account() validate_docs_for_voucher_types(["Purchase Invoice"]) validate_docs_for_deferred_accounting([], [self.name]) @@ -852,7 +875,9 @@ class PurchaseInvoice(BuyingController): if update_outstanding == "No": update_voucher_outstanding( voucher_type=self.doctype, - voucher_no=self.return_against if cint(self.is_return) and self.return_against else self.name, + voucher_no=self.return_against + if (cint(self.is_return) and self.return_against) + else self.name, account=self.credit_to, party_type="Supplier", party=self.supplier, @@ -1546,6 +1571,9 @@ class PurchaseInvoice(BuyingController): def make_payment_gl_entries(self, gl_entries): # Make Cash GL Entries if cint(self.is_paid) and self.cash_bank_account and self.paid_amount: + against_voucher = self.name + if self.is_return and self.return_against and not self.update_outstanding_for_self: + against_voucher = self.return_against bank_account_currency = get_account_currency(self.cash_bank_account) # CASH, make payment entries gl_entries.append( @@ -1560,9 +1588,7 @@ class PurchaseInvoice(BuyingController): if self.party_account_currency == self.company_currency else self.paid_amount, "debit_in_transaction_currency": self.paid_amount, - "against_voucher": self.return_against - if cint(self.is_return) and self.return_against - else self.name, + "against_voucher": against_voucher, "against_voucher_type": self.doctype, "cost_center": self.cost_center, "project": self.project, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 09029405e65..110b4834611 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -182,7 +182,7 @@ class AccountsController(TransactionBase): if not get_meta(self.doctype).has_field("outstanding_amount"): return - if self.get("is_return") and self.return_against and not self.get("is_pos"): + if self.get("is_return") and self.return_against and not (self.get("is_pos") or self.get("is_paid")): against_voucher_outstanding = frappe.get_value( self.doctype, self.return_against, "outstanding_amount" )