From e95dce6d8d8782d1a36da56eec87dcb1384d4350 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 11:54:12 +0530 Subject: [PATCH 01/19] refactor: remove balance fields from jv account --- .../journal_entry_account.json | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 3ba8cea94b..6e694a615d 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -9,12 +9,10 @@ "field_order": [ "account", "account_type", - "balance", "col_break1", "bank_account", "party_type", "party", - "party_balance", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -63,17 +61,6 @@ "label": "Account Type", "print_hide": 1 }, - { - "fieldname": "balance", - "fieldtype": "Currency", - "label": "Account Balance", - "no_copy": 1, - "oldfieldname": "balance", - "oldfieldtype": "Data", - "options": "account_currency", - "print_hide": 1, - "read_only": 1 - }, { "default": ":Company", "description": "If Income or Expense", @@ -107,14 +94,6 @@ "label": "Party", "options": "party_type" }, - { - "fieldname": "party_balance", - "fieldtype": "Currency", - "label": "Party Balance", - "options": "account_currency", - "print_hide": 1, - "read_only": 1 - }, { "fieldname": "currency_section", "fieldtype": "Section Break", @@ -284,7 +263,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-06-16 14:11:13.507807", + "modified": "2023-10-12 11:52:46.397186", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", From aba3065573c2cdb1ead297a836bfcfff068b1e55 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:04:44 +0530 Subject: [PATCH 02/19] refactor: remove balance formatter --- .../doctype/journal_entry/journal_entry.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 22b6880ad5..b39cc8bd99 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -184,7 +184,6 @@ var update_jv_details = function(doc, r) { $.each(r, function(i, d) { var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts"); frappe.model.set_value(row.doctype, row.name, "account", d.account) - frappe.model.set_value(row.doctype, row.name, "balance", d.balance) }); refresh_field("accounts"); } @@ -193,7 +192,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro onload() { this.load_defaults(); this.setup_queries(); - this.setup_balance_formatter(); erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype); } @@ -292,19 +290,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro } - setup_balance_formatter() { - const formatter = function(value, df, options, doc) { - var currency = frappe.meta.get_field_currency(df, doc); - var dr_or_cr = value ? ('') : ""; - return "
" - + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency)) - + " " + dr_or_cr - + "
"; - }; - this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter); - this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter); - } - reference_name(doc, cdt, cdn) { var d = frappe.get_doc(cdt, cdn); From c99e7ca9994ab4681b832ceab7d52d5410c0e106 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:07:36 +0530 Subject: [PATCH 03/19] refactor: exclude balance while setting acc details --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 9 ++++----- erpnext/accounts/doctype/journal_entry/journal_entry.py | 5 ++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index b39cc8bd99..957f734103 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -397,11 +397,11 @@ frappe.ui.form.on("Journal Entry Account", { } }, cost_center: function(frm, dt, dn) { - erpnext.journal_entry.set_account_balance(frm, dt, dn); + erpnext.journal_entry.set_account_details(frm, dt, dn); }, account: function(frm, dt, dn) { - erpnext.journal_entry.set_account_balance(frm, dt, dn); + erpnext.journal_entry.set_account_details(frm, dt, dn); }, debit_in_account_currency: function(frm, cdt, cdn) { @@ -585,14 +585,14 @@ $.extend(erpnext.journal_entry, { }); $.extend(erpnext.journal_entry, { - set_account_balance: function(frm, dt, dn) { + set_account_details: function(frm, dt, dn) { var d = locals[dt][dn]; if(d.account) { if(!frm.doc.company) frappe.throw(__("Please select Company first")); if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first")); return frappe.call({ - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type", + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_details_and_party_type", args: { account: d.account, date: frm.doc.posting_date, @@ -600,7 +600,6 @@ $.extend(erpnext.journal_entry, { debit: flt(d.debit_in_account_currency), credit: flt(d.credit_in_account_currency), exchange_rate: d.exchange_rate, - cost_center: d.cost_center }, callback: function(r) { if(r.message) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 85ef6f76d2..eef23e3fa4 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1410,8 +1410,8 @@ def get_party_account_and_balance(company, party_type, party, cost_center=None): @frappe.whitelist() -def get_account_balance_and_party_type( - account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None +def get_account_details_and_party_type( + account, date, company, debit=None, credit=None, exchange_rate=None ): """Returns dict of account balance and party type to be set in Journal Entry on selection of account.""" if not frappe.has_permission("Account"): @@ -1433,7 +1433,6 @@ def get_account_balance_and_party_type( party_type = "" grid_values = { - "balance": get_balance_on(account, date, cost_center=cost_center), "party_type": party_type, "account_type": account_details.account_type, "account_currency": account_details.account_currency or company_currency, From 4d38bd85175535b93f524541942bf8854e96f788 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:09:58 +0530 Subject: [PATCH 04/19] refactor: exclude balances while setting currency --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 3 +-- erpnext/accounts/doctype/journal_entry/journal_entry.py | 9 +-------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 957f734103..4847ffd2ef 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -385,13 +385,12 @@ frappe.ui.form.on("Journal Entry Account", { if(!d.account && d.party_type && d.party) { if(!frm.doc.company) frappe.throw(__("Please select Company")); return frm.call({ - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance", + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_currency", child: d, args: { company: frm.doc.company, party_type: d.party_type, party: d.party, - cost_center: d.cost_center } }); } diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index eef23e3fa4..4e425a3776 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1390,21 +1390,14 @@ def get_outstanding(args): @frappe.whitelist() -def get_party_account_and_balance(company, party_type, party, cost_center=None): +def get_party_account_and_currency(company, party_type, party): if not frappe.has_permission("Account"): frappe.msgprint(_("No Permission"), raise_exception=1) account = get_party_account(party_type, party, company) - account_balance = get_balance_on(account=account, cost_center=cost_center) - party_balance = get_balance_on( - party_type=party_type, party=party, company=company, cost_center=cost_center - ) - return { "account": account, - "balance": account_balance, - "party_balance": party_balance, "account_currency": frappe.get_cached_value("Account", account, "account_currency"), } From d94f45531a5a5d9798801f00e774422d601c4b78 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:13:21 +0530 Subject: [PATCH 05/19] refactor: remove controller logic for setting balances --- .../doctype/journal_entry/journal_entry.py | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 4e425a3776..006fb5618f 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -68,7 +68,6 @@ class JournalEntry(AccountsController): self.set_print_format_fields() self.validate_credit_debit_note() self.validate_empty_accounts_table() - self.set_account_and_party_balance() self.validate_inter_company_accounts() self.validate_depr_entry_voucher_type() @@ -1061,21 +1060,6 @@ class JournalEntry(AccountsController): if not self.get("accounts"): frappe.throw(_("Accounts table cannot be blank.")) - def set_account_and_party_balance(self): - account_balance = {} - party_balance = {} - for d in self.get("accounts"): - if d.account not in account_balance: - account_balance[d.account] = get_balance_on(account=d.account, date=self.posting_date) - - if (d.party_type, d.party) not in party_balance: - party_balance[(d.party_type, d.party)] = get_balance_on( - party_type=d.party_type, party=d.party, date=self.posting_date, company=self.company - ) - - d.account_balance = account_balance[d.account] - d.party_balance = party_balance[(d.party_type, d.party)] - @frappe.whitelist() def get_default_bank_cash_account(company, account_type=None, mode_of_payment=None, account=None): @@ -1241,8 +1225,6 @@ def get_payment_entry(ref_doc, args): "account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"), "account_currency": args.get("party_account_currency") or get_account_currency(args.get("party_account")), - "balance": get_balance_on(args.get("party_account")), - "party_balance": get_balance_on(party=args.get("party"), party_type=args.get("party_type")), "exchange_rate": exchange_rate, args.get("amount_field_party"): args.get("amount"), "is_advance": args.get("is_advance"), @@ -1406,7 +1388,7 @@ def get_party_account_and_currency(company, party_type, party): def get_account_details_and_party_type( account, date, company, debit=None, credit=None, exchange_rate=None ): - """Returns dict of account balance and party type to be set in Journal Entry on selection of account.""" + """Returns dict of account details and party type to be set in Journal Entry on selection of account.""" if not frappe.has_permission("Account"): frappe.msgprint(_("No Permission"), raise_exception=1) From f4c8d63fe014281f3fb01c6d00eb8eaeadec4ab8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:20:01 +0530 Subject: [PATCH 06/19] feat: allow on submit for selected fields --- .../journal_entry_account/journal_entry_account.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 6e694a615d..06907369a5 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -39,6 +39,7 @@ ], "fields": [ { + "allow_on_submit": 1, "bold": 1, "columns": 2, "fieldname": "account", @@ -55,6 +56,7 @@ "width": "250px" }, { + "allow_on_submit": 1, "fieldname": "account_type", "fieldtype": "Data", "hidden": 1, @@ -62,6 +64,7 @@ "print_hide": 1 }, { + "allow_on_submit": 1, "default": ":Company", "description": "If Income or Expense", "fieldname": "cost_center", @@ -200,6 +203,7 @@ "no_copy": 1 }, { + "allow_on_submit": 1, "fieldname": "project", "fieldtype": "Link", "label": "Project", @@ -247,6 +251,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "fieldname": "bank_account", "fieldtype": "Link", "label": "Bank Account", @@ -263,7 +268,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-12 11:52:46.397186", + "modified": "2023-10-12 12:18:49.224840", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", From e4d657e6fde0b35a064ac2928def0e62dbf93359 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:46:50 +0530 Subject: [PATCH 07/19] feat: update after submit in JV --- .../doctype/journal_entry/journal_entry.json | 14 ++++++++++++-- .../doctype/journal_entry/journal_entry.py | 6 ++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 2eb54a54d5..7135c37815 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -64,7 +64,8 @@ "stock_entry", "subscription_section", "auto_repeat", - "amended_from" + "amended_from", + "repost_required" ], "fields": [ { @@ -543,13 +544,22 @@ "label": "Is System Generated", "no_copy": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "repost_required", + "fieldtype": "Check", + "hidden": 1, + "label": "Repost Required", + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 176, "is_submittable": 1, "links": [], - "modified": "2023-08-10 14:32:22.366895", + "modified": "2023-10-12 12:32:34.234167", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 006fb5618f..1d02e025fc 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -86,6 +86,12 @@ class JournalEntry(AccountsController): self.update_inter_company_jv() self.update_invoice_discounting() + def on_update_after_submit(self): + if hasattr(self, "repost_required"): + child_tables = {"accounts": ("account", "account_type", "bank_account")} + self.needs_repost = self.check_if_fields_updated([], child_tables) + self.db_set("repost_required", self.needs_repost) + def on_cancel(self): # References for this Journal are removed on the `on_cancel` event in accounts_controller super(JournalEntry, self).on_cancel() From 9ab55a5bd8fd114b074cd0f34289903dec2bf1b4 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:48:14 +0530 Subject: [PATCH 08/19] refactor: better abstraction for controller code --- .../purchase_invoice/purchase_invoice.py | 1 + .../doctype/sales_invoice/sales_invoice.py | 1 + erpnext/controllers/accounts_controller.py | 31 +++++++------------ 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 85ed1260d3..6533e4b9b8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -536,6 +536,7 @@ class PurchaseInvoice(BuyingController): "cash_bank_account", "write_off_account", "unrealized_profit_loss_account", + "is_opening", ] child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f380825db7..cad67cca66 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -530,6 +530,7 @@ class SalesInvoice(SellingController): "write_off_account", "loyalty_redemption_account", "unrealized_profit_loss_account", + "is_opening", ] child_tables = { "items": ("income_account", "expense_account", "discount_account"), diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6812940ee2..8d763acf33 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2216,27 +2216,20 @@ class AccountsController(TransactionBase): doc_before_update = self.get_doc_before_save() accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"] - # Check if opening entry check updated - needs_repost = doc_before_update.get("is_opening") != self.is_opening + # Parent Level Accounts excluding party account + fields_to_check += accounting_dimensions + for field in fields_to_check: + if doc_before_update.get(field) != self.get(field): + return True - if not needs_repost: - # Parent Level Accounts excluding party account - fields_to_check += accounting_dimensions - for field in fields_to_check: - if doc_before_update.get(field) != self.get(field): - needs_repost = 1 - break + # Check for child tables + for table in child_tables: + if check_if_child_table_updated( + doc_before_update.get(table), self.get(table), child_tables[table] + ): + return True - if not needs_repost: - # Check for child tables - for table in child_tables: - needs_repost = check_if_child_table_updated( - doc_before_update.get(table), self.get(table), child_tables[table] - ) - if needs_repost: - break - - return needs_repost + return False @frappe.whitelist() def repost_accounting_entries(self): From c77c296b8b3873e87124a2aa3f2b33105ffb62ba Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 13:10:59 +0530 Subject: [PATCH 09/19] feat: validate before allowing repost --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 1d02e025fc..14ec795e2e 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -77,6 +77,13 @@ class JournalEntry(AccountsController): if not self.title: self.title = self.get_title() + def validate_for_repost(self): + self.validate_party() + self.validate_multi_currency() + if not frappe.flags.is_reverse_depr_entry: + self.validate_against_jv() + self.validate_stock_accounts() + def on_submit(self): self.validate_cheque_info() self.check_credit_limit() @@ -90,6 +97,7 @@ class JournalEntry(AccountsController): if hasattr(self, "repost_required"): child_tables = {"accounts": ("account", "account_type", "bank_account")} self.needs_repost = self.check_if_fields_updated([], child_tables) + self.validate_for_repost() self.db_set("repost_required", self.needs_repost) def on_cancel(self): From bfa0c0981c027001f4ca73624c0a872081ee1673 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 13:31:15 +0530 Subject: [PATCH 10/19] feat: repost ledger button in JV --- .../doctype/journal_entry/journal_entry.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 4847ffd2ef..0f209e6dba 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -14,6 +14,25 @@ frappe.ui.form.on("Journal Entry", { refresh: function(frm) { erpnext.toggle_naming_series(); + if (frm.doc.repost_required && frm.doc.docstatus===1) { + frm.set_intro(__("Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update.")); + frm.add_custom_button(__('Repost Accounting Entries'), + () => { + frm.call({ + doc: frm.doc, + method: 'repost_accounting_entries', + freeze: true, + freeze_message: __('Reposting...'), + callback: (r) => { + if (!r.exc) { + frappe.msgprint(__('Accounting Entries are reposted.')); + frm.refresh(); + } + } + }); + }).removeClass('btn-default').addClass('btn-warning'); + } + if(frm.doc.docstatus > 0) { frm.add_custom_button(__('Ledger'), function() { frappe.route_options = { From de46fa8e66888c41b605081e7a1a8e755fc991cb Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 15:04:56 +0530 Subject: [PATCH 11/19] refactor: use qb for JV tests --- .../journal_entry/test_journal_entry.py | 163 +++++++++--------- 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index a6e920b7ef..edf0c2e7ff 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -166,43 +166,37 @@ class TestJournalEntry(unittest.TestCase): jv.get("accounts")[1].credit_in_account_currency = 5000 jv.submit() - gl_entries = frappe.db.sql( - """select account, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - jv.name, - as_dict=1, - ) + self.voucher_no = jv.name - self.assertTrue(gl_entries) + self.fields = [ + "account", + "account_currency", + "debit", + "debit_in_account_currency", + "credit", + "credit_in_account_currency", + ] - expected_values = { - "_Test Bank USD - _TC": { - "account_currency": "USD", - "debit": 5000, - "debit_in_account_currency": 100, - "credit": 0, - "credit_in_account_currency": 0, - }, - "_Test Bank - _TC": { + self.expected_gle = [ + { + "account": "_Test Bank - _TC", "account_currency": "INR", "debit": 0, "debit_in_account_currency": 0, "credit": 5000, "credit_in_account_currency": 5000, }, - } + { + "account": "_Test Bank USD - _TC", + "account_currency": "USD", + "debit": 5000, + "debit_in_account_currency": 100, + "credit": 0, + "credit_in_account_currency": 0, + }, + ] - for field in ( - "account_currency", - "debit", - "debit_in_account_currency", - "credit", - "credit_in_account_currency", - ): - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[gle.account][field], gle[field]) + self.check_gl_entries() # cancel jv.cancel() @@ -228,43 +222,37 @@ class TestJournalEntry(unittest.TestCase): rjv.posting_date = nowdate() rjv.submit() - gl_entries = frappe.db.sql( - """select account, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - rjv.name, - as_dict=1, - ) + self.voucher_no = rjv.name - self.assertTrue(gl_entries) + self.fields = [ + "account", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ] - expected_values = { - "_Test Bank USD - _TC": { + self.expected_gle = [ + { + "account": "_Test Bank USD - _TC", "account_currency": "USD", "debit": 0, "debit_in_account_currency": 0, "credit": 5000, "credit_in_account_currency": 100, }, - "Sales - _TC": { + { + "account": "Sales - _TC", "account_currency": "INR", "debit": 5000, "debit_in_account_currency": 5000, "credit": 0, "credit_in_account_currency": 0, }, - } + ] - for field in ( - "account_currency", - "debit", - "debit_in_account_currency", - "credit", - "credit_in_account_currency", - ): - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[gle.account][field], gle[field]) + self.check_gl_entries() def test_disallow_change_in_account_currency_for_a_party(self): # create jv in USD @@ -344,23 +332,25 @@ class TestJournalEntry(unittest.TestCase): jv.insert() jv.submit() - expected_values = { - "_Test Cash - _TC": {"cost_center": cost_center}, - "_Test Bank - _TC": {"cost_center": cost_center}, - } + self.voucher_no = jv.name - gl_entries = frappe.db.sql( - """select account, cost_center, debit, credit - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - jv.name, - as_dict=1, - ) + self.fields = [ + "account", + "cost_center", + ] - self.assertTrue(gl_entries) + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "cost_center": cost_center, + }, + { + "account": "_Test Cash - _TC", + "cost_center": cost_center, + }, + ] - for gle in gl_entries: - self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + self.check_gl_entries() def test_jv_with_project(self): from erpnext.projects.doctype.project.test_project import make_project @@ -387,23 +377,22 @@ class TestJournalEntry(unittest.TestCase): jv.insert() jv.submit() - expected_values = { - "_Test Cash - _TC": {"project": project_name}, - "_Test Bank - _TC": {"project": project_name}, - } + self.voucher_no = jv.name - gl_entries = frappe.db.sql( - """select account, project, debit, credit - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - jv.name, - as_dict=1, - ) + self.fields = ["account", "project"] - self.assertTrue(gl_entries) + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "project": project_name, + }, + { + "account": "_Test Cash - _TC", + "project": project_name, + }, + ] - for gle in gl_entries: - self.assertEqual(expected_values[gle.account]["project"], gle.project) + self.check_gl_entries() def test_jv_account_and_party_balance_with_cost_centre(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center @@ -426,6 +415,24 @@ class TestJournalEntry(unittest.TestCase): account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center) self.assertEqual(expected_account_balance, account_balance) + def check_gl_entries(self): + gl = frappe.qb.DocType("GL Entry") + query = frappe.qb.from_(gl) + for field in self.fields: + query = query.select(gl[field]) + + query = query.where( + (gl.voucher_type == "Journal Entry") + & (gl.voucher_no == self.voucher_no) + & (gl.is_cancelled == 0) + ).orderby(gl.account) + + gl_entries = query.run(as_dict=True) + + for i in range(len(self.expected_gle)): + for field in self.fields: + self.assertEqual(self.expected_gle[i][field], gl_entries[i][field]) + def make_journal_entry( account1, From 569d0961f26720ae5843e781e51192d7a29a4ff2 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 15:06:04 +0530 Subject: [PATCH 12/19] test: reposting entries for JV --- .../journal_entry/test_journal_entry.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index edf0c2e7ff..7106d9a1bc 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -415,6 +415,68 @@ class TestJournalEntry(unittest.TestCase): account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center) self.assertEqual(expected_account_balance, account_balance) + def test_repost_accounting_entries(self): + from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + + jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False) + jv.multi_currency = 0 + jv.submit() + + self.voucher_no = jv.name + + self.fields = [ + "account", + "debit_in_account_currency", + "credit_in_account_currency", + "cost_center", + ] + + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "debit_in_account_currency": 0, + "credit_in_account_currency": 100, + "cost_center": "_Test Cost Center - _TC", + }, + { + "account": "_Test Cash - _TC", + "debit_in_account_currency": 100, + "credit_in_account_currency": 0, + "cost_center": "_Test Cost Center - _TC", + }, + ] + + self.check_gl_entries() + + cost_center = "_Test Cost Center for BS Account - _TC" + create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") + + jv.accounts[0].account = "_Test Bank - _TC" + jv.accounts[0].cost_center = "_Test Cost Center for BS Account - _TC" + jv.accounts[1].account = "_Test Cash - _TC" + + jv.save() + jv.load_from_db() + self.assertTrue(jv.repost_required) + jv.repost_accounting_entries() + + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "debit_in_account_currency": 100, + "credit_in_account_currency": 0, + "cost_center": "_Test Cost Center for BS Account - _TC", + }, + { + "account": "_Test Cash - _TC", + "debit_in_account_currency": 0, + "credit_in_account_currency": 100, + "cost_center": "_Test Cost Center - _TC", + }, + ] + + self.check_gl_entries() + def check_gl_entries(self): gl = frappe.qb.DocType("GL Entry") query = frappe.qb.from_(gl) From e2cccc4005370c1bed3477e6096406335ee23867 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 15:57:38 +0530 Subject: [PATCH 13/19] chore: correct typo --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 0f209e6dba..48d865777c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -15,7 +15,7 @@ frappe.ui.form.on("Journal Entry", { erpnext.toggle_naming_series(); if (frm.doc.repost_required && frm.doc.docstatus===1) { - frm.set_intro(__("Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update.")); + frm.set_intro(__("Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update.")); frm.add_custom_button(__('Repost Accounting Entries'), () => { frm.call({ From 528ed9c986d4d2a29c58c0dbea8a695ab8c7627c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 16:54:41 +0530 Subject: [PATCH 14/19] fix: check child rows before update --- erpnext/controllers/accounts_controller.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 8d763acf33..3babd67c3f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3251,17 +3251,14 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil def check_if_child_table_updated( child_table_before_update, child_table_after_update, fields_to_check ): - accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"] + fields_to_check = list(fields_to_check) + get_accounting_dimensions() + ["cost_center", "project"] + # Check if any field affecting accounting entry is altered - for index, item in enumerate(child_table_after_update): + for index, item in enumerate(child_table_before_update): for field in fields_to_check: if child_table_before_update[index].get(field) != item.get(field): return True - for dimension in accounting_dimensions: - if child_table_before_update[index].get(dimension) != item.get(dimension): - return True - return False From fed8cf41967b1e813ad88426cb4c97c694810151 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 17:40:57 +0530 Subject: [PATCH 15/19] fix: test for reposting pi --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3babd67c3f..5fcdf2ddef 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3256,7 +3256,7 @@ def check_if_child_table_updated( # Check if any field affecting accounting entry is altered for index, item in enumerate(child_table_before_update): for field in fields_to_check: - if child_table_before_update[index].get(field) != item.get(field): + if child_table_after_update[index].get(field) != item.get(field): return True return False From 781bdd2ec96f4102ca5068f3dc2e6e974da17408 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 8 Feb 2024 15:35:10 +0530 Subject: [PATCH 16/19] fix: disable editable account heads --- .../accounts/doctype/journal_entry/journal_entry.py | 13 +++---------- .../journal_entry_account.json | 5 +---- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index df67faf924..647ab38172 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -149,13 +149,6 @@ class JournalEntry(AccountsController): if not self.title: self.title = self.get_title() - def validate_for_repost(self): - self.validate_party() - self.validate_multi_currency() - if not frappe.flags.is_reverse_depr_entry: - self.validate_against_jv() - self.validate_stock_accounts() - def submit(self): if len(self.accounts) > 100: msgprint(_("The task has been enqueued as a background job."), alert=True) @@ -181,9 +174,9 @@ class JournalEntry(AccountsController): def on_update_after_submit(self): if hasattr(self, "repost_required"): - child_tables = {"accounts": ("account", "account_type", "bank_account")} - self.needs_repost = self.check_if_fields_updated([], child_tables) - self.validate_for_repost() + self.needs_repost = self.check_if_fields_updated( + fields_to_check=[], child_tables={"accounts": []} + ) self.db_set("repost_required", self.needs_repost) def on_cancel(self): diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 6aee48630a..a61f8bf62f 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -39,7 +39,6 @@ ], "fields": [ { - "allow_on_submit": 1, "bold": 1, "columns": 2, "fieldname": "account", @@ -56,7 +55,6 @@ "width": "250px" }, { - "allow_on_submit": 1, "fieldname": "account_type", "fieldtype": "Data", "hidden": 1, @@ -253,7 +251,6 @@ "fieldtype": "Column Break" }, { - "allow_on_submit": 1, "fieldname": "bank_account", "fieldtype": "Link", "label": "Bank Account", @@ -270,7 +267,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-12-03 23:21:22.205409", + "modified": "2024-02-05 01:10:50.224840", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", From 8bb83e267ceb516672de9c8b803ac0ae3277b342 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 8 Feb 2024 16:09:28 +0530 Subject: [PATCH 17/19] fix: allow editable accounting dimensions for repostable doctypes --- .../accounting_dimension/accounting_dimension.py | 6 ++++++ .../accounts/doctype/journal_entry/journal_entry.py | 12 +++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 295cd983b3..5258214dad 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -11,6 +11,10 @@ from frappe.model import core_doctypes_list from frappe.model.document import Document from frappe.utils import cstr +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + get_allowed_types_from_settings, +) + class AccountingDimension(Document): # begin: auto-generated types @@ -106,6 +110,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None): doc_count = len(get_accounting_dimensions()) count = 0 + repostable_doctypes = get_allowed_types_from_settings() for doctype in doclist: @@ -121,6 +126,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None): "options": doc.document_type, "insert_after": insert_after_field, "owner": "Administrator", + "allow_on_submit": 1 if doctype in repostable_doctypes else 0, } meta = frappe.get_meta(doctype, cached=False) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 647ab38172..2a02cd711c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -13,6 +13,10 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import ( get_party_account_based_on_invoice_discounting, ) +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + validate_docs_for_deferred_accounting, + validate_docs_for_voucher_types, +) from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) @@ -149,6 +153,10 @@ class JournalEntry(AccountsController): if not self.title: self.title = self.get_title() + def validate_for_repost(self): + validate_docs_for_voucher_types(["Journal Entry"]) + validate_docs_for_deferred_accounting([self.name], []) + def submit(self): if len(self.accounts) > 100: msgprint(_("The task has been enqueued as a background job."), alert=True) @@ -177,7 +185,9 @@ class JournalEntry(AccountsController): self.needs_repost = self.check_if_fields_updated( fields_to_check=[], child_tables={"accounts": []} ) - self.db_set("repost_required", self.needs_repost) + if self.needs_repost: + self.validate_for_repost() + self.db_set("repost_required", self.needs_repost) def on_cancel(self): # References for this Journal are removed on the `on_cancel` event in accounts_controller From ecdc4c8e9ba2eb345e7443ca8271957b4de6851e Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 8 Feb 2024 16:36:30 +0530 Subject: [PATCH 18/19] feat: add patch for making repostable dimension fields editable --- erpnext/patches.txt | 1 + ...on_submit_dimensions_for_repostable_doctypes.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4ead7e71f0..63ba2f8f8b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -353,6 +353,7 @@ execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency" execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format")) erpnext.patches.v14_0.update_total_asset_cost_field erpnext.patches.v15_0.create_advance_payment_status +erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 diff --git a/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py b/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py new file mode 100644 index 0000000000..e75610d0a5 --- /dev/null +++ b/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py @@ -0,0 +1,14 @@ +import frappe + +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + get_allowed_types_from_settings, +) + + +def execute(): + for dt in get_allowed_types_from_settings(): + for dimension in get_accounting_dimensions(): + frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1) From 8c85404191ccf5ff8494eb10d851ccaa699fbc60 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 9 Feb 2024 13:11:51 +0530 Subject: [PATCH 19/19] fix: test for repost accounting in JVs --- .../journal_entry/test_journal_entry.py | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 7106d9a1bc..798d3bb6c8 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -418,10 +418,18 @@ class TestJournalEntry(unittest.TestCase): def test_repost_accounting_entries(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + # Configure Repost Accounting Ledger for JVs + settings = frappe.get_doc("Repost Accounting Ledger Settings") + if not [x for x in settings.allowed_types if x.document_type == "Journal Entry"]: + settings.append("allowed_types", {"document_type": "Journal Entry", "allowed": True}) + settings.save() + + # Create JV with defaut cost center - _Test Cost Center jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False) jv.multi_currency = 0 jv.submit() + # Check GL entries before reposting self.voucher_no = jv.name self.fields = [ @@ -448,33 +456,18 @@ class TestJournalEntry(unittest.TestCase): self.check_gl_entries() - cost_center = "_Test Cost Center for BS Account - _TC" + # Change cost center for bank account - _Test Cost Center for BS Account create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") - - jv.accounts[0].account = "_Test Bank - _TC" - jv.accounts[0].cost_center = "_Test Cost Center for BS Account - _TC" - jv.accounts[1].account = "_Test Cash - _TC" - + jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC" jv.save() - jv.load_from_db() + + # Check if repost flag gets set on update after submit self.assertTrue(jv.repost_required) jv.repost_accounting_entries() - self.expected_gle = [ - { - "account": "_Test Bank - _TC", - "debit_in_account_currency": 100, - "credit_in_account_currency": 0, - "cost_center": "_Test Cost Center for BS Account - _TC", - }, - { - "account": "_Test Cash - _TC", - "debit_in_account_currency": 0, - "credit_in_account_currency": 100, - "cost_center": "_Test Cost Center - _TC", - }, - ] - + # Check GL entries after reposting + jv.load_from_db() + self.expected_gle[0]["cost_center"] = "_Test Cost Center for BS Account - _TC" self.check_gl_entries() def check_gl_entries(self):