Merge pull request #22219 from Anurag810/quality_procedure_fix

fix: Child is shown in Parent process if added from tree view
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index d36b115..8f67858 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -11,4 +11,4 @@
     - 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
+        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.com/repo/frappe%2Ffrappe_docker/requests
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 7145fea..13f5cb0 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -1376,6 +1376,12 @@
    "read": 1,
    "role": "Purchase Manager",
    "write": 1
+  },
+  {
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "Accounts User"
   }
  ],
  "search_fields": "status, transaction_date, supplier,grand_total",
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 1712369..813286f 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -118,7 +118,7 @@
 		self.assertEqual(po.get("items")[0].amount, 1400)
 		self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
 
-	
+
 	def test_add_new_item_in_update_child_qty_rate(self):
 		po = create_purchase_order(do_not_save=1)
 		po.items[0].qty = 4
@@ -144,7 +144,7 @@
 		self.assertEquals(len(po.get('items')), 2)
 		self.assertEqual(po.status, 'To Receive and Bill')
 
-	
+
 	def test_remove_item_in_update_child_qty_rate(self):
 		po = create_purchase_order(do_not_save=1)
 		po.items[0].qty = 4
@@ -185,6 +185,23 @@
 		self.assertEquals(len(po.get('items')), 1)
 		self.assertEqual(po.status, 'To Receive and Bill')
 
+	def test_update_child_qty_rate_perm(self):
+		po = create_purchase_order(item_code= "_Test Item", qty=4)
+
+		user = 'test@example.com'
+		test_user = frappe.get_doc('User', user)
+		test_user.add_roles("Accounts User")
+		frappe.set_user(user)
+
+		# update qty
+		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.items[0].name}])
+		self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
+
+		# add new item
+		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
+		self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
+		frappe.set_user("Administrator")
+
 	def test_update_qty(self):
 		po = create_purchase_order()
 
@@ -689,7 +706,7 @@
 		po.save()
 		self.assertEqual(po.schedule_date, add_days(nowdate(), 2))
 
-	
+
 	def test_po_optional_blanket_order(self):
 		"""
 			Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1.
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index eecb143..f54b593 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1137,8 +1137,8 @@
 	child_item.item_name = item.item_name
 	child_item.description = item.description
 	child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
+	child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
 	child_item.uom = item.stock_uom
-	child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
 	child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
 	if not child_item.warehouse:
 		frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
@@ -1157,8 +1157,8 @@
 	child_item.item_name = item.item_name
 	child_item.description = item.description
 	child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date
+	child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
 	child_item.uom = item.stock_uom
-	child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
 	child_item.base_rate = 1 # Initiallize value will update in parent validation
 	child_item.base_amount = 1 # Initiallize value will update in parent validation
 	return child_item
@@ -1190,6 +1190,26 @@
 
 @frappe.whitelist()
 def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
+	def check_permissions(doc, perm_type='create'):
+		try:
+			doc.check_permission(perm_type)
+		except:
+			action = "add" if perm_type == 'create' else "update"
+			frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions"))
+
+	def get_new_child_item(item_row):
+		if parent_doctype == "Sales Order":
+			return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
+		if parent_doctype == "Purchase Order":
+			return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
+
+	def validate_quantity(child_item, d):
+		if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
+			frappe.throw(_("Cannot set quantity less than delivered quantity"))
+
+		if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
+			frappe.throw(_("Cannot set quantity less than received quantity"))
+
 	data = json.loads(trans_items)
 
 	sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
@@ -1201,20 +1221,29 @@
 		new_child_flag = False
 		if not d.get("docname"):
 			new_child_flag = True
-			if parent_doctype == "Sales Order":
-				child_item  = set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d)
-			if parent_doctype == "Purchase Order":
-				child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d)
+			check_permissions(parent, 'create')
+			child_item = get_new_child_item(d)
 		else:
+			check_permissions(parent, 'write')
 			child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
-			if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")):
+
+			prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
+			prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
+			prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
+
+			if parent_doctype == 'Sales Order':
+				prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
+			elif parent_doctype == 'Purchase Order':
+				prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
+
+			rate_unchanged = prev_rate == new_rate
+			qty_unchanged = prev_qty == new_qty
+			conversion_factor_unchanged = prev_con_fac == new_con_fac
+			date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
+			if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged:
 				continue
 
-		if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
-			frappe.throw(_("Cannot set quantity less than delivered quantity"))
-
-		if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
-			frappe.throw(_("Cannot set quantity less than received quantity"))
+		validate_quantity(child_item, d)
 
 		child_item.qty = flt(d.get("qty"))
 		precision = child_item.precision("rate") or 2
@@ -1225,6 +1254,18 @@
 		else:
 			child_item.rate = flt(d.get("rate"))
 
+		if d.get("conversion_factor"):
+			if child_item.stock_uom == child_item.uom:
+				child_item.conversion_factor = 1
+			else:
+				child_item.conversion_factor = flt(d.get('conversion_factor'))
+
+		if d.get("delivery_date") and parent_doctype == 'Sales Order':
+			child_item.delivery_date = d.get('delivery_date')
+
+		if d.get("schedule_date") and parent_doctype == 'Purchase Order':
+			child_item.schedule_date = d.get('schedule_date')
+
 		if flt(child_item.price_list_rate):
 			if flt(child_item.rate) > flt(child_item.price_list_rate):
 				#  if rate is greater than price_list_rate, set margin
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 50b17ab..1f95e00 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -102,7 +102,7 @@
 			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(
+			msg = _("The value {0} is already assigned to an existing 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"))
 
diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
index 1b0c9f6..6dedaa8 100644
--- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
+++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
@@ -73,10 +73,16 @@
 
 	if customer_exists:
 		frappe.rename_doc("Customer", old_name, customer_name)
-		billing_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Billing"})
-		shipping_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Shipping"})
-		rename_address(billing_address, customer)
-		rename_address(shipping_address, customer)
+		for address_type in ("Billing", "Shipping",):
+			try:
+				address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": address_type})
+				rename_address(address, customer)
+			except (
+				frappe.DoesNotExistError,
+				frappe.DuplicateEntryError,
+				frappe.ValidationError,
+			):
+				pass
 	else:
 		create_address(raw_billing_data, customer, "Billing")
 		create_address(raw_shipping_data, customer, "Shipping")
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
index 64c3b2d..25ffd28 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
@@ -8,6 +8,7 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils import get_request_session
+from requests.exceptions import HTTPError
 from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
 from erpnext.erpnext_integrations.utils import get_webhook_address
 from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log
@@ -29,19 +30,24 @@
 		webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
 		# url = get_shopify_url('admin/webhooks.json', self)
 		created_webhooks = [d.method for d in self.webhooks]
-		url = get_shopify_url('admin/api/2019-04/webhooks.json', self)
+		url = get_shopify_url('admin/api/2020-04/webhooks.json', self)
 		for method in webhooks:
 			session = get_request_session()
 			try:
-				d = session.post(url, data=json.dumps({
+				res = session.post(url, data=json.dumps({
 					"webhook": {
 						"topic": method,
 						"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'),
 						"format": "json"
 						}
 					}), headers=get_header(self))
-				d.raise_for_status()
-				self.update_webhook_table(method, d.json())
+				res.raise_for_status()
+				self.update_webhook_table(method, res.json())
+
+			except HTTPError as e:
+				error_message = res.json().get('errors', e)
+				make_shopify_log(status="Warning", exception=error_message, rollback=True)
+
 			except Exception as e:
 				make_shopify_log(status="Warning", exception=e, rollback=True)
 
@@ -50,13 +56,18 @@
 		deleted_webhooks = []
 
 		for d in self.webhooks:
-			url = get_shopify_url('admin/api/2019-04/webhooks/{0}.json'.format(d.webhook_id), self)
+			url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self)
 			try:
 				res = session.delete(url, headers=get_header(self))
 				res.raise_for_status()
 				deleted_webhooks.append(d)
+
+			except HTTPError as e:
+				error_message = res.json().get('errors', e)
+				make_shopify_log(status="Warning", exception=error_message, rollback=True)
+
 			except Exception as e:
-				frappe.log_error(message=frappe.get_traceback(), title=e)
+				frappe.log_error(message=e, title='Shopify Webhooks Issue')
 
 		for d in deleted_webhooks:
 			self.remove(d)
@@ -125,4 +136,3 @@
 	}
 
 	create_custom_fields(custom_fields)
-
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
index bde1011..f9f0bb3 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
@@ -8,7 +8,7 @@
 shopify_variants_attr_list = ["option1", "option2", "option3"]
 
 def sync_item_from_shopify(shopify_settings, item):
-	url = get_shopify_url("admin/api/2019-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
+	url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
 	session = get_request_session()
 
 	try:
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
index 0c13b6a..3dc7c1e 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
@@ -70,6 +70,7 @@
 	if frappe.db.get_value('Item', item, 'is_stock_item'):
 		frappe.throw(_(msg))
 
+@frappe.whitelist()
 def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
 	fields = ['name', 'practitioner_name', 'mobile_phone']
 
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 9d7cdc2..742cc8e 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -238,6 +238,9 @@
 		"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",
 		"on_trash": "erpnext.regional.check_deletion_permission"
 	},
+	"Purchase Invoice": {
+		"on_submit": "erpnext.regional.india.utils.make_reverse_charge_entries"
+	},
 	"Payment Entry": {
 		"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"],
 		"on_trash": "erpnext.regional.check_deletion_permission"
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 279c453..d74dc24 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -699,3 +699,4 @@
 erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
 erpnext.patches.v13_0.update_sla_enhancements
 erpnext.patches.v12_0.update_address_template_for_india
+erpnext.patches.v13_0.update_issue_metrics
diff --git a/erpnext/patches/v13_0/update_issue_metrics.py b/erpnext/patches/v13_0/update_issue_metrics.py
new file mode 100644
index 0000000..6d76235
--- /dev/null
+++ b/erpnext/patches/v13_0/update_issue_metrics.py
@@ -0,0 +1,33 @@
+from __future__ import unicode_literals
+import frappe
+
+from frappe.core.doctype.communication.communication import set_avg_response_time
+from erpnext.support.doctype.issue.issue import set_resolution_time, set_user_resolution_time
+
+def execute():
+	if frappe.db.exists('DocType', 'Issue'):
+		frappe.reload_doctype('Issue')
+
+		count = 0
+		for parent in frappe.get_all('Issue', order_by='creation desc'):
+			parent_doc = frappe.get_doc('Issue', parent.name)
+
+			communication = frappe.get_all('Communication', filters={
+				'reference_doctype': 'Issue',
+				'reference_name': parent.name,
+				'communication_medium': 'Email',
+				'sent_or_received': 'Sent'
+			}, order_by = 'creation asc', limit=1)
+
+			if communication:
+				communication_doc = frappe.get_doc('Communication', communication[0].name)
+				set_avg_response_time(parent_doc, communication_doc)
+
+			if parent_doc.status in ['Closed', 'Resolved']:
+				set_resolution_time(parent_doc)
+				set_user_resolution_time(parent_doc)
+
+			# commit after every 100 records
+			count += 1
+			if count % 100 == 0:
+				frappe.db.commit()
\ No newline at end of file
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 2cd79b5..bcab0d8 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -487,7 +487,14 @@
 			fieldtype: 'Date',
 			fieldname: frm.doc.doctype == 'Sales Order' ? "delivery_date" : "schedule_date",
 			in_list_view: 1,
-			label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date")
+			label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date"),
+			reqd: 1
+		})
+		fields.splice(3, 0, {
+			fieldtype: 'Float',
+			fieldname: "conversion_factor",
+			in_list_view: 1,
+			label: __("Conversion Factor")
 		})
 	}
 
@@ -536,6 +543,7 @@
 			"item_code": d.item_code,
 			"delivery_date": d.delivery_date,
 			"schedule_date": d.schedule_date,
+			"conversion_factor": d.conversion_factor,
 			"qty": d.qty,
 			"rate": d.rate,
 		});
diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss
index 735b417..617e916 100644
--- a/erpnext/public/scss/website.scss
+++ b/erpnext/public/scss/website.scss
@@ -81,4 +81,10 @@
 
 .place-order-container {
 	text-align: right;
+}
+
+.kb-card {
+	.card-body > .card-title {
+		line-height: 1.3;
+	}
 }
\ No newline at end of file
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
index 35f9cf6..888b2da 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
@@ -52,7 +52,7 @@
 			<td class="disabled"></td>
 			<td class="disabled"></td>
 		<tr>
-			<td>(d) {{__("Inward Supplies(liable to reverse charge")}}</td>
+			<td>(d) {{__("Inward Supplies(liable to reverse charge)")}}</td>
 			<td class="right">{{ flt(data.sup_details.isup_rev.txval, 2) }}</td>
 			<td class="right">{{ flt(data.sup_details.isup_rev.iamt, 2) }}</td>
 			<td class="right">{{ flt(data.sup_details.isup_rev.camt, 2) }}</td>
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index 9e7a023..619734f 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -158,7 +158,7 @@
 
 		self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"])
 		self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"])
-		self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Registered Regular"], reverse_charge="Y")
+		self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y")
 		self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2)
 		self.set_itc_details(itc_details)
 
@@ -192,31 +192,27 @@
 		for d in self.report_dict["itc_elg"]["itc_avl"]:
 
 			itc_type = itc_type_map.get(d["ty"])
-			gst_category = "Registered Regular"
+			gst_category = ["Registered Regular"]
 
 			if d["ty"] == 'ISRC':
 				reverse_charge = "Y"
+				itc_type = 'All Other ITC'
+				gst_category = ['Unregistered', 'Overseas']
 			else:
 				reverse_charge = "N"
 
 			for account_head in self.account_heads:
+				for category in gst_category:
+					for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
+						d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2)
 
-				d["iamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('igst_account')), {}).get("amount"), 2)
-				d["camt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cgst_account')), {}).get("amount"), 2)
-				d["samt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('sgst_account')), {}).get("amount"), 2)
-				d["csamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cess_account')), {}).get("amount"), 2)
-
-			net_itc["iamt"] += flt(d["iamt"], 2)
-			net_itc["camt"] += flt(d["camt"], 2)
-			net_itc["samt"] += flt(d["samt"], 2)
-			net_itc["csamt"] += flt(d["csamt"], 2)
+			for key in ['iamt', 'camt', 'samt', 'csamt']:
+				net_itc[key] += flt(d[key], 2)
 
 		for account_head in self.account_heads:
 			itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1]
-			itc_inelg["iamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("igst_account")), {}).get("amount"), 2)
-			itc_inelg["camt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cgst_account")), {}).get("amount"), 2)
-			itc_inelg["samt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("sgst_account")), {}).get("amount"), 2)
-			itc_inelg["csamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cess_account")), {}).get("amount"), 2)
+			for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
+				itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2)
 
 	def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"):
 
@@ -274,17 +270,16 @@
 			""" #nosec
 			.format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin"))))
 
-	def get_itc_details(self, reverse_charge='N'):
-
+	def get_itc_details(self):
 		itc_amount = frappe.db.sql("""
 			select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge
 			from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
-			where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
+			where s.docstatus = 1 and t.parent = s.name
 			and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
 			and s.company_gstin = %s
 			group by t.account_head, s.gst_category, s.eligibility_for_itc
 			""",
-			(reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+			(self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
 
 		itc_details = {}
 
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 3085a31..9fe29eb 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -9,6 +9,8 @@
 from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
 from erpnext.regional.india import number_state_mapping
 from six import string_types
+from erpnext.accounts.general_ledger import make_gl_entries
+from erpnext.accounts.utils import get_account_currency
 
 def validate_gstin_for_india(doc, method):
 	if hasattr(doc, 'gst_state') and doc.gst_state:
@@ -658,5 +660,53 @@
 			elif val:
 				gst_accounts[val] = acc
 
-
 	return gst_accounts
+
+def make_reverse_charge_entries(doc, method):
+	country = frappe.get_cached_value('Company', doc.company, 'country')
+
+	if country != 'India':
+		return
+
+	if doc.reverse_charge == 'Y':
+		gl_entries = []
+		gst_accounts = get_gst_accounts(doc.company)
+		gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
+			+ gst_accounts.get('igst_account')
+
+		for tax in doc.get('taxes'):
+			if tax.category not in ("Total", "Valuation and Total"):
+				continue
+
+			if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
+				account_currency = get_account_currency(tax.account_head)
+
+				gl_entries.append(doc.get_gl_dict(
+					{
+						"account": tax.account_head,
+						"cost_center": tax.cost_center,
+						"posting_date": doc.posting_date,
+						"against": doc.supplier,
+						"credit": tax.base_tax_amount_after_discount_amount,
+						"credits_in_account_currency": tax.base_tax_amount_after_discount_amount \
+							if account_currency==doc.company_currency \
+							else tax.tax_amount_after_discount_amount
+					}, account_currency, item=tax)
+				)
+
+				gl_entries.append(doc.get_gl_dict(
+					{
+						"account": doc.credit_to if doc.doctype == 'Purchase Invoice' else doc.debit_to,
+						"cost_center": doc.cost_center,
+						"posting_date": doc.posting_date,
+						"party_type": 'Supplier',
+						"party": doc.supplier,
+						"against": tax.account_head,
+						"debit": tax.base_tax_amount_after_discount_amount,
+						"debit_in_account_currency": tax.base_tax_amount_after_discount_amount \
+							if account_currency==doc.company_currency \
+							else tax.tax_amount_after_discount_amount
+					}, account_currency, item=doc)
+				)
+
+		make_gl_entries(gl_entries)
\ No newline at end of file
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index ac3bc20..682dfed 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -339,6 +339,7 @@
 
 	return lp_details
 
+@frappe.whitelist()
 def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
 	from erpnext.controllers.queries import get_fields
 
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index b8b0d40..74e742f 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -400,6 +400,23 @@
 		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}])
 		self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
 
+	def test_update_child_qty_rate_perm(self):
+		so = make_sales_order(item_code= "_Test Item", qty=4)
+
+		user = 'test@example.com'
+		test_user = frappe.get_doc('User', user)
+		test_user.add_roles("Accounts User")
+		frappe.set_user(user)
+
+		# update qty
+		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}])
+		self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+
+		# add new item
+		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
+		self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+		frappe.set_user("Administrator")
+
 	def test_warehouse_user(self):
 		frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
 		frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index e593499..eff17f8 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -769,22 +769,23 @@
    "label": "Against Blanket Order"
   },
   {
-    "fieldname": "bom_no",
-    "fieldtype": "Link",
-    "label": "BOM No",
-    "no_copy": 1,
-    "options": "BOM",
-    "print_hide": 1
-   },
-   {
-    "fieldname": "manufacturing_section_section",
-    "fieldtype": "Section Break",
-    "label": "Manufacturing Section"
-   }
+   "fieldname": "bom_no",
+   "fieldtype": "Link",
+   "label": "BOM No",
+   "no_copy": 1,
+   "options": "BOM",
+   "print_hide": 1
+  },
+  {
+   "fieldname": "manufacturing_section_section",
+   "fieldtype": "Section Break",
+   "label": "Manufacturing Section"
+  }
  ],
  "idx": 1,
  "istable": 1,
- "modified": "2020-05-15 18:13:43.006493",
+ "links": [],
+ "modified": "2020-05-29 20:54:32.309460",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order Item",
diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
index e78d0ff..f15f63d 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
@@ -5,7 +5,7 @@
 import calendar
 import frappe
 from frappe import _
-from frappe.utils import cint, cstr
+from frappe.utils import cint, cstr, getdate
 
 def execute(filters=None):
 	common_columns = [
@@ -160,7 +160,7 @@
 	return columns, data, None, None, None, 1
 
 def get_customer_stats(filters, tree_view=False):
-	""" Calculates number of new and repeated customers. """
+	""" Calculates number of new and repeated customers and revenue. """
 	company_condition = ''
 	if filters.get('company'):
 		company_condition = ' and company=%(company)s'
@@ -174,14 +174,14 @@
 		filters, as_dict=1):
 
 		key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
+		new_or_repeat = 'new' if si.customer not in customers else 'repeat'
 		customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
 
-		if not si.customer in customers:
-			customers_in[key]['new'][0] += 1
-			customers_in[key]['new'][1] += si.base_grand_total
+		# if filters.from_date <= si.posting_date.strftime('%Y-%m-%d'):
+		if getdate(filters.from_date) <= getdate(si.posting_date):
+				customers_in[key][new_or_repeat][0] += 1
+				customers_in[key][new_or_repeat][1] += si.base_grand_total
+		if new_or_repeat == 'new':
 			customers.append(si.customer)
-		else:
-			customers_in[key]['repeat'][0] += 1
-			customers_in[key]['repeat'][1] += si.base_grand_total
 
 	return customers_in
diff --git a/erpnext/www/support/index.html b/erpnext/www/support/index.html
index 824f0bd..93da503 100644
--- a/erpnext/www/support/index.html
+++ b/erpnext/www/support/index.html
@@ -4,28 +4,31 @@
 <section class="section section-padding-top section-padding-bottom">
 	<div class='container'>
 		<div class="hero-content">
-			<h1 class="h1">{{ _(greeting_title) or _("We're here to help") }}</h1>
-			<p class="hero-subtitle">{{ greeting_subtitle or _("Browse help topics.") }}</p>
+			<h1 class="hero-title">{{ greeting_title or _("We're here to help!") }}</h1>
+			{% if greeting_subtitle %}
+			<p class="hero-subtitle">{{ greeting_subtitle }}</p>
+			{% endif %}
 		</div>
 	</div>
 </section>
 
 {% if favorite_article_list %}
-<section class="section section-padding-top section-padding-bottom">
+<section class="section section-padding-top section-padding-bottom bg-light">
 	<div class='container'>
-		<h3>{{ _("Frequently Read Articles") }}</h3>
+		<h2>{{ _("Frequently Read Articles") }}</h2>
 		<div class="row">
 			{% for favorite_article in favorite_article_list %}
-				<div class="mt-4 col-12 col-sm-6 col-lg-4">
-					<div class="card card-md h-100">
-						<div class="card-body">
-							<h6 class="card-subtitle mb-2 text-uppercase small text-muted">{{ favorite_article['category'] }}</h6>
-							<h3 class="card-title">{{ favorite_article['title'] }}</h3>
-							<p class="card-text">{{ favorite_article['description'] }}</p>
-						</div>
-						<a href="{{ favorite_article['route'] }}" class="stretched-link"></a>
+			<div class="mt-4 col-12 col-sm-6 col-lg-4">
+				<div class="card card-md h-100 kb-card">
+					<div class="card-body">
+						<h6 class="card-subtitle mb-2 text-uppercase small text-muted">
+							{{ favorite_article['category'] }}</h6>
+						<h3 class="card-title">{{ favorite_article['title'] }}</h3>
+						<p class="card-text">{{ favorite_article['description'] }}</p>
 					</div>
+					<a href="{{ favorite_article['route'] }}" class="stretched-link"></a>
 				</div>
+			</div>
 			{% endfor %}
 		</div>
 	</div>
@@ -33,9 +36,9 @@
 {% endif %}
 
 {% if help_article_list %}
-<section class="section section-padding-top section-padding-bottom bg-light">
+<section class="section section-padding-top section-padding-bottom">
 	<div class='container'>
-		<h3>{{ _("Help Articles") }}</h3>
+		<h2>{{ _("Help Articles") }}</h2>
 		<div class="row">
 			{% for item in help_article_list %}
 			<div class="mt-5 col-12 col-sm-6 col-lg-4">
diff --git a/erpnext/www/support/index.py b/erpnext/www/support/index.py
index 58ca8f7..5d26743 100644
--- a/erpnext/www/support/index.py
+++ b/erpnext/www/support/index.py
@@ -49,8 +49,8 @@
 	favorite_article_list=[]
 	for article in favorite_articles:
 		description = frappe.utils.strip_html(article.content)
-		if len(description) > 175:
-			description = description[:172] + '...'
+		if len(description) > 120:
+			description = description[:120] + '...'
 		favorite_article_dict = {
 			'title': article.title,
 			'description': description,