Merge branch 'develop' of https://github.com/frappe/erpnext into editable_inovice
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 73ec051..7abf3f3 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -64,6 +64,25 @@
 
 		this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
 
+		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 (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 97e5f40..892fa17 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": [
@@ -1035,6 +1036,7 @@
    "read_only": 1
   },
   {
+   "allow_on_submit": 1,
    "depends_on": "redeem_loyalty_points",
    "fieldname": "loyalty_redemption_account",
    "fieldtype": "Link",
@@ -1333,6 +1335,7 @@
    "options": "fa fa-money"
   },
   {
+   "allow_on_submit": 1,
    "depends_on": "is_pos",
    "fieldname": "cash_bank_account",
    "fieldtype": "Link",
@@ -1432,6 +1435,7 @@
    "print_hide": 1
   },
   {
+   "allow_on_submit": 1,
    "depends_on": "is_pos",
    "fieldname": "account_for_change_amount",
    "fieldtype": "Link",
@@ -1480,6 +1484,7 @@
    "hide_seconds": 1
   },
   {
+   "allow_on_submit": 1,
    "fieldname": "write_off_account",
    "fieldtype": "Link",
    "hide_days": 1,
@@ -1703,6 +1708,7 @@
    "read_only": 1
   },
   {
+   "allow_on_submit": 1,
    "default": "No",
    "fieldname": "is_opening",
    "fieldtype": "Select",
@@ -1917,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",
@@ -1959,6 +1966,7 @@
    "label": "Disable Rounded Total"
   },
   {
+   "allow_on_submit": 1,
    "fieldname": "additional_discount_account",
    "fieldtype": "Link",
    "label": "Discount Account",
@@ -2097,6 +2105,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 +2125,7 @@
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2022-10-11 13:07:36.488095",
+ "modified": "2022-10-23 10:52:47.416251",
  "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 0c03c55..11cce8f 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -11,6 +11,9 @@
 
 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 @@
 		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 @@
 
 		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:
@@ -368,6 +374,7 @@
 			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"
@@ -514,6 +521,81 @@
 	def on_update(self):
 		self.set_paid_amount()
 
+	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
+
+			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):
+		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
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index cb0d1a7..855380e 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2729,6 +2729,31 @@
 
 		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 = "TDS Payable - _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()],
+			["TDS Payable - _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.
@@ -3286,6 +3311,7 @@
 		"""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,
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 77055f9..62c3ced 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -438,6 +438,7 @@
    "label": "Accounting Details"
   },
   {
+   "allow_on_submit": 1,
    "fieldname": "income_account",
    "fieldtype": "Link",
    "label": "Income Account",
@@ -450,6 +451,7 @@
    "width": "120px"
   },
   {
+   "allow_on_submit": 1,
    "fieldname": "expense_account",
    "fieldtype": "Link",
    "label": "Expense Account",
@@ -469,6 +471,7 @@
    "print_hide": 1
   },
   {
+   "allow_on_submit": 1,
    "default": ":Company",
    "fieldname": "cost_center",
    "fieldtype": "Link",
@@ -800,6 +803,7 @@
    "options": "Finance Book"
   },
   {
+   "allow_on_submit": 1,
    "fieldname": "project",
    "fieldtype": "Link",
    "label": "Project",
@@ -822,7 +826,6 @@
    "label": "Incoming Rate (Costing)",
    "no_copy": 1,
    "options": "Company:company:default_currency",
-   "precision": "6",
    "print_hide": 1
   },
   {
@@ -835,6 +838,7 @@
    "read_only": 1
   },
   {
+   "allow_on_submit": 1,
    "fieldname": "discount_account",
    "fieldtype": "Link",
    "label": "Discount Account",
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 3a871bf..e236577 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 c1fe72b..a07f75d 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -143,6 +143,12 @@
 
 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", "");