From f5245f6b3fdf8f89702458fd7bb2e04e4eacd927 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:26:20 +0530 Subject: [PATCH 01/14] feat: allow on submit fields (cherry picked from commit e922ec60eb44196255a1e4b9cce839d6f7f05493) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json --- .../purchase_invoice/purchase_invoice.json | 25 ++++++++++++++----- .../purchase_invoice_item.json | 1 + .../purchase_taxes_and_charges.json | 2 ++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 7e848a0b4e8..864f0b81890 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", "auto_repeat", "update_auto_repeat_reference", @@ -190,8 +191,7 @@ "inter_company_invoice_reference", "is_old_subcontracting_flow", "remarks", - "connections_tab", - "column_break_38" + "connections_tab" ], "fields": [ { @@ -987,6 +987,7 @@ "print_hide": 1 }, { + "allow_on_submit": 1, "fieldname": "cash_bank_account", "fieldtype": "Link", "label": "Cash/Bank Account", @@ -1050,6 +1051,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "depends_on": "eval:flt(doc.write_off_amount)!=0", "fieldname": "write_off_account", "fieldtype": "Link", @@ -1213,6 +1215,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "default": "No", "fieldname": "is_opening", "fieldtype": "Select", @@ -1345,6 +1348,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", @@ -1495,10 +1499,6 @@ "fieldname": "column_break_6", "fieldtype": "Column Break" }, - { - "fieldname": "column_break_38", - "fieldtype": "Column Break" - }, { "fieldname": "column_break_50", "fieldtype": "Column Break" @@ -1569,13 +1569,26 @@ "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": [], +<<<<<<< HEAD "modified": "2023-10-01 21:01:47.282533", +======= + "modified": "2023-09-21 12:22:04.545106", +>>>>>>> e922ec60eb (feat: allow on submit fields) "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 04f28beda9e..c7357360ec0 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -468,6 +468,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 d86abade924..347cae05b72 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 79e414cb9747570e2ca747d1a9ab7dd18a453b3b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:28:07 +0530 Subject: [PATCH 02/14] refactor: move reposting logic to common controller (cherry picked from commit 68effd93bdb1a91a8625d983cd9b8afeb7b1eb3b) # Conflicts: # erpnext/accounts/doctype/sales_invoice/sales_invoice.py --- .../doctype/sales_invoice/sales_invoice.py | 72 +++++++------------ erpnext/controllers/accounts_controller.py | 55 ++++++++++++++ 2 files changed, 80 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 7d431b4205b..a8525522df3 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, @@ -529,54 +526,32 @@ 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) +<<<<<<< HEAD # validate if deferred revenue is enabled for any item # Don't allow to update the invoice if deferred revenue is enabled +======= + 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: +>>>>>>> 68effd93bd (refactor: move reposting logic to common controller) for item in self.get("items"): if item.enable_deferred_revenue: frappe.throw( @@ -584,6 +559,7 @@ class SalesInvoice(SellingController): "Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission." ).format(item.item_code) ) +<<<<<<< HEAD self.db_set("repost_required", needs_repost) @@ -601,6 +577,8 @@ class SalesInvoice(SellingController): return True return False +======= +>>>>>>> 68effd93bd (refactor: move reposting logic to common controller) @frappe.whitelist() def repost_accounting_entries(self): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 534119762af..78f171089b3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2159,6 +2159,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): @@ -3044,6 +3082,23 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.set_status() +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 cde848dc7fa755201391fd024e94e0a6935c632e Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:29:14 +0530 Subject: [PATCH 03/14] feat: add repost btn in invoice (cherry picked from commit e77814fbc0b15a47c8ecde579fc0d2a9e200a476) --- .../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 ceb8204bd5d..d8f02c19bee 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -59,6 +59,25 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. this.show_stock_ledger(); } + 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 2d13dda49c59784fee4257ff56ebf679617713f9 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:30:53 +0530 Subject: [PATCH 04/14] feat: allow repost for pi (cherry picked from commit 23470bf52dd8f7b5fee127e5bc506938ae9632f3) --- .../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 9737ee2c53e..1842ad903b9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -529,6 +529,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 6c8a65e03b9ab4b57f5c254566ea9a57c79dd2e8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:41:59 +0530 Subject: [PATCH 05/14] fix: linting issues (cherry picked from commit c88f6d1fa7f28453d81cabf6726bf8eb1b5d8969) --- 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 1842ad903b9..cde6221da49 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -533,7 +533,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 a512d27dbb53999556b0194dd4f69e17147995d1 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 22 Sep 2023 11:22:25 +0530 Subject: [PATCH 06/14] test: reposted acc entries for pi (cherry picked from commit c66c4385759b25243f946b708055bccc12561809) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py --- .../purchase_invoice/purchase_invoice.js | 4 +- .../purchase_invoice/test_purchase_invoice.py | 86 ++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index d8f02c19bee..ee5a50af058 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -60,7 +60,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({ @@ -70,7 +70,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 30265aeb50e..51208885044 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1710,6 +1710,65 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): self.assertTrue(return_pi.docstatus == 1) +<<<<<<< HEAD +======= + def test_advance_entries_as_asset(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry + + account = create_account( + parent_account="Current Assets - _TC", + account_name="Advances Paid", + company="_Test Company", + account_type="Receivable", + ) + + set_advance_flag(company="_Test Company", flag=1, default_account=account) + + pe = create_payment_entry( + company="_Test Company", + payment_type="Pay", + party_type="Supplier", + party="_Test Supplier", + paid_from="Cash - _TC", + paid_to="Creditors - _TC", + paid_amount=500, + ) + pe.submit() + + pi = make_purchase_invoice( + company="_Test Company", + do_not_save=True, + do_not_submit=True, + rate=1000, + price_list_rate=1000, + qty=1, + ) + pi.base_grand_total = 1000 + pi.grand_total = 1000 + pi.set_advances() + for advance in pi.advances: + advance.allocated_amount = 500 if advance.reference_name == pe.name else 0 + pi.save() + pi.submit() + + self.assertEqual(pi.advances[0].allocated_amount, 500) + + # Check GL Entry against payment doctype + expected_gle = [ + ["Advances Paid - _TC", 0.0, 500, nowdate()], + ["Cash - _TC", 0.0, 500, nowdate()], + ["Creditors - _TC", 500, 0.0, nowdate()], + ["Creditors - _TC", 500, 0.0, nowdate()], + ] + + check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry") + + pi.load_from_db() + self.assertEqual(pi.outstanding_amount, 500) + + set_advance_flag(company="_Test Company", flag=0, default_account="") + +>>>>>>> c66c438575 (test: reposted acc entries for pi) def test_gl_entries_for_standalone_debit_note(self): make_purchase_invoice(qty=5, rate=500, update_stock=True) @@ -1796,7 +1855,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, @@ -1826,6 +1884,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 check_gl_entries( doc, From 8c83bbc0962fbad193ef91cd7ee5158018712c21 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:11:46 +0530 Subject: [PATCH 07/14] refactor: remove unused method (cherry picked from commit ba7212c98b81a43e5f63d010eadf99635c95e349) --- .../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 a8525522df3..361e5769df5 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -580,17 +580,6 @@ class SalesInvoice(SellingController): ======= >>>>>>> 68effd93bd (refactor: move reposting logic to common controller) - @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 677525b2cf5f9c9b422c6e145b1ec3cc1354813c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:13:33 +0530 Subject: [PATCH 08/14] refactor: use repost accounting legder (cherry picked from commit 7ebf0836833d9226ca9e0d2d231b35be4f438842) --- 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 78f171089b3..072537dcbb4 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2189,10 +2189,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 c9bcf79e83d16e17627fd116c45e1ea2abfd21dc Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:18:06 +0530 Subject: [PATCH 09/14] refactor: remove repeated validation for voucher (cherry picked from commit a856091ff4756462b6cf0f02493f6fb6ddf59921) # Conflicts: # erpnext/accounts/doctype/sales_invoice/sales_invoice.py --- .../doctype/purchase_invoice/purchase_invoice.py | 13 ------------- .../accounts/doctype/sales_invoice/sales_invoice.py | 4 +++- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index cde6221da49..c17062c943a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -538,23 +538,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 361e5769df5..6b8baa8b2c9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -539,10 +539,10 @@ 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) +<<<<<<< HEAD <<<<<<< HEAD # validate if deferred revenue is enabled for any item # Don't allow to update the invoice if deferred revenue is enabled @@ -580,6 +580,8 @@ class SalesInvoice(SellingController): ======= >>>>>>> 68effd93bd (refactor: move reposting logic to common controller) +======= +>>>>>>> a856091ff4 (refactor: remove repeated validation for voucher) def set_paid_amount(self): paid_amount = 0.0 base_paid_amount = 0.0 From 4123e7b2447d7ed5de4957ac48ea1deff5a50649 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:30:42 +0530 Subject: [PATCH 10/14] fix: do not run bg job for single doc (cherry picked from commit 1856050ef9022d5688bcd98ea4edb146946b8b7a) --- .../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 4cf2ed2f46c..e533fed35b4 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 bec3e8ed96f24ea6b048f88d23d3dbc6bc457e0f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 20:29:38 +0530 Subject: [PATCH 11/14] fix: call validate before setting repost flag (cherry picked from commit 8ef0d8870830b1a4f848a91d201fb740e7f47aa0) --- .../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 c17062c943a..f6ec446ef35 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, @@ -487,6 +490,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() @@ -538,8 +546,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 e533fed35b4..dbb0971fdea 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 3dc68e3b002ec0f072809d9d94a175b16ea98e8d Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 20:30:50 +0530 Subject: [PATCH 12/14] fix: validation for si (cherry picked from commit 61c6ebbb95b0e7ecbb86312646fe862b9a471d95) --- .../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 6b8baa8b2c9..c26d3c0283b 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, ) @@ -173,6 +176,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: @@ -539,7 +548,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) <<<<<<< HEAD From debfbc4761085ade3e2c826e18fef9cecd4dfd5e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 26 Sep 2023 14:23:21 +0530 Subject: [PATCH 13/14] refactor: remove references in repost doctypes upon parent doc delet (cherry picked from commit ed7f67b1a85a1c2ef47bba77267e20cf6bbced71) --- .../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 8d56c9bb11d..5b7cd2b0b20 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 5175fd169ff..ed8d395a0ec 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 072537dcbb4..7207743e095 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -242,13 +242,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 From b0ac0973275a58628d0236071ec921eac4781c9e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 10 Oct 2023 11:16:12 +0530 Subject: [PATCH 14/14] chore: resolve conflicts --- .../purchase_invoice/purchase_invoice.json | 4 -- .../purchase_invoice/test_purchase_invoice.py | 59 ------------------- .../doctype/sales_invoice/sales_invoice.py | 40 ------------- 3 files changed, 103 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 864f0b81890..1f3b17ee147 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1584,11 +1584,7 @@ "idx": 204, "is_submittable": 1, "links": [], -<<<<<<< HEAD "modified": "2023-10-01 21:01:47.282533", -======= - "modified": "2023-09-21 12:22:04.545106", ->>>>>>> e922ec60eb (feat: allow on submit fields) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 51208885044..47126d3846f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1710,65 +1710,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): self.assertTrue(return_pi.docstatus == 1) -<<<<<<< HEAD -======= - def test_advance_entries_as_asset(self): - from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry - - account = create_account( - parent_account="Current Assets - _TC", - account_name="Advances Paid", - company="_Test Company", - account_type="Receivable", - ) - - set_advance_flag(company="_Test Company", flag=1, default_account=account) - - pe = create_payment_entry( - company="_Test Company", - payment_type="Pay", - party_type="Supplier", - party="_Test Supplier", - paid_from="Cash - _TC", - paid_to="Creditors - _TC", - paid_amount=500, - ) - pe.submit() - - pi = make_purchase_invoice( - company="_Test Company", - do_not_save=True, - do_not_submit=True, - rate=1000, - price_list_rate=1000, - qty=1, - ) - pi.base_grand_total = 1000 - pi.grand_total = 1000 - pi.set_advances() - for advance in pi.advances: - advance.allocated_amount = 500 if advance.reference_name == pe.name else 0 - pi.save() - pi.submit() - - self.assertEqual(pi.advances[0].allocated_amount, 500) - - # Check GL Entry against payment doctype - expected_gle = [ - ["Advances Paid - _TC", 0.0, 500, nowdate()], - ["Cash - _TC", 0.0, 500, nowdate()], - ["Creditors - _TC", 500, 0.0, nowdate()], - ["Creditors - _TC", 500, 0.0, nowdate()], - ] - - check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry") - - pi.load_from_db() - self.assertEqual(pi.outstanding_amount, 500) - - set_advance_flag(company="_Test Company", flag=0, default_account="") - ->>>>>>> c66c438575 (test: reposted acc entries for pi) def test_gl_entries_for_standalone_debit_note(self): make_purchase_invoice(qty=5, rate=500, update_stock=True) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index c26d3c0283b..03aca8ad588 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -551,46 +551,6 @@ class SalesInvoice(SellingController): self.validate_for_repost() self.db_set("repost_required", self.needs_repost) -<<<<<<< HEAD -<<<<<<< HEAD - # validate if deferred revenue is enabled for any item - # Don't allow to update the invoice if deferred revenue is enabled -======= - 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: ->>>>>>> 68effd93bd (refactor: move reposting logic to common controller) - 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) - ) -<<<<<<< HEAD - - 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 -======= ->>>>>>> 68effd93bd (refactor: move reposting logic to common controller) - -======= ->>>>>>> a856091ff4 (refactor: remove repeated validation for voucher) def set_paid_amount(self): paid_amount = 0.0 base_paid_amount = 0.0