perf: improve gl entry submission (#20676)

* perf: improve gl entry submission

* perf: add indexes

* fix: replace **kwargs with *args

* fix: syntax error

* fix: remove cypress

* fix: travis

* chore: remove purchase invoice from status updater

* fix: set_staus args

Co-Authored-By: Nabin Hait <nabinhait@gmail.com>

* fix: only update status for invoices & fees

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
diff --git a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json
index 5c3519a..02b0c4d 100644
--- a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json
+++ b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json
@@ -18,7 +18,8 @@
    "in_list_view": 1,
    "label": "Invoice",
    "options": "Sales Invoice",
-   "reqd": 1
+   "reqd": 1,
+   "search_index": 1
   },
   {
    "fetch_from": "sales_invoice.customer",
@@ -60,7 +61,7 @@
   }
  ],
  "istable": 1,
- "modified": "2019-09-26 11:05:36.016772",
+ "modified": "2020-02-20 16:16:20.724620",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Discounted Invoice",
diff --git a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py
index 93dfcc1..109737f 100644
--- a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py
+++ b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py
@@ -7,4 +7,4 @@
 from frappe.model.document import Document
 
 class DiscountedInvoice(Document):
-	pass
+	pass
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 041e419..f9e4fd7 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -232,11 +232,36 @@
 		if bal < 0 and not on_cancel:
 			frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal)))
 
-	# Update outstanding amt on against voucher
 	if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
+		update_outstanding_amt_in_ref(against_voucher, against_voucher_type, bal)
+
+def update_outstanding_amt_in_ref(against_voucher, against_voucher_type, bal):
+	data = []
+	# Update outstanding amt on against voucher
+	if against_voucher_type == "Fees":
 		ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
 		ref_doc.db_set('outstanding_amount', bal)
 		ref_doc.set_status(update=True)
+		return
+	elif against_voucher_type == "Purchase Invoice":
+		from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_status
+		data = frappe.db.get_value(against_voucher_type, against_voucher, 
+			["name as purchase_invoice", "outstanding_amount", 
+			"is_return", "due_date", "docstatus"])
+	elif against_voucher_type == "Sales Invoice":
+		from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_status
+		data = frappe.db.get_value(against_voucher_type, against_voucher, 
+			["name as sales_invoice", "outstanding_amount", "is_discounted", 
+			"is_return", "due_date", "docstatus"])
+
+	precision = frappe.get_precision(against_voucher_type, "outstanding_amount")
+	data = list(data)
+	data.append(precision)
+	status = get_status(data)
+	frappe.db.set_value(against_voucher_type, against_voucher, {
+		'outstanding_amount': bal,
+		'status': status
+	})
 
 def validate_frozen_account(account, adv_adj=None):
 	frozen_account = frappe.db.get_value("Account", account, "freeze_account")
@@ -274,6 +299,9 @@
 		if d.against != new_against:
 			frappe.db.set_value("GL Entry", d.name, "against", new_against)
 
+def on_doctype_update():
+	frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"])
+	frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
 
 def rename_gle_sle_docs():
 	for doctype in ["GL Entry", "Stock Ledger Entry"]:
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index a68c368..847fbed 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -125,6 +125,27 @@
 			else:
 				self.remarks = _("No Remarks")
 
+	def set_status(self, update=False, status=None, update_modified=True):
+		if self.is_new():
+			if self.get('amended_from'):
+				self.status = 'Draft'
+			return
+
+		if not status:
+			precision = self.precision("outstanding_amount")
+			args = [
+				self.name,
+				self.outstanding_amount,
+				self.is_return, 
+				self.due_date, 
+				self.docstatus,
+				precision
+			]
+			status = get_status(args)
+
+		if update:
+			self.db_set('status', status, update_modified = update_modified)
+
 	def set_missing_values(self, for_validate=False):
 		if not self.credit_to:
 			self.credit_to = get_party_account("Supplier", self.supplier, self.company)
@@ -1007,6 +1028,34 @@
 		# calculate totals again after applying TDS
 		self.calculate_taxes_and_totals()
 
+def get_status(*args):
+	purchase_invoice, outstanding_amount, is_return, due_date, docstatus, precision = args[0]
+
+	outstanding_amount = flt(outstanding_amount, precision)
+	due_date = getdate(due_date)
+	now_date = getdate()
+
+	if docstatus == 2:
+		status = "Cancelled"
+	elif docstatus == 1:
+		if outstanding_amount > 0 and due_date < now_date:
+			status = "Overdue"
+		elif outstanding_amount > 0 and due_date >= now_date:
+			status = "Unpaid"
+		#Check if outstanding amount is 0 due to debit note issued against invoice
+		elif outstanding_amount <= 0 and is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': purchase_invoice, 'docstatus': 1}):
+			status = "Debit Note Issued"
+		elif is_return == 1:
+			status = "Return"
+		elif outstanding_amount <=0:
+			status = "Paid"
+		else:
+			status = "Submitted"
+	else:
+		status = "Draft"
+	
+	return status
+
 def get_list_context(context=None):
 	from erpnext.controllers.website_list_for_contact import get_list_context
 	list_context = get_list_context(context)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 658e703..bcaa394 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1217,62 +1217,83 @@
 
 		self.set_missing_values(for_validate = True)
 
-	def get_discounting_status(self):
-		status = None
-		if self.is_discounted:
-			invoice_discounting_list = frappe.db.sql("""
-				select status
-				from `tabInvoice Discounting` id, `tabDiscounted Invoice` d
-				where
-					id.name = d.parent
-					and d.sales_invoice=%s
-					and id.docstatus=1
-					and status in ('Disbursed', 'Settled')
-			""", self.name)
-			for d in invoice_discounting_list:
-				status = d[0]
-				if status == "Disbursed":
-					break
-		return status
-
 	def set_status(self, update=False, status=None, update_modified=True):
 		if self.is_new():
 			if self.get('amended_from'):
 				self.status = 'Draft'
 			return
 
-		precision = self.precision("outstanding_amount")
-		outstanding_amount = flt(self.outstanding_amount, precision)
-		due_date = getdate(self.due_date)
-		nowdate = getdate()
-		discountng_status = self.get_discounting_status()
-
 		if not status:
-			if self.docstatus == 2:
-				status = "Cancelled"
-			elif self.docstatus == 1:
-				if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
-					self.status = "Overdue and Discounted"
-				elif outstanding_amount > 0 and due_date < nowdate:
-					self.status = "Overdue"
-				elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discountng_status=='Disbursed':
-					self.status = "Unpaid and Discounted"
-				elif outstanding_amount > 0 and due_date >= nowdate:
-					self.status = "Unpaid"
-				#Check if outstanding amount is 0 due to credit note issued against invoice
-				elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
-					self.status = "Credit Note Issued"
-				elif self.is_return == 1:
-					self.status = "Return"
-				elif outstanding_amount<=0:
-					self.status = "Paid"
-				else:
-					self.status = "Submitted"
-			else:
-				self.status = "Draft"
+			precision = self.precision("outstanding_amount")
+			args = [
+				self.name,
+				self.outstanding_amount,
+				self.is_discounted, 
+				self.is_return, 
+				self.due_date, 
+				self.docstatus,
+				precision,
+			]
+			status = get_status(args)
 
 		if update:
-			self.db_set('status', self.status, update_modified = update_modified)
+			self.db_set('status', status, update_modified = update_modified)
+
+def get_discounting_status(sales_invoice):
+	status = None
+
+	invoice_discounting_list = frappe.db.sql("""
+		select status
+		from `tabInvoice Discounting` id, `tabDiscounted Invoice` d
+		where
+			id.name = d.parent
+			and d.sales_invoice=%s
+			and id.docstatus=1
+			and status in ('Disbursed', 'Settled')
+	""", sales_invoice)
+
+	for d in invoice_discounting_list:
+		status = d[0]
+		if status == "Disbursed":
+			break
+
+	return status
+
+def get_status(*args):
+	sales_invoice, outstanding_amount, is_discounted, is_return, due_date, docstatus, precision = args[0]
+	
+	discounting_status = None
+	if is_discounted:
+		discounting_status = get_discounting_status(sales_invoice)
+
+	outstanding_amount = flt(outstanding_amount, precision)
+	due_date = getdate(due_date)
+	now_date = getdate()
+
+	if docstatus == 2:
+		status = "Cancelled"
+	elif docstatus == 1:
+		if outstanding_amount > 0 and due_date < now_date and is_discounted and discounting_status=='Disbursed':
+			status = "Overdue and Discounted"
+		elif outstanding_amount > 0 and due_date < now_date:
+			status = "Overdue"
+		elif outstanding_amount > 0 and due_date >= now_date and is_discounted and discounting_status=='Disbursed':
+			status = "Unpaid and Discounted"
+		elif outstanding_amount > 0 and due_date >= now_date:
+			status = "Unpaid"
+		#Check if outstanding amount is 0 due to credit note issued against invoice
+		elif outstanding_amount <= 0 and is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': sales_invoice, 'docstatus': 1}):
+			status = "Credit Note Issued"
+		elif is_return == 1:
+			status = "Return"
+		elif outstanding_amount <=0:
+			status = "Paid"
+		else:
+			status = "Submitted"
+	else:
+		status = "Draft"
+	
+	return status
 
 def validate_inter_company_party(doctype, party, company, inter_company_reference):
 	if not party:
@@ -1444,7 +1465,7 @@
 		"party": party,
 		"company": company
 	}
-
+  
 def get_internal_party(parties, link_doctype, doc):
 	if len(parties) == 1:
 			party = parties[0].name
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index bb1b7e3..6d53530 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -140,8 +140,11 @@
 	gle = frappe.get_doc(args)
 	gle.flags.ignore_permissions = 1
 	gle.flags.from_repost = from_repost
-	gle.insert()
+	gle.validate()
+	gle.flags.ignore_permissions = True
+	gle.db_insert()
 	gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
+	gle.flags.ignore_validate = True
 	gle.submit()
 
 def validate_account_for_perpetual_inventory(gl_map):
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 8b275a6..b465a10 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -44,17 +44,6 @@
 		["Closed", "eval:self.status=='Closed'"],
 		["On Hold", "eval:self.status=='On Hold'"],
 	],
-	"Purchase Invoice": [
-		["Draft", None],
-		["Submitted", "eval:self.docstatus==1"],
-		["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"],
-		["Return", "eval:self.is_return==1 and self.docstatus==1"],
-		["Debit Note Issued",
-		 "eval:self.outstanding_amount <= 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"],
-		["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"],
-		["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"],
-		["Cancelled", "eval:self.docstatus==2"],
-	],
 	"Purchase Order": [
 		["Draft", None],
 		["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"],