From 83d821d8c470d54e3f52a6c05beacaafdd8779f9 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 3 Jul 2026 11:28:56 +0530 Subject: [PATCH 1/3] test: add coverage for Journal Entry Template --- .../test_journal_entry_template.py | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py b/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py index 616327e8493..ea1306140bd 100644 --- a/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py +++ b/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py @@ -1,9 +1,52 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe + +import unittest + +import frappe from erpnext.tests.utils import ERPNextTestSuite +COMPANY = "_Test Company" + class TestJournalEntryTemplate(ERPNextTestSuite): - pass + """Journal Entry Template's only real rule is validate_party: party_type is + allowed only on Receivable/Payable accounts, and a party needs a party_type.""" + + def setUp(self): + frappe.set_user("Administrator") + + def make_template(self, rows, company=COMPANY): + doc = frappe.new_doc("Journal Entry Template") + doc.template_title = f"_Test JET {frappe.generate_hash(length=6)}" + doc.company = company + doc.voucher_type = "Journal Entry" + doc.naming_series = frappe.get_meta("Journal Entry").get_field("naming_series").options.split("\n")[0] + for row in rows: + doc.append("accounts", row) + return doc + + def test_party_type_only_on_receivable_or_payable_account(self): + # Cash is neither Receivable nor Payable, so a party_type here is invalid + doc = self.make_template([{"account": "Cash - _TC", "party_type": "Customer"}]) + self.assertRaises(frappe.ValidationError, doc.validate) + + def test_party_requires_party_type(self): + doc = self.make_template([{"account": "Debtors - _TC", "party": "_Test Customer"}]) + self.assertRaises(frappe.ValidationError, doc.validate) + + @unittest.expectedFailure + def test_account_from_other_company_is_rejected(self): + # SUSPECTED BUG: unlike Item Tax Template / Mode of Payment, this template never + # checks that each row's account belongs to self.company, so a row pointing at + # another company's account saves. Asserts the behaviour we'd want. + other_receivable = frappe.get_all( + "Account", + {"company": "_Test Company 1", "account_type": "Receivable", "is_group": 0}, + pluck="name", + )[0] + doc = self.make_template( + [{"account": other_receivable, "party_type": "Customer", "party": "_Test Customer"}] + ) + self.assertRaises(frappe.ValidationError, doc.insert) From abded56174457aaa8c122221457297bc2b5896c2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 3 Jul 2026 12:04:42 +0530 Subject: [PATCH 2/3] test: guard account lookup and lock missing company-check behaviour --- .../test_journal_entry_template.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py b/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py index ea1306140bd..9b94cd4e35e 100644 --- a/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py +++ b/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py @@ -1,8 +1,6 @@ # Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest - import frappe from erpnext.tests.utils import ERPNextTestSuite @@ -36,17 +34,16 @@ class TestJournalEntryTemplate(ERPNextTestSuite): doc = self.make_template([{"account": "Debtors - _TC", "party": "_Test Customer"}]) self.assertRaises(frappe.ValidationError, doc.validate) - @unittest.expectedFailure - def test_account_from_other_company_is_rejected(self): + def test_account_from_other_company_is_accepted(self): # SUSPECTED BUG: unlike Item Tax Template / Mode of Payment, this template never # checks that each row's account belongs to self.company, so a row pointing at - # another company's account saves. Asserts the behaviour we'd want. - other_receivable = frappe.get_all( - "Account", - {"company": "_Test Company 1", "account_type": "Receivable", "is_group": 0}, - pluck="name", - )[0] + # another company's account saves. Locking the current (wrong) behaviour. + other_receivable = frappe.db.get_value( + "Account", {"company": "_Test Company 1", "account_type": "Receivable", "is_group": 0}, "name" + ) + self.assertTrue(other_receivable, "need a receivable account in _Test Company 1") doc = self.make_template( [{"account": other_receivable, "party_type": "Customer", "party": "_Test Customer"}] ) - self.assertRaises(frappe.ValidationError, doc.insert) + doc.insert() + self.assertTrue(frappe.db.exists("Journal Entry Template", doc.name)) From 2cc02e61d94bd40c653d69e251ae685124295037 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 3 Jul 2026 14:15:19 +0530 Subject: [PATCH 3/3] fix: validate Journal Entry Template rows belong to its company --- .../journal_entry_template.py | 14 ++++++++++++++ .../test_journal_entry_template.py | 8 ++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py index f86706774fc..e552ee1ca20 100644 --- a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py +++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py @@ -45,6 +45,20 @@ class JournalEntryTemplate(Document): def validate(self): self.validate_party() + self.validate_account_company() + + def validate_account_company(self): + """Each row's account must belong to the template's company.""" + for account in self.accounts: + if ( + account.account + and frappe.get_cached_value("Account", account.account, "company") != self.company + ): + frappe.throw( + _("Row {0}: Account {1} does not belong to company {2}").format( + account.idx, account.account, self.company + ) + ) def validate_party(self): """ diff --git a/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py b/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py index 9b94cd4e35e..8b6bed1bca0 100644 --- a/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py +++ b/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py @@ -34,10 +34,7 @@ class TestJournalEntryTemplate(ERPNextTestSuite): doc = self.make_template([{"account": "Debtors - _TC", "party": "_Test Customer"}]) self.assertRaises(frappe.ValidationError, doc.validate) - def test_account_from_other_company_is_accepted(self): - # SUSPECTED BUG: unlike Item Tax Template / Mode of Payment, this template never - # checks that each row's account belongs to self.company, so a row pointing at - # another company's account saves. Locking the current (wrong) behaviour. + def test_account_from_other_company_is_rejected(self): other_receivable = frappe.db.get_value( "Account", {"company": "_Test Company 1", "account_type": "Receivable", "is_group": 0}, "name" ) @@ -45,5 +42,4 @@ class TestJournalEntryTemplate(ERPNextTestSuite): doc = self.make_template( [{"account": other_receivable, "party_type": "Customer", "party": "_Test Customer"}] ) - doc.insert() - self.assertTrue(frappe.db.exists("Journal Entry Template", doc.name)) + self.assertRaises(frappe.ValidationError, doc.insert)