Calculate due date in Purchase Invoice on the basis of Supplier invoice date  (#12913)

* pass bill_date as parameter and calculate due_date on that basis

* calculate payment_schedule on the basis of bill_date if present else posting_date

* add bill_date as an argument to get_party_details

* pass bill_date on supplier trigger in purchase invoice
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 26cc598..39f8039 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -103,10 +103,11 @@
 		erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
 			{
 				posting_date: this.frm.doc.posting_date,
+				bill_date: this.frm.doc.bill_date,
 				party: this.frm.doc.supplier,
 				party_type: "Supplier",
 				account: this.frm.doc.credit_to,
-				price_list: this.frm.doc.buying_price_list,
+				price_list: this.frm.doc.buying_price_list
 			}, function() {
 				me.apply_pricing_rule();
 			})
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 21b71ff..d7e14e1 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -96,7 +96,7 @@
 		if not self.credit_to:
 			self.credit_to = get_party_account("Supplier", self.supplier, self.company)
 		if not self.due_date:
-			self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company)
+			self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company,  self.bill_date)
 
 		super(PurchaseInvoice, self).set_missing_values(for_validate)
 
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 55d2c21..90bb0bb 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -23,22 +23,19 @@
 
 @frappe.whitelist()
 def get_party_details(party=None, account=None, party_type="Customer", company=None,
-	posting_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False):
+	posting_date=None, bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False):
 
 	if not party:
 		return {}
-
 	if not frappe.db.exists(party_type, party):
 		frappe.throw(_("{0}: {1} does not exists").format(party_type, party))
-
 	return _get_party_details(party, account, party_type,
-		company, posting_date, price_list, currency, doctype, ignore_permissions)
+		company, posting_date, bill_date, price_list, currency, doctype, ignore_permissions)
 
 def _get_party_details(party=None, account=None, party_type="Customer", company=None,
-	posting_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False):
+	posting_date=None, bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False):
 
-	out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, doctype))
-
+	out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
 	party = out[party_type.lower()]
 
 	if not ignore_permissions and not frappe.has_permission(party_type, "read", party):
@@ -150,7 +147,7 @@
 	out["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list
 
 
-def set_account_and_due_date(party, account, party_type, company, posting_date, doctype):
+def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype):
 	if doctype not in ["Sales Invoice", "Purchase Invoice"]:
 		# not an invoice
 		return {
@@ -161,12 +158,12 @@
 		account = get_party_account(party_type, party, company)
 
 	account_fieldname = "debit_to" if party_type=="Customer" else "credit_to"
-
 	out = {
 		party_type.lower(): party,
 		account_fieldname : account,
-		"due_date": get_due_date(posting_date, party_type, party, company)
+		"due_date": get_due_date(posting_date, party_type, party, company, bill_date)
 	}
+
 	return out
 
 @frappe.whitelist()
@@ -268,32 +265,34 @@
 
 
 @frappe.whitelist()
-def get_due_date(posting_date, party_type, party, company=None):
+def get_due_date(posting_date, party_type, party, company=None, bill_date=None):
 	"""Get due date from `Payment Terms Template`"""
 	due_date = None
-	if posting_date and party:
-		due_date = posting_date
+	if (bill_date or posting_date) and party:
+		due_date = bill_date or posting_date
 		template_name = get_pyt_term_template(party, party_type, company)
 		if template_name:
-			due_date = get_due_date_from_template(template_name, posting_date).strftime("%Y-%m-%d")
+			due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")
 		else:
 			if party_type == "Supplier":
 				supplier_type = frappe.db.get_value(party_type, party, fieldname="supplier_type")
 				template_name = frappe.db.get_value("Supplier Type", supplier_type, fieldname="payment_terms")
 				if template_name:
-					due_date = get_due_date_from_template(template_name, posting_date).strftime("%Y-%m-%d")
-
+					due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")
+	# If due date is calculated from bill_date, check this condition
+	if getdate(due_date) < getdate(posting_date):
+		due_date = posting_date
 	return due_date
 
-
-def get_due_date_from_template(template_name, posting_date):
+def get_due_date_from_template(template_name, posting_date, bill_date):
 	"""
 	Inspects all `Payment Term`s from the a `Payment Terms Template` and returns the due
 	date after considering all the `Payment Term`s requirements.
 	:param template_name: Name of the `Payment Terms Template`
 	:return: String representing the calculated due date
 	"""
-	due_date = getdate(posting_date)
+	due_date = getdate(bill_date or posting_date)
+
 	template = frappe.get_doc('Payment Terms Template', template_name)
 
 	for term in template.terms:
@@ -303,14 +302,13 @@
 			due_date = max(due_date, add_days(get_last_day(due_date), term.credit_days))
 		else:
 			due_date = max(due_date, add_months(get_last_day(due_date), term.credit_months))
-
 	return due_date
 
-def validate_due_date(posting_date, due_date, party_type, party, company=None):
+def validate_due_date(posting_date, due_date, party_type, party, company=None, bill_date=None):
 	if getdate(due_date) < getdate(posting_date):
 		frappe.throw(_("Due Date cannot be before Posting Date"))
 	else:
-		default_due_date = get_due_date(posting_date, party_type, party, company)
+		default_due_date = get_due_date(posting_date, party_type, party, company, bill_date)
 		if not default_due_date:
 			return
 
@@ -363,7 +361,6 @@
 def get_pyt_term_template(party_name, party_type, company=None):
 	if party_type not in ("Customer", "Supplier"):
 		return
-
 	template = None
 	if party_type == 'Customer':
 		customer = frappe.db.get_value("Customer", party_name,
@@ -377,13 +374,11 @@
 		supplier = frappe.db.get_value("Supplier", party_name,
 			fieldname=['payment_terms', "supplier_type"], as_dict=1)
 		template = supplier.payment_terms
-
 		if not template and supplier.supplier_type:
 			template = frappe.db.get_value("Supplier Type", supplier.supplier_type, fieldname='payment_terms')
 
 	if not template and company:
 		template = frappe.db.get_value("Company", company, fieldname='payment_terms')
-
 	return template
 
 def validate_party_frozen_disabled(party_type, party_name):
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index b7017c1..1425160 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -137,7 +137,7 @@
 
 			validate_due_date(self.posting_date, self.due_date, "Customer", self.customer, self.company)
 		elif self.doctype == "Purchase Invoice":
-			validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company)
+			validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company, self.bill_date)
 
 	def set_price_list_currency(self, buying_or_selling):
 		if self.meta.get_field("posting_date"):
@@ -908,7 +908,7 @@
 		where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
 
 @frappe.whitelist()
-def get_payment_terms(terms_template, posting_date=None, grand_total=None):
+def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None):
 	if not terms_template:
 		return
 
@@ -916,13 +916,13 @@
 
 	schedule = []
 	for d in terms_doc.get("terms"):
-		term_details = get_payment_term_details(d, posting_date, grand_total)
+		term_details = get_payment_term_details(d, posting_date, grand_total, bill_date)
 		schedule.append(term_details)
 
 	return schedule
 
 @frappe.whitelist()
-def get_payment_term_details(term, posting_date=None, grand_total=None):
+def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None):
 	term_details = frappe._dict()
 	if isinstance(term, unicode):
 		term = frappe.get_doc("Payment Term", term)
@@ -931,17 +931,23 @@
 	term_details.description = term.description
 	term_details.invoice_portion = term.invoice_portion
 	term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
-	if posting_date:
-		term_details.due_date = get_due_date(posting_date, term)
+	if bill_date:
+		term_details.due_date = get_due_date(term, bill_date)
+	elif posting_date:
+		term_details.due_date = get_due_date(term, posting_date)
+
+	if getdate(term_details.due_date) < getdate(posting_date):
+		term_details.due_date = posting_date
+
 	return term_details
 
-def get_due_date(posting_date, term):
+def get_due_date(term, posting_date=None, bill_date=None):
 	due_date = None
+	date = bill_date or posting_date
 	if term.due_date_based_on == "Day(s) after invoice date":
-		due_date = add_days(posting_date, term.credit_days)
+		due_date = add_days(date, term.credit_days)
 	elif term.due_date_based_on == "Day(s) after the end of the invoice month":
-		due_date = add_days(get_last_day(posting_date), term.credit_days)
+		due_date = add_days(get_last_day(date), term.credit_days)
 	elif term.due_date_based_on == "Month(s) after the end of the invoice month":
-		due_date = add_months(get_last_day(posting_date), term.credit_months)
-
+		due_date = add_months(get_last_day(date), term.credit_months)
 	return due_date
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index e8c3262..274bb3e 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -518,6 +518,7 @@
 					args: {
 						"posting_date": me.frm.doc.posting_date,
 						"party_type": me.frm.doc.doctype == "Sales Invoice" ? "Customer" : "Supplier",
+						"bill_date": me.frm.doc.bill_date,
 						"party": me.frm.doc.doctype == "Sales Invoice" ? me.frm.doc.customer : me.frm.doc.supplier,
 						"company": me.frm.doc.company
 					},
@@ -561,9 +562,12 @@
 		}
 	},
 
+	bill_date: function() {
+		this.posting_date();
+	},
+
 	recalculate_terms: function() {
 		const doc = this.frm.doc;
-
 		if (doc.payment_terms_template) {
 			this.payment_terms_template();
 		} else if (doc.payment_schedule) {
@@ -1272,14 +1276,14 @@
 		var me = this;
 		const doc = this.frm.doc;
 		if(doc.payment_terms_template && doc.doctype !== 'Delivery Note') {
-			var posting_date = doc.bill_date || doc.posting_date || doc.transaction_date;
-
+			var posting_date = doc.posting_date || doc.transaction_date;
 			frappe.call({
 				method: "erpnext.controllers.accounts_controller.get_payment_terms",
 				args: {
 					terms_template: doc.payment_terms_template,
 					posting_date: posting_date,
-					grand_total: doc.rounded_total || doc.grand_total
+					grand_total: doc.rounded_total || doc.grand_total,
+					bill_date: doc.bill_date
 				},
 				callback: function(r) {
 					if(r.message && !r.exc) {
@@ -1297,6 +1301,7 @@
 				method: "erpnext.controllers.accounts_controller.get_payment_term_details",
 				args: {
 					term: row.payment_term,
+					bill_date: this.frm.doc.bill_date,
 					posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date,
 					grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total
 				},
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index 75d5ce9..5480aed 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -18,6 +18,7 @@
 			args = {
 				party: frm.doc.supplier,
 				party_type: "Supplier",
+				bill_date: frm.doc.bill_date,
 				price_list: frm.doc.buying_price_list
 			};
 		}