From d3759b397160ec343fc3085db0a37552c78b28c0 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 15 Jul 2023 19:32:56 +0530 Subject: [PATCH 01/14] fix: make offsetting entry for acc dimensions --- .../accounting_dimension_detail.json | 22 +++++++++- .../doctype/journal_entry/journal_entry.py | 44 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json b/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json index e9e1f43f99..7b6120a583 100644 --- a/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json +++ b/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json @@ -8,7 +8,10 @@ "reference_document", "default_dimension", "mandatory_for_bs", - "mandatory_for_pl" + "mandatory_for_pl", + "column_break_lqns", + "automatically_post_balancing_accounting_entry", + "offsetting_account" ], "fields": [ { @@ -50,6 +53,23 @@ "fieldtype": "Check", "in_list_view": 1, "label": "Mandatory For Profit and Loss Account" + }, + { + "default": "0", + "fieldname": "automatically_post_balancing_accounting_entry", + "fieldtype": "Check", + "label": "Automatically post balancing accounting entry" + }, + { + "fieldname": "offsetting_account", + "fieldtype": "Link", + "label": "Offsetting Account", + "mandatory_depends_on": "eval: doc.automatically_post_balancing_accounting_entry", + "options": "Account" + }, + { + "fieldname": "column_break_lqns", + "fieldtype": "Column Break" } ], "istable": 1, diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 83312dbd22..ed6122c57d 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -905,8 +905,52 @@ class JournalEntry(AccountsController): item=d, ) ) + + self.make_acc_dimensions_offsetting_entry(gl_map, d) + return gl_map + def make_acc_dimensions_offsetting_entry(self, gl_map, d): + accounting_dimensions = frappe.db.get_list("Accounting Dimension", {"disabled": 0}, pluck="name") + for dimension in accounting_dimensions: + dimension_details = frappe.db.get_values( + "Accounting Dimension Detail", + {"parent": dimension}, + ["automatically_post_balancing_accounting_entry", "offsetting_account"], + )[0] + if dimension_details[0] == 1: + offsetting_account = dimension_details[1] + gl_map.append( + self.get_gl_dict( + { + "account": offsetting_account, + "party_type": d.party_type, + "due_date": self.due_date, + "party": d.party, + "against": d.against_account, + "debit": flt(d.credit, d.precision("credit")), + "credit": flt(d.debit, d.precision("credit")), + "account_currency": d.account_currency, + "debit_in_account_currency": flt( + d.credit_in_account_currency, d.precision("credit_in_account_currency") + ), + "credit_in_account_currency": flt( + d.debit_in_account_currency, d.precision("debit_in_account_currency") + ), + "against_voucher_type": d.reference_type, + "against_voucher": d.reference_name, + "remarks": _( + "Offsetting for Accounting Dimension - {dimension}".format(dimension=dimension) + ), + "voucher_detail_no": d.reference_detail_no, + "cost_center": d.cost_center, + "project": d.project, + "finance_book": self.finance_book, + }, + item=d, + ) + ) + def make_gl_entries(self, cancel=0, adv_adj=0): from erpnext.accounts.general_ledger import make_gl_entries From 4e09de4db2d13fc251d36378cc9aaff775ff910c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 17 Jul 2023 11:47:33 +0530 Subject: [PATCH 02/14] fix: fetch accounting dimension details specific to company --- .../accounts/doctype/journal_entry/journal_entry.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ed6122c57d..334e8b002c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -915,10 +915,11 @@ class JournalEntry(AccountsController): for dimension in accounting_dimensions: dimension_details = frappe.db.get_values( "Accounting Dimension Detail", - {"parent": dimension}, + {"parent": dimension, "company": self.company}, ["automatically_post_balancing_accounting_entry", "offsetting_account"], - )[0] - if dimension_details[0] == 1: + ) + dimension_details = dimension_details[0] if len(dimension_details) > 0 else None + if dimension_details and dimension_details[0] == 1: offsetting_account = dimension_details[1] gl_map.append( self.get_gl_dict( @@ -939,9 +940,7 @@ class JournalEntry(AccountsController): ), "against_voucher_type": d.reference_type, "against_voucher": d.reference_name, - "remarks": _( - "Offsetting for Accounting Dimension - {dimension}".format(dimension=dimension) - ), + "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension), "voucher_detail_no": d.reference_detail_no, "cost_center": d.cost_center, "project": d.project, From 22ba12172fc1143efd778aee4cd296cf6970d736 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 17 Jul 2023 15:17:53 +0530 Subject: [PATCH 03/14] fix: make offsetting entry for all doctypes --- .../doctype/journal_entry/journal_entry.py | 43 ------------------ erpnext/accounts/general_ledger.py | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 334e8b002c..83312dbd22 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -905,51 +905,8 @@ class JournalEntry(AccountsController): item=d, ) ) - - self.make_acc_dimensions_offsetting_entry(gl_map, d) - return gl_map - def make_acc_dimensions_offsetting_entry(self, gl_map, d): - accounting_dimensions = frappe.db.get_list("Accounting Dimension", {"disabled": 0}, pluck="name") - for dimension in accounting_dimensions: - dimension_details = frappe.db.get_values( - "Accounting Dimension Detail", - {"parent": dimension, "company": self.company}, - ["automatically_post_balancing_accounting_entry", "offsetting_account"], - ) - dimension_details = dimension_details[0] if len(dimension_details) > 0 else None - if dimension_details and dimension_details[0] == 1: - offsetting_account = dimension_details[1] - gl_map.append( - self.get_gl_dict( - { - "account": offsetting_account, - "party_type": d.party_type, - "due_date": self.due_date, - "party": d.party, - "against": d.against_account, - "debit": flt(d.credit, d.precision("credit")), - "credit": flt(d.debit, d.precision("credit")), - "account_currency": d.account_currency, - "debit_in_account_currency": flt( - d.credit_in_account_currency, d.precision("credit_in_account_currency") - ), - "credit_in_account_currency": flt( - d.debit_in_account_currency, d.precision("debit_in_account_currency") - ), - "against_voucher_type": d.reference_type, - "against_voucher": d.reference_name, - "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension), - "voucher_detail_no": d.reference_detail_no, - "cost_center": d.cost_center, - "project": d.project, - "finance_book": self.finance_book, - }, - item=d, - ) - ) - def make_gl_entries(self, cancel=0, adv_adj=0): from erpnext.accounts.general_ledger import make_gl_entries diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index f1dad875fa..1299c44307 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -30,6 +30,7 @@ def make_gl_entries( from_repost=False, ): if gl_map: + make_acc_dimensions_offsetting_entry(gl_map) if not cancel: validate_accounting_period(gl_map) validate_disabled_accounts(gl_map) @@ -54,6 +55,50 @@ def make_gl_entries( make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) +def make_acc_dimensions_offsetting_entry(gl_map): + accounting_dimensions_to_offset = get_accounting_dimensions_for_offsetting_entry(gl_map) + if len(accounting_dimensions_to_offset) == 0: + return + + offsetting_entries = [] + for gle in gl_map: + for dimension in accounting_dimensions_to_offset: + dimension_details = frappe.db.get_values( + "Accounting Dimension Detail", + {"parent": dimension, "company": gle.company}, + ["automatically_post_balancing_accounting_entry", "offsetting_account"], + ) + dimension_details = dimension_details[0] if len(dimension_details) > 0 else None + if dimension_details and dimension_details[0] == 1: + offsetting_account = dimension_details[1] + offsetting_entry = gle.copy() + offsetting_entry.update( + { + "account": offsetting_account, + "debit": flt(gle.credit), + "credit": flt(gle.debit), + "debit_in_account_currency": flt(gle.credit_in_account_currency), + "credit_in_account_currency": flt(gle.debit_in_account_currency), + "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension), + "against_voucher": None, + } + ) + offsetting_entry["against_voucher_type"] = None + offsetting_entries.append(offsetting_entry) + gl_map += offsetting_entries + + +def get_accounting_dimensions_for_offsetting_entry(gl_map): + acc_dimensions = frappe.db.get_list("Accounting Dimension", {"disabled": 0}, pluck="name") + accounting_dimensions_to_offset = [] + for acc_dimension in acc_dimensions: + fieldname = acc_dimension.lower().replace(" ", "_") + values = set([entry[fieldname] for entry in gl_map]) + if len(values) > 1: + accounting_dimensions_to_offset.append(acc_dimension) + return accounting_dimensions_to_offset + + def validate_disabled_accounts(gl_map): accounts = [d.account for d in gl_map if d.account] From 4004427892aedf332e2b5ae3c47c4debcdf6ae05 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 17 Jul 2023 17:57:03 +0530 Subject: [PATCH 04/14] test: TB report balanced whenfiltered using acc dimension --- .../trial_balance/test_trial_balance.py | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 erpnext/accounts/report/trial_balance/test_trial_balance.py diff --git a/erpnext/accounts/report/trial_balance/test_trial_balance.py b/erpnext/accounts/report/trial_balance/test_trial_balance.py new file mode 100644 index 0000000000..d4d055464f --- /dev/null +++ b/erpnext/accounts/report/trial_balance/test_trial_balance.py @@ -0,0 +1,111 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import today + +from erpnext.accounts.report.trial_balance.trial_balance import execute + + +class TestTrialBalance(FrappeTestCase): + def setUp(self): + from erpnext.accounts.doctype.account.test_account import create_account + from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + from erpnext.accounts.utils import get_fiscal_year + + self.company = create_company() + create_cost_center( + cost_center_name="Test Cost Center", + company="Trial Balance Company", + parent_cost_center="Trial Balance Company - TBC", + ) + create_account( + account_name="Offsetting", + company="Trial Balance Company", + parent_account="Temporary Accounts - TBC", + ) + self.fiscal_year = get_fiscal_year(today(), company="Trial Balance Company")[0] + create_accounting_dimension() + + def test_offsetting_entries_for_accounting_dimensions(self): + """ + Checks if Trial Balance Report is balanced when filtered using a particular Accounting Dimension + """ + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + + frappe.db.sql("delete from `tabSales Invoice` where company='Trial Balance Company'") + frappe.db.sql("delete from `tabGL Entry` where company='Trial Balance Company'") + + branch1 = frappe.new_doc("Branch") + branch1.branch = "Location 1" + branch1.insert() + branch2 = frappe.new_doc("Branch") + branch2.branch = "Location 2" + branch2.insert() + + si = create_sales_invoice( + company=self.company, + debit_to="Debtors - TBC", + cost_center="Test Cost Center - TBC", + income_account="Sales - TBC", + do_not_submit=1, + ) + si.branch = "Location 1" + si.items[0].branch = "Location 2" + si.save() + si.submit() + + filters = frappe._dict( + {"company": self.company, "fiscal_year": self.fiscal_year, "branch": ["Location 1"]} + ) + total_row = execute(filters)[1][-1] + self.assertEqual(total_row["debit"], total_row["credit"]) + + def tearDown(self): + disable_dimension() + + +def create_company(**args): + args = frappe._dict(args) + company = frappe.get_doc( + { + "doctype": "Company", + "company_name": args.company_name or "Trial Balance Company", + "country": args.country or "India", + "default_currency": args.currency or "INR", + } + ) + company.insert(ignore_if_duplicate=True) + return company.name + + +def create_accounting_dimension(**args): + args = frappe._dict(args) + document_type = args.document_type or "Branch" + if not frappe.db.exists("Accounting Dimension", {"document_type": "Branch"}): + accounting_dimension = frappe.get_doc( + {"doctype": "Accounting Dimension", "document_type": document_type} + ).insert() + else: + accounting_dimension = frappe.get_doc("Accounting Dimension", document_type) + accounting_dimension.disabled = 0 + + accounting_dimension.append( + "dimension_defaults", + { + "company": args.company or "Trial Balance Company", + "automatically_post_balancing_accounting_entry": 1, + "offsetting_account": args.offsetting_account or "Offsetting - TBC", + }, + ) + accounting_dimension.save() + return accounting_dimension.name + + +def disable_dimension(**args): + args = frappe._dict(args) + document_type = args.document_type or "Branch" + dimension = frappe.get_doc("Accounting Dimension", document_type) + dimension.disabled = 1 + dimension.save() From ed3bef1840ac7482f673a69f01dfdb7608037955 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 17 Jul 2023 18:40:52 +0530 Subject: [PATCH 05/14] fix: dict value for dimension for gl entries defined without the dimension --- erpnext/accounts/general_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 1299c44307..e4f30160df 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -93,7 +93,7 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map): accounting_dimensions_to_offset = [] for acc_dimension in acc_dimensions: fieldname = acc_dimension.lower().replace(" ", "_") - values = set([entry[fieldname] for entry in gl_map]) + values = set([entry.get(fieldname) for entry in gl_map]) if len(values) > 1: accounting_dimensions_to_offset.append(acc_dimension) return accounting_dimensions_to_offset From 1e1e4b93c17e7c30f14a59237ffa54cfe093594c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 18 Jul 2023 12:12:24 +0530 Subject: [PATCH 06/14] fix: divide offsetting amount for multiple dimensions --- erpnext/accounts/general_ledger.py | 11 ++++++----- .../accounts/report/trial_balance/trial_balance.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index e4f30160df..a26812c7a6 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -57,7 +57,8 @@ def make_gl_entries( def make_acc_dimensions_offsetting_entry(gl_map): accounting_dimensions_to_offset = get_accounting_dimensions_for_offsetting_entry(gl_map) - if len(accounting_dimensions_to_offset) == 0: + no_of_dimensions = len(accounting_dimensions_to_offset) + if no_of_dimensions == 0: return offsetting_entries = [] @@ -75,10 +76,10 @@ def make_acc_dimensions_offsetting_entry(gl_map): offsetting_entry.update( { "account": offsetting_account, - "debit": flt(gle.credit), - "credit": flt(gle.debit), - "debit_in_account_currency": flt(gle.credit_in_account_currency), - "credit_in_account_currency": flt(gle.debit_in_account_currency), + "debit": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0, + "credit": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0, + "debit_in_account_currency": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0, + "credit_in_account_currency": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0, "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension), "against_voucher": None, } diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index d51c4c4acb..5a9b51b080 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -241,7 +241,7 @@ def get_opening_balance( lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"]) cost_center = frappe.qb.DocType("Cost Center") opening_balance = opening_balance.where( - closing_balance.cost_center.in_( + closing_balance.cost_center.isin( frappe.qb.from_(cost_center) .select("name") .where((cost_center.lft >= lft) & (cost_center.rgt <= rgt)) From 3a3ffa23076f368c4c24fbec1777205a12c76146 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 18 Jul 2023 12:51:09 +0530 Subject: [PATCH 07/14] fix: divide offsetting amount only when account exists --- erpnext/accounts/general_ledger.py | 57 +++++++++++++++++------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index a26812c7a6..994c7c30aa 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -56,7 +56,9 @@ def make_gl_entries( def make_acc_dimensions_offsetting_entry(gl_map): - accounting_dimensions_to_offset = get_accounting_dimensions_for_offsetting_entry(gl_map) + accounting_dimensions_to_offset = get_accounting_dimensions_for_offsetting_entry( + gl_map, gl_map[0].company + ) no_of_dimensions = len(accounting_dimensions_to_offset) if no_of_dimensions == 0: return @@ -64,36 +66,41 @@ def make_acc_dimensions_offsetting_entry(gl_map): offsetting_entries = [] for gle in gl_map: for dimension in accounting_dimensions_to_offset: - dimension_details = frappe.db.get_values( - "Accounting Dimension Detail", - {"parent": dimension, "company": gle.company}, - ["automatically_post_balancing_accounting_entry", "offsetting_account"], + offsetting_account = dimension.offsetting_account + offsetting_entry = gle.copy() + offsetting_entry.update( + { + "account": offsetting_account, + "debit": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0, + "credit": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0, + "debit_in_account_currency": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0, + "credit_in_account_currency": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0, + "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension.name), + "against_voucher": None, + } ) - dimension_details = dimension_details[0] if len(dimension_details) > 0 else None - if dimension_details and dimension_details[0] == 1: - offsetting_account = dimension_details[1] - offsetting_entry = gle.copy() - offsetting_entry.update( - { - "account": offsetting_account, - "debit": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0, - "credit": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0, - "debit_in_account_currency": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0, - "credit_in_account_currency": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0, - "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension), - "against_voucher": None, - } - ) - offsetting_entry["against_voucher_type"] = None - offsetting_entries.append(offsetting_entry) + offsetting_entry["against_voucher_type"] = None + offsetting_entries.append(offsetting_entry) gl_map += offsetting_entries -def get_accounting_dimensions_for_offsetting_entry(gl_map): - acc_dimensions = frappe.db.get_list("Accounting Dimension", {"disabled": 0}, pluck="name") +def get_accounting_dimensions_for_offsetting_entry(gl_map, company): + acc_dimension = frappe.qb.DocType("Accounting Dimension") + dimension_detail = frappe.qb.DocType("Accounting Dimension Detail") + acc_dimensions = ( + frappe.qb.from_(acc_dimension) + .inner_join(dimension_detail) + .on(acc_dimension.name == dimension_detail.parent) + .select(acc_dimension.name, dimension_detail.offsetting_account) + .where( + (acc_dimension.disabled == 0) + & (dimension_detail.company == company) + & (dimension_detail.automatically_post_balancing_accounting_entry == 1) + ) + ).run(as_dict=True) accounting_dimensions_to_offset = [] for acc_dimension in acc_dimensions: - fieldname = acc_dimension.lower().replace(" ", "_") + fieldname = acc_dimension.name.lower().replace(" ", "_") values = set([entry.get(fieldname) for entry in gl_map]) if len(values) > 1: accounting_dimensions_to_offset.append(acc_dimension) From 77deac4fb904a6a727afbd0eb174c2abbaed8834 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 18 Jul 2023 15:51:01 +0530 Subject: [PATCH 08/14] test: PI offsetting entry for accounting dimension --- .../purchase_invoice/test_purchase_invoice.py | 85 ++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 8c96480478..4aaed4ccec 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1736,6 +1736,72 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): rate = flt(sle.stock_value_difference) / flt(sle.actual_qty) self.assertAlmostEqual(returned_inv.items[0].rate, rate) + def test_offsetting_entries_for_accounting_dimensions(self): + from erpnext.accounts.doctype.account.test_account import create_account + + create_account( + account_name="Offsetting", + company="_Test Company", + parent_account="Temporary Accounts - _TC", + ) + + clear_dimension_defaults("Branch") + accounting_dimension = frappe.get_doc("Accounting Dimension", "Branch") + accounting_dimension.disabled = 0 + accounting_dimension.append( + "dimension_defaults", + { + "company": "_Test Company", + "automatically_post_balancing_accounting_entry": 1, + "offsetting_account": "Offsetting - _TC", + }, + ) + accounting_dimension.save() + + branch1 = frappe.new_doc("Branch") + branch1.branch = "Location 1" + branch1.insert(ignore_if_duplicate=True) + branch2 = frappe.new_doc("Branch") + branch2.branch = "Location 2" + branch2.insert(ignore_if_duplicate=True) + + pi = make_purchase_invoice( + company="_Test Company", + customer="_Test Supplier", + do_not_save=True, + do_not_submit=True, + rate=1000, + price_list_rate=1000, + qty=1, + ) + pi.branch = branch1.branch + pi.items[0].branch = branch2.branch + pi.save() + pi.submit() + + expected_gle = [ + ["_Test Account Cost for Goods Sold - _TC", 1000, 0.0, nowdate(), {"branch": branch2.branch}], + ["Creditors - _TC", 0.0, 1000, nowdate(), {"branch": branch1.branch}], + ["Offsetting - _TC", 1000, 0.0, nowdate(), {"branch": branch1.branch}], + ["Offsetting - _TC", 0.0, 1000, nowdate(), {"branch": branch2.branch}], + ] + + check_gl_entries( + self, + pi.name, + expected_gle, + nowdate(), + voucher_type="Purchase Invoice", + check_acc_dimensions=True, + ) + clear_dimension_defaults("Branch") + + +def clear_dimension_defaults(dimension_name): + accounting_dimension = frappe.get_doc("Accounting Dimension", dimension_name) + accounting_dimension.dimension_defaults = [] + accounting_dimension.save() + def set_advance_flag(company, flag, default_account): frappe.db.set_value( @@ -1748,9 +1814,16 @@ def set_advance_flag(company, flag, default_account): ) -def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type="Purchase Invoice"): +def check_gl_entries( + doc, + voucher_no, + expected_gle, + posting_date, + voucher_type="Purchase Invoice", + check_acc_dimensions=False, +): gl = frappe.qb.DocType("GL Entry") - q = ( + query = ( frappe.qb.from_(gl) .select(gl.account, gl.debit, gl.credit, gl.posting_date) .where( @@ -1761,13 +1834,19 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type=" ) .orderby(gl.posting_date, gl.account, gl.creation) ) - gl_entries = q.run(as_dict=True) + if check_acc_dimensions: + for col in list(expected_gle[0][4].keys()): + query = query.select(col) + gl_entries = query.run(as_dict=True) for i, gle in enumerate(gl_entries): doc.assertEqual(expected_gle[i][0], gle.account) doc.assertEqual(expected_gle[i][1], gle.debit) doc.assertEqual(expected_gle[i][2], gle.credit) doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) + if check_acc_dimensions: + for acc_dimension in expected_gle[i][4]: + doc.assertEqual(expected_gle[i][4][acc_dimension], gle[acc_dimension]) def create_tax_witholding_category(category_name, company, account): From b3f6d991b5fd8347311ef05506237f409c1b327b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 19 Jul 2023 12:02:26 +0530 Subject: [PATCH 09/14] fix: duplicate acc dimension in test --- .../purchase_invoice/test_purchase_invoice.py | 23 ++++--------------- .../trial_balance/test_trial_balance.py | 12 ++++++++-- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 4aaed4ccec..846b8bd47c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1738,6 +1738,10 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): def test_offsetting_entries_for_accounting_dimensions(self): from erpnext.accounts.doctype.account.test_account import create_account + from erpnext.accounts.report.trial_balance.test_trial_balance import ( + clear_dimension_defaults, + create_accounting_dimension, + ) create_account( account_name="Offsetting", @@ -1745,18 +1749,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): parent_account="Temporary Accounts - _TC", ) - clear_dimension_defaults("Branch") - accounting_dimension = frappe.get_doc("Accounting Dimension", "Branch") - accounting_dimension.disabled = 0 - accounting_dimension.append( - "dimension_defaults", - { - "company": "_Test Company", - "automatically_post_balancing_accounting_entry": 1, - "offsetting_account": "Offsetting - _TC", - }, - ) - accounting_dimension.save() + create_accounting_dimension() branch1 = frappe.new_doc("Branch") branch1.branch = "Location 1" @@ -1797,12 +1790,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): clear_dimension_defaults("Branch") -def clear_dimension_defaults(dimension_name): - accounting_dimension = frappe.get_doc("Accounting Dimension", dimension_name) - accounting_dimension.dimension_defaults = [] - accounting_dimension.save() - - def set_advance_flag(company, flag, default_account): frappe.db.set_value( "Company", diff --git a/erpnext/accounts/report/trial_balance/test_trial_balance.py b/erpnext/accounts/report/trial_balance/test_trial_balance.py index d4d055464f..1a29e9f692 100644 --- a/erpnext/accounts/report/trial_balance/test_trial_balance.py +++ b/erpnext/accounts/report/trial_balance/test_trial_balance.py @@ -39,10 +39,10 @@ class TestTrialBalance(FrappeTestCase): branch1 = frappe.new_doc("Branch") branch1.branch = "Location 1" - branch1.insert() + branch1.insert(ignore_if_duplicate=True) branch2 = frappe.new_doc("Branch") branch2.branch = "Location 2" - branch2.insert() + branch2.insert(ignore_if_duplicate=True) si = create_sales_invoice( company=self.company, @@ -91,6 +91,8 @@ def create_accounting_dimension(**args): accounting_dimension = frappe.get_doc("Accounting Dimension", document_type) accounting_dimension.disabled = 0 + clear_dimension_defaults(document_type) + accounting_dimension.load_from_db() accounting_dimension.append( "dimension_defaults", { @@ -109,3 +111,9 @@ def disable_dimension(**args): dimension = frappe.get_doc("Accounting Dimension", document_type) dimension.disabled = 1 dimension.save() + + +def clear_dimension_defaults(dimension_name): + accounting_dimension = frappe.get_doc("Accounting Dimension", dimension_name) + accounting_dimension.dimension_defaults = [] + accounting_dimension.save() From e19a6f5dcba50f70ad1378a9b19236be4e1d7ab1 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 19 Jul 2023 12:26:57 +0530 Subject: [PATCH 10/14] fix: fetch acc dimensions correctly when fieldname is different from name --- erpnext/accounts/general_ledger.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 994c7c30aa..eacb318d04 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -91,7 +91,7 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company): frappe.qb.from_(acc_dimension) .inner_join(dimension_detail) .on(acc_dimension.name == dimension_detail.parent) - .select(acc_dimension.name, dimension_detail.offsetting_account) + .select(acc_dimension.fieldname, dimension_detail.offsetting_account) .where( (acc_dimension.disabled == 0) & (dimension_detail.company == company) @@ -100,8 +100,7 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company): ).run(as_dict=True) accounting_dimensions_to_offset = [] for acc_dimension in acc_dimensions: - fieldname = acc_dimension.name.lower().replace(" ", "_") - values = set([entry.get(fieldname) for entry in gl_map]) + values = set([entry.get(acc_dimension.fieldname) for entry in gl_map]) if len(values) > 1: accounting_dimensions_to_offset.append(acc_dimension) return accounting_dimensions_to_offset From 23e56d3ec18f0c8006bf9709520ee48847529bcf Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 19 Jul 2023 16:07:32 +0530 Subject: [PATCH 11/14] fix: clear dimension defaults after test --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 2 ++ erpnext/accounts/report/trial_balance/test_trial_balance.py | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 846b8bd47c..4180beff8a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1741,6 +1741,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): from erpnext.accounts.report.trial_balance.test_trial_balance import ( clear_dimension_defaults, create_accounting_dimension, + disable_dimension, ) create_account( @@ -1788,6 +1789,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): check_acc_dimensions=True, ) clear_dimension_defaults("Branch") + disable_dimension() def set_advance_flag(company, flag, default_account): diff --git a/erpnext/accounts/report/trial_balance/test_trial_balance.py b/erpnext/accounts/report/trial_balance/test_trial_balance.py index 1a29e9f692..732035dc9f 100644 --- a/erpnext/accounts/report/trial_balance/test_trial_balance.py +++ b/erpnext/accounts/report/trial_balance/test_trial_balance.py @@ -63,6 +63,7 @@ class TestTrialBalance(FrappeTestCase): self.assertEqual(total_row["debit"], total_row["credit"]) def tearDown(self): + clear_dimension_defaults("Branch") disable_dimension() @@ -91,8 +92,6 @@ def create_accounting_dimension(**args): accounting_dimension = frappe.get_doc("Accounting Dimension", document_type) accounting_dimension.disabled = 0 - clear_dimension_defaults(document_type) - accounting_dimension.load_from_db() accounting_dimension.append( "dimension_defaults", { From 3f5afb9cac22a8399c2157bca32a1793f6b31e8b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 19 Jul 2023 18:15:59 +0530 Subject: [PATCH 12/14] fix: reset dimension defaults when company changedin test --- .../purchase_invoice/test_purchase_invoice.py | 2 +- .../report/trial_balance/test_trial_balance.py | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 4180beff8a..d2f19a1b25 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1750,7 +1750,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): parent_account="Temporary Accounts - _TC", ) - create_accounting_dimension() + create_accounting_dimension(company="_Test Company", offsetting_account="Offsetting - _TC") branch1 = frappe.new_doc("Branch") branch1.branch = "Location 1" diff --git a/erpnext/accounts/report/trial_balance/test_trial_balance.py b/erpnext/accounts/report/trial_balance/test_trial_balance.py index 732035dc9f..cd0429be4c 100644 --- a/erpnext/accounts/report/trial_balance/test_trial_balance.py +++ b/erpnext/accounts/report/trial_balance/test_trial_balance.py @@ -84,14 +84,14 @@ def create_company(**args): def create_accounting_dimension(**args): args = frappe._dict(args) document_type = args.document_type or "Branch" - if not frappe.db.exists("Accounting Dimension", {"document_type": "Branch"}): - accounting_dimension = frappe.get_doc( - {"doctype": "Accounting Dimension", "document_type": document_type} - ).insert() - else: + if frappe.db.exists("Accounting Dimension", document_type): accounting_dimension = frappe.get_doc("Accounting Dimension", document_type) accounting_dimension.disabled = 0 - + else: + accounting_dimension = frappe.new_doc("Accounting Dimension") + accounting_dimension.document_type = document_type + accounting_dimension.insert() + accounting_dimension.save() accounting_dimension.append( "dimension_defaults", { @@ -101,7 +101,6 @@ def create_accounting_dimension(**args): }, ) accounting_dimension.save() - return accounting_dimension.name def disable_dimension(**args): From 4f9242d699e8aa5c5595a12cafe763737978b01b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 27 Jul 2023 15:45:48 +0530 Subject: [PATCH 13/14] fix: dimension name in remark --- erpnext/accounts/general_ledger.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index eacb318d04..a348911f03 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -66,15 +66,16 @@ def make_acc_dimensions_offsetting_entry(gl_map): offsetting_entries = [] for gle in gl_map: for dimension in accounting_dimensions_to_offset: - offsetting_account = dimension.offsetting_account offsetting_entry = gle.copy() + debit = flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0 + credit = flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0 offsetting_entry.update( { - "account": offsetting_account, - "debit": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0, - "credit": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0, - "debit_in_account_currency": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0, - "credit_in_account_currency": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0, + "account": dimension.offsetting_account, + "debit": debit, + "credit": credit, + "debit_in_account_currency": debit, + "credit_in_account_currency": credit, "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension.name), "against_voucher": None, } @@ -91,7 +92,7 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company): frappe.qb.from_(acc_dimension) .inner_join(dimension_detail) .on(acc_dimension.name == dimension_detail.parent) - .select(acc_dimension.fieldname, dimension_detail.offsetting_account) + .select(acc_dimension.fieldname, acc_dimension.name, dimension_detail.offsetting_account) .where( (acc_dimension.disabled == 0) & (dimension_detail.company == company) From ecca9cb023f91c7504e53b4f709b01622f156fd9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 29 Jul 2023 11:52:54 +0530 Subject: [PATCH 14/14] fix: Add company filters for account --- .../accounting_dimension.js | 11 +++++++ .../accounting_dimension.py | 10 +++++++ .../purchase_invoice/test_purchase_invoice.py | 29 +++++++++++-------- erpnext/accounts/general_ledger.py | 7 ++++- .../trial_balance/test_trial_balance.py | 3 +- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index 2fa1d53c60..2f53f7b640 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -15,6 +15,17 @@ frappe.ui.form.on('Accounting Dimension', { }; }); + frm.set_query("offsetting_account", "dimension_defaults", function(doc, cdt, cdn) { + let d = locals[cdt][cdn]; + return { + filters: { + company: d.company, + root_type: ["in", ["Asset", "Liability"]], + is_group: 0 + } + } + }); + if (!frm.is_new()) { frm.add_custom_button(__('Show {0}', [frm.doc.document_type]), function () { frappe.set_route("List", frm.doc.document_type); diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 81ff6a52db..ea44734e75 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -39,6 +39,8 @@ class AccountingDimension(Document): if not self.is_new(): self.validate_document_type_change() + self.validate_dimension_defaults() + def validate_document_type_change(self): doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type") if doctype_before_save != self.document_type: @@ -46,6 +48,14 @@ class AccountingDimension(Document): message += _("Please create a new Accounting Dimension if required.") frappe.throw(message) + def validate_dimension_defaults(self): + companies = [] + for default in self.get("dimension_defaults"): + if default.company not in companies: + companies.append(default.company) + else: + frappe.throw(_("Company {0} is added more than once").format(frappe.bold(default.company))) + def after_insert(self): if frappe.flags.in_test: make_dimension_in_accounting_doctypes(doc=self) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index d2f19a1b25..486e01e00f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1774,10 +1774,10 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi.submit() expected_gle = [ - ["_Test Account Cost for Goods Sold - _TC", 1000, 0.0, nowdate(), {"branch": branch2.branch}], - ["Creditors - _TC", 0.0, 1000, nowdate(), {"branch": branch1.branch}], - ["Offsetting - _TC", 1000, 0.0, nowdate(), {"branch": branch1.branch}], - ["Offsetting - _TC", 0.0, 1000, nowdate(), {"branch": branch2.branch}], + ["_Test Account Cost for Goods Sold - _TC", 1000, 0.0, nowdate(), branch2.branch], + ["Creditors - _TC", 0.0, 1000, nowdate(), branch1.branch], + ["Offsetting - _TC", 1000, 0.0, nowdate(), branch1.branch], + ["Offsetting - _TC", 0.0, 1000, nowdate(), branch2.branch], ] check_gl_entries( @@ -1786,7 +1786,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): expected_gle, nowdate(), voucher_type="Purchase Invoice", - check_acc_dimensions=True, + additional_columns=["branch"], ) clear_dimension_defaults("Branch") disable_dimension() @@ -1809,7 +1809,7 @@ def check_gl_entries( expected_gle, posting_date, voucher_type="Purchase Invoice", - check_acc_dimensions=False, + additional_columns=None, ): gl = frappe.qb.DocType("GL Entry") query = ( @@ -1823,9 +1823,11 @@ def check_gl_entries( ) .orderby(gl.posting_date, gl.account, gl.creation) ) - if check_acc_dimensions: - for col in list(expected_gle[0][4].keys()): - query = query.select(col) + + if additional_columns: + for col in additional_columns: + query = query.select(gl[col]) + gl_entries = query.run(as_dict=True) for i, gle in enumerate(gl_entries): @@ -1833,9 +1835,12 @@ def check_gl_entries( doc.assertEqual(expected_gle[i][1], gle.debit) doc.assertEqual(expected_gle[i][2], gle.credit) doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) - if check_acc_dimensions: - for acc_dimension in expected_gle[i][4]: - doc.assertEqual(expected_gle[i][4][acc_dimension], gle[acc_dimension]) + + if additional_columns: + j = 4 + for col in additional_columns: + doc.assertEqual(expected_gle[i][j], gle[col]) + j += 1 def create_tax_witholding_category(category_name, company, account): diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index a348911f03..addc9d5754 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -30,8 +30,8 @@ def make_gl_entries( from_repost=False, ): if gl_map: - make_acc_dimensions_offsetting_entry(gl_map) if not cancel: + make_acc_dimensions_offsetting_entry(gl_map) validate_accounting_period(gl_map) validate_disabled_accounts(gl_map) gl_map = process_gl_map(gl_map, merge_entries) @@ -64,6 +64,7 @@ def make_acc_dimensions_offsetting_entry(gl_map): return offsetting_entries = [] + for gle in gl_map: for dimension in accounting_dimensions_to_offset: offsetting_entry = gle.copy() @@ -82,12 +83,14 @@ def make_acc_dimensions_offsetting_entry(gl_map): ) offsetting_entry["against_voucher_type"] = None offsetting_entries.append(offsetting_entry) + gl_map += offsetting_entries def get_accounting_dimensions_for_offsetting_entry(gl_map, company): acc_dimension = frappe.qb.DocType("Accounting Dimension") dimension_detail = frappe.qb.DocType("Accounting Dimension Detail") + acc_dimensions = ( frappe.qb.from_(acc_dimension) .inner_join(dimension_detail) @@ -99,11 +102,13 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company): & (dimension_detail.automatically_post_balancing_accounting_entry == 1) ) ).run(as_dict=True) + accounting_dimensions_to_offset = [] for acc_dimension in acc_dimensions: values = set([entry.get(acc_dimension.fieldname) for entry in gl_map]) if len(values) > 1: accounting_dimensions_to_offset.append(acc_dimension) + return accounting_dimensions_to_offset diff --git a/erpnext/accounts/report/trial_balance/test_trial_balance.py b/erpnext/accounts/report/trial_balance/test_trial_balance.py index cd0429be4c..4682ac4500 100644 --- a/erpnext/accounts/report/trial_balance/test_trial_balance.py +++ b/erpnext/accounts/report/trial_balance/test_trial_balance.py @@ -91,7 +91,8 @@ def create_accounting_dimension(**args): accounting_dimension = frappe.new_doc("Accounting Dimension") accounting_dimension.document_type = document_type accounting_dimension.insert() - accounting_dimension.save() + + accounting_dimension.set("dimension_defaults", []) accounting_dimension.append( "dimension_defaults", {