Merge pull request #22121 from rohitwaghchaure/error-report-exponential-smoothing-report

fix: 'ForecastingReport' object has no attribute 'total_demand'
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
new file mode 100644
index 0000000..d36b115
--- /dev/null
+++ b/.github/workflows/docker-release.yml
@@ -0,0 +1,14 @@
+name: Trigger Docker build on release
+on:
+  release:
+    types: [created]
+jobs:
+  curl:
+    runs-on: ubuntu-latest
+    container:
+      image: alpine:latest
+    steps:
+    - name: curl
+      run: |
+        apk add curl bash
+        curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.org/repo/frappe%2Ffrappe_docker/requests
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index d40e58b..66aa180 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -169,9 +169,11 @@
 
 	def append_subtotal_row(self, party):
 		sub_total_row = self.total_row_map.get(party)
-		self.data.append(sub_total_row)
-		self.data.append({})
-		self.update_sub_total_row(sub_total_row, 'Total')
+
+		if sub_total_row:
+			self.data.append(sub_total_row)
+			self.data.append({})
+			self.update_sub_total_row(sub_total_row, 'Total')
 
 	def get_voucher_balance(self, gle):
 		if self.filters.get("sales_person"):
@@ -232,7 +234,8 @@
 
 		if self.filters.get('group_by_party'):
 			self.append_subtotal_row(self.previous_party)
-			self.data.append(self.total_row_map.get('Total'))
+			if self.data:
+				self.data.append(self.total_row_map.get('Total'))
 
 	def append_row(self, row):
 		self.allocate_future_payments(row)
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
index 4ac0f65..a9fb237 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -111,7 +111,7 @@
 	# {"purchase_invoice": list of dict of all gle created for this invoice}
 	gle_map = {}
 	gle = frappe.db.get_all('GL Entry',\
-		{"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]]},
+		{"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]], 'is_cancelled': 0},
 		["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"])
 
 	for d in gle:
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 29f8dd5..50b17ab 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -70,7 +70,7 @@
 
 		else:
 			attributes_list = attribute_values.get(attribute.lower(), [])
-			validate_item_attribute_value(attributes_list, attribute, value, item.name)
+			validate_item_attribute_value(attributes_list, attribute, value, item.name, from_variant=True)
 
 def validate_is_incremental(numeric_attribute, attribute, value, item):
 	from_range = numeric_attribute.from_range
@@ -93,13 +93,20 @@
 			.format(attribute, from_range, to_range, increment, item),
 			InvalidItemAttributeValueError, title=_('Invalid Attribute'))
 
-def validate_item_attribute_value(attributes_list, attribute, attribute_value, item):
+def validate_item_attribute_value(attributes_list, attribute, attribute_value, item, from_variant=True):
 	allow_rename_attribute_value = frappe.db.get_single_value('Item Variant Settings', 'allow_rename_attribute_value')
 	if allow_rename_attribute_value:
 		pass
 	elif attribute_value not in attributes_list:
-		frappe.throw(_("The value {0} is already assigned to an exisiting Item {2}.").format(
-			attribute_value, attribute, item), InvalidItemAttributeValueError, title=_('Rename Not Allowed'))
+		if from_variant:
+			frappe.throw(_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
+				frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)), InvalidItemAttributeValueError, title=_("Invalid Value"))
+		else:
+			msg = _("The value {0} is already assigned to an exisiting Item {1}.").format(
+				frappe.bold(attribute_value), frappe.bold(item))
+			msg += "<br>" + _("To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings.").format(frappe.bold("Allow Rename Attribute Value"))
+
+			frappe.throw(msg, InvalidItemAttributeValueError, title=_('Edit Not Allowed'))
 
 def get_attribute_values(item):
 	if not frappe.flags.attribute_values:
diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py
index 7046038..d59f909 100644
--- a/erpnext/erpnext_integrations/connectors/shopify_connection.py
+++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py
@@ -241,14 +241,17 @@
 	return taxes
 
 def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
+	"""Shipping lines represents the shipping details,
+		each such shipping detail consists of a list of tax_lines"""
 	for shipping_charge in shipping_lines:
-		taxes.append({
-			"charge_type": _("Actual"),
-			"account_head": get_tax_account_head(shipping_charge),
-			"description": shipping_charge["title"],
-			"tax_amount": shipping_charge["price"],
-			"cost_center": shopify_settings.cost_center
-		})
+		for tax in shipping_charge.get("tax_lines"):
+			taxes.append({
+				"charge_type": _("Actual"),
+				"account_head": get_tax_account_head(tax),
+				"description": tax["title"],
+				"tax_amount": tax["price"],
+				"cost_center": shopify_settings.cost_center
+			})
 
 	return taxes
 
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index 473aae6..15ce468 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -38,6 +38,9 @@
 	},
 
 	validate: function(frm) {
+		if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1){
+			frm.doc.half_day_date = frm.doc.from_date;
+		}
 		frm.toggle_reqd("half_day_date", frm.doc.half_day == 1);
 	},
 
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index f2968bc..5eb84d7 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -33,6 +33,7 @@
 		self.validate_block_days()
 		self.validate_salary_processed_days()
 		self.validate_attendance()
+		self.set_half_day_date()
 		if frappe.db.get_value("Leave Type", self.leave_type, 'is_optional_leave'):
 			self.validate_optional_leave()
 		self.validate_applicable_after()
@@ -292,6 +293,10 @@
 				frappe.throw(_("{0} is not in Optional Holiday List").format(formatdate(day)), NotAnOptionalHoliday)
 			day = add_days(day, 1)
 
+	def set_half_day_date(self):
+		if self.from_date == self.to_date and self.half_day == 1:
+			self.half_day_date = self.from_date
+
 	def notify_employee(self):
 		employee = frappe.get_doc("Employee", self.employee)
 		if not employee.user_id:
diff --git a/erpnext/loan_management/desk_page/loan/loan.json b/erpnext/loan_management/desk_page/loan/loan.json
index d79860a..48193b0 100644
--- a/erpnext/loan_management/desk_page/loan/loan.json
+++ b/erpnext/loan_management/desk_page/loan/loan.json
@@ -23,7 +23,7 @@
   {
    "hidden": 0,
    "label": "Reports",
-   "links": "[\n    {\n        \"dependencies\": [\n            \"Loan Repayment\"\n        ],\n        \"doctype\": \"Loan Repayment\",\n        \"incomplete_dependencies\": [\n            \"Loan Repayment\"\n        ],\n        \"is_query_report\": true,\n        \"label\": \"Loan Repayment and Closure\",\n        \"name\": \"Loan Repayment and Closure\",\n        \"route\": \"#query-report/Loan Repayment and Closure\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Loan Security Pledge\"\n        ],\n        \"doctype\": \"Loan Security Pledge\",\n        \"incomplete_dependencies\": [\n            \"Loan Security Pledge\"\n        ],\n        \"is_query_report\": true,\n        \"label\": \"Loan Security Status\",\n        \"name\": \"Loan Security Status\",\n        \"route\": \"#query-report/Loan Security Status\",\n        \"type\": \"report\"\n    }\n]"
+   "links": "[\n    {\n        \"doctype\": \"Loan Repayment\",\n        \"is_query_report\": true,\n        \"label\": \"Loan Repayment and Closure\",\n        \"name\": \"Loan Repayment and Closure\",\n        \"route\": \"#query-report/Loan Repayment and Closure\",\n        \"type\": \"report\"\n    },\n    {\n        \"doctype\": \"Loan Security Pledge\",\n        \"is_query_report\": true,\n        \"label\": \"Loan Security Status\",\n        \"name\": \"Loan Security Status\",\n        \"route\": \"#query-report/Loan Security Status\",\n        \"type\": \"report\"\n    }\n]"
   }
  ],
  "category": "Modules",
@@ -34,11 +34,10 @@
  "docstatus": 0,
  "doctype": "Desk Page",
  "extends_another_page": 0,
- "hide_custom": 0,
  "idx": 0,
  "is_standard": 1,
  "label": "Loan",
- "modified": "2020-05-28 13:37:42.017709",
+ "modified": "2020-06-07 19:42:14.947902",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan",
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index c9e36a8..d44088b 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -27,6 +27,7 @@
 
 	def on_cancel(self):
 		self.make_gl_entries(cancel=1)
+		self.ignore_linked_doctypes = ['GL Entry']
 
 	def set_missing_values(self):
 		if not self.disbursement_date:
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index 094b9c6..e6ceb55 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -31,6 +31,7 @@
 			self.update_is_accrued()
 
 		self.make_gl_entries(cancel=1)
+		self.ignore_linked_doctypes = ['GL Entry']
 
 	def update_is_accrued(self):
 		frappe.db.set_value('Repayment Schedule', self.repayment_schedule_name, 'is_accrued', 0)
@@ -176,21 +177,23 @@
 	return term_loans
 
 def make_loan_interest_accrual_entry(args):
-		loan_interest_accrual = frappe.new_doc("Loan Interest Accrual")
-		loan_interest_accrual.loan = args.loan
-		loan_interest_accrual.applicant_type = args.applicant_type
-		loan_interest_accrual.applicant = args.applicant
-		loan_interest_accrual.interest_income_account = args.interest_income_account
-		loan_interest_accrual.loan_account = args.loan_account
-		loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, 2)
-		loan_interest_accrual.interest_amount = flt(args.interest_amount, 2)
-		loan_interest_accrual.posting_date = args.posting_date or nowdate()
-		loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
-		loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name
-		loan_interest_accrual.payable_principal_amount = args.payable_principal
+	precision = cint(frappe.db.get_default("currency_precision")) or 2
 
-		loan_interest_accrual.save()
-		loan_interest_accrual.submit()
+	loan_interest_accrual = frappe.new_doc("Loan Interest Accrual")
+	loan_interest_accrual.loan = args.loan
+	loan_interest_accrual.applicant_type = args.applicant_type
+	loan_interest_accrual.applicant = args.applicant
+	loan_interest_accrual.interest_income_account = args.interest_income_account
+	loan_interest_accrual.loan_account = args.loan_account
+	loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision)
+	loan_interest_accrual.interest_amount = flt(args.interest_amount, precision)
+	loan_interest_accrual.posting_date = args.posting_date or nowdate()
+	loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
+	loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name
+	loan_interest_accrual.payable_principal_amount = args.payable_principal
+
+	loan_interest_accrual.save()
+	loan_interest_accrual.submit()
 
 
 def get_no_of_days_for_interest_accural(loan, posting_date):
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 2ab668a..c28994e 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -6,7 +6,7 @@
 import frappe, erpnext
 import json
 from frappe import _
-from frappe.utils import flt, getdate
+from frappe.utils import flt, getdate, cint
 from six import iteritems
 from frappe.model.document import Document
 from frappe.utils import date_diff, add_days, getdate, add_months, get_first_day, get_datetime
@@ -29,8 +29,11 @@
 	def on_cancel(self):
 		self.mark_as_unpaid()
 		self.make_gl_entries(cancel=1)
+		self.ignore_linked_doctypes = ['GL Entry']
 
 	def set_missing_values(self, amounts):
+		precision = cint(frappe.db.get_default("currency_precision")) or 2
+
 		if not self.posting_date:
 			self.posting_date = get_datetime()
 
@@ -38,24 +41,26 @@
 			self.cost_center = erpnext.get_default_cost_center(self.company)
 
 		if not self.interest_payable:
-			self.interest_payable = flt(amounts['interest_amount'], 2)
+			self.interest_payable = flt(amounts['interest_amount'], precision)
 
 		if not self.penalty_amount:
-			self.penalty_amount = flt(amounts['penalty_amount'], 2)
+			self.penalty_amount = flt(amounts['penalty_amount'], precision)
 
 		if not self.pending_principal_amount:
-			self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2)
+			self.pending_principal_amount = flt(amounts['pending_principal_amount'], precision)
 
 		if not self.payable_principal_amount and self.is_term_loan:
-			self.payable_principal_amount = flt(amounts['payable_principal_amount'], 2)
+			self.payable_principal_amount = flt(amounts['payable_principal_amount'], precision)
 
 		if not self.payable_amount:
-			self.payable_amount = flt(amounts['payable_amount'], 2)
+			self.payable_amount = flt(amounts['payable_amount'], precision)
 
 		if amounts.get('due_date'):
 			self.due_date = amounts.get('due_date')
 
 	def validate_amount(self):
+		precision = cint(frappe.db.get_default("currency_precision")) or 2
+
 		if not self.amount_paid:
 			frappe.throw(_("Amount paid cannot be zero"))
 
@@ -63,11 +68,13 @@
 			msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
 			frappe.throw(msg)
 
-		if self.payment_type == "Loan Closure" and flt(self.amount_paid, 2) < flt(self.payable_amount, 2):
+		if self.payment_type == "Loan Closure" and flt(self.amount_paid, precision) < flt(self.payable_amount, precision):
 			msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount)
 			frappe.throw(msg)
 
 	def update_paid_amount(self):
+		precision = cint(frappe.db.get_default("currency_precision")) or 2
+
 		loan = frappe.get_doc("Loan", self.against_loan)
 
 		for payment in self.repayment_details:
@@ -75,9 +82,9 @@
 				SET paid_principal_amount = `paid_principal_amount` + %s,
 					paid_interest_amount = `paid_interest_amount` + %s
 				WHERE name = %s""",
-				(flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
+				(flt(payment.paid_principal_amount, precision), flt(payment.paid_interest_amount, precision), payment.loan_interest_accrual))
 
-		if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
+		if flt(loan.total_principal_paid + self.principal_amount_paid, precision) >= flt(loan.total_payment, precision):
 			if loan.is_secured_loan:
 				frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
 			else:
@@ -253,6 +260,7 @@
 # So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
 
 def get_amounts(amounts, against_loan, posting_date, payment_type):
+	precision = cint(frappe.db.get_default("currency_precision")) or 2
 
 	against_loan_doc = frappe.get_doc("Loan", against_loan)
 	loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
@@ -282,8 +290,8 @@
 		payable_principal_amount += entry.payable_principal_amount
 
 		pending_accrual_entries.setdefault(entry.name, {
-			'interest_amount': flt(entry.interest_amount),
-			'payable_principal_amount': flt(entry.payable_principal_amount)
+			'interest_amount': flt(entry.interest_amount, precision),
+			'payable_principal_amount': flt(entry.payable_principal_amount, precision)
 		})
 
 		if not final_due_date:
@@ -301,11 +309,11 @@
 		per_day_interest = (payable_principal_amount * (loan_type_details.rate_of_interest / 100))/365
 		total_pending_interest += (pending_days * per_day_interest)
 
-	amounts["pending_principal_amount"] = pending_principal_amount
-	amounts["payable_principal_amount"] = payable_principal_amount
-	amounts["interest_amount"] = total_pending_interest
-	amounts["penalty_amount"] = penalty_amount
-	amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount
+	amounts["pending_principal_amount"] = flt(pending_principal_amount, precision)
+	amounts["payable_principal_amount"] = flt(payable_principal_amount, precision)
+	amounts["interest_amount"] = flt(total_pending_interest, precision)
+	amounts["penalty_amount"] = flt(penalty_amount, precision)
+	amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
 	amounts["pending_accrual_entries"] = pending_accrual_entries
 
 	if final_due_date:
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json
index 51c5cb9..1dd3710 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.json
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.json
@@ -41,6 +41,7 @@
    "options": "Company:company:default_currency"
   },
   {
+   "default": "0",
    "fieldname": "rate_of_interest",
    "fieldtype": "Percent",
    "label": "Rate of Interest (%) Yearly",
@@ -143,7 +144,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-04-15 00:24:43.259963",
+ "modified": "2020-06-07 18:55:59.346292",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Type",
diff --git a/erpnext/loan_management/report/loan_security_status/loan_security_status.py b/erpnext/loan_management/report/loan_security_status/loan_security_status.py
index ea6a2ee6..1951855 100644
--- a/erpnext/loan_management/report/loan_security_status/loan_security_status.py
+++ b/erpnext/loan_management/report/loan_security_status/loan_security_status.py
@@ -76,7 +76,8 @@
 			"fieldtype": "Link",
 			"fieldname": "currency",
 			"options": "Currency",
-			"width": 50
+			"width": 50,
+			"hidden": 1
 		}
 	]
 
@@ -84,17 +85,13 @@
 
 def get_data(filters):
 
-	loan_security_price_map = frappe._dict(frappe.get_all("Loan Security",
-		fields=["name", "loan_security_price"], as_list=1
-	))
-
 	data = []
 	conditions = get_conditions(filters)
 
 	loan_security_pledges = frappe.db.sql("""
 		SELECT
 			p.name, p.applicant, p.loan, p.status, p.pledge_time,
-			c.loan_security, c.qty
+			c.loan_security, c.qty, c.loan_security_price, c.amount
 		FROM
 			`tabLoan Security Pledge` p, `tabPledge` c
 		WHERE
@@ -115,8 +112,8 @@
 		row["pledge_time"] = pledge.pledge_time
 		row["loan_security"] = pledge.loan_security
 		row["qty"] = pledge.qty
-		row["loan_security_price"] = loan_security_price_map.get(pledge.loan_security)
-		row["loan_security_value"] = row["loan_security_price"] * pledge.qty
+		row["loan_security_price"] = pledge.loan_security_price
+		row["loan_security_value"] = pledge.amount
 		row["currency"] = default_currency
 
 		data.append(row)
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 3253a49..2543eec 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -112,13 +112,14 @@
 		if self.routing:
 			self.set("operations", [])
 			for d in frappe.get_all("BOM Operation", fields = ["*"],
-				filters = {'parenttype': 'Routing', 'parent': self.routing}):
+				filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="idx"):
 				child = self.append('operations', {
 					"operation": d.operation,
 					"workstation": d.workstation,
 					"description": d.description,
 					"time_in_mins": d.time_in_mins,
-					"batch_size": d.batch_size
+					"batch_size": d.batch_size,
+					"idx": d.idx
 				})
 				child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
 
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index df19995..4b93242 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -64,9 +64,21 @@
 				}, order_by="creation desc")
 	return frappe.get_doc("Member", members[0]['name'])
 
+def verify_signature(data):
+	signature = frappe.request.headers.get('X-Razorpay-Signature')
+
+	settings = frappe.get_doc("Membership Settings")
+	key = settings.get_webhook_secret()
+
+	controller = frappe.get_doc("Razorpay Settings")
+
+	controller.verify_signature(data, signature, key)
+
+
 @frappe.whitelist(allow_guest=True)
 def trigger_razorpay_subscription(*args, **kwargs):
 	data = frappe.request.get_data()
+	verify_signature(data)
 
 	if isinstance(data, six.string_types):
 		data = json.loads(data)
@@ -113,7 +125,6 @@
 	return True
 
 
-
 def notify_failure(log):
 	try:
 		content = """Dear System Manager,
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
index c01a0b2..8c0e3a4 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.js
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
@@ -1,8 +1,30 @@
 // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
-frappe.ui.form.on('Membership Settings', {
+frappe.ui.form.on("Membership Settings", {
 	refresh: function(frm) {
+		if (frm.doc.webhook_secret) {
+			frm.add_custom_button(__("Revoke <Key></Key>"), () => {
+				frm.call("revoke_key").then(() => {
+					frm.refresh();
+				})
+			});
+		}
+		frm.trigger("add_generate_button");
+	},
 
-	}
+	add_generate_button: function(frm) {
+		let label;
+
+		if (frm.doc.webhook_secret) {
+			label = __("Regenerate Webhook Secret");
+		} else {
+			label = __("Generate Webhook Secret");
+		}
+		frm.add_custom_button(label, () => {
+			frm.call("generate_webhook_key").then(() => {
+				frm.refresh();
+			});
+		});
+	},
 });
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
index 56b8eac..52b9d01 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
@@ -8,7 +8,8 @@
   "enable_razorpay",
   "razorpay_settings_section",
   "billing_cycle",
-  "billing_frequency"
+  "billing_frequency",
+  "webhook_secret"
  ],
  "fields": [
   {
@@ -34,11 +35,17 @@
    "fieldname": "billing_frequency",
    "fieldtype": "Int",
    "label": "Billing Frequency"
+  },
+  {
+   "fieldname": "webhook_secret",
+   "fieldtype": "Password",
+   "label": "Webhook Secret",
+   "read_only": 1
   }
  ],
  "issingle": 1,
  "links": [],
- "modified": "2020-04-07 18:42:51.496807",
+ "modified": "2020-05-22 12:38:27.103759",
  "modified_by": "Administrator",
  "module": "Non Profit",
  "name": "Membership Settings",
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.py b/erpnext/non_profit/doctype/membership_settings/membership_settings.py
index 2b8e37f..f3b2eee 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.py
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.py
@@ -4,11 +4,27 @@
 
 from __future__ import unicode_literals
 import frappe
+from frappe import _
 from frappe.integrations.utils import get_payment_gateway_controller
 from frappe.model.document import Document
 
 class MembershipSettings(Document):
-	pass
+	def generate_webhook_key(self):
+		key = frappe.generate_hash(length=20)
+		self.webhook_secret = key
+		self.save()
+
+		frappe.msgprint(
+			_("Here is your webhook secret, this will be shown to you only once.") + "<br><br>" + key,
+			_("Webhook Secret")
+		);
+
+	def revoke_key(self):
+		self.webhook_secret = None;
+		self.save()
+
+	def get_webhook_secret(self):
+		return self.get_password(fieldname="webhook_secret", raise_exception=False)
 
 @frappe.whitelist()
 def get_plans_for_membership(*args, **kwargs):
diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
index 6b9567c..31a7545 100644
--- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
@@ -64,7 +64,8 @@
 				"buying_price_list": self.default_buying_price_list
 			}
 
-			if not invoices_args.get("invoice_no", ''): return
+			if not invoices_args.get("bill_no", ''):
+				frappe.throw(_("Numero has not set in the XML file"))
 
 			supp_dict = get_supplier_details(file_content)
 			invoices_args["destination_code"] = get_destination_code_from_file(file_content)
diff --git a/erpnext/selling/desk_page/selling/selling.json b/erpnext/selling/desk_page/selling/selling.json
index c32a7c8..9ec6343 100644
--- a/erpnext/selling/desk_page/selling/selling.json
+++ b/erpnext/selling/desk_page/selling/selling.json
@@ -29,7 +29,7 @@
  "category": "Modules",
  "charts": [
   {
-   "chart_name": "Income",
+   "chart_name": "Incoming Bills (Purchase Invoice)",
    "label": "Income"
   }
  ],
@@ -43,7 +43,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Selling",
- "modified": "2020-05-28 13:46:08.314240",
+ "modified": "2020-06-03 13:23:24.861706",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling",
diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py
index 71b998f..2f75bbd 100644
--- a/erpnext/stock/doctype/item_attribute/item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/item_attribute.py
@@ -34,7 +34,7 @@
 			if self.numeric_values:
 				validate_is_incremental(self, self.name, item.value, item.name)
 			else:
-				validate_item_attribute_value(attributes_list, self.name, item.value, item.name)
+				validate_item_attribute_value(attributes_list, self.name, item.value, item.name, from_variant=False)
 
 	def validate_numeric(self):
 		if self.numeric_values:
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 3562181..3a8deb6 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -18,7 +18,7 @@
 
 		// formatter for material request item
 		frm.set_indicator_formatter('item_code',
-			function(doc) { return (doc.qty<=doc.ordered_qty) ? "green" : "orange"; });
+			function(doc) { return (doc.stock_qty<=doc.ordered_qty) ? "green" : "orange"; });
 
 		frm.set_query("item_code", "items", function() {
 			return {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index e9568ee..50c18f6 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -25,7 +25,7 @@
 
 		frm.custom_make_buttons = {
 			'Stock Entry': 'Return',
-			'Purchase Invoice': 'Invoice'
+			'Purchase Invoice': 'Purchase Invoice'
 		};
 
 		frm.set_query("expense_account", "items", function() {