From f1e80d351583f471d7894d0d0ab08159ce763452 Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Wed, 18 Dec 2024 15:21:46 +0530 Subject: [PATCH 1/4] feat: add company validation for all accounting dimension doctypes --- erpnext/controllers/accounts_controller.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 40c410eed05c..0fe8c6e4e8db 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -270,6 +270,20 @@ def validate(self): self.set_total_in_words() self.set_default_letter_head() + if self.get("docstatus") == 1 and self.get("company"): + accounting_dimensions = ["Cost Center", "Project"] + accounting_dimensions += frappe.get_all("Accounting Dimension", pluck="name") + + for dimension in accounting_dimensions: + if self.get(frappe.scrub(dimension)): + doc = frappe.get_doc(dimension, self.get(frappe.scrub(dimension))) + if hasattr(doc, "company") and doc.company != self.company: + frappe.throw( + _("{0} {1} is not part of the current company").format( + dimension, frappe.bold(self.get(frappe.scrub(dimension))) + ) + ) + def set_default_letter_head(self): if hasattr(self, "letter_head") and not self.letter_head: self.letter_head = frappe.db.get_value("Company", self.company, "default_letter_head") From eb00bad020ba6fb95c515e9b533c5010eb14f74f Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Fri, 20 Dec 2024 16:19:31 +0530 Subject: [PATCH 2/4] fix: validate company level accounting dimension in child tables --- erpnext/controllers/accounts_controller.py | 46 ++++++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0fe8c6e4e8db..3fc7f01d029f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -270,17 +270,47 @@ def validate(self): self.set_total_in_words() self.set_default_letter_head() - if self.get("docstatus") == 1 and self.get("company"): - accounting_dimensions = ["Cost Center", "Project"] - accounting_dimensions += frappe.get_all("Accounting Dimension", pluck="name") + self.validate_accounting_dimensions() - for dimension in accounting_dimensions: - if self.get(frappe.scrub(dimension)): - doc = frappe.get_doc(dimension, self.get(frappe.scrub(dimension))) + def validate_accounting_dimensions(self): + if not self.company: + return + parent_doctype = frappe.get_meta(self.doctype).__dict__ + parent_doctype = frappe._dict(parent_doctype) + accounting_dimensions_doctypes = frappe.get_hooks("accounting_dimension_doctypes") + child_table_list = [] + for field in parent_doctype.get("fields"): + field = frappe._dict(field.__dict__) + if field.fieldtype == "Table" and field.options in accounting_dimensions_doctypes: + child_table_list.append(field.fieldname) + accounting_dimensions = ["Cost Center", "Project"] + accounting_dimensions.extend(frappe.get_all("Accounting Dimension", pluck="name")) + + for dimension in accounting_dimensions: + if dimension_value := self.get(frappe.scrub(dimension)): + doc = frappe.get_doc(dimension, dimension_value) + if hasattr(doc, "company") and doc.company != self.company: + frappe.throw( + _("{0}: {1} does not belong to the Company: {2}").format( + dimension, frappe.bold(dimension_value), self.company + ) + ) + if child_table_list: + self.validate_child_accounting_dimensions(accounting_dimensions, child_table_list) + + def validate_child_accounting_dimensions(self, accounting_dimensions, child_table_list): + for child_table in child_table_list: + for child in self.get(child_table, []): + for dimension in accounting_dimensions: + dimension_field = frappe.scrub(dimension) + dimension_value = child.get(dimension_field) + if not dimension_value: + continue + doc = frappe.get_doc(dimension, dimension_value) if hasattr(doc, "company") and doc.company != self.company: frappe.throw( - _("{0} {1} is not part of the current company").format( - dimension, frappe.bold(self.get(frappe.scrub(dimension))) + _("Row {0}: {1} {2} does not belong to the Company: {3}").format( + child.idx, dimension, frappe.bold(dimension_value), self.company ) ) From 2c713e946bdfb02ec4408ef6ccaeda7e771dca38 Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Fri, 20 Dec 2024 16:23:34 +0530 Subject: [PATCH 3/4] test: add new unit test to validate company level accounting dimesnions in doctypes --- .../test_accounting_dimension.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index ccc9f1353b6e..7a18bcd15638 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -81,6 +81,52 @@ def tearDown(self): frappe.flags.accounting_dimensions_details = None frappe.flags.dimension_filter_map = None + def test_company_level_accounting_dimension_validation(self): + company = frappe.new_doc("Company") + company.company_name = "_Test New Company" + company.abbr = "TNC" + company.default_currency = "INR" + company.country = "India" + company.save() + + cost_center = frappe.get_doc( + { + "doctype": "Cost Center", + "cost_center_name": "Main - TNC", + "parent_cost_center": "_Test New Company - TNC", + "is_group": 0, + "company": "_Test New Company", + } + ) + cost_center.save() + + company1 = frappe.new_doc("Company") + company1.company_name = "_Test Demo Company" + company1.abbr = "TDC" + company1.default_currency = "INR" + company1.country = "India" + company1.save() + + cost_center1 = frappe.get_doc( + { + "doctype": "Cost Center", + "cost_center_name": "Main - TDC", + "parent_cost_center": "_Test Demo Company - TDC", + "is_group": 0, + "company": "_Test Demo Company", + } + ) + cost_center1.save() + + si = create_sales_invoice( + company="_Test New Company", customer="_Test Customer", cost_center="Main - TDC", do_not_save=True + ) + self.assertRaises(frappe.ValidationError, si.save) + + si.update({"cost_center": "Main - TNC"}) + si.get("items")[0].update({"cost_center": "Main - TDC"}) + self.assertRaises(frappe.ValidationError, si.save) + def create_dimension(): frappe.set_user("Administrator") From 74fc1e4cc3180ced102b1d746a544c297b6335fd Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Fri, 20 Dec 2024 16:27:00 +0530 Subject: [PATCH 4/4] chore: update company in department accounting dimension --- .../sales_invoice/test_sales_invoice.py | 19 ++++++++++++++++--- .../tests/test_accounts_controller.py | 10 ++++++++++ .../doctype/timesheet/test_timesheet.py | 12 +++++++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6ec1df5d7121..8b31da6863e0 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -77,7 +77,7 @@ def make(self): @classmethod def setUpClass(cls): super().setUpClass() - cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0)) + # cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0)) unlink_payment_on_cancel_of_invoice() @classmethod @@ -3115,6 +3115,15 @@ def test_sales_invoice_against_supplier_usd_with_dimensions(self): # enable common party accounting frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1) + department = frappe.new_doc("Department") + department.department_name = "Test Department" + department.company = "_Test Company" + department.save() + + location = frappe.get_doc("Accounting Dimension", "Location") + location.dimension_defaults[0].mandatory_for_bs = 0 + location.save() + # create a dimension and make it mandatory if not frappe.get_all("Accounting Dimension", filters={"document_type": "Department"}): dim = frappe.get_doc( @@ -3139,7 +3148,7 @@ def test_sales_invoice_against_supplier_usd_with_dimensions(self): si = create_sales_invoice( customer=customer, parent_cost_center="_Test Cost Center - _TC", do_not_submit=True ) - si.department = "All Departments" + si.department = "Test Department - _TC" si.save().submit() # check outstanding of sales invoice @@ -3156,7 +3165,7 @@ def test_sales_invoice_against_supplier_usd_with_dimensions(self): "party": si.customer, "reference_type": si.doctype, "reference_name": si.name, - "department": "All Departments", + "department": "Test Department - _TC", }, pluck="credit_in_account_currency", ) @@ -4281,8 +4290,12 @@ def test_total_billed_amount(self): project = frappe.new_doc("Project") project.project_name = "Test Total Billed Amount" + project.company = "_Test Company" project.save() + location = frappe.get_doc("Accounting Dimension", "Location") + location.dimension_defaults[0].mandatory_for_bs = 0 + location.save() si.project = project.name si.save() si.submit() diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index f54bf29863fb..3f3f1b4f1069 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -1467,6 +1467,13 @@ def test_90_dimensions_filter(self): """ self.setup_dimensions() rate_in_account_currency = 1 + department = frappe.get_all( + "Department", + {"name": ["in", ["Management", "Operations", "Legal", "Research & Development"]]}, + ["name", "company"], + ) + for dept in department: + frappe.db.set_value("Department", dept.name, "company", "_Test Company") # Invoices si1 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True) @@ -1535,6 +1542,7 @@ def test_90_dimensions_filter(self): def test_91_cr_note_should_inherit_dimension(self): self.setup_dimensions() rate_in_account_currency = 1 + frappe.db.set_value("Department", "Management", "company", "_Test Company") # Invoice si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True) @@ -1581,6 +1589,7 @@ def test_92_dimension_inhertiance_exc_gain_loss(self): self.setup_dimensions() rate_in_account_currency = 1 dpt = "Research & Development" + frappe.db.set_value("Department", dpt, "company", "_Test Company") si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_save=True) si.department = dpt @@ -1616,6 +1625,7 @@ def test_92_dimension_inhertiance_exc_gain_loss(self): def test_93_dimension_inheritance_on_advance(self): self.setup_dimensions() dpt = "Research & Development" + frappe.db.set_value("Department", dpt, "company", "_Test Company") adv = self.create_payment_entry(amount=1, source_exc_rate=85) adv.department = dpt diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index ba2f95ba59b0..5902b377ceed 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -55,13 +55,19 @@ def test_sales_invoice_from_timesheet(self): def test_timesheet_billing_based_on_project(self): emp = make_employee("test_employee_6@salary.com") - project = frappe.get_value("Project", {"project_name": "_Test Project"}) + project = frappe.get_doc("Project", "_T-Project-00001") + project.company = "_Test Company" + project.save() + + location = frappe.get_doc("Accounting Dimension", "Location") + location.dimension_defaults[0].mandatory_for_bs = 0 + location.save() timesheet = make_timesheet( - emp, simulate=True, is_billable=1, project=project, company="_Test Company" + emp, simulate=True, is_billable=1, project=project.name, company="_Test Company" ) sales_invoice = create_sales_invoice(do_not_save=True) - sales_invoice.project = project + sales_invoice.project = project.name sales_invoice.submit() ts = frappe.get_doc("Timesheet", timesheet.name)