Merge pull request #62753 from diptanilsaha/fix-payment-reconciliation-access

fix(accounts): check company, party and account access in payment reconciliation
This commit is contained in:
mergify[bot]
2026-10-29 09:23:26 +00:00
committed by GitHub
2 changed files with 50 additions and 0 deletions
@@ -135,6 +135,7 @@ class PaymentReconciliation(Document):
@frappe.whitelist()
def get_unreconciled_entries(self):
self.validate_access()
self.get_nonreconciled_payment_entries()
self.get_invoice_entries()
@@ -478,6 +479,7 @@ class PaymentReconciliation(Document):
@frappe.whitelist()
def allocate_entries(self, args: dict):
self.validate_access()
self.validate_entries()
exc_gain_loss_posting_date = frappe.db.get_single_value(
@@ -600,6 +602,7 @@ class PaymentReconciliation(Document):
@frappe.whitelist()
def reconcile(self):
self.validate_access()
if frappe.get_single_value("Accounts Settings", "auto_reconcile_payments"):
running_doc = is_any_doc_running(
dict(
@@ -655,6 +658,13 @@ class PaymentReconciliation(Document):
return payment_details
def validate_access(self):
if self.company:
erpnext.require_permission("Company", self.company, "select")
if self.receivable_payable_account:
erpnext.require_permission("Account", self.receivable_payable_account)
erpnext.require_party_permission(self.party_type, self.party)
def check_mandatory_to_fetch(self):
for fieldname in ["company", "party_type", "party", "receivable_payable_account"]:
if not self.get(fieldname):
@@ -409,6 +409,32 @@ class TestPaymentReconciliation(ERPNextTestSuite):
outstanding, {(sales_invoice.name, self.debit_to): 50, (je.name, self.debit_to): 100}
)
def test_reconciliation_needs_access_to_the_company(self):
self.create_sales_invoice(qty=1, rate=300)
self.create_payment_entry(amount=300).save().submit()
outsider = make_user_restricted_to_company("pr-outsider@example.com", "_Test Company 1")
insider = make_user_restricted_to_company("pr-insider@example.com", self.company)
pr = self.create_payment_reconciliation()
frappe.set_user(outsider)
try:
self.assertRaises(frappe.PermissionError, pr.get_unreconciled_entries)
self.assertRaises(
frappe.PermissionError, pr.allocate_entries, frappe._dict(invoices=[], payments=[])
)
self.assertRaises(frappe.PermissionError, pr.reconcile)
finally:
frappe.set_user("Administrator")
self.assertEqual(pr.get("invoices"), [])
frappe.set_user(insider)
try:
pr.get_unreconciled_entries()
finally:
frappe.set_user("Administrator")
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(len(pr.get("payments")), 1)
def test_filter_min_max(self):
# check filter condition minimum and maximum amount
self.create_sales_invoice(qty=1, rate=300)
@@ -3190,3 +3216,17 @@ def make_period_closing_voucher(company, cost_center, posting_date=None, submit=
pcv.submit()
return pcv
def make_user_restricted_to_company(user, company):
if not frappe.db.exists("User", user):
frappe.get_doc(
{
"doctype": "User",
"email": user,
"first_name": user.split("@")[0],
"roles": [{"role": "Accounts User"}],
}
).insert()
frappe.permissions.add_user_permission("Company", company, user)
return user