From e922ec60eb44196255a1e4b9cce839d6f7f05493 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:26:20 +0530 Subject: [PATCH 01/13] feat: allow on submit fields --- .../purchase_invoice/purchase_invoice.json | 23 +++++++++++++------ .../purchase_invoice_item.json | 1 + .../purchase_taxes_and_charges.json | 2 ++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 0599e19d9b..f3c01816cc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -166,6 +166,7 @@ "against_expense_account", "column_break_63", "unrealized_profit_loss_account", + "repost_required", "subscription_section", "subscription", "auto_repeat", @@ -191,8 +192,7 @@ "inter_company_invoice_reference", "is_old_subcontracting_flow", "remarks", - "connections_tab", - "column_break_38" + "connections_tab" ], "fields": [ { @@ -990,6 +990,7 @@ "print_hide": 1 }, { + "allow_on_submit": 1, "fieldname": "cash_bank_account", "fieldtype": "Link", "label": "Cash/Bank Account", @@ -1053,6 +1054,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "depends_on": "eval:flt(doc.write_off_amount)!=0", "fieldname": "write_off_account", "fieldtype": "Link", @@ -1217,6 +1219,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "default": "No", "fieldname": "is_opening", "fieldtype": "Select", @@ -1349,6 +1352,7 @@ "options": "Project" }, { + "allow_on_submit": 1, "depends_on": "eval:doc.is_internal_supplier", "description": "Unrealized Profit/Loss account for intra-company transfers", "fieldname": "unrealized_profit_loss_account", @@ -1504,10 +1508,6 @@ "fieldname": "column_break_6", "fieldtype": "Column Break" }, - { - "fieldname": "column_break_38", - "fieldtype": "Column Break" - }, { "fieldname": "column_break_50", "fieldtype": "Column Break" @@ -1578,13 +1578,22 @@ "fieldname": "use_company_roundoff_cost_center", "fieldtype": "Check", "label": "Use Company Default Round Off Cost Center" + }, + { + "default": "0", + "fieldname": "repost_required", + "fieldtype": "Check", + "hidden": 1, + "label": "Repost Required", + "options": "Account", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-07-25 17:22:59.145031", + "modified": "2023-09-21 12:22:04.545106", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 81c7577467..3690142aac 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -473,6 +473,7 @@ "label": "Accounting" }, { + "allow_on_submit": 1, "fieldname": "expense_account", "fieldtype": "Link", "label": "Expense Head", diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index d86abade92..347cae05b7 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -86,6 +86,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "columns": 2, "fieldname": "account_head", "fieldtype": "Link", @@ -97,6 +98,7 @@ "reqd": 1 }, { + "allow_on_submit": 1, "default": ":Company", "fieldname": "cost_center", "fieldtype": "Link", From 68effd93bdb1a91a8625d983cd9b8afeb7b1eb3b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:28:07 +0530 Subject: [PATCH 02/13] refactor: move reposting logic to common controller --- .../doctype/sales_invoice/sales_invoice.py | 100 +++++------------- erpnext/controllers/accounts_controller.py | 55 ++++++++++ 2 files changed, 81 insertions(+), 74 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 7bdb2b49ce..87f4bd0023 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -11,9 +11,6 @@ from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form import erpnext from erpnext.accounts.deferred_revenue import validate_service_stop_date -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( - get_accounting_dimensions, -) from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( get_loyalty_program_details_with_points, validate_loyalty_points, @@ -517,79 +514,34 @@ class SalesInvoice(SellingController): def on_update_after_submit(self): if hasattr(self, "repost_required"): - needs_repost = 0 - - # Check if any field affecting accounting entry is altered - doc_before_update = self.get_doc_before_save() - accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"] - - # Check if opening entry check updated - if doc_before_update.get("is_opening") != self.is_opening: - needs_repost = 1 - - if not needs_repost: - # Parent Level Accounts excluding party account - for field in ( - "additional_discount_account", - "cash_bank_account", - "account_for_change_amount", - "write_off_account", - "loyalty_redemption_account", - "unrealized_profit_loss_account", - ): - if doc_before_update.get(field) != self.get(field): - needs_repost = 1 - break - - # Check for parent accounting dimensions - for dimension in accounting_dimensions: - if doc_before_update.get(dimension) != self.get(dimension): - needs_repost = 1 - break - - # Check for child tables - if self.check_if_child_table_updated( - "items", - doc_before_update, - ("income_account", "expense_account", "discount_account"), - accounting_dimensions, - ): - needs_repost = 1 - - if self.check_if_child_table_updated( - "taxes", doc_before_update, ("account_head",), accounting_dimensions - ): - needs_repost = 1 - + fields_to_check = [ + "additional_discount_account", + "cash_bank_account", + "account_for_change_amount", + "write_off_account", + "loyalty_redemption_account", + "unrealized_profit_loss_account", + ] + child_tables = { + "items": ("income_account", "expense_account", "discount_account"), + "taxes": ("account_head",), + } + self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) + self.validate_deferred_accounting_before_repost() self.validate_accounts() + self.db_set("repost_required", self.needs_repost) - # validate if deferred revenue is enabled for any item - # Don't allow to update the invoice if deferred revenue is enabled - if needs_repost: - for item in self.get("items"): - if item.enable_deferred_revenue: - frappe.throw( - _( - "Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission." - ).format(item.item_code) - ) - - self.db_set("repost_required", needs_repost) - - def check_if_child_table_updated( - self, child_table, doc_before_update, fields_to_check, accounting_dimensions - ): - # Check if any field affecting accounting entry is altered - for index, item in enumerate(self.get(child_table)): - for field in fields_to_check: - if doc_before_update.get(child_table)[index].get(field) != item.get(field): - return True - - for dimension in accounting_dimensions: - if doc_before_update.get(child_table)[index].get(dimension) != item.get(dimension): - return True - - return False + def validate_deferred_accounting_before_repost(self): + # validate if deferred revenue is enabled for any item + # Don't allow to update the invoice if deferred revenue is enabled + if self.needs_repost: + for item in self.get("items"): + if item.enable_deferred_revenue: + frappe.throw( + _( + "Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission." + ).format(item.item_code) + ) @frappe.whitelist() def repost_accounting_entries(self): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e635aa7924..08978640bc 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2186,6 +2186,44 @@ class AccountsController(TransactionBase): _("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx) ) + def check_if_fields_updated(self, fields_to_check, child_tables): + # Check if any field affecting accounting entry is altered + 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 + + 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 + + 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 + + @frappe.whitelist() + def repost_accounting_entries(self): + if self.repost_required: + self.docstatus = 2 + self.make_gl_entries_on_cancel() + self.docstatus = 1 + self.make_gl_entries() + self.db_set("repost_required", 0) + else: + frappe.throw(_("No updates pending for reposting")) + @frappe.whitelist() def get_tax_rate(account_head): @@ -3191,6 +3229,23 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.create_stock_reservation_entries() +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"] + # Check if any field affecting accounting entry is altered + for index, item in enumerate(child_table_after_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 + + @erpnext.allow_regional def validate_regional(doc): pass From e77814fbc0b15a47c8ecde579fc0d2a9e200a476 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:29:14 +0530 Subject: [PATCH 03/13] feat: add repost btn in invoice --- .../purchase_invoice/purchase_invoice.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index c8c9ad1b3a..d895a947e2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -65,6 +65,25 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm); } + if (this.frm.doc.repost_required && this.frm.doc.docstatus===1) { + this.frm.set_intro(__("Accounting entries for this invoice needs to be reposted. Please click on 'Repost' button to update.")); + this.frm.add_custom_button(__('Repost Accounting Entries'), + () => { + this.frm.call({ + doc: this.frm.doc, + method: 'repost_accounting_entries', + freeze: true, + freeze_message: __('Reposting...'), + callback: (r) => { + if (!r.exc) { + frappe.msgprint(__('Accounting Entries are reposted')); + me.frm.refresh(); + } + } + }); + }).removeClass('btn-default').addClass('btn-warning'); + } + if(!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0){ if(doc.on_hold) { this.frm.add_custom_button( From 23470bf52dd8f7b5fee127e5bc506938ae9632f3 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:30:53 +0530 Subject: [PATCH 04/13] feat: allow repost for pi --- .../purchase_invoice/purchase_invoice.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 55972719f8..fbe4e8b41a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -522,6 +522,31 @@ class PurchaseInvoice(BuyingController): self.process_common_party_accounting() + def on_update_after_submit(self): + if hasattr(self, "repost_required"): + fields_to_check = [ + "cash_bank_account", + "write_off_account" "unrealized_profit_loss_account", + ] + child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} + self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) + self.validate_deferred_accounting_before_repost() + self.validate_write_off_account() + self.validate_expense_account() + self.db_set("repost_required", self.needs_repost) + + def validate_deferred_accounting_before_repost(self): + # validate if deferred expense is enabled for any item + # Don't allow to update the invoice if deferred expense is enabled + if self.needs_repost: + for item in self.get("items"): + if item.enable_deferred_expense: + frappe.throw( + _( + "Deferred Expense is enabled for item {0}. You cannot update the invoice after submission." + ).format(item.item_code) + ) + def make_gl_entries(self, gl_entries=None, from_repost=False): if not gl_entries: gl_entries = self.get_gl_entries() From c88f6d1fa7f28453d81cabf6726bf8eb1b5d8969 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:41:59 +0530 Subject: [PATCH 05/13] fix: linting issues --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index fbe4e8b41a..97661e8824 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -526,7 +526,8 @@ class PurchaseInvoice(BuyingController): if hasattr(self, "repost_required"): fields_to_check = [ "cash_bank_account", - "write_off_account" "unrealized_profit_loss_account", + "write_off_account", + "unrealized_profit_loss_account", ] child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) From c66c4385759b25243f946b708055bccc12561809 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 22 Sep 2023 11:22:25 +0530 Subject: [PATCH 06/13] test: reposted acc entries for pi --- .../purchase_invoice/purchase_invoice.js | 4 +-- .../purchase_invoice/test_purchase_invoice.py | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index d895a947e2..095617dbcf 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -66,7 +66,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. } if (this.frm.doc.repost_required && this.frm.doc.docstatus===1) { - this.frm.set_intro(__("Accounting entries for this invoice needs to be reposted. Please click on 'Repost' button to update.")); + this.frm.set_intro(__("Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update.")); this.frm.add_custom_button(__('Repost Accounting Entries'), () => { this.frm.call({ @@ -76,7 +76,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. freeze_message: __('Reposting...'), callback: (r) => { if (!r.exc) { - frappe.msgprint(__('Accounting Entries are reposted')); + frappe.msgprint(__('Accounting Entries are reposted.')); me.frm.refresh(); } } diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index b4dd75a714..0aaea060b5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1744,7 +1744,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi = make_purchase_invoice( company="_Test Company", - customer="_Test Supplier", do_not_save=True, do_not_submit=True, rate=1000, @@ -1862,7 +1861,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi = make_purchase_invoice( company="_Test Company", - customer="_Test Supplier", do_not_save=True, do_not_submit=True, rate=1000, @@ -1892,6 +1890,32 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): clear_dimension_defaults("Branch") disable_dimension() + def test_repost_accounting_entries(self): + pi = make_purchase_invoice( + rate=1000, + price_list_rate=1000, + qty=1, + ) + expected_gle = [ + ["_Test Account Cost for Goods Sold - _TC", 1000, 0.0, nowdate()], + ["Creditors - _TC", 0.0, 1000, nowdate()], + ] + check_gl_entries(self, pi.name, expected_gle, nowdate()) + + pi.items[0].expense_account = "Service - _TC" + pi.save() + pi.load_from_db() + self.assertTrue(pi.repost_required) + pi.repost_accounting_entries() + + expected_gle = [ + ["Creditors - _TC", 0.0, 1000, nowdate()], + ["Service - _TC", 1000, 0.0, nowdate()], + ] + check_gl_entries(self, pi.name, expected_gle, nowdate()) + pi.load_from_db() + self.assertFalse(pi.repost_required) + def set_advance_flag(company, flag, default_account): frappe.db.set_value( From ba7212c98b81a43e5f63d010eadf99635c95e349 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:11:46 +0530 Subject: [PATCH 07/13] refactor: remove unused method --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 87f4bd0023..bc6bc1fd62 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -543,17 +543,6 @@ class SalesInvoice(SellingController): ).format(item.item_code) ) - @frappe.whitelist() - def repost_accounting_entries(self): - if self.repost_required: - self.docstatus = 2 - self.make_gl_entries_on_cancel() - self.docstatus = 1 - self.make_gl_entries() - self.db_set("repost_required", 0) - else: - frappe.throw(_("No updates pending for reposting")) - def set_paid_amount(self): paid_amount = 0.0 base_paid_amount = 0.0 From 7ebf0836833d9226ca9e0d2d231b35be4f438842 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:13:33 +0530 Subject: [PATCH 08/13] refactor: use repost accounting legder --- erpnext/controllers/accounts_controller.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 08978640bc..f29eee7737 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2216,10 +2216,11 @@ class AccountsController(TransactionBase): @frappe.whitelist() def repost_accounting_entries(self): if self.repost_required: - self.docstatus = 2 - self.make_gl_entries_on_cancel() - self.docstatus = 1 - self.make_gl_entries() + repost_ledger = frappe.new_doc("Repost Accounting Ledger") + repost_ledger.company = self.company + repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name}) + repost_ledger.insert() + repost_ledger.submit() self.db_set("repost_required", 0) else: frappe.throw(_("No updates pending for reposting")) From a856091ff4756462b6cf0f02493f6fb6ddf59921 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:18:06 +0530 Subject: [PATCH 09/13] refactor: remove repeated validation for voucher --- .../doctype/purchase_invoice/purchase_invoice.py | 13 ------------- .../accounts/doctype/sales_invoice/sales_invoice.py | 13 ------------- 2 files changed, 26 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 97661e8824..1b1485fbe9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -531,23 +531,10 @@ class PurchaseInvoice(BuyingController): ] child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) - self.validate_deferred_accounting_before_repost() self.validate_write_off_account() self.validate_expense_account() self.db_set("repost_required", self.needs_repost) - def validate_deferred_accounting_before_repost(self): - # validate if deferred expense is enabled for any item - # Don't allow to update the invoice if deferred expense is enabled - if self.needs_repost: - for item in self.get("items"): - if item.enable_deferred_expense: - frappe.throw( - _( - "Deferred Expense is enabled for item {0}. You cannot update the invoice after submission." - ).format(item.item_code) - ) - def make_gl_entries(self, gl_entries=None, from_repost=False): if not gl_entries: gl_entries = self.get_gl_entries() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bc6bc1fd62..cded062352 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -527,22 +527,9 @@ class SalesInvoice(SellingController): "taxes": ("account_head",), } self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) - self.validate_deferred_accounting_before_repost() self.validate_accounts() self.db_set("repost_required", self.needs_repost) - def validate_deferred_accounting_before_repost(self): - # validate if deferred revenue is enabled for any item - # Don't allow to update the invoice if deferred revenue is enabled - if self.needs_repost: - for item in self.get("items"): - if item.enable_deferred_revenue: - frappe.throw( - _( - "Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission." - ).format(item.item_code) - ) - def set_paid_amount(self): paid_amount = 0.0 base_paid_amount = 0.0 From 1856050ef9022d5688bcd98ea4edb146946b8b7a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:30:42 +0530 Subject: [PATCH 10/13] fix: do not run bg job for single doc --- .../repost_accounting_ledger.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 4cf2ed2f46..e533fed35b 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -139,14 +139,17 @@ class RepostAccountingLedger(Document): return rendered_page def on_submit(self): - job_name = "repost_accounting_ledger_" + self.name - frappe.enqueue( - method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost", - account_repost_doc=self.name, - is_async=True, - job_name=job_name, - ) - frappe.msgprint(_("Repost has started in the background")) + if len(self.vouchers) > 1: + job_name = "repost_accounting_ledger_" + self.name + frappe.enqueue( + method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost", + account_repost_doc=self.name, + is_async=True, + job_name=job_name, + ) + frappe.msgprint(_("Repost has started in the background")) + else: + start_repost(self.name) @frappe.whitelist() From 8ef0d8870830b1a4f848a91d201fb740e7f47aa0 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 20:29:38 +0530 Subject: [PATCH 11/13] fix: call validate before setting repost flag --- .../purchase_invoice/purchase_invoice.py | 11 ++++- .../repost_accounting_ledger.py | 46 ++++++++++--------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 1b1485fbe9..85ed1260d3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -11,6 +11,9 @@ from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, import erpnext from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + validate_docs_for_deferred_accounting, +) from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( check_if_return_invoice_linked_with_payment_entry, get_total_in_party_account_currency, @@ -484,6 +487,11 @@ class PurchaseInvoice(BuyingController): _("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt) ) + def validate_for_repost(self): + self.validate_write_off_account() + self.validate_expense_account() + validate_docs_for_deferred_accounting([], [self.name]) + def on_submit(self): super(PurchaseInvoice, self).on_submit() @@ -531,8 +539,7 @@ class PurchaseInvoice(BuyingController): ] child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) - self.validate_write_off_account() - self.validate_expense_account() + self.validate_for_repost() self.db_set("repost_required", self.needs_repost) def make_gl_entries(self, gl_entries=None, from_repost=False): diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index e533fed35b..dbb0971fde 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -21,29 +21,8 @@ class RepostAccountingLedger(Document): def validate_for_deferred_accounting(self): sales_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Sales Invoice"] - docs_with_deferred_revenue = frappe.db.get_all( - "Sales Invoice Item", - filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True}, - fields=["parent"], - as_list=1, - ) - purchase_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Purchase Invoice"] - docs_with_deferred_expense = frappe.db.get_all( - "Purchase Invoice Item", - filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1}, - fields=["parent"], - as_list=1, - ) - - if docs_with_deferred_revenue or docs_with_deferred_expense: - frappe.throw( - _("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format( - frappe.bold( - comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue]) - ) - ) - ) + validate_docs_for_deferred_accounting(sales_docs, purchase_docs) def validate_for_closed_fiscal_year(self): if self.vouchers: @@ -184,3 +163,26 @@ def start_repost(account_repost_doc=str) -> None: doc.make_gl_entries() frappe.db.commit() + + +def validate_docs_for_deferred_accounting(sales_docs, purchase_docs): + docs_with_deferred_revenue = frappe.db.get_all( + "Sales Invoice Item", + filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True}, + fields=["parent"], + as_list=1, + ) + + docs_with_deferred_expense = frappe.db.get_all( + "Purchase Invoice Item", + filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1}, + fields=["parent"], + as_list=1, + ) + + if docs_with_deferred_revenue or docs_with_deferred_expense: + frappe.throw( + _("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format( + frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])) + ) + ) From 61c6ebbb95b0e7ecbb86312646fe862b9a471d95 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 20:30:50 +0530 Subject: [PATCH 12/13] fix: validation for si --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index cded062352..f380825db7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -15,6 +15,9 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( get_loyalty_program_details_with_points, validate_loyalty_points, ) +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + validate_docs_for_deferred_accounting, +) from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) @@ -165,6 +168,12 @@ class SalesInvoice(SellingController): self.validate_account_for_change_amount() self.validate_income_account() + def validate_for_repost(self): + self.validate_write_off_account() + self.validate_account_for_change_amount() + self.validate_income_account() + validate_docs_for_deferred_accounting([self.name], []) + def validate_fixed_asset(self): for d in self.get("items"): if d.is_fixed_asset and d.meta.get_field("asset") and d.asset: @@ -527,7 +536,7 @@ class SalesInvoice(SellingController): "taxes": ("account_head",), } self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) - self.validate_accounts() + self.validate_for_repost() self.db_set("repost_required", self.needs_repost) def set_paid_amount(self): From ed7f67b1a85a1c2ef47bba77267e20cf6bbced71 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 26 Sep 2023 14:23:21 +0530 Subject: [PATCH 13/13] refactor: remove references in repost doctypes upon parent doc delet --- .../repost_accounting_ledger.json | 5 ++- .../repost_payment_ledger.json | 5 ++- erpnext/controllers/accounts_controller.py | 37 ++++++++++++++++--- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json index 8d56c9bb11..5b7cd2b0b2 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json @@ -55,7 +55,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-07-27 15:47:58.975034", + "modified": "2023-09-26 14:21:27.362567", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger", @@ -77,5 +77,6 @@ ], "sort_field": "modified", "sort_order": "DESC", - "states": [] + "states": [], + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json index 5175fd169f..ed8d395a0e 100644 --- a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json +++ b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json @@ -99,7 +99,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-11-08 07:38:40.079038", + "modified": "2023-09-26 14:21:35.719727", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Payment Ledger", @@ -155,5 +155,6 @@ ], "sort_field": "modified", "sort_order": "DESC", - "states": [] + "states": [], + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f29eee7737..6efa09bb99 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -243,13 +243,38 @@ class AccountsController(TransactionBase): _doc.cancel() _doc.delete() - def on_trash(self): - # delete references in 'Repost Payment Ledger' - rpi = frappe.qb.DocType("Repost Payment Ledger Items") - frappe.qb.from_(rpi).delete().where( - (rpi.voucher_type == self.doctype) & (rpi.voucher_no == self.name) - ).run() + def _remove_references_in_repost_doctypes(self): + repost_doctypes = ["Repost Payment Ledger Items", "Repost Accounting Ledger Items"] + for _doctype in repost_doctypes: + dt = frappe.qb.DocType(_doctype) + rows = ( + frappe.qb.from_(dt) + .select(dt.name, dt.parent, dt.parenttype) + .where((dt.voucher_type == self.doctype) & (dt.voucher_no == self.name)) + .run(as_dict=True) + ) + + if rows: + references_map = frappe._dict() + for x in rows: + references_map.setdefault((x.parenttype, x.parent), []).append(x.name) + + for doc, rows in references_map.items(): + repost_doc = frappe.get_doc(doc[0], doc[1]) + + for row in rows: + if _doctype == "Repost Payment Ledger Items": + repost_doc.remove(repost_doc.get("repost_vouchers", {"name": row})[0]) + else: + repost_doc.remove(repost_doc.get("vouchers", {"name": row})[0]) + + repost_doc.flags.ignore_validate_update_after_submit = True + repost_doc.flags.ignore_links = True + repost_doc.save(ignore_permissions=True) + + def on_trash(self): + self._remove_references_in_repost_doctypes() self._remove_references_in_unreconcile() # delete sl and gl entries on deletion of transaction