From 05a112758796a39f71cd7a4363e93db9f54e02f5 Mon Sep 17 00:00:00 2001 From: Pandiyan P Date: Tue, 8 Sep 2026 16:29:20 +0530 Subject: [PATCH] fix(accounts): reject same-account internal transfers (#58529) (cherry picked from commit 36a4dfe7979c0fd31dc4a574ebe7be3b0558d17c) # Conflicts: # erpnext/accounts/doctype/payment_entry/test_payment_entry.py --- .../doctype/payment_entry/payment_entry.js | 32 ++++++++++++------- .../doctype/payment_entry/payment_entry.py | 5 +++ .../payment_entry/test_payment_entry.py | 17 ++++++++++ 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 5a5815ae4c7..50f50b553cc 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -45,23 +45,27 @@ frappe.ui.form.on("Payment Entry", { }, setup: function (frm) { - frm.set_query("paid_from", function () { + frm.set_query("paid_from", function (doc) { frm.events.validate_company(frm); var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type) ? ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; + let filters = { + account_type: ["in", account_types], + is_group: 0, + company: doc.company, + }; if (frm.doc.party_type == "Shareholder") { account_types.push("Equity"); } + if (doc.payment_type == "Internal Transfer" && doc.paid_to) { + filters.name = ["!=", doc.paid_to]; + } return { - filters: { - account_type: ["in", account_types], - is_group: 0, - company: frm.doc.company, - }, + filters, }; }); @@ -105,21 +109,25 @@ frappe.ui.form.on("Payment Entry", { } }); - frm.set_query("paid_to", function () { + frm.set_query("paid_to", function (doc) { frm.events.validate_company(frm); var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type) ? ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; + let filters = { + account_type: ["in", account_types], + is_group: 0, + company: doc.company, + }; if (frm.doc.party_type == "Shareholder") { account_types.push("Equity"); } + if (doc.payment_type == "Internal Transfer" && doc.paid_from) { + filters.name = ["!=", doc.paid_from]; + } return { - filters: { - account_type: ["in", account_types], - is_group: 0, - company: frm.doc.company, - }, + filters, }; }); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a16e6d01639..31abb641760 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -90,6 +90,7 @@ class PaymentEntry(AccountsController): self.set_liability_account() self.set_missing_ref_details(force=True) self.validate_payment_type() + self.validate_internal_transfer_accounts() self.validate_party_details() self.set_exchange_rate() self.validate_mandatory() @@ -531,6 +532,10 @@ class PaymentEntry(AccountsController): if self.payment_type not in ("Receive", "Pay", "Internal Transfer"): frappe.throw(_("Payment Type must be one of Receive, Pay and Internal Transfer")) + def validate_internal_transfer_accounts(self): + if self.payment_type == "Internal Transfer" and self.paid_from and self.paid_from == self.paid_to: + frappe.throw(_("Paid From and Paid To accounts must be different for an Internal Transfer.")) + def validate_party_details(self): if self.party and not frappe.db.exists(self.party_type, self.party): frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party)) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 1c010e7d74b..a274975b736 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -733,6 +733,23 @@ class TestPaymentEntry(FrappeTestCase): self.validate_gl_entries(pe.name, expected_gle) + def test_internal_transfer_rejects_same_account(self): + pe = frappe.new_doc("Payment Entry") + pe.payment_type = "Internal Transfer" + pe.company = "_Test Company" + pe.paid_from = "_Test Bank - _TC" + pe.paid_to = "_Test Bank - _TC" + pe.paid_amount = 100 + pe.received_amount = 100 + pe.reference_no = "same-account-transfer" + pe.reference_date = nowdate() + + self.assertRaisesRegex( + frappe.ValidationError, + "Paid From and Paid To accounts must be different", + pe.insert, + ) + def test_payment_against_negative_sales_invoice(self): si1 = create_sales_invoice()