From 0104d0107cc4002d273e949239f005cad8bc9822 Mon Sep 17 00:00:00 2001 From: yashtilala412 Date: Mon, 29 Sep 2025 13:44:34 +0530 Subject: [PATCH] feat(education/fee_category): Enforce validation for mandatory accounting defaults across all companies --- .../doctype/fee_category/fee_category.py | 26 ++++++++++++++ .../doctype/fee_category/test_fee_category.py | 34 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/education/education/doctype/fee_category/fee_category.py b/education/education/doctype/fee_category/fee_category.py index 1df0f4ed..074e0de1 100644 --- a/education/education/doctype/fee_category/fee_category.py +++ b/education/education/doctype/fee_category/fee_category.py @@ -10,6 +10,7 @@ class FeeCategory(Document): def validate(self): self.update_defaults_from_item_group() self.validate_duplicate_item_defaults() + self.validate_all_companies_covered() def after_insert(self): # create an item @@ -52,6 +53,30 @@ def validate_duplicate_item_defaults(self): if len(companies) != len(set(companies)): frappe.throw(_("Cannot set multiple Item Defaults for a company.")) + def validate_all_companies_covered(self): + """Validate that Item Defaults are set for all active, mandatory companies.""" + + # 1. Get all active companies (not group companies) + active_companies = frappe.db.get_list("Company", filters={"is_group": 0}, pluck="name") + + # 2. Get companies covered in the Fee Category's Item Defaults table + covered_companies = [d.company for d in self.item_defaults] + + # If the defaults table is empty, skip the check (defaults will be fetched from Item Group later) + if not self.item_defaults: + return + + # Find missing companies + missing_companies = list(set(active_companies) - set(covered_companies)) + + if missing_companies: + frappe.throw( + _("Please set Accounting Defaults for the following active companies: {0}").format( + ", ".join(missing_companies) + ), + title=_("Missing Company Defaults") + ) + def create_item(doc, use_name_field=True): name_field = doc.name if use_name_field else doc.fees_category @@ -82,6 +107,7 @@ def update_item(fee_category): ) item_default_companies = [d.company for d in item_defaults] fee_category_companies = [d.company for d in fee_category.item_defaults] + for fee_category_default in fee_category.item_defaults: if fee_category_default.company not in item_default_companies: add_item_defaults(item, fee_category_default) diff --git a/education/education/doctype/fee_category/test_fee_category.py b/education/education/doctype/fee_category/test_fee_category.py index 820b774e..b935327a 100644 --- a/education/education/doctype/fee_category/test_fee_category.py +++ b/education/education/doctype/fee_category/test_fee_category.py @@ -111,3 +111,37 @@ def test_fee_component_duplicate_default(self): for default in default_array: fee_component.append("item_defaults", default) self.assertRaises(frappe.ValidationError, fee_component.save) + + def test_missing_company_default(self): + """ + Validation must fail if Fee Category Defaults are missing for an active company. + """ + # Create a second company for the test environment + create_company("Missing Test Co") + + fee_category_name = "Test Fee Category Missing Company" + fee_category = create_fee_category(fee_category_name) + defaults = get_defaults() + + # Set default for ONLY the primary company (_Test Company) + fee_category.append( + "item_defaults", + { + "company": "_Test Company", + "income_account": defaults.default_income_account, + "selling_cost_center": defaults.cost_center, + }, + ) + + # Expect an error since "Missing Test Co" is not covered + with self.assertRaises(frappe.exceptions.ValidationError) as cm: + fee_category.save() + + # Assert the error message is correct + self.assertIn( + "Please set Accounting Defaults for the following active companies", + str(cm.exception), + ) + + # Clean up the created company + frappe.delete_doc("Company", "Missing Test Co")