From 60efd3e2195a1e87cade172786ce38917fe9ab8f Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Fri, 17 Jan 2025 11:41:52 +0530 Subject: [PATCH 1/4] feat: add company level validation for accounting dimension --- .../accounting_dimension.json | 3 +- erpnext/controllers/accounts_controller.py | 37 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json index 0e0e713f31b..ccd2b09e7d8 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json @@ -31,7 +31,8 @@ "label": "Reference Document Type", "options": "DocType", "read_only_depends_on": "eval:!doc.__islocal", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "default": "0", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0b23f1bcfa6..5f2b27355a4 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -8,7 +8,7 @@ from collections import defaultdict import frappe from frappe import _, bold, qb, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied -from frappe.query_builder import Criterion +from frappe.query_builder import Criterion, DocType from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.functions import Abs, Sum from frappe.utils import ( @@ -269,6 +269,7 @@ class AccountsController(TransactionBase): self.set_total_in_words() self.set_default_letter_head() + self.validate_company_in_accounting_dimension() def set_default_letter_head(self): if hasattr(self, "letter_head") and not self.letter_head: @@ -407,6 +408,40 @@ class AccountsController(TransactionBase): for row in batches: frappe.delete_doc("Batch", row.name) + def validate_company_in_accounting_dimension(self): + doc_field = DocType("DocField") + accounting_dimension = DocType("Accounting Dimension") + query = ( + frappe.qb.from_(accounting_dimension) + .select(accounting_dimension.document_type) + .join(doc_field) + .on(doc_field.parent == accounting_dimension.document_type) + .where(doc_field.fieldname == "company") + ).run(as_list=True) + + dimension_list = sum(query, ["Project"]) + self.validate_company(dimension_list) + + if childs := self.get_all_children(): + for child in childs: + self.validate_company(dimension_list, child) + + def validate_company(self, dimension_list, child=None): + for dimension in dimension_list: + if not child: + dimension_value = self.get(frappe.scrub(dimension)) + else: + dimension_value = child.get(frappe.scrub(dimension)) + + if dimension_value: + company = frappe.get_cached_value(dimension, dimension_value, "company") + if company and company != self.company: + frappe.throw( + _("{0}: {1} does not belong to the Company: {2}").format( + dimension, frappe.bold(dimension_value), self.company + ) + ) + def validate_return_against_account(self): if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against: cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to" From c94091d68ffc1453b9042f6f2f675d81dcd76849 Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Fri, 17 Jan 2025 11:45:51 +0530 Subject: [PATCH 2/4] test: add new unit test for company validation in accounting dimension --- .../tests/test_accounts_controller.py | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 6bc11b72677..100d53b29b1 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -16,6 +16,7 @@ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.party import get_party_account from erpnext.buying.doctype.purchase_order.test_purchase_order import prepare_data_for_internal_transfer +from erpnext.projects.doctype.project.test_project import make_project from erpnext.stock.doctype.item.test_item import create_item @@ -1472,32 +1473,32 @@ class TestAccountsController(IntegrationTestCase): # Invoices si1 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True) - si1.department = "Management" + si1.department = "Management - _TC" si1.save().submit() si2 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True) - si2.department = "Operations" + si2.department = "Operations - _TC" si2.save().submit() # Payments cr_note1 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True) - cr_note1.department = "Management" + cr_note1.department = "Management - _TC" cr_note1.is_return = 1 cr_note1.save().submit() cr_note2 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True) - cr_note2.department = "Legal" + cr_note2.department = "Legal - _TC" cr_note2.is_return = 1 cr_note2.save().submit() pe1 = get_payment_entry(si1.doctype, si1.name) pe1.references = [] - pe1.department = "Research & Development" + pe1.department = "Research & Development - _TC" pe1.save().submit() pe2 = get_payment_entry(si1.doctype, si1.name) pe2.references = [] - pe2.department = "Management" + pe2.department = "Management - _TC" pe2.save().submit() je1 = self.create_journal_entry( @@ -1510,7 +1511,7 @@ class TestAccountsController(IntegrationTestCase): ) je1.accounts[0].party_type = "Customer" je1.accounts[0].party = self.customer - je1.accounts[0].department = "Management" + je1.accounts[0].department = "Management - _TC" je1.save().submit() # assert dimension filter's result @@ -1519,17 +1520,17 @@ class TestAccountsController(IntegrationTestCase): self.assertEqual(len(pr.invoices), 2) self.assertEqual(len(pr.payments), 5) - pr.department = "Legal" + pr.department = "Legal - _TC" pr.get_unreconciled_entries() self.assertEqual(len(pr.invoices), 0) self.assertEqual(len(pr.payments), 1) - pr.department = "Management" + pr.department = "Management - _TC" pr.get_unreconciled_entries() self.assertEqual(len(pr.invoices), 1) self.assertEqual(len(pr.payments), 3) - pr.department = "Research & Development" + pr.department = "Research & Development - _TC" pr.get_unreconciled_entries() self.assertEqual(len(pr.invoices), 0) self.assertEqual(len(pr.payments), 1) @@ -1540,17 +1541,17 @@ class TestAccountsController(IntegrationTestCase): # Invoice si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True) - si.department = "Management" + si.department = "Management - _TC" si.save().submit() # Payment cr_note = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True) - cr_note.department = "Management" + cr_note.department = "Management - _TC" cr_note.is_return = 1 cr_note.save().submit() pr = self.create_payment_reconciliation() - pr.department = "Management" + pr.department = "Management - _TC" pr.get_unreconciled_entries() self.assertEqual(len(pr.invoices), 1) self.assertEqual(len(pr.payments), 1) @@ -1582,7 +1583,7 @@ class TestAccountsController(IntegrationTestCase): # Sales Invoice in Foreign Currency self.setup_dimensions() rate_in_account_currency = 1 - dpt = "Research & Development" + dpt = "Research & Development - _TC" si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_save=True) si.department = dpt @@ -1617,7 +1618,7 @@ class TestAccountsController(IntegrationTestCase): def test_93_dimension_inheritance_on_advance(self): self.setup_dimensions() - dpt = "Research & Development" + dpt = "Research & Development - _TC" adv = self.create_payment_entry(amount=1, source_exc_rate=85) adv.department = dpt @@ -2075,3 +2076,13 @@ class TestAccountsController(IntegrationTestCase): journal_voucher = frappe.get_doc("Journal Entry", exc_je_for_pi[0].parent) purchase_invoice = frappe.get_doc("Purchase Invoice", pi.name) self.assertEqual(purchase_invoice.advances[0].difference_posting_date, journal_voucher.posting_date) + + def test_company_validation_in_dimension(self): + si = create_sales_invoice(do_not_submit=True) + project = make_project({"project_name": "_Test Demo Project1", "company": "_Test Company 1"}) + si.project = project.name + self.assertRaises(frappe.ValidationError, si.save) + + si_1 = create_sales_invoice(do_not_submit=True) + si_1.items[0].project = project.name + self.assertRaises(frappe.ValidationError, si_1.save) From 454067198ee8a8f649cc9db7109032587c43a098 Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Fri, 17 Jan 2025 11:46:17 +0530 Subject: [PATCH 3/4] fix: set company related values --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 1 + erpnext/projects/doctype/project/test_records.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 2c3104a0615..9bb16355456 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4294,6 +4294,7 @@ class TestSalesInvoice(IntegrationTestCase): si = create_sales_invoice(do_not_submit=True) project = frappe.new_doc("Project") + project.company = "_Test Company" project.project_name = "Test Total Billed Amount" project.save() diff --git a/erpnext/projects/doctype/project/test_records.toml b/erpnext/projects/doctype/project/test_records.toml index f59441bfc06..3e0a04598b4 100644 --- a/erpnext/projects/doctype/project/test_records.toml +++ b/erpnext/projects/doctype/project/test_records.toml @@ -1,4 +1,4 @@ [[Project]] project_name = "_Test Project" status = "Open" - +company = "_Test Company" From 36bae552992f4d9009e96397db052e6e85cc2775 Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Tue, 21 Jan 2025 17:38:24 +0530 Subject: [PATCH 4/4] chore: update variable names for improved readability --- erpnext/controllers/accounts_controller.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5f2b27355a4..988fef0aedf 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -411,7 +411,7 @@ class AccountsController(TransactionBase): def validate_company_in_accounting_dimension(self): doc_field = DocType("DocField") accounting_dimension = DocType("Accounting Dimension") - query = ( + dimension_list = ( frappe.qb.from_(accounting_dimension) .select(accounting_dimension.document_type) .join(doc_field) @@ -419,12 +419,11 @@ class AccountsController(TransactionBase): .where(doc_field.fieldname == "company") ).run(as_list=True) - dimension_list = sum(query, ["Project"]) + dimension_list = sum(dimension_list, ["Project"]) self.validate_company(dimension_list) - if childs := self.get_all_children(): - for child in childs: - self.validate_company(dimension_list, child) + for child in self.get_all_children() or []: + self.validate_company(dimension_list, child) def validate_company(self, dimension_list, child=None): for dimension in dimension_list: