diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py index cf1d52b086b..1dde96c223d 100644 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py @@ -37,6 +37,59 @@ class TestAccountingPeriod(IntegrationTestCase): doc = create_sales_invoice(do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC") self.assertRaises(ClosedAccountingPeriod, doc.save) + def test_accounting_period_exempted_role(self): + # Create Accounting Period with exempted role + ap = create_accounting_period( + period_name="Test Accounting Period Exempted", + exempted_role="Accounts Manager", + start_date="2025-12-01", + end_date="2025-12-31", + ) + ap.save() + + # Create users + users = frappe.get_all("User", filters={"email": ["like", "test%"]}, limit=1) + user = None + + if users[0].name: + user = frappe.get_doc("User", users[0].name) + else: + user = frappe.get_doc( + { + "doctype": "User", + "email": "test1@example.com", + "first_name": "Test1", + } + ) + user.insert() + + user.roles = [] + user.append("roles", {"role": "Accounts User"}) + + # ---- Non-exempted user should FAIL ---- + user.save(ignore_permissions=True) + frappe.clear_cache(user=user.name) + + frappe.set_user(user.name) + posting_date = "2025-12-11" + doc = create_sales_invoice( + do_not_save=1, + posting_date=posting_date, + ) + + with self.assertRaises(frappe.ValidationError): + doc.submit() + + # ---- Exempted role should PASS ---- + user.append("roles", {"role": "Accounts Manager"}) + user.save(ignore_permissions=True) + frappe.clear_cache(user=user.name) + + doc = create_sales_invoice(do_not_save=1, posting_date=posting_date) + + doc.submit() # Should not raise + self.assertEqual(doc.docstatus, 1) + def tearDown(self): for d in frappe.get_all("Accounting Period"): frappe.delete_doc("Accounting Period", d.name) @@ -51,5 +104,6 @@ def create_accounting_period(**args): accounting_period.company = args.company or "_Test Company" accounting_period.period_name = args.period_name or "_Test_Period_Name_1" accounting_period.append("closed_documents", {"document_type": "Sales Invoice", "closed": 1}) + accounting_period.exempted_role = args.exempted_role or "" return accounting_period diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 47323ad0f40..f3ad258baeb 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -153,7 +153,7 @@ def validate_disabled_accounts(gl_map): def validate_accounting_period(gl_map): accounting_periods = frappe.db.sql( """ SELECT - ap.name as name + ap.name as name, ap.exempted_role as exempted_role FROM `tabAccounting Period` ap, `tabClosed Document` cd WHERE @@ -173,6 +173,10 @@ def validate_accounting_period(gl_map): ) if accounting_periods: + if accounting_periods[0].exempted_role: + exempted_roles = accounting_periods[0].exempted_role + if exempted_roles in frappe.get_roles(): + return frappe.throw( _( "You cannot create or cancel any accounting entries with in the closed Accounting Period {0}"