From 30da6ab2c1b870047d78b0f52196c906c6d3533d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 Oct 2022 11:10:38 +0530 Subject: [PATCH 1/9] feat: Editable Sales Invoice --- .../doctype/sales_invoice/sales_invoice.js | 21 ++++++ .../doctype/sales_invoice/sales_invoice.json | 12 +++- .../doctype/sales_invoice/sales_invoice.py | 70 ++++++++++++++++++- 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 73ec051c6d..7a5d3922f3 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -64,6 +64,27 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e this.frm.toggle_reqd("due_date", !this.frm.doc.is_return); + if (this.frm.doc.repost_required) { + 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')); + this.frm.trigger('refresh'); + } + } + }); + }); + + $(`["${encodeURIComponent("Repost Accounting Entries")}"]`).css('color', 'red'); + } + if (this.frm.doc.is_return) { this.frm.return_print_format = "Sales Invoice Return"; } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 97e5f4017e..b98cd3ad67 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -207,6 +207,7 @@ "is_internal_customer", "is_discounted", "remarks", + "repost_required", "connections_tab" ], "fields": [ @@ -1703,6 +1704,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "default": "No", "fieldname": "is_opening", "fieldtype": "Select", @@ -2097,6 +2099,14 @@ "hide_seconds": 1, "label": "Write Off", "width": "50%" + }, + { + "default": "0", + "fieldname": "repost_required", + "fieldtype": "Check", + "hidden": 1, + "label": "Repost Required", + "read_only": 1 } ], "icon": "fa fa-file-text", @@ -2109,7 +2119,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-10-11 13:07:36.488095", + "modified": "2022-10-15 19:15:49.526529", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index afd5a59df4..4c38883913 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -11,6 +11,9 @@ 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, @@ -100,13 +103,11 @@ class SalesInvoice(SellingController): self.validate_debit_to_acc() self.clear_unallocated_advances("Sales Invoice Advance", "advances") self.add_remarks() - self.validate_write_off_account() - self.validate_account_for_change_amount() self.validate_fixed_asset() self.set_income_account_for_fixed_assets() self.validate_item_cost_centers() - self.validate_income_account() self.check_conversion_rate() + self.validate_accounts() validate_inter_company_party( self.doctype, self.customer, self.company, self.inter_company_invoice_reference @@ -170,6 +171,11 @@ class SalesInvoice(SellingController): self.reset_default_field_value("set_warehouse", "items", "warehouse") + def validate_accounts(self): + self.validate_write_off_account() + self.validate_account_for_change_amount() + self.validate_income_account() + 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: @@ -514,6 +520,64 @@ class SalesInvoice(SellingController): def on_update(self): self.set_paid_amount() + def on_update_after_submit(self): + 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() + + # 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 parent level + for index, item in enumerate(self.get("items")): + for field in ( + "income_account", + "expense_account", + "discount_account", + "deferred_revenue_account", + ): + if doc_before_update.get("items")[index].get(field) != item.get(field): + needs_repost = 1 + break + + for dimension in accounting_dimensions: + if doc_before_update.get("items")[index].get(dimension) != item.get(dimension): + needs_repost = 1 + break + + self.validate_accounts() + self.db_set("repost_required", needs_repost) + + @frappe.whitelist() + def repost_accounting_entries(self): + self.docstatus = 2 + self.make_gl_entries_on_cancel() + self.docstatus = 1 + self.make_gl_entries() + self.db_set("repost_required", 0) + def set_paid_amount(self): paid_amount = 0.0 base_paid_amount = 0.0 From e626107d3de7f95f15627a266e652cdfedf020a1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 Oct 2022 16:48:13 +0530 Subject: [PATCH 2/9] chore: Update allow on submit for Sales Invoice fields --- .../doctype/sales_invoice/sales_invoice.js | 6 ++---- .../doctype/sales_invoice/sales_invoice.py | 20 ++++++++++++------- .../sales_invoice_item.json | 8 ++++++-- .../sales_taxes_and_charges.json | 7 +++++-- erpnext/public/js/controllers/accounts.js | 6 ++++++ 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 7a5d3922f3..435ae9e482 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -76,13 +76,11 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e callback: (r) => { if (!r.exc) { frappe.msgprint(__('Accounting Entries are reposted')); - this.frm.trigger('refresh'); + me.frm.refresh(); } } }); - }); - - $(`["${encodeURIComponent("Repost Accounting Entries")}"]`).css('color', 'red'); + }).removeClass('btn-default').addClass('btn-warning'); } if (this.frm.doc.is_return) { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4c38883913..891ae1c4b2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -522,9 +522,10 @@ class SalesInvoice(SellingController): def on_update_after_submit(self): 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() + accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"] # Check if opening entry check updated if doc_before_update.get("is_opening") != self.is_opening: @@ -552,12 +553,7 @@ class SalesInvoice(SellingController): # Check for parent level for index, item in enumerate(self.get("items")): - for field in ( - "income_account", - "expense_account", - "discount_account", - "deferred_revenue_account", - ): + for field in ("income_account", "expense_account", "discount_account"): if doc_before_update.get("items")[index].get(field) != item.get(field): needs_repost = 1 break @@ -567,6 +563,16 @@ class SalesInvoice(SellingController): needs_repost = 1 break + for index, tax in enumerate(self.get("taxes")): + if doc_before_update.get("taxes")[index].get("account_head") != tax.get("account_head"): + needs_repost = 1 + break + + for dimension in accounting_dimensions: + if doc_before_update.get("taxes")[index].get(dimension) != tax.get(dimension): + needs_repost = 1 + break + self.validate_accounts() self.db_set("repost_required", needs_repost) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index a307a6c17c..342a3cc98b 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -436,6 +436,7 @@ "label": "Accounting Details" }, { + "allow_on_submit": 1, "fieldname": "income_account", "fieldtype": "Link", "label": "Income Account", @@ -448,6 +449,7 @@ "width": "120px" }, { + "allow_on_submit": 1, "fieldname": "expense_account", "fieldtype": "Link", "label": "Expense Account", @@ -467,6 +469,7 @@ "print_hide": 1 }, { + "allow_on_submit": 1, "default": ":Company", "fieldname": "cost_center", "fieldtype": "Link", @@ -798,6 +801,7 @@ "options": "Finance Book" }, { + "allow_on_submit": 1, "fieldname": "project", "fieldtype": "Link", "label": "Project", @@ -820,7 +824,6 @@ "label": "Incoming Rate (Costing)", "no_copy": 1, "options": "Company:company:default_currency", - "precision": "6", "print_hide": 1 }, { @@ -833,6 +836,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "fieldname": "discount_account", "fieldtype": "Link", "label": "Discount Account", @@ -876,7 +880,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-10-10 20:57:38.340026", + "modified": "2022-10-17 12:51:44.825398", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index 3a871bfced..e236577e11 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -51,6 +51,7 @@ "oldfieldtype": "Data" }, { + "allow_on_submit": 1, "columns": 2, "fieldname": "account_head", "fieldtype": "Link", @@ -63,6 +64,7 @@ "search_index": 1 }, { + "allow_on_submit": 1, "default": ":Company", "fieldname": "cost_center", "fieldtype": "Link", @@ -216,12 +218,13 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-08-05 20:04:01.726867", + "modified": "2022-10-17 13:08:17.776528", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "states": [] } \ No newline at end of file diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index c1fe72bb48..a07f75d1c5 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -143,6 +143,12 @@ var get_payment_mode_account = function(frm, mode_of_payment, callback) { cur_frm.cscript.account_head = function(doc, cdt, cdn) { var d = locals[cdt][cdn]; + + if (doc.docstatus == 1) { + // Should not trigger any changes on change post submit + return; + } + if(!d.charge_type && d.account_head){ frappe.msgprint(__("Please select Charge Type first")); frappe.model.set_value(cdt, cdn, "account_head", ""); From 42e4c37f15e2a0613be9104d390bfdc3032f85b7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 Oct 2022 20:09:07 +0530 Subject: [PATCH 3/9] chore: Break into smaller functions --- .../doctype/sales_invoice/sales_invoice.py | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 891ae1c4b2..1047e88606 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -551,31 +551,38 @@ class SalesInvoice(SellingController): needs_repost = 1 break - # Check for parent level - for index, item in enumerate(self.get("items")): - for field in ("income_account", "expense_account", "discount_account"): - if doc_before_update.get("items")[index].get(field) != item.get(field): - 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 - for dimension in accounting_dimensions: - if doc_before_update.get("items")[index].get(dimension) != item.get(dimension): - needs_repost = 1 - break - - for index, tax in enumerate(self.get("taxes")): - if doc_before_update.get("taxes")[index].get("account_head") != tax.get("account_head"): - needs_repost = 1 - break - - for dimension in accounting_dimensions: - if doc_before_update.get("taxes")[index].get(dimension) != tax.get(dimension): - needs_repost = 1 - break + if self.check_if_child_table_updated( + "taxes", doc_before_update, ("account_head",), accounting_dimensions + ): + needs_repost = 1 self.validate_accounts() 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 + @frappe.whitelist() def repost_accounting_entries(self): self.docstatus = 2 From 1105e520315cbee4915e696706ecc3bfa1f0af8f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Oct 2022 22:55:08 +0530 Subject: [PATCH 4/9] chore: Update allow on submit fields --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index b98cd3ad67..892fa173c5 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1036,6 +1036,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "depends_on": "redeem_loyalty_points", "fieldname": "loyalty_redemption_account", "fieldtype": "Link", @@ -1334,6 +1335,7 @@ "options": "fa fa-money" }, { + "allow_on_submit": 1, "depends_on": "is_pos", "fieldname": "cash_bank_account", "fieldtype": "Link", @@ -1433,6 +1435,7 @@ "print_hide": 1 }, { + "allow_on_submit": 1, "depends_on": "is_pos", "fieldname": "account_for_change_amount", "fieldtype": "Link", @@ -1481,6 +1484,7 @@ "hide_seconds": 1 }, { + "allow_on_submit": 1, "fieldname": "write_off_account", "fieldtype": "Link", "hide_days": 1, @@ -1919,6 +1923,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "depends_on": "eval:doc.is_internal_customer", "description": "Unrealized Profit / Loss account for intra-company transfers", "fieldname": "unrealized_profit_loss_account", @@ -1961,6 +1966,7 @@ "label": "Disable Rounded Total" }, { + "allow_on_submit": 1, "fieldname": "additional_discount_account", "fieldtype": "Link", "label": "Discount Account", @@ -2119,7 +2125,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-10-15 19:15:49.526529", + "modified": "2022-10-23 10:52:47.416251", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From ed98015a5639889086a7662256bf743dfe387ffc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Oct 2022 23:03:50 +0530 Subject: [PATCH 5/9] test: Add unit tests --- .../doctype/sales_invoice/sales_invoice.py | 96 ++++++++++--------- .../sales_invoice/test_sales_invoice.py | 26 +++++ 2 files changed, 76 insertions(+), 46 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 1047e88606..e2ed9d3501 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -521,52 +521,53 @@ class SalesInvoice(SellingController): self.set_paid_amount() def on_update_after_submit(self): - needs_repost = 0 + 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 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, - ): + # Check if opening entry check updated + if doc_before_update.get("is_opening") != self.is_opening: needs_repost = 1 - if self.check_if_child_table_updated( - "taxes", doc_before_update, ("account_head",), accounting_dimensions - ): - 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 - self.validate_accounts() - self.db_set("repost_required", needs_repost) + # 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 + + self.validate_accounts() + self.db_set("repost_required", needs_repost) def check_if_child_table_updated( self, child_table, doc_before_update, fields_to_check, accounting_dimensions @@ -585,11 +586,14 @@ class SalesInvoice(SellingController): @frappe.whitelist() def repost_accounting_entries(self): - self.docstatus = 2 - self.make_gl_entries_on_cancel() - self.docstatus = 1 - self.make_gl_entries() - self.db_set("repost_required", 0) + 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 diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 301d3e136e..ed04747264 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2728,6 +2728,31 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1)) + # Update Invoice post submit and then check GL Entries again + + si.load_from_db() + si.items[0].income_account = "Service - _TC" + si.additional_discount_account = "_Test Account Sales - _TC" + si.taxes[0].account_head = "VAT 5% - _TC" + si.save() + + si.load_from_db() + self.assertTrue(si.repost_required) + + si.repost_accounting_entries() + + expected_gle = [ + ["_Test Account Sales - _TC", 22.0, 0.0, nowdate()], + ["Debtors - _TC", 88, 0.0, nowdate()], + ["Service - _TC", 0.0, 100.0, nowdate()], + ["VAT 5% - _TC", 0.0, 10.0, nowdate()], + ] + + check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1)) + + si.load_from_db() + self.assertFalse(si.repost_required) + def test_asset_depreciation_on_sale_with_pro_rata(self): """ Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale. @@ -3269,6 +3294,7 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): """select account, debit, credit, posting_date from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s + and is_cancelled = 0 order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1, From 1a980123a2c83c669735a87d1d59815666de6170 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 24 Oct 2022 10:08:55 +0530 Subject: [PATCH 6/9] chore: Update tests --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ed04747264..d6e05fb91b 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2733,7 +2733,7 @@ class TestSalesInvoice(unittest.TestCase): si.load_from_db() si.items[0].income_account = "Service - _TC" si.additional_discount_account = "_Test Account Sales - _TC" - si.taxes[0].account_head = "VAT 5% - _TC" + si.taxes[0].account_head = "TDS Payable - _TC" si.save() si.load_from_db() @@ -2745,7 +2745,7 @@ class TestSalesInvoice(unittest.TestCase): ["_Test Account Sales - _TC", 22.0, 0.0, nowdate()], ["Debtors - _TC", 88, 0.0, nowdate()], ["Service - _TC", 0.0, 100.0, nowdate()], - ["VAT 5% - _TC", 0.0, 10.0, nowdate()], + ["TDS Payable - _TC", 0.0, 10.0, nowdate()], ] check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1)) From 0966867c0835d1066edf4146156e18d7f91da2f1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 5 Nov 2022 16:35:43 +0530 Subject: [PATCH 7/9] chore: Reset repost_required_flag on cancel --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 2 +- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 435ae9e482..7abf3f31d9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -64,7 +64,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e this.frm.toggle_reqd("due_date", !this.frm.doc.is_return); - if (this.frm.doc.repost_required) { + 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'), () => { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0cc67d5d83..11cce8f604 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -374,6 +374,7 @@ class SalesInvoice(SellingController): self.repost_future_sle_and_gle() frappe.db.set(self, "status", "Cancelled") + self.db_set("repost_required", 0) if ( frappe.db.get_single_value("Selling Settings", "sales_update_frequency") == "Each Transaction" From 5fe55176ecc7b148380dd9940c5c34dda2aa9cd9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 7 Nov 2022 16:42:12 +0530 Subject: [PATCH 8/9] chore: Enable no-copy for repost required field --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 892fa173c5..15d1d25878 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2112,6 +2112,7 @@ "fieldtype": "Check", "hidden": 1, "label": "Repost Required", + "no_copy": 1, "read_only": 1 } ], @@ -2125,7 +2126,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-10-23 10:52:47.416251", + "modified": "2022-11-07 16:02:07.972258", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From e29f756146b42c203d6e2a456c5e1ebf13d2ee19 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 7 Nov 2022 16:42:45 +0530 Subject: [PATCH 9/9] chore: Validate for deferred revenue invoices --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 11cce8f604..e796c99da9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -568,6 +568,17 @@ class SalesInvoice(SellingController): needs_repost = 1 self.validate_accounts() + + # validate if deferred revenue is enabled for any item + # Don't allow to update the invoice if deferred revenue is enabled + 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(