fix(accounts): apply user permissions in party balance reports

(cherry picked from commit f0c96eb914)

# Conflicts:
#	erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py
#	erpnext/accounts/report/trial_balance_for_party/test_trial_balance_for_party.py
#	erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
This commit is contained in:
diptanilsaha
2026-10-26 16:22:00 +00:00
committed by Mergify
parent 6ae2594a63
commit 26725aee7e
4 changed files with 323 additions and 1 deletions
@@ -14,6 +14,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimension_with_children,
)
from erpnext.accounts.report.trial_balance.trial_balance import validate_company_permission
TREE_DOCTYPES = frozenset(
["Customer Group", "Territory", "Supplier Group", "Sales Partner", "Sales Person", "Cost Center"]
@@ -49,6 +50,8 @@ class PartyLedgerSummaryReport:
if not self.filters.get("company"):
frappe.throw(_("{0} is mandatory").format(_("Company")))
validate_company_permission(self.filters.company)
if self.filters.from_date > self.filters.to_date:
frappe.throw(_("From Date must be before To Date"))
@@ -378,6 +381,11 @@ class PartyLedgerSummaryReport:
(gle[dimension.fieldname]).isin(self.filters.get(dimension.fieldname))
)
from frappe.desk.reportview import build_match_conditions
if match_conditions := build_match_conditions("GL Entry"):
query = query.where(Bracket(LiteralValue(match_conditions)))
return query
def get_return_invoices(self):
@@ -304,3 +304,88 @@ class TestCustomerLedgerSummary(FrappeTestCase, AccountsTestMixin):
f"Field {field} does not match expected value. "
f"Expected: {expected_value}, Got: {actual_value}",
)
<<<<<<< HEAD
=======
def test_several_group_cost_centers_include_all_their_children(self):
for group in ("_Test CLS Group A", "_Test CLS Group B"):
create_cost_center(cost_center_name=group, is_group=1)
create_cost_center(cost_center_name=f"{group} Child", parent_cost_center=f"{group} - _TC")
self.cost_center = f"{group} Child - _TC"
self.create_sales_invoice()
filters = {
"company": self.company,
"from_date": today(),
"to_date": today(),
"cost_center": ["_Test CLS Group A - _TC", "_Test CLS Group B - _TC"],
}
report = execute(filters)[1]
self.assertEqual([(row.party, row.invoiced_amount) for row in report], [(self.customer, 2000.0)])
def test_refund_of_credit_note_from_before_the_period_is_a_negative_payment(self):
earlier = add_days(today(), -10)
si = self.create_sales_invoice(do_not_submit=True)
si.update({"posting_date": earlier, "set_posting_time": 1})
si.save().submit()
credit_note = self.create_credit_note(si.name, do_not_submit=True)
credit_note.update({"posting_date": earlier, "set_posting_time": 1})
credit_note.items[0].qty = -3
credit_note.save().submit()
get_payment_entry("Sales Invoice", credit_note.name, bank_account=self.cash).insert().submit()
row = execute({"company": self.company, "from_date": today(), "to_date": today()})[1][0]
self.assertEqual(
(row.opening_balance, row.invoiced_amount, row.paid_amount, row.closing_balance),
(700.0, 0.0, -300.0, 1000.0),
)
def test_company_without_permission_is_refused(self):
user = "test_cls_company_permission@example.com"
if not frappe.db.exists("User", user):
frappe.get_doc(
{"doctype": "User", "email": user, "first_name": "CLS", "roles": [{"role": "Accounts User"}]}
).insert()
frappe.permissions.add_user_permission("Company", "_Test Company 1", user)
frappe.set_user(user)
try:
self.assertRaises(
frappe.PermissionError,
execute,
{"company": self.company, "from_date": today(), "to_date": today()},
)
finally:
frappe.set_user("Administrator")
def test_amounts_follow_gl_entry_permissions(self):
from erpnext.accounts.doctype.account.test_account import create_account
other_receivable = create_account(
account_name="_Test CLS Receivable",
company=self.company,
parent_account="Accounts Receivable - _TC",
account_type="Receivable",
)
self.create_sales_invoice()
permitted_receivable, self.debit_to = self.debit_to, other_receivable
self.create_sales_invoice()
user = "test_cls_account_permission@example.com"
if not frappe.db.exists("User", user):
frappe.get_doc(
{"doctype": "User", "email": user, "first_name": "CLS", "roles": [{"role": "Accounts User"}]}
).insert()
frappe.permissions.add_user_permission("Account", permitted_receivable, user)
frappe.set_user(user)
try:
report = execute({"company": self.company, "from_date": today(), "to_date": today()})[1]
finally:
frappe.set_user("Administrator")
self.assertEqual([(row.party, row.invoiced_amount) for row in report], [(self.customer, 1000.0)])
>>>>>>> f0c96eb (fix(accounts): apply user permissions in party balance reports)
@@ -0,0 +1,201 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.trial_balance_for_party.trial_balance_for_party import execute
from erpnext.tests.utils import ERPNextTestSuite
class TestTrialBalanceForParty(ERPNextTestSuite):
def run_report(self, **extra):
filters = frappe._dict(
{
"company": "_Test Company",
"party_type": "Customer",
"fiscal_year": "_Test Fiscal Year 2026",
"from_date": "2026-01-01",
"to_date": "2026-12-31",
**extra,
}
)
return execute(filters)[1]
def party_row(self, party, **extra):
return next(row for row in self.run_report(party=party, **extra) if row.get("party") == party)
def test_sales_invoice_shown_as_period_debit(self):
customer = "_Test Customer"
create_sales_invoice(customer=customer, qty=1, rate=10000, posting_date="2026-06-01")
row = self.party_row(customer)
self.assertEqual(row["opening_debit"], 0)
self.assertEqual(row["debit"], 10000)
self.assertEqual(row["credit"], 0)
self.assertEqual(row["closing_debit"], 10000)
self.assertEqual(row["closing_credit"], 0)
def test_receipt_nets_invoice_in_closing(self):
customer = "_Test Customer"
create_sales_invoice(customer=customer, qty=1, rate=10000, posting_date="2026-06-01")
create_payment_entry(
payment_type="Receive",
party_type="Customer",
party=customer,
paid_from="Debtors - _TC",
paid_to="_Test Bank - _TC",
paid_amount=4000,
save=True,
submit=True,
)
row = self.party_row(customer)
self.assertEqual(row["debit"], 10000)
self.assertEqual(row["credit"], 4000)
# closing nets debit against credit: 10000 - 4000
self.assertEqual(row["closing_debit"], 6000)
self.assertEqual(row["closing_credit"], 0)
def test_prior_period_invoice_shown_as_opening(self):
customer = "_Test Customer"
# invoice dated before from_date should land in the opening balance, not within-period
create_sales_invoice(customer=customer, qty=1, rate=10000, posting_date="2025-12-01")
row = self.party_row(customer)
self.assertEqual(row["opening_debit"], 10000)
self.assertEqual(row["debit"], 0)
self.assertEqual(row["closing_debit"], 10000)
def test_exclude_zero_balance_parties(self):
customer = "_Test Customer"
create_sales_invoice(customer=customer, qty=1, rate=10000, posting_date="2026-06-01")
create_payment_entry(
payment_type="Receive",
party_type="Customer",
party=customer,
paid_from="Debtors - _TC",
paid_to="_Test Bank - _TC",
paid_amount=10000,
save=True,
submit=True,
)
# fully settled party still shows by default ...
self.assertEqual(self.party_row(customer)["closing_debit"], 0)
# ... but is hidden when zero-balance parties are excluded
parties = {row.get("party") for row in self.run_report(exclude_zero_balance_parties=1)}
self.assertNotIn(customer, parties)
def test_purchase_invoice_shown_as_supplier_credit(self):
supplier = "_Test Supplier"
make_purchase_invoice(supplier=supplier, qty=1, rate=8000, posting_date="2026-06-01")
row = self.party_row(supplier, party_type="Supplier")
self.assertEqual(row["credit"], 8000)
self.assertEqual(row["debit"], 0)
self.assertEqual(row["closing_credit"], 8000)
self.assertEqual(row["closing_debit"], 0)
def test_totals_row_sums_party_rows(self):
create_sales_invoice(customer="_Test Customer 1", qty=1, rate=10000, posting_date="2026-06-01")
create_sales_invoice(customer="_Test Customer 2", qty=1, rate=6000, posting_date="2026-06-01")
data = self.run_report()
totals = data[-1] # totals row is appended last
party_rows = data[:-1]
for column in (
"opening_debit",
"opening_credit",
"debit",
"credit",
"closing_debit",
"closing_credit",
):
self.assertEqual(totals[column], sum(row[column] for row in party_rows))
def test_totals_cover_only_permitted_parties(self):
create_sales_invoice(customer="_Test Customer 1", qty=1, rate=10000, posting_date="2026-06-01")
create_sales_invoice(customer="_Test Customer 2", qty=1, rate=6000, posting_date="2026-06-01")
user = "test_tbp_party_permission@example.com"
if not frappe.db.exists("User", user):
frappe.get_doc(
{"doctype": "User", "email": user, "first_name": "TBP", "roles": [{"role": "Accounts User"}]}
).insert()
frappe.permissions.add_user_permission("Customer", "_Test Customer 1", user)
frappe.set_user(user)
try:
data = self.run_report()
finally:
frappe.set_user("Administrator")
self.assertEqual({row["party"] for row in data[:-1]}, {"_Test Customer 1"})
self.assertEqual(data[-1]["debit"], 10000)
def test_company_without_permission_is_refused(self):
user = "test_tbp_company_permission@example.com"
if not frappe.db.exists("User", user):
frappe.get_doc(
{"doctype": "User", "email": user, "first_name": "TBP", "roles": [{"role": "Accounts User"}]}
).insert()
frappe.permissions.add_user_permission("Company", "_Test Company 1", user)
frappe.set_user(user)
try:
self.assertRaises(frappe.PermissionError, self.run_report)
finally:
frappe.set_user("Administrator")
def test_balances_follow_gl_entry_permissions(self):
from erpnext.accounts.doctype.account.test_account import create_account
other_receivable = create_account(
account_name="_Test TBP Receivable",
company="_Test Company",
parent_account="Accounts Receivable - _TC",
account_type="Receivable",
)
create_sales_invoice(customer="_Test Customer", qty=1, rate=10000, posting_date="2026-06-01")
create_sales_invoice(
customer="_Test Customer", qty=1, rate=6000, posting_date="2026-06-01", debit_to=other_receivable
)
user = "test_tbp_account_permission@example.com"
if not frappe.db.exists("User", user):
frappe.get_doc(
{"doctype": "User", "email": user, "first_name": "TBP", "roles": [{"role": "Accounts User"}]}
).insert()
frappe.permissions.add_user_permission("Account", "Debtors - _TC", user)
frappe.set_user(user)
try:
row = self.party_row("_Test Customer")
finally:
frappe.set_user("Administrator")
self.assertEqual(row["debit"], 10000)
def test_alternative_finance_book_entries_excluded(self):
customer = "_Test Customer"
create_sales_invoice(customer=customer, qty=1, rate=10000, posting_date="2026-06-01")
self.make_finance_book_credit(customer, 1500, "2025-12-15")
self.make_finance_book_credit(customer, 4000, "2026-06-10")
row = self.party_row(customer, include_default_book_entries=1)
self.assertEqual((row["opening_credit"], row["credit"], row["closing_debit"]), (0, 0, 10000))
row = self.party_row(customer, finance_book="Test Finance Book 1")
self.assertEqual((row["opening_credit"], row["credit"], row["closing_debit"]), (1500, 4000, 4500))
def make_finance_book_credit(self, customer: str, amount: float, posting_date: str) -> None:
journal = make_journal_entry(
"Sales - _TC", "Debtors - _TC", amount, posting_date=posting_date, save=False
)
journal.accounts[1].update({"party_type": "Customer", "party": customer})
journal.finance_book = "Test Finance Book 1"
journal.submit()
@@ -4,15 +4,25 @@
import frappe
from frappe import _
from frappe.desk.reportview import build_match_conditions
from frappe.query_builder.functions import Sum
<<<<<<< HEAD
from frappe.utils import cint, flt
=======
from frappe.utils import cint, cstr, flt
from pypika.terms import Bracket, LiteralValue
>>>>>>> f0c96eb (fix(accounts): apply user permissions in party balance reports)
from erpnext.accounts.report.general_ledger.general_ledger import get_accounts_with_children
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
from erpnext.accounts.report.trial_balance.trial_balance import (
validate_company_permission,
validate_filters,
)
from erpnext.accounts.utils import get_currency_precision
def execute(filters=None):
validate_company_permission(filters.company)
validate_filters(filters)
show_party_name = is_party_name_visible(filters)
@@ -127,6 +137,15 @@ def get_opening_balances(filters, account_filter=None):
if account_filter:
query = query.where(GL_Entry.account.isin(account_filter))
<<<<<<< HEAD
=======
if finance_book_condition := get_finance_book_condition(GL_Entry, filters):
query = query.where(finance_book_condition)
if match_conditions := build_match_conditions("GL Entry"):
query = query.where(Bracket(LiteralValue(match_conditions)))
>>>>>>> f0c96eb (fix(accounts): apply user permissions in party balance reports)
gle = query.run(as_dict=True)
opening = frappe._dict()
@@ -162,6 +181,15 @@ def get_balances_within_period(filters, account_filter=None):
if account_filter:
query = query.where(GL_Entry.account.isin(account_filter))
<<<<<<< HEAD
=======
if finance_book_condition := get_finance_book_condition(GL_Entry, filters):
query = query.where(finance_book_condition)
if match_conditions := build_match_conditions("GL Entry"):
query = query.where(Bracket(LiteralValue(match_conditions)))
>>>>>>> f0c96eb (fix(accounts): apply user permissions in party balance reports)
gle = query.run(as_dict=True)
balances_within_period = frappe._dict()