Merge branch 'develop' into item_group_on_update_items
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json
index cf55d55..5858f10 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json
@@ -30,6 +30,7 @@
    "fieldtype": "Link",
    "label": "Reference Document Type",
    "options": "DocType",
+   "read_only_depends_on": "eval:!doc.__islocal",
    "reqd": 1
   },
   {
@@ -48,7 +49,7 @@
   }
  ],
  "links": [],
- "modified": "2020-03-22 20:34:39.805728",
+ "modified": "2021-02-08 16:37:53.936656",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounting Dimension",
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index ef0d3a3..dd26c4c 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -29,6 +29,16 @@
 		if exists and self.is_new():
 			frappe.throw("Document Type already used as a dimension")
 
+		if not self.is_new():
+			self.validate_document_type_change()
+
+	def validate_document_type_change(self):
+		doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type")
+		if doctype_before_save != self.document_type:
+			message = _("Cannot change Reference Document Type.")
+			message += _("Please create a new Accounting Dimension if required.")
+			frappe.throw(message)
+
 	def after_insert(self):
 		if frappe.flags.in_test:
 			make_dimension_in_accounting_doctypes(doc=self)
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json
index c0327ad..0f3fbc0 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json
@@ -108,7 +108,7 @@
  ],
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-12-16 15:27:23.659285",
+ "modified": "2021-02-03 12:04:58.678402",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounting Dimension Filter",
@@ -125,6 +125,30 @@
    "role": "System Manager",
    "share": 1,
    "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
+   "write": 1
   }
  ],
  "quick_entry": 1,
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 791b03a..f7a15c0 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -88,19 +88,19 @@
 		voucher_type = ('Sales Invoice'
 			if self.party_type == 'Customer' else "Purchase Invoice")
 
-		return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type,
-				(sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount,
+		return frappe.db.sql(""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type,
+				(sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount,
 				account_currency as currency
-			FROM `tab{doc}`, `tabGL Entry`
+			FROM `tab{doc}` doc, `tabGL Entry` gl
 			WHERE
-				(`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no)
-				and `tab{doc}`.{party_type_field} = %(party)s
-				and `tab{doc}`.is_return = 1 and `tab{doc}`.return_against IS NULL
-				and `tabGL Entry`.against_voucher_type = %(voucher_type)s
-				and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
-				and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
-				and `tabGL Entry`.is_cancelled = 0
-			GROUP BY `tab{doc}`.name
+				(doc.name = gl.against_voucher or doc.name = gl.voucher_no)
+				and doc.{party_type_field} = %(party)s
+				and doc.is_return = 1 and ifnull(doc.return_against, "") = ""
+				and gl.against_voucher_type = %(voucher_type)s
+				and doc.docstatus = 1 and gl.party = %(party)s
+				and gl.party_type = %(party_type)s and gl.account = %(account)s
+				and gl.is_cancelled = 0
+			GROUP BY doc.name
 			Having
 				amount > 0
 		""".format(
@@ -113,7 +113,7 @@
 				'party_type': self.party_type,
 				'voucher_type': voucher_type,
 				'account': self.receivable_payable_account
-			}, as_dict=1)
+			}, as_dict=1, debug=1)
 
 	def add_payment_entries(self, entries):
 		self.set('payments', [])
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 1b97050..53ac996 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -3,6 +3,7 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
+import json
 import frappe
 from frappe import _
 from frappe.model.document import Document
@@ -82,18 +83,37 @@
 			self.make_communication_entry()
 
 		elif self.payment_channel == "Phone":
-			controller = get_payment_gateway_controller(self.payment_gateway)
-			payment_record = dict(
-				reference_doctype="Payment Request",
-				reference_docname=self.name,
-				payment_reference=self.reference_name,
-				grand_total=self.grand_total,
-				sender=self.email_to,
-				currency=self.currency,
-				payment_gateway=self.payment_gateway
-			)
-			controller.validate_transaction_currency(self.currency)
-			controller.request_for_payment(**payment_record)
+			self.request_phone_payment()
+
+	def request_phone_payment(self):
+		controller = get_payment_gateway_controller(self.payment_gateway)
+		request_amount = self.get_request_amount()
+
+		payment_record = dict(
+			reference_doctype="Payment Request",
+			reference_docname=self.name,
+			payment_reference=self.reference_name,
+			request_amount=request_amount,
+			sender=self.email_to,
+			currency=self.currency,
+			payment_gateway=self.payment_gateway
+		)
+
+		controller.validate_transaction_currency(self.currency)
+		controller.request_for_payment(**payment_record)
+	
+	def get_request_amount(self):
+		data_of_completed_requests = frappe.get_all("Integration Request", filters={
+			'reference_doctype': self.doctype,
+			'reference_docname': self.name,
+			'status': 'Completed'
+		}, pluck="data")
+
+		if not data_of_completed_requests:
+			return self.grand_total
+
+		request_amounts = sum([json.loads(d).get('request_amount') for d in data_of_completed_requests])
+		return request_amounts
 
 	def on_cancel(self):
 		self.check_if_payment_entry_exists()
@@ -351,8 +371,8 @@
 		if args.order_type == "Shopping Cart" or args.mute_email:
 			pr.flags.mute_email = True
 
+		pr.insert(ignore_permissions=True)
 		if args.submit_doc:
-			pr.insert(ignore_permissions=True)
 			pr.submit()
 
 	if args.order_type == "Shopping Cart":
@@ -412,8 +432,8 @@
 
 def get_gateway_details(args):
 	"""return gateway and payment account of default payment gateway"""
-	if args.get("payment_gateway"):
-		return get_payment_gateway_account(args.get("payment_gateway"))
+	if args.get("payment_gateway_account"):
+		return get_payment_gateway_account(args.get("payment_gateway_account"))
 
 	if args.order_type == "Shopping Cart":
 		payment_gateway_account = frappe.get_doc("Shopping Cart Settings").payment_gateway_account
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index 8a10e2c..5eba62c 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -45,7 +45,8 @@
 
 	def test_payment_request_linkings(self):
 		so_inr = make_sales_order(currency="INR")
-		pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com")
+		pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com",
+			payment_gateway_account="_Test Gateway - INR")
 
 		self.assertEqual(pr.reference_doctype, "Sales Order")
 		self.assertEqual(pr.reference_name, so_inr.name)
@@ -54,7 +55,8 @@
 		conversion_rate = get_exchange_rate("USD", "INR")
 
 		si_usd = create_sales_invoice(currency="USD", conversion_rate=conversion_rate)
-		pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com")
+		pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
+			payment_gateway_account="_Test Gateway - USD")
 
 		self.assertEqual(pr.reference_doctype, "Sales Invoice")
 		self.assertEqual(pr.reference_name, si_usd.name)
@@ -68,7 +70,7 @@
 
 		so_inr = make_sales_order(currency="INR")
 		pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com",
-			mute_email=1, submit_doc=1, return_doc=1)
+			mute_email=1, payment_gateway_account="_Test Gateway - INR", submit_doc=1, return_doc=1)
 		pe = pr.set_as_paid()
 
 		so_inr = frappe.get_doc("Sales Order", so_inr.name)
@@ -79,7 +81,7 @@
 			currency="USD", conversion_rate=50)
 
 		pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
-			mute_email=1, payment_gateway="_Test Gateway - USD", submit_doc=1, return_doc=1)
+			mute_email=1, payment_gateway_account="_Test Gateway - USD", submit_doc=1, return_doc=1)
 
 		pe = pr.set_as_paid()
 
@@ -106,7 +108,7 @@
 			currency="USD", conversion_rate=50)
 
 		pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
-			mute_email=1, payment_gateway="_Test Gateway - USD", submit_doc=1, return_doc=1)
+			mute_email=1, payment_gateway_account="_Test Gateway - USD", submit_doc=1, return_doc=1)
 
 		pe = pr.create_payment_entry()
 		pr.load_from_db()
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
index 73367fd..9ea616f 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -21,7 +21,7 @@
 			return { filters: { 'status': 'Open', 'docstatus': 1 } };
 		});
 		
-		if (frm.doc.docstatus === 0) frm.set_value("period_end_date", frappe.datetime.now_datetime());
+		if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime());
 		if (frm.doc.docstatus === 1) set_html_data(frm);
 	},
 
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
index edf3d5a..f5224a2 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
@@ -20,11 +20,16 @@
 		self.validate_pos_invoices()
 	
 	def validate_pos_closing(self):
-		user = frappe.get_all("POS Closing Entry", 
-			filters = { "user": self.user, "docstatus": 1, "pos_profile": self.pos_profile },
-			or_filters = {
-				"period_start_date": ("between", [self.period_start_date, self.period_end_date]),
-				"period_end_date": ("between", [self.period_start_date, self.period_end_date])
+		user = frappe.db.sql("""
+			SELECT name FROM `tabPOS Closing Entry`
+			WHERE
+				user = %(user)s AND docstatus = 1 AND pos_profile = %(profile)s AND
+				(period_start_date between %(start)s and %(end)s OR period_end_date between %(start)s and %(end)s)
+			""", {
+				'user': self.user,
+				'profile': self.pos_profile,
+				'start': self.period_start_date,
+				'end': self.period_end_date
 			})
 
 		if user:
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index 07c8e44..493bd44 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -195,18 +195,43 @@
 	},
 
 	request_for_payment: function (frm) {
+		if (!frm.doc.contact_mobile) {
+			frappe.throw(__('Please enter mobile number first.'));
+		}
+		frm.dirty();
 		frm.save().then(() => {
-			frappe.dom.freeze();
-			frappe.call({
-				method: 'create_payment_request',
-				doc: frm.doc,
-			})
+			frappe.dom.freeze(__('Waiting for payment...'));
+			frappe
+				.call({
+					method: 'create_payment_request',
+					doc: frm.doc
+				})
 				.fail(() => {
 					frappe.dom.unfreeze();
-					frappe.msgprint('Payment request failed');
+					frappe.msgprint(__('Payment request failed'));
 				})
-				.then(() => {
-					frappe.msgprint('Payment request sent successfully');
+				.then(({ message }) => {
+					const payment_request_name = message.name;
+					setTimeout(() => {
+						frappe.db.get_value('Payment Request', payment_request_name, ['status', 'grand_total']).then(({ message }) => {
+							if (message.status != 'Paid') {
+								frappe.dom.unfreeze();
+								frappe.msgprint({
+									message: __('Payment Request took too long to respond. Please try requesting for payment again.'),
+									title: __('Request Timeout')
+								});
+							} else if (frappe.dom.freeze_count != 0) {
+								frappe.dom.unfreeze();
+								cur_frm.reload_doc();
+								cur_pos.payment.events.submit_invoice();
+
+								frappe.show_alert({
+									message: __("Payment of {0} received successfully.", [format_currency(message.grand_total, frm.doc.currency, 0)]),
+									indicator: 'green'
+								});
+							}
+						});
+					}, 60000);
 				});
 		});
 	}
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 8d8babb..94573f9 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -317,13 +317,14 @@
 				)
 				customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
 				selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
+				if customer_currency != profile.get('currency'):
+					self.set('currency', customer_currency)
+
 			else:
 				selling_price_list = profile.get('selling_price_list')
 
 			if selling_price_list:
 				self.set('selling_price_list', selling_price_list)
-			if customer_currency != profile.get('currency'):
-				self.set('currency', customer_currency)
 
 			# set pos values in items
 			for item in self.get("items"):
@@ -383,22 +384,48 @@
 				if not self.contact_mobile:
 					frappe.throw(_("Please enter the phone number first"))
 
-				payment_gateway = frappe.db.get_value("Payment Gateway Account", {
-					"payment_account": pay.account,
-				})
-				record = {
-					"payment_gateway": payment_gateway,
-					"dt": "POS Invoice",
-					"dn": self.name,
-					"payment_request_type": "Inward",
-					"party_type": "Customer",
-					"party": self.customer,
-					"mode_of_payment": pay.mode_of_payment,
-					"recipient_id": self.contact_mobile,
-					"submit_doc": True
-				}
+				pay_req = self.get_existing_payment_request(pay)
+				if not pay_req:
+					pay_req = self.get_new_payment_request(pay)
+					pay_req.submit()
+				else:
+					pay_req.request_phone_payment()
 
-				return make_payment_request(**record)
+				return pay_req
+	
+	def get_new_payment_request(self, mop):
+		payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
+			"payment_account": mop.account,
+		}, ["name"])
+
+		args = {
+			"dt": "POS Invoice",
+			"dn": self.name,
+			"recipient_id": self.contact_mobile,
+			"mode_of_payment": mop.mode_of_payment,
+			"payment_gateway_account": payment_gateway_account,
+			"payment_request_type": "Inward",
+			"party_type": "Customer",
+			"party": self.customer,
+			"return_doc": True
+		}
+		return make_payment_request(**args)
+
+	def get_existing_payment_request(self, pay):
+		payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
+			"payment_account": pay.account,
+		}, ["name"])
+
+		args = {
+			'doctype': 'Payment Request',
+			'reference_doctype': 'POS Invoice',
+			'reference_name': self.name,
+			'payment_gateway_account': payment_gateway_account,
+			'email_to': self.contact_mobile
+		}
+		pr = frappe.db.exists(args)
+		if pr:
+			return frappe.get_doc('Payment Request', pr[0][0])
 
 @frappe.whitelist()
 def get_stock_availability(item_code, warehouse):
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index 58409cd..c88d679 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -212,8 +212,8 @@
 	invoice_by_customer = get_invoice_customer_map(invoices)
 
 	if len(invoices) >= 5 and closing_entry:
-		enqueue_job(create_merge_logs, invoice_by_customer, closing_entry)
 		closing_entry.set_status(update=True, status='Queued')
+		enqueue_job(create_merge_logs, invoice_by_customer, closing_entry)
 	else:
 		create_merge_logs(invoice_by_customer, closing_entry)
 
@@ -225,8 +225,8 @@
 	)
 
 	if len(merge_logs) >= 5:
-		enqueue_job(cancel_merge_logs, merge_logs, closing_entry)
 		closing_entry.set_status(update=True, status='Queued')
+		enqueue_job(cancel_merge_logs, merge_logs, closing_entry)
 	else:
 		cancel_merge_logs(merge_logs, closing_entry)
 
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index 998003a..928b373 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -52,8 +52,8 @@
 
 		row = {
 			'item_code': d.item_code,
-			'item_name': item_record.item_name,
-			'item_group': item_record.item_group,
+			'item_name': item_record.item_name if item_record else d.item_name,
+			'item_group': item_record.item_group if item_record else d.item_group,
 			'description': d.description,
 			'invoice': d.parent,
 			'posting_date': d.posting_date,
@@ -383,6 +383,7 @@
 			`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
 			`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
 			`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
+			`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
 			`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
 			`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
 			`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index db6484a..19c97b6 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1504,6 +1504,7 @@
 	parent.flags.ignore_validate_update_after_submit = True
 	parent.set_qty_as_per_stock_uom()
 	parent.calculate_taxes_and_totals()
+	parent.set_total_in_words()
 	if parent_doctype == "Sales Order":
 		make_packing_list(parent)
 		parent.set_gross_profit()
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 6abfe04..c61b67b 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -446,9 +446,13 @@
 		check_list, chk_dupl_itm = [], []
 		if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
 			return
+		if self.doctype == "Sales Invoice" and self.is_consolidated:
+			return
+		if self.doctype == "POS Invoice":
+			return
 
 		for d in self.get('items'):
-			if self.doctype in ["POS Invoice","Sales Invoice"]:
+			if self.doctype == "Sales Invoice":
 				stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
 				non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note]
 			elif self.doctype == "Delivery Note":
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 4b5e347..2ae9dc7 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -313,7 +313,7 @@
 		return serialized_items
 
 	def validate_warehouse(self):
-		from erpnext.stock.utils import validate_warehouse_company
+		from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse
 
 		warehouses = list(set([d.warehouse for d in
 			self.get("items") if getattr(d, "warehouse", None)]))
@@ -329,6 +329,7 @@
 		warehouses.extend(from_warehouse)
 
 		for w in warehouses:
+			validate_disabled_warehouse(w)
 			validate_warehouse_company(w, self.company)
 
 	def update_billing_percentage(self, update_modified=True):
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 938cbfd..d1d0968 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -352,7 +352,7 @@
 	leads = frappe.get_all('Lead', or_filters={
 		'phone': ['like', '%{}'.format(number)],
 		'mobile_no': ['like', '%{}'.format(number)]
-	}, limit=1)
+	}, limit=1, order_by="creation DESC")
 
 	lead = leads[0].name if leads else None
 
@@ -361,4 +361,4 @@
 def daily_open_lead():
 	leads = frappe.get_all("Lead", filters = [["contact_date", "Between", [nowdate(), nowdate()]]])
 	for lead in leads:
-		frappe.db.set_value("Lead", lead.name, "status", "Open")
\ No newline at end of file
+		frappe.db.set_value("Lead", lead.name, "status", "Open")
diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py
index 4ccd9bd..f244daf 100644
--- a/erpnext/crm/doctype/utils.py
+++ b/erpnext/crm/doctype/utils.py
@@ -78,7 +78,9 @@
 
 def strip_number(number):
 	if not number: return
-	# strip 0 from the start of the number for proper number comparisions
+	# strip + and 0 from the start of the number for proper number comparisions
+	# eg. +7888383332 should match with 7888383332
 	# eg. 07888383332 should match with 7888383332
+	number = number.lstrip('+')
 	number = number.lstrip('0')
 	return number
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py
index d33b0a7..554c6b0 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py
@@ -5,7 +5,7 @@
 
 class MpesaConnector():
 	def __init__(self, env="sandbox", app_key=None, app_secret=None, sandbox_url="https://sandbox.safaricom.co.ke",
-		live_url="https://safaricom.co.ke"):
+		live_url="https://api.safaricom.co.ke"):
 		"""Setup configuration for Mpesa connector and generate new access token."""
 		self.env = env
 		self.app_key = app_key
@@ -102,14 +102,14 @@
 			"BusinessShortCode": business_shortcode,
 			"Password": encoded.decode("utf-8"),
 			"Timestamp": time,
-			"TransactionType": "CustomerPayBillOnline",
 			"Amount": amount,
 			"PartyA": int(phone_number),
-			"PartyB": business_shortcode,
+			"PartyB": reference_code,
 			"PhoneNumber": int(phone_number),
 			"CallBackURL": callback_url,
 			"AccountReference": reference_code,
-			"TransactionDesc": description
+			"TransactionDesc": description,
+			"TransactionType": "CustomerPayBillOnline" if self.env == "sandbox" else "CustomerBuyGoodsOnline"
 		}
 		headers = {'Authorization': 'Bearer {0}'.format(self.authentication_token), 'Content-Type': "application/json"}
 
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json
index fc7b310..407f826 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json
@@ -11,8 +11,10 @@
   "consumer_secret",
   "initiator_name",
   "till_number",
+  "transaction_limit",
   "sandbox",
   "column_break_4",
+  "business_shortcode",
   "online_passkey",
   "security_credential",
   "get_account_balance",
@@ -84,10 +86,24 @@
    "fieldname": "get_account_balance",
    "fieldtype": "Button",
    "label": "Get Account Balance"
+  },
+  {
+   "depends_on": "eval:(doc.sandbox==0)",
+   "fieldname": "business_shortcode",
+   "fieldtype": "Data",
+   "label": "Business Shortcode",
+   "mandatory_depends_on": "eval:(doc.sandbox==0)"
+  },
+  {
+   "default": "150000",
+   "fieldname": "transaction_limit",
+   "fieldtype": "Float",
+   "label": "Transaction Limit",
+   "non_negative": 1
   }
  ],
  "links": [],
- "modified": "2020-09-25 20:21:38.215494",
+ "modified": "2021-01-29 12:02:16.106942",
  "modified_by": "Administrator",
  "module": "ERPNext Integrations",
  "name": "Mpesa Settings",
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
index 1cad84d..b571802 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
@@ -33,13 +33,34 @@
 		create_mode_of_payment('Mpesa-' + self.payment_gateway_name, payment_type="Phone")
 
 	def request_for_payment(self, **kwargs):
-		if frappe.flags.in_test:
-			from erpnext.erpnext_integrations.doctype.mpesa_settings.test_mpesa_settings import get_payment_request_response_payload
-			response = frappe._dict(get_payment_request_response_payload())
-		else:
-			response = frappe._dict(generate_stk_push(**kwargs))
+		args = frappe._dict(kwargs)
+		request_amounts = self.split_request_amount_according_to_transaction_limit(args)
 
-		self.handle_api_response("CheckoutRequestID", kwargs, response)
+		for i, amount in enumerate(request_amounts):
+			args.request_amount = amount
+			if frappe.flags.in_test:
+				from erpnext.erpnext_integrations.doctype.mpesa_settings.test_mpesa_settings import get_payment_request_response_payload
+				response = frappe._dict(get_payment_request_response_payload(amount))
+			else:
+				response = frappe._dict(generate_stk_push(**args))
+
+			self.handle_api_response("CheckoutRequestID", args, response)
+
+	def split_request_amount_according_to_transaction_limit(self, args):
+		request_amount = args.request_amount
+		if request_amount > self.transaction_limit:
+			# make multiple requests
+			request_amounts = []
+			requests_to_be_made = frappe.utils.ceil(request_amount / self.transaction_limit) # 480/150 = ceil(3.2) = 4
+			for i in range(requests_to_be_made):
+				amount = self.transaction_limit
+				if i == requests_to_be_made - 1:
+					amount = request_amount - (self.transaction_limit * i) # for 4th request, 480 - (150 * 3) = 30
+				request_amounts.append(amount)
+		else:
+			request_amounts = [request_amount]
+		
+		return request_amounts
 
 	def get_account_balance_info(self):
 		payload = dict(
@@ -67,7 +88,8 @@
 			req_name = getattr(response, global_id)
 			error = None
 
-		create_request_log(request_dict, "Host", "Mpesa", req_name, error)
+		if not frappe.db.exists('Integration Request', req_name):
+			create_request_log(request_dict, "Host", "Mpesa", req_name, error)
 
 		if error:
 			frappe.throw(_(getattr(response, "errorMessage")), title=_("Transaction Error"))
@@ -80,6 +102,8 @@
 
 		mpesa_settings = frappe.get_doc("Mpesa Settings", args.payment_gateway[6:])
 		env = "production" if not mpesa_settings.sandbox else "sandbox"
+		# for sandbox, business shortcode is same as till number
+		business_shortcode = mpesa_settings.business_shortcode if env == "production" else mpesa_settings.till_number
 
 		connector = MpesaConnector(env=env,
 			app_key=mpesa_settings.consumer_key,
@@ -87,10 +111,12 @@
 
 		mobile_number = sanitize_mobile_number(args.sender)
 
-		response = connector.stk_push(business_shortcode=mpesa_settings.till_number,
-			passcode=mpesa_settings.get_password("online_passkey"), amount=args.grand_total,
+		response = connector.stk_push(
+			business_shortcode=business_shortcode, amount=args.request_amount,
+			passcode=mpesa_settings.get_password("online_passkey"),
 			callback_url=callback_url, reference_code=mpesa_settings.till_number,
-			phone_number=mobile_number, description="POS Payment")
+			phone_number=mobile_number, description="POS Payment"
+		)
 
 		return response
 
@@ -108,29 +134,72 @@
 	transaction_response = frappe._dict(kwargs["Body"]["stkCallback"])
 
 	checkout_id = getattr(transaction_response, "CheckoutRequestID", "")
-	request = frappe.get_doc("Integration Request", checkout_id)
-	transaction_data = frappe._dict(loads(request.data))
+	integration_request = frappe.get_doc("Integration Request", checkout_id)
+	transaction_data = frappe._dict(loads(integration_request.data))
+	total_paid = 0 # for multiple integration request made against a pos invoice
+	success = False # for reporting successfull callback to point of sale ui
 
 	if transaction_response['ResultCode'] == 0:
-		if request.reference_doctype and request.reference_docname:
+		if integration_request.reference_doctype and integration_request.reference_docname:
 			try:
-				doc = frappe.get_doc(request.reference_doctype,
-					request.reference_docname)
-				doc.run_method("on_payment_authorized", 'Completed')
-
 				item_response = transaction_response["CallbackMetadata"]["Item"]
+				amount = fetch_param_value(item_response, "Amount", "Name")
 				mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name")
-				frappe.db.set_value("POS Invoice", doc.reference_name, "mpesa_receipt_number", mpesa_receipt)
-				request.handle_success(transaction_response)
+				pr = frappe.get_doc(integration_request.reference_doctype, integration_request.reference_docname)
+
+				mpesa_receipts, completed_payments = get_completed_integration_requests_info(
+					integration_request.reference_doctype,
+					integration_request.reference_docname,
+					checkout_id
+				)
+
+				total_paid = amount + sum(completed_payments)
+				mpesa_receipts = ', '.join(mpesa_receipts + [mpesa_receipt])
+
+				if total_paid >= pr.grand_total:
+					pr.run_method("on_payment_authorized", 'Completed')
+					success = True
+
+				frappe.db.set_value("POS Invoice", pr.reference_name, "mpesa_receipt_number", mpesa_receipts)
+				integration_request.handle_success(transaction_response)
 			except Exception:
-				request.handle_failure(transaction_response)
+				integration_request.handle_failure(transaction_response)
 				frappe.log_error(frappe.get_traceback())
 
 	else:
-		request.handle_failure(transaction_response)
+		integration_request.handle_failure(transaction_response)
 
-	frappe.publish_realtime('process_phone_payment', doctype="POS Invoice",
-		docname=transaction_data.payment_reference, user=request.owner, message=transaction_response)
+	frappe.publish_realtime(
+		event='process_phone_payment',
+		doctype="POS Invoice",
+		docname=transaction_data.payment_reference,
+		user=integration_request.owner,
+		message={
+			'amount': total_paid,
+			'success': success,
+			'failure_message': transaction_response["ResultDesc"] if transaction_response['ResultCode'] != 0 else ''
+		},
+	)
+
+def get_completed_integration_requests_info(reference_doctype, reference_docname, checkout_id):
+	output_of_other_completed_requests = frappe.get_all("Integration Request", filters={
+		'name': ['!=', checkout_id],
+		'reference_doctype': reference_doctype,
+		'reference_docname': reference_docname,
+		'status': 'Completed'
+	}, pluck="output")
+
+	mpesa_receipts, completed_payments = [], []
+
+	for out in output_of_other_completed_requests:
+		out = frappe._dict(loads(out))
+		item_response = out["CallbackMetadata"]["Item"]
+		completed_amount = fetch_param_value(item_response, "Amount", "Name")
+		completed_mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name")
+		completed_payments.append(completed_amount)
+		mpesa_receipts.append(completed_mpesa_receipt)
+	
+	return mpesa_receipts, completed_payments
 
 def get_account_balance(request_payload):
 	"""Call account balance API to send the request to the Mpesa Servers."""
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
index 49f6d95..2948796 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
@@ -9,6 +9,10 @@
 from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
 
 class TestMpesaSettings(unittest.TestCase):
+	def tearDown(self):
+		frappe.db.sql('delete from `tabMpesa Settings`')
+		frappe.db.sql('delete from `tabIntegration Request` where integration_request_service = "Mpesa"')
+
 	def test_creation_of_payment_gateway(self):
 		create_mpesa_settings(payment_gateway_name="_Test")
 
@@ -40,6 +44,8 @@
 			}
 		}))
 
+		integration_request.delete()
+
 	def test_processing_of_callback_payload(self):
 		create_mpesa_settings(payment_gateway_name="Payment")
 		mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
@@ -56,10 +62,16 @@
 		# test payment request creation
 		self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
 
-		callback_response = get_payment_callback_payload()
+		# submitting payment request creates integration requests with random id
+		integration_req_ids = frappe.get_all("Integration Request", filters={
+			'reference_doctype': pr.doctype,
+			'reference_docname': pr.name,
+		}, pluck="name")
+
+		callback_response = get_payment_callback_payload(Amount=500, CheckoutRequestID=integration_req_ids[0])
 		verify_transaction(**callback_response)
 		# test creation of integration request
-		integration_request = frappe.get_doc("Integration Request", "ws_CO_061020201133231972")
+		integration_request = frappe.get_doc("Integration Request", integration_req_ids[0])
 
 		# test integration request creation and successful update of the status on receiving callback response
 		self.assertTrue(integration_request)
@@ -69,8 +81,120 @@
 		integration_request.reload()
 		self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
 		self.assertEquals(integration_request.status, "Completed")
+		
+		frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
+		integration_request.delete()
+		pr.reload()
+		pr.cancel()
+		pr.delete()
+		pos_invoice.delete()
+
+	def test_processing_of_multiple_callback_payload(self):
+		create_mpesa_settings(payment_gateway_name="Payment")
+		mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
+		frappe.db.set_value("Account", mpesa_account, "account_currency", "KES")
+		frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500")
+		frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES")
+
+		pos_invoice = create_pos_invoice(do_not_submit=1)
+		pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 1000})
+		pos_invoice.contact_mobile = "093456543894"
+		pos_invoice.currency = "KES"
+		pos_invoice.save()
+
+		pr = pos_invoice.create_payment_request()
+		# test payment request creation
+		self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
+
+		# submitting payment request creates integration requests with random id
+		integration_req_ids = frappe.get_all("Integration Request", filters={
+			'reference_doctype': pr.doctype,
+			'reference_docname': pr.name,
+		}, pluck="name")
+
+		# create random receipt nos and send it as response to callback handler
+		mpesa_receipt_numbers = [frappe.utils.random_string(5) for d in integration_req_ids]
+
+		integration_requests = []
+		for i in range(len(integration_req_ids)):
+			callback_response = get_payment_callback_payload(
+				Amount=500,
+				CheckoutRequestID=integration_req_ids[i],
+				MpesaReceiptNumber=mpesa_receipt_numbers[i]
+			)
+			# handle response manually
+			verify_transaction(**callback_response)
+			# test completion of integration request
+			integration_request = frappe.get_doc("Integration Request", integration_req_ids[i])
+			self.assertEquals(integration_request.status, "Completed")
+			integration_requests.append(integration_request)
+
+		# check receipt number once all the integration requests are completed
+		pos_invoice.reload()
+		self.assertEquals(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers))
 
 		frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
+		[d.delete() for d in integration_requests]
+		pr.reload()
+		pr.cancel()
+		pr.delete()
+		pos_invoice.delete()
+	
+	def test_processing_of_only_one_succes_callback_payload(self):
+		create_mpesa_settings(payment_gateway_name="Payment")
+		mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
+		frappe.db.set_value("Account", mpesa_account, "account_currency", "KES")
+		frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500")
+		frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES")
+
+		pos_invoice = create_pos_invoice(do_not_submit=1)
+		pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 1000})
+		pos_invoice.contact_mobile = "093456543894"
+		pos_invoice.currency = "KES"
+		pos_invoice.save()
+
+		pr = pos_invoice.create_payment_request()
+		# test payment request creation
+		self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
+
+		# submitting payment request creates integration requests with random id
+		integration_req_ids = frappe.get_all("Integration Request", filters={
+			'reference_doctype': pr.doctype,
+			'reference_docname': pr.name,
+		}, pluck="name")
+
+		# create random receipt nos and send it as response to callback handler
+		mpesa_receipt_numbers = [frappe.utils.random_string(5) for d in integration_req_ids]
+
+		callback_response = get_payment_callback_payload(
+			Amount=500,
+			CheckoutRequestID=integration_req_ids[0],
+			MpesaReceiptNumber=mpesa_receipt_numbers[0]
+		)
+		# handle response manually
+		verify_transaction(**callback_response)
+		# test completion of integration request
+		integration_request = frappe.get_doc("Integration Request", integration_req_ids[0])
+		self.assertEquals(integration_request.status, "Completed")
+
+		# now one request is completed
+		# second integration request fails
+		# now retrying payment request should make only one integration request again
+		pr = pos_invoice.create_payment_request()
+		new_integration_req_ids = frappe.get_all("Integration Request", filters={
+			'reference_doctype': pr.doctype,
+			'reference_docname': pr.name,
+			'name': ['not in', integration_req_ids]
+		}, pluck="name")
+
+		self.assertEquals(len(new_integration_req_ids), 1)
+
+		frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
+		frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'")
+		pr.reload()
+		pr.cancel()
+		pr.delete()
+		pos_invoice.delete()
 
 def create_mpesa_settings(payment_gateway_name="Express"):
 	if frappe.db.exists("Mpesa Settings", payment_gateway_name):
@@ -160,16 +284,19 @@
 	}
 		}
 
-def get_payment_request_response_payload():
+def get_payment_request_response_payload(Amount=500):
 	"""Response received after successfully calling the stk push process request API."""
+
+	CheckoutRequestID = frappe.utils.random_string(10)
+
 	return {
 		"MerchantRequestID": "8071-27184008-1",
-		"CheckoutRequestID": "ws_CO_061020201133231972",
+		"CheckoutRequestID": CheckoutRequestID,
 		"ResultCode": 0,
 		"ResultDesc": "The service request is processed successfully.",
 		"CallbackMetadata": {
 			"Item": [
-				{ "Name": "Amount", "Value": 500.0 },
+				{ "Name": "Amount", "Value": Amount },
 				{ "Name": "MpesaReceiptNumber", "Value": "LGR7OWQX0R" },
 				{ "Name": "TransactionDate", "Value": 20201006113336 },
 				{ "Name": "PhoneNumber", "Value": 254723575670 }
@@ -177,41 +304,26 @@
 		}
 	}
 
-
-def get_payment_callback_payload():
+def get_payment_callback_payload(Amount=500, CheckoutRequestID="ws_CO_061020201133231972", MpesaReceiptNumber="LGR7OWQX0R"):
 	"""Response received from the server as callback after calling the stkpush process request API."""
 	return {
 		"Body":{
-		"stkCallback":{
-			"MerchantRequestID":"19465-780693-1",
-			"CheckoutRequestID":"ws_CO_061020201133231972",
-			"ResultCode":0,
-			"ResultDesc":"The service request is processed successfully.",
-			"CallbackMetadata":{
-			"Item":[
-				{
-				"Name":"Amount",
-				"Value":500
-				},
-				{
-				"Name":"MpesaReceiptNumber",
-				"Value":"LGR7OWQX0R"
-				},
-				{
-				"Name":"Balance"
-				},
-				{
-				"Name":"TransactionDate",
-				"Value":20170727154800
-				},
-				{
-				"Name":"PhoneNumber",
-				"Value":254721566839
+			"stkCallback":{
+				"MerchantRequestID":"19465-780693-1",
+				"CheckoutRequestID":CheckoutRequestID,
+				"ResultCode":0,
+				"ResultDesc":"The service request is processed successfully.",
+				"CallbackMetadata":{
+					"Item":[
+						{ "Name":"Amount", "Value":Amount },
+						{ "Name":"MpesaReceiptNumber", "Value":MpesaReceiptNumber },
+						{ "Name":"Balance" },
+						{ "Name":"TransactionDate", "Value":20170727154800 },
+						{ "Name":"PhoneNumber", "Value":254721566839 }
+					]
 				}
-			]
 			}
 		}
-		}
 	}
 
 def get_account_balance_callback_payload():
diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
index 8a918b0..a8c7720 100644
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
@@ -139,12 +139,13 @@
 	inpatient_record.phone = patient_obj.phone
 	inpatient_record.inpatient = "Scheduled"
 	inpatient_record.scheduled_date = today()
+	inpatient_record.company = "_Test Company"
 	return inpatient_record
 
 
 def get_healthcare_service_unit(unit_name=None):
 	if not unit_name:
-		service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
+		service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1, "company": "_Test Company"})
 	else:
 		service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name})
 
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
index 0d3f45f..4b461f1 100644
--- a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
+++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
@@ -119,7 +119,7 @@
 	ip_record.expected_length_of_stay = 0
 	ip_record.save()
 	ip_record.reload()
-	service_unit = get_healthcare_service_unit()
+	service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
 	admit_patient(ip_record, service_unit, now_datetime())
 
 	ipmo = create_ipmo(patient)
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index bc1d762..109d921 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -78,7 +78,7 @@
 	"Job Opening", "Student Admission"]
 
 website_context = {
-	"favicon": 	"/assets/erpnext/images/favicon.png",
+	"favicon": 	"/assets/erpnext/images/erpnext-favicon.svg",
 	"splash_image": "/assets/erpnext/images/erpnext-logo.svg"
 }
 
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index f4b214a..c0e614a 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -16,11 +16,13 @@
 		employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
 		employee.date_of_birth = "1992" + frappe.utils.nowdate()[4:]
 		employee.company_email = "test@example.com"
+		employee.company = "_Test Company"
 		employee.save()
 
 		from erpnext.hr.doctype.employee.employee import get_employees_who_are_born_today, send_birthday_reminders
 
-		self.assertTrue(employee.name in [e.name for e in get_employees_who_are_born_today()])
+		employees_born_today = get_employees_who_are_born_today()
+		self.assertTrue(employees_born_today.get("_Test Company"))
 
 		frappe.db.sql("delete from `tabEmail Queue`")
 
diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py
index c397a3f..7e650f7 100644
--- a/erpnext/hr/doctype/job_offer/job_offer.py
+++ b/erpnext/hr/doctype/job_offer/job_offer.py
@@ -16,7 +16,7 @@
 
 	def validate(self):
 		self.validate_vacancies()
-		job_offer = frappe.db.exists("Job Offer",{"job_applicant": self.job_applicant})
+		job_offer = frappe.db.exists("Job Offer",{"job_applicant": self.job_applicant, "docstatus": ["!=", 2]})
 		if job_offer and job_offer != self.name:
 			frappe.throw(_("Job Offer: {0} is already for Job Applicant: {1}").format(frappe.bold(job_offer), frappe.bold(self.job_applicant)))
 
diff --git a/erpnext/hr/doctype/leave_application/leave_application_list.js b/erpnext/hr/doctype/leave_application/leave_application_list.js
index cbb4b73..a3c03b1 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_list.js
+++ b/erpnext/hr/doctype/leave_application/leave_application_list.js
@@ -1,5 +1,6 @@
 frappe.listview_settings['Leave Application'] = {
 	add_fields: ["leave_type", "employee", "employee_name", "total_leave_days", "from_date", "to_date"],
+	has_indicator_for_draft: 1,
 	get_indicator: function (doc) {
 		if (doc.status === "Approved") {
 			return [__("Approved"), "green", "status,=,Approved"];
diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
index 4608212..c5929c6 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
@@ -36,6 +36,8 @@
 	conditions, filters = get_conditions(filters)
 	columns, days = get_columns(filters)
 	att_map = get_attendance_list(conditions, filters)
+	if not att_map:
+		return columns, [], None, None
 
 	if filters.group_by:
 		emp_map, group_by_parameters = get_employee_details(filters.group_by, filters.company)
@@ -65,10 +67,14 @@
 	if filters.group_by:
 		emp_att_map = {}
 		for parameter in group_by_parameters:
-			data.append([ "<b>"+ parameter + "</b>"])
-			record, aaa = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list)
-			emp_att_map.update(aaa)
-			data += record
+			emp_map_set = set([key for key in emp_map[parameter].keys()])
+			att_map_set = set([key for key in att_map.keys()])
+			if (att_map_set & emp_map_set):
+				parameter_row = ["<b>"+ parameter + "</b>"] + ['' for day in range(filters["total_days_in_month"] + 2)]
+				data.append(parameter_row)
+				record, emp_att_data = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list)
+				emp_att_map.update(emp_att_data)
+				data += record
 	else:
 		record, emp_att_map = add_data(emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list)
 		data += record
@@ -237,6 +243,9 @@
 		status from tabAttendance where docstatus = 1 %s order by employee, attendance_date""" %
 		conditions, filters, as_dict=1)
 
+	if not attendance_list:
+		msgprint(_("No attendance record found"), alert=True, indicator="orange")
+
 	att_map = {}
 	for d in attendance_list:
 		att_map.setdefault(d.employee, frappe._dict()).setdefault(d.day_of_month, "")
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index 4693d44..21fd7c2 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -386,7 +386,8 @@
 		r.description = r.web_long_description or r.description
 		r.image = r.website_image or r.image
 		product_info = get_product_info_for_website(r.item_code, skip_quotation_creation=True).get('product_info')
-		r.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None
+		if product_info:
+			r.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None
 
 	return results
 
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 9e807f7..ea81b3e 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -288,7 +288,7 @@
 def make_salary_slip(source_name, target_doc=None):
 	target = frappe.new_doc("Salary Slip")
 	set_missing_values(source_name, target)
-	target.run_method("get_emp_and_leave_details")
+	target.run_method("get_emp_and_working_day_details")
 
 	return target
 
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 4a40e8e..7326238 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -2,7 +2,7 @@
 	"css/erpnext.css": [
 		"public/less/erpnext.less",
 		"public/less/hub.less",
-		"public/less/call_popup.less",
+		"public/scss/call_popup.scss",
 		"public/scss/point-of-sale.scss"
 	],
 	"css/marketplace.css": [
diff --git a/erpnext/public/images/erp-icon.svg b/erpnext/public/images/erp-icon.svg
deleted file mode 100644
index 6bec40c..0000000
--- a/erpnext/public/images/erp-icon.svg
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="88px" height="88px" viewBox="0 0 88 88" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
-    <title>erpnext-logo</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="erpnext-logo" transform="translate(-2.000000, -2.000000)" fill-rule="nonzero">
-            <g id="g1422-7-2" transform="translate(0.025630, 0.428785)" fill="#5E64FF">
-                <g id="g1418-4-6" transform="translate(0.268998, 0.867736)">
-                    <g id="g1416-4-9" transform="translate(0.749997, 0.000000)">
-                        <path d="M14.1845844,0.703479866 L75.0387175,0.703479866 C82.3677094,0.703479866 88.2679029,6.60367875 88.2679029,13.9326374 L88.2679029,74.7868158 C88.2679029,82.1157744 82.3677094,88.0159833 75.0387175,88.0159833 L14.1845844,88.0159833 C6.85569246,88.0159833 0.955398949,82.1157744 0.955398949,74.7868158 L0.955398949,13.9326374 C0.955398949,6.60367875 6.85569246,0.703479866 14.1845844,0.703479866 L14.1845844,0.703479866 Z" id="path1414-3-4"></path>
-                    </g>
-                </g>
-            </g>
-            <g id="g1444-6-7" transform="translate(27.708247, 23.320960)" fill="#FFFFFF">
-                <path d="M4.06942472,0.507006595 C3.79457554,0.507006595 3.52673783,0.534925429 3.26792241,0.587619847 C3.00908052,0.640314265 2.75926093,0.717948309 2.52171801,0.818098395 C2.40292009,0.868173438 2.28745592,0.924056085 2.17495509,0.985013441 C1.94997987,1.10692286 1.73828674,1.24983755 1.54244215,1.41134187 C0.661062132,2.13811791 0.100674618,3.23899362 0.100674618,4.4757567 L0.100674618,4.71760174 L0.100674618,39.9531653 L0.100674618,40.1945182 C0.100674618,42.3932057 1.87073716,44.1632683 4.06942472,44.1632683 L31.8263867,44.1632683 C34.0250742,44.1632683 35.7951368,42.3932057 35.7951368,40.1945182 L35.7951368,39.9531653 C35.7951368,37.7544777 34.0250742,35.9844152 31.8263867,35.9844152 L8.28000399,35.9844152 L8.28000399,26.0992376 L25.7874571,26.0992376 C27.9861447,26.0992376 29.7562072,24.3291751 29.7562072,22.1304875 L29.7562072,21.8891611 C29.7562072,19.6904735 27.9861447,17.920411 25.7874571,17.920411 L8.28000399,17.920411 L8.28000399,8.68635184 L31.8263867,8.68635184 C34.0250742,8.68635184 35.7951368,6.9162893 35.7951368,4.71760174 L35.7951368,4.4757567 C35.7951368,2.27706914 34.0250742,0.507006595 31.8263867,0.507006595 L4.06942472,0.507006595 Z" id="rect1436-8-4"></path>
-            </g>
-        </g>
-    </g>
-</svg>
\ No newline at end of file
diff --git a/erpnext/public/images/erpnext-12.svg b/erpnext/public/images/erpnext-12.svg
deleted file mode 100644
index fcc8e46..0000000
--- a/erpnext/public/images/erpnext-12.svg
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="165px" height="88px" viewBox="0 0 165 88" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
-    <title>version-12</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="version-12" transform="translate(-2.000000, -2.000000)">
-            <g id="erp-icon" fill-rule="nonzero">
-                <g id="g1422-7-2" transform="translate(0.025630, 0.428785)" fill="#5E64FF">
-                    <g id="g1418-4-6" transform="translate(0.268998, 0.867736)">
-                        <g id="g1416-4-9" transform="translate(0.749997, 0.000000)">
-                            <path d="M14.1845844,0.703479866 L75.0387175,0.703479866 C82.3677094,0.703479866 88.2679029,6.60367875 88.2679029,13.9326374 L88.2679029,74.7868158 C88.2679029,82.1157744 82.3677094,88.0159833 75.0387175,88.0159833 L14.1845844,88.0159833 C6.85569246,88.0159833 0.955398949,82.1157744 0.955398949,74.7868158 L0.955398949,13.9326374 C0.955398949,6.60367875 6.85569246,0.703479866 14.1845844,0.703479866 L14.1845844,0.703479866 Z" id="path1414-3-4"></path>
-                        </g>
-                    </g>
-                </g>
-                <g id="g1444-6-7" transform="translate(27.708247, 23.320960)" fill="#FFFFFF">
-                    <path d="M4.06942472,0.507006595 C3.79457554,0.507006595 3.52673783,0.534925429 3.26792241,0.587619847 C3.00908052,0.640314265 2.75926093,0.717948309 2.52171801,0.818098395 C2.40292009,0.868173438 2.28745592,0.924056085 2.17495509,0.985013441 C1.94997987,1.10692286 1.73828674,1.24983755 1.54244215,1.41134187 C0.661062132,2.13811791 0.100674618,3.23899362 0.100674618,4.4757567 L0.100674618,4.71760174 L0.100674618,39.9531653 L0.100674618,40.1945182 C0.100674618,42.3932057 1.87073716,44.1632683 4.06942472,44.1632683 L31.8263867,44.1632683 C34.0250742,44.1632683 35.7951368,42.3932057 35.7951368,40.1945182 L35.7951368,39.9531653 C35.7951368,37.7544777 34.0250742,35.9844152 31.8263867,35.9844152 L8.28000399,35.9844152 L8.28000399,26.0992376 L25.7874571,26.0992376 C27.9861447,26.0992376 29.7562072,24.3291751 29.7562072,22.1304875 L29.7562072,21.8891611 C29.7562072,19.6904735 27.9861447,17.920411 25.7874571,17.920411 L8.28000399,17.920411 L8.28000399,8.68635184 L31.8263867,8.68635184 C34.0250742,8.68635184 35.7951368,6.9162893 35.7951368,4.71760174 L35.7951368,4.4757567 C35.7951368,2.27706914 34.0250742,0.507006595 31.8263867,0.507006595 L4.06942472,0.507006595 Z" id="rect1436-8-4"></path>
-                </g>
-            </g>
-            <text id="12" font-family="SourceSansPro-Regular, Source Sans Pro" font-size="72" font-weight="normal" letter-spacing="-0.386831313" fill="#D1D8DD">
-                <tspan x="99" y="71">12</tspan>
-            </text>
-        </g>
-    </g>
-</svg>
\ No newline at end of file
diff --git a/erpnext/public/images/erpnext-favicon.svg b/erpnext/public/images/erpnext-favicon.svg
new file mode 100644
index 0000000..a3ac3bb
--- /dev/null
+++ b/erpnext/public/images/erpnext-favicon.svg
@@ -0,0 +1,5 @@
+<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0 12C0 5.37258 5.37258 0 12 0H88C94.6274 0 100 5.37258 100 12V88C100 94.6274 94.6274 100 88 100H12C5.37258 100 0 94.6274 0 88V12Z" fill="#0089FF"/>
+<path d="M65.7097 32.9462H67.3871V24H33V32.9462H43.9032H65.7097Z" fill="white"/>
+<path d="M43.9032 66.2151V53.914H65.7097V44.9677H43.9032H33V75.1613H67.6667V66.2151H43.9032Z" fill="white"/>
+</svg>
\ No newline at end of file
diff --git a/erpnext/public/images/erpnext-footer.png b/erpnext/public/images/erpnext-footer.png
deleted file mode 100644
index ffff775..0000000
--- a/erpnext/public/images/erpnext-footer.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/images/erpnext-logo.png b/erpnext/public/images/erpnext-logo.png
deleted file mode 100644
index 115faaa..0000000
--- a/erpnext/public/images/erpnext-logo.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/images/erpnext_logo.svg b/erpnext/public/images/erpnext_logo.svg
deleted file mode 100644
index af3a849..0000000
--- a/erpnext/public/images/erpnext_logo.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
-	<path d="M5.45455 0H30.5454C33.5673 0 36 2.43272 36 5.45454V30.5455C36 33.5673 33.5673 36 30.5454 36H5.45455C2.43276 36 0 33.5673 0 30.5455V5.45454C0 2.43272 2.43276 0 5.45455 0Z" fill="#2996F1"/>
-	<path d="M12.277 8.99994C12.1637 8.99994 12.0532 9.01145 11.9465 9.03318C11.8398 9.0549 11.7368 9.08691 11.6389 9.12821C11.5899 9.14885 11.5423 9.17189 11.4959 9.19703C11.4031 9.24729 11.3158 9.30622 11.2351 9.37281C10.8717 9.67247 10.6406 10.1264 10.6406 10.6363V10.736V25.2641V25.3636C10.6406 26.2701 11.3704 26.9999 12.277 26.9999H23.7215C24.6281 26.9999 25.3579 26.2701 25.3579 25.3636V25.2641C25.3579 24.3575 24.6281 23.6277 23.7215 23.6277H14.0131V19.5519H21.2316C22.1381 19.5519 22.868 18.8221 22.868 17.9156V17.8161C22.868 16.9095 22.1381 16.1797 21.2316 16.1797H14.0131V12.3724H23.7215C24.6281 12.3724 25.3579 11.6426 25.3579 10.736V10.6363C25.3579 9.72976 24.6281 8.99994 23.7215 8.99994H12.277Z" fill="white"/>
-</svg>
\ No newline at end of file
diff --git a/erpnext/public/images/favicon.png b/erpnext/public/images/favicon.png
deleted file mode 100644
index b694885..0000000
--- a/erpnext/public/images/favicon.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/images/splash.png b/erpnext/public/images/splash.png
deleted file mode 100644
index 8e5d055..0000000
--- a/erpnext/public/images/splash.png
+++ /dev/null
Binary files differ
diff --git a/erpnext/public/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js
index be1745e..c954f12 100644
--- a/erpnext/public/js/call_popup/call_popup.js
+++ b/erpnext/public/js/call_popup/call_popup.js
@@ -7,10 +7,103 @@
 	}
 
 	make() {
+		frappe.utils.play_sound('incoming-call');
 		this.dialog = new frappe.ui.Dialog({
 			'static': true,
-			'minimizable': true,
-			'fields': [{
+			'minimizable': true
+		});
+		this.dialog.get_close_btn().show();
+		this.setup_dialog();
+		this.set_call_status();
+		frappe.utils.bind_actions_with_object(this.dialog.$body, this);
+		this.dialog.$wrapper.addClass('call-popup');
+		this.dialog.get_close_btn().unbind('click').click(this.close_modal.bind(this));
+		this.dialog.show();
+	}
+
+	setup_dialog() {
+		this.setup_call_details();
+		this.dialog.$body.empty().append(this.caller_info);
+	}
+
+	set_indicator(color, blink=false) {
+		let classes = `indicator ${color} ${blink ? 'blink': ''}`;
+		this.dialog.header.find('.indicator').attr('class', classes);
+	}
+
+	set_call_status(call_status) {
+		let title = '';
+		call_status = call_status || this.call_log.status;
+		if (['Ringing'].includes(call_status) || !call_status) {
+			title = __('Incoming call from {0}', [this.get_caller_name() || this.caller_number]);
+			this.set_indicator('blue', true);
+		} else if (call_status === 'In Progress') {
+			title = __('Call Connected');
+			this.set_indicator('green');
+		} else if (['No Answer', 'Missed'].includes(call_status)) {
+			this.set_indicator('yellow');
+			title = __('Call Missed');
+		} else if (['Completed', 'Busy', 'Failed'].includes(call_status)) {
+			this.set_indicator('red');
+			title = __('Call Ended');
+		} else {
+			this.set_indicator('blue');
+			title = call_status;
+		}
+		this.dialog.set_title(title);
+	}
+
+	update_call_log(call_log, missed) {
+		this.call_log = call_log;
+		this.set_call_status(missed ? 'Missed': null);
+	}
+
+	close_modal() {
+		this.dialog.hide();
+		delete erpnext.call_popup;
+	}
+
+	call_ended(call_log, missed) {
+		frappe.utils.play_sound('call-disconnect');
+		this.update_call_log(call_log, missed);
+		setTimeout(() => {
+			if (!this.dialog.get_value('call_summary')) {
+				this.close_modal();
+			}
+		}, 60000);
+		this.clear_listeners();
+	}
+
+	get_caller_name() {
+		const contact_link = this.get_contact_link();
+		return contact_link.link_title || contact_link.link_name;
+	}
+
+	get_contact_link() {
+		let log = this.call_log;
+		let contact_link = log.links.find(d => d.link_doctype === 'Contact');
+		return contact_link || {};
+	}
+
+	setup_listener() {
+		frappe.realtime.on(`call_${this.call_log.id}_ended`, call_log => {
+			this.call_ended(call_log);
+		});
+
+		frappe.realtime.on(`call_${this.call_log.id}_missed`, call_log => {
+			this.call_ended(call_log, true);
+		});
+	}
+
+	clear_listeners() {
+		frappe.realtime.off(`call_${this.call_log.id}_ended`);
+		frappe.realtime.off(`call_${this.call_log.id}_missed`);
+	}
+
+	setup_call_details() {
+		this.caller_info = $(`<div></div>`);
+		this.call_details = new frappe.ui.FieldGroup({
+			fields: [{
 				'fieldname': 'name',
 				'label': 'Name',
 				'default': this.get_caller_name() || __('Unknown Caller'),
@@ -19,17 +112,17 @@
 			}, {
 				'fieldtype': 'Button',
 				'label': __('Open Contact'),
-				'click': () => frappe.set_route('Form', 'Contact', this.call_log.contact),
-				'depends_on': () => this.call_log.contact
-			}, {
-				'fieldtype': 'Button',
-				'label': __('Open Lead'),
-				'click': () => frappe.set_route('Form', 'Lead', this.call_log.lead),
-				'depends_on': () => this.call_log.lead
+				'click': () => frappe.set_route('Form', 'Contact', this.get_contact_link().link_name),
+				'depends_on': () => this.get_caller_name()
 			}, {
 				'fieldtype': 'Button',
 				'label': __('Create New Contact'),
-				'click': () => frappe.new_doc('Contact', { 'mobile_no': this.caller_number }),
+				'click': this.create_new_contact.bind(this),
+				'depends_on': () => !this.get_caller_name()
+			}, {
+				'fieldtype': 'Button',
+				'label': __('Create New Customer'),
+				'click': this.create_new_customer.bind(this),
 				'depends_on': () => !this.get_caller_name()
 			}, {
 				'fieldtype': 'Button',
@@ -45,25 +138,8 @@
 				'default': this.caller_number,
 				'read_only': 1
 			}, {
-				'fielname': 'last_interaction',
 				'fieldtype': 'Section Break',
-				'label': __('Activity'),
-				'depends_on': () => this.get_caller_name()
-			}, {
-				'fieldtype': 'Small Text',
-				'label': __('Last Issue'),
-				'fieldname': 'last_issue',
-				'read_only': true,
-				'depends_on': () => this.call_log.contact,
-				'default': `<i class="text-muted">${__('No issue has been raised by the caller.')}<i>`
-			}, {
-				'fieldtype': 'Small Text',
-				'label': __('Last Communication'),
-				'fieldname': 'last_communication',
-				'read_only': true,
-				'default': `<i class="text-muted">${__('No communication found.')}<i>`
-			}, {
-				'fieldtype': 'Section Break',
+				'hide_border': 1,
 			}, {
 				'fieldtype': 'Small Text',
 				'label': __('Call Summary'),
@@ -72,7 +148,7 @@
 				'fieldtype': 'Button',
 				'label': __('Save'),
 				'click': () => {
-					const call_summary = this.dialog.get_value('call_summary');
+					const call_summary = this.call_details.get_value('call_summary');
 					if (!call_summary) return;
 					frappe.xcall('erpnext.telephony.doctype.call_log.call_log.add_call_summary', {
 						'call_log': this.call_log.name,
@@ -94,108 +170,42 @@
 					});
 				}
 			}],
+			body: this.caller_info
 		});
-		this.set_call_status();
-		this.dialog.get_close_btn().show();
-		this.make_last_interaction_section();
-		this.dialog.$body.addClass('call-popup');
-		this.dialog.set_secondary_action(this.close_modal.bind(this));
-		frappe.utils.play_sound('incoming-call');
-		this.dialog.show();
+		this.call_details.make();
 	}
 
-	set_indicator(color, blink=false) {
-		let classes = `indicator ${color} ${blink ? 'blink': ''}`;
-		this.dialog.header.find('.indicator').attr('class', classes);
+	is_known_caller() {
+		return Boolean(this.get_caller_name());
 	}
 
-	set_call_status(call_status) {
-		let title = '';
-		call_status = call_status || this.call_log.status;
-		if (['Ringing'].includes(call_status) || !call_status) {
-			title = __('Incoming call from {0}', [this.get_caller_name() || this.caller_number]);
-			this.set_indicator('blue', true);
-		} else if (call_status === 'In Progress') {
-			title = __('Call Connected');
-			this.set_indicator('yellow');
-		} else if (call_status === 'Missed') {
-			this.set_indicator('red');
-			title = __('Call Missed');
-		} else if (['Completed', 'Disconnected'].includes(call_status)) {
-			this.set_indicator('red');
-			title = __('Call Disconnected');
-		} else {
-			this.set_indicator('blue');
-			title = call_status;
-		}
-		this.dialog.set_title(title);
+	create_new_customer() {
+		// to avoid quick entry form
+		const new_customer = frappe.model.get_new_doc('Customer');
+		new_customer.mobile_no = this.caller_number;
+		frappe.set_route('Form', new_customer.doctype, new_customer.name);
 	}
 
-	update_call_log(call_log) {
-		this.call_log = call_log;
-		this.set_call_status();
-	}
-
-	close_modal() {
-		this.dialog.hide();
-		delete erpnext.call_popup;
-	}
-
-	call_disconnected(call_log) {
-		frappe.utils.play_sound('call-disconnect');
-		this.update_call_log(call_log);
-		setTimeout(() => {
-			if (!this.dialog.get_value('call_summary')) {
-				this.close_modal();
-			}
-		}, 30000);
-	}
-
-	make_last_interaction_section() {
-		frappe.xcall('erpnext.crm.doctype.utils.get_last_interaction', {
-			'contact': this.call_log.contact,
-			'lead': this.call_log.lead
-		}).then(data => {
-			const comm_field = this.dialog.get_field('last_communication');
-			if (data.last_communication) {
-				const comm = data.last_communication;
-				comm_field.set_value(comm.content);
-			}
-
-			if (data.last_issue) {
-				const issue = data.last_issue;
-				const issue_field = this.dialog.get_field("last_issue");
-				issue_field.set_value(issue.subject);
-				issue_field.$wrapper.append(`
-					<a class="text-medium" href="/app/issue?customer=${issue.customer}">
-						${__('View all issues from {0}', [issue.customer])}
-					</a>
-				`);
-			}
-		});
-	}
-
-	get_caller_name() {
-		let log = this.call_log;
-		return log.contact_name || log.lead_name;
-	}
-
-	setup_listener() {
-		frappe.realtime.on(`call_${this.call_log.id}_disconnected`, call_log => {
-			this.call_disconnected(call_log);
-			// Remove call disconnect listener after the call is disconnected
-			frappe.realtime.off(`call_${this.call_log.id}_disconnected`);
-		});
+	create_new_contact() {
+		// TODO: fix new_doc, it should accept child table values
+		const new_contact = frappe.model.get_new_doc('Contact');
+		const phone_no = frappe.model.add_child(new_contact, 'Contact Phone', 'phone_nos');
+		phone_no.phone = this.caller_number;
+		phone_no.is_primary_mobile_no = 1;
+		frappe.set_route('Form', new_contact.doctype, new_contact.name);
 	}
 }
 
 $(document).on('app_ready', function () {
 	frappe.realtime.on('show_call_popup', call_log => {
-		if (!erpnext.call_popup) {
-			erpnext.call_popup = new CallPopup(call_log);
+		let call_popup = erpnext.call_popup;
+		if (call_popup && call_log.name === call_popup.call_log.name) {
+			call_popup.update_call_log(call_log);
+			call_popup.dialog.show();
 		} else {
-			erpnext.call_popup.update_call_log(call_log);
-			erpnext.call_popup.dialog.show();
+			erpnext.call_popup = new CallPopup(call_log);
 		}
 	});
 });
+
+window.CallPopup = CallPopup;
diff --git a/erpnext/public/js/templates/call_link.html b/erpnext/public/js/templates/call_link.html
index 08bdf14..071078c 100644
--- a/erpnext/public/js/templates/call_link.html
+++ b/erpnext/public/js/templates/call_link.html
@@ -1,32 +1,31 @@
 <div class="call-detail-wrapper">
-	<div class="left-arrow"></div>
-	<div class="head text-muted">
+	<div class="head flex justify-between">
+		<div>
+			<span class="bold"> {{ type }} Call</span>
+			{% if (duration) %}
+			<span class="text-muted"> • {{ frappe.format(duration, { fieldtype: "Duration" }) }}</span>
+			{% endif %}
+			<span class="text-muted"> • {{ comment_when(creation) }}</span>
+		</div>
 		<span>
-			<i class="fa fa-phone"> </i>
-		<span>
-		<span> {{ type }} Call</span>
-		-
-		<span> {{ frappe.format(duration, { fieldtype: "Duration" }) }}</span>
-		-
-		<span> {{ comment_when(creation) }}</span>
-		-
-		<!-- <span> {{ status }}</span>
-		- -->
-		<a class="text-muted" href="#Form/Call Log/{{name}}">Details</a>
-		{% if (show_call_button) { %}
-			<a class="pull-right">Callback</a>
-		{% } %}
+			<a class="action-btn" href="/app/call-log/{{ name }}" title="{{ __("Open Call Log") }}">
+				<svg class="icon icon-sm">
+					<use href="#icon-link-url" class="like-icon"></use>
+				</svg>
+			</a>
+		</span>
 	</div>
-	<div class="body padding">
+
+
+	<div class="body pt-3">
 		{% if (type === "Incoming") { %}
 			<span> Incoming call from {{ from }}, received by {{ to }}</span>
 		{% } else { %}
 			<span> Outgoing Call made by {{ from }} to {{ to }}</span>
 		{% } %}
-		<hr>
-		<div class="summary">
+		<div class="summary pt-3">
 		{% if (summary) { %}
-			<span>{{ summary }}</span>
+			<i>{{ summary }}</i>
 		{% } else { %}
 			<i class="text-muted">{{ __("No Summary") }}</i>
 		{% } %}
diff --git a/erpnext/public/less/call_popup.less b/erpnext/public/less/call_popup.less
deleted file mode 100644
index 32e85ce..0000000
--- a/erpnext/public/less/call_popup.less
+++ /dev/null
@@ -1,9 +0,0 @@
-.call-popup {
-	a:hover {
-		text-decoration: underline;
-	}
-	.for-description {
-		max-height: 250px;
-		overflow: scroll;
-	}
-}
\ No newline at end of file
diff --git a/erpnext/public/scss/call_popup.scss b/erpnext/public/scss/call_popup.scss
new file mode 100644
index 0000000..95e3182
--- /dev/null
+++ b/erpnext/public/scss/call_popup.scss
@@ -0,0 +1,21 @@
+.call-popup {
+	a:hover {
+		text-decoration: underline;
+	}
+	.for-description {
+		max-height: 250px;
+		overflow: scroll;
+	}
+}
+
+audio {
+	height: 40px;
+	width: 100%;
+	max-width: 500px;
+	background-color: var(--control-bg);
+	border-radius: var(--border-radius-sm);
+	&-webkit-media-controls-panel {
+		background: var(--control-bg);
+	}
+	outline: none;
+}
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index c8e7e14..5f701f2 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -20,11 +20,13 @@
 
 def validate_einvoice_fields(doc):
 	einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
-	invalid_doctype = doc.doctype not in ['Sales Invoice']
+	invalid_doctype = doc.doctype != 'Sales Invoice'
 	invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
 	company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
+	no_taxes_applied = len(doc.get('taxes', [])) == 0
 
-	if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return
+	if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction or no_taxes_applied:
+		return
 
 	if doc.docstatus == 0 and doc._action == 'save':
 		if doc.irn:
@@ -303,7 +305,7 @@
 			_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
 			title=_('Missing Fields')
 		)
-	if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
+	if invoice.gst_category != 'Overseas' and not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
 		frappe.throw(
 			_('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'),
 			title=_('Missing Fields')
@@ -444,6 +446,8 @@
 	def get_credentials(self):
 		if self.invoice:
 			gstin = self.get_seller_gstin()
+			if not self.e_invoice_settings.enable:
+				frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings")))
 			credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin)
 		else:
 			credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
@@ -817,4 +821,4 @@
 @frappe.whitelist()
 def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
 	gsp_connector = GSPConnector(doctype, docname)
-	gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
\ No newline at end of file
+	gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py
index c1c8aed..4e57ff7 100644
--- a/erpnext/regional/report/irs_1099/irs_1099.py
+++ b/erpnext/regional/report/irs_1099/irs_1099.py
@@ -32,6 +32,10 @@
 
 	data = []
 	columns = get_columns()
+	conditions = ""
+	if filters.supplier_group:
+		conditions += "AND s.supplier_group = %s" %frappe.db.escape(filters.get("supplier_group"))
+
 	data = frappe.db.sql("""
 		SELECT
 			s.supplier_group as "supplier_group",
@@ -46,15 +50,17 @@
 				AND s.irs_1099 = 1
 				AND gl.fiscal_year = %(fiscal_year)s
 				AND gl.party_type = "Supplier"
+				AND gl.company = %(company)s
+				{conditions}
+			
 		GROUP BY
 			gl.party
+
 		ORDER BY
-			gl.party DESC
-	""", {
-		"fiscal_year": filters.fiscal_year,
-		"supplier_group": filters.supplier_group,
-		"company": filters.company
-	}, as_dict=True)
+			gl.party DESC""".format(conditions=conditions), {
+				"fiscal_year": filters.fiscal_year,
+				"company": filters.company
+			}, as_dict=True)
 
 	return columns, data
 
@@ -79,13 +85,13 @@
 			"fieldname": "tax_id",
 			"label": _("Tax ID"),
 			"fieldtype": "Data",
-			"width": 120
+			"width": 200
 		},
 		{
 			"fieldname": "payments",
 			"label": _("Total Payments"),
 			"fieldtype": "Currency",
-			"width": 120
+			"width": 200
 		}
 	]
 
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index bf8b7fc..c452594 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -126,7 +126,9 @@
 		'''If Customer created from Lead, update lead status to "Converted"
 		update Customer link in Quotation, Opportunity'''
 		if self.lead_name:
-			frappe.db.set_value('Lead', self.lead_name, 'status', 'Converted', update_modified=False)
+			lead = frappe.get_doc('Lead', self.lead_name)
+			lead.status = 'Converted'
+			lead.save()
 
 	def create_lead_address_contact(self):
 		if self.lead_name:
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index e259367..cbfab82 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -325,6 +325,9 @@
 		create_dn_against_so(so.name, 4)
 		make_sales_invoice(so.name)
 
+		prev_total = so.get("base_total")
+		prev_total_in_words = so.get("base_in_words")
+
 		first_item_of_so = so.get("items")[0]
 		trans_item = json.dumps([
 			{'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \
@@ -340,6 +343,12 @@
 		self.assertEqual(so.get("items")[-1].amount, 1400)
 		self.assertEqual(so.status, 'To Deliver and Bill')
 
+		updated_total = so.get("base_total")
+		updated_total_in_words = so.get("base_in_words")
+
+		self.assertEqual(updated_total, prev_total+1400)
+		self.assertNotEqual(updated_total_in_words, prev_total_in_words)
+
 	def test_update_child_removing_item(self):
 		so = make_sales_order(**{
 			"item_list": [{
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index f753b6d..044e803 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -477,7 +477,7 @@
 		const taxes = frm.doc.taxes.map(t => {
 			return {
 				description: t.description, rate: t.rate
-			}
+			};
 		});
 		this.render_taxes(frm.doc.total_taxes_and_charges, taxes);
 	}
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index c9b8ad9..bcbac3b 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -153,32 +153,23 @@
 				me.$payment_modes.find(`.${mode}-amount`).css('display', 'none');
 				me.$payment_modes.find(`.${mode}-name`).css('display', 'inline');
 
-				const doc = me.events.get_frm().doc;
 				me.selected_mode = me[`${mode}_control`];
 				me.selected_mode && me.selected_mode.$input.get(0).focus();
-				const current_value = me.selected_mode ? me.selected_mode.get_value() : undefined;
-				!current_value && doc.grand_total > doc.paid_amount && me.selected_mode ?
-					me.selected_mode.set_value(doc.grand_total - doc.paid_amount) : '';
+				me.auto_set_remaining_amount();
 			}
 		});
 
-		frappe.realtime.on("process_phone_payment", function(data) {
-			frappe.dom.unfreeze();
-			cur_frm.reload_doc();
-			let message = data["ResultDesc"];
-			let title = __("Payment Failed");
+		frappe.ui.form.on('POS Invoice', 'contact_mobile', (frm) => {
+			const contact = frm.doc.contact_mobile;
+			const request_button = $(this.request_for_payment_field.$input[0]);
+			if (contact) {
+				request_button.removeClass('btn-default').addClass('btn-primary');
+			} else {
+				request_button.removeClass('btn-primary').addClass('btn-default');
+      }
+    });
 
-			if (data["ResultCode"] == 0) {
-				title = __("Payment Received");
-				$('.btn.btn-xs.btn-default[data-fieldname=request_for_payment]').html(`Payment Received`);
-				me.events.submit_invoice();
-			}
-
-			frappe.msgprint({
-				"message": message,
-				"title": title
-			});
-		});
+		this.setup_listener_for_payments();
 
 		this.$payment_modes.on('click', '.shortcut', () => {
 			const value = $(this).attr('data-value');
@@ -224,6 +215,41 @@
 		});
 	}
 
+	setup_listener_for_payments() {
+		frappe.realtime.on("process_phone_payment", (data) => {
+			const doc = this.events.get_frm().doc;
+			const { response, amount, success, failure_message } = data;
+			let message, title;
+
+			if (success) {
+				title = __("Payment Received");
+				if (amount >= doc.grand_total) {
+					frappe.dom.unfreeze();
+					message = __("Payment of {0} received successfully.", [format_currency(amount, doc.currency, 0)]);
+					this.events.submit_invoice();
+					cur_frm.reload_doc();
+
+				} else {
+					message = __("Payment of {0} received successfully. Waiting for other requests to complete...", [format_currency(amount, doc.currency, 0)]);
+				}
+			} else if (failure_message) {
+				message = failure_message;
+				title = __("Payment Failed");
+			}
+
+			frappe.msgprint({ "message": message, "title": title });
+		});
+	}
+
+	auto_set_remaining_amount() {
+		const doc = this.events.get_frm().doc;
+		const remaining_amount = doc.grand_total - doc.paid_amount;
+		const current_value = this.selected_mode ? this.selected_mode.get_value() : undefined;
+		if (!current_value && remaining_amount > 0 && this.selected_mode) {
+			this.selected_mode.set_value(remaining_amount);
+		}
+	}
+
 	attach_shortcuts() {
 		const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl';
 		this.$component.find('.submit-order-btn').attr("title", `${ctrl_label}+Enter`);
@@ -333,9 +359,11 @@
 					fieldtype: 'Currency',
 					placeholder: __('Enter {0} amount.', [p.mode_of_payment]),
 					onchange: function() {
-						if (this.value || this.value == 0) {
-							frappe.model.set_value(p.doctype, p.name, 'amount', flt(this.value))
-								.then(() => me.update_totals_section());
+						const current_value = frappe.model.get_value(p.doctype, p.name, 'amount');
+						if (current_value != this.value) {
+							frappe.model
+								.set_value(p.doctype, p.name, 'amount', flt(this.value))
+								.then(() => me.update_totals_section())
 
 							const formatted_currency = format_currency(this.value, currency);
 							me.$payment_modes.find(`.${mode}-amount`).html(formatted_currency);
diff --git a/erpnext/setup/doctype/customer_group/customer_group.json b/erpnext/setup/doctype/customer_group/customer_group.json
index 10f9bd0..0e2ed9e 100644
--- a/erpnext/setup/doctype/customer_group/customer_group.json
+++ b/erpnext/setup/doctype/customer_group/customer_group.json
@@ -139,7 +139,7 @@
  "idx": 1,
  "is_tree": 1,
  "links": [],
- "modified": "2020-03-18 18:10:13.048492",
+ "modified": "2021-02-08 17:01:52.162202",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Customer Group",
@@ -189,6 +189,15 @@
    "permlevel": 1,
    "read": 1,
    "role": "Sales Manager"
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "report": 1,
+   "role": "Customer",
+   "select": 1,
+   "share": 1
   }
  ],
  "search_fields": "parent_customer_group",
diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json
index 31624ed..e835214 100644
--- a/erpnext/setup/doctype/item_group/item_group.json
+++ b/erpnext/setup/doctype/item_group/item_group.json
@@ -214,7 +214,7 @@
  "is_tree": 1,
  "links": [],
  "max_attachments": 3,
- "modified": "2020-12-30 12:57:38.876956",
+ "modified": "2021-02-08 17:02:44.951572",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Item Group",
@@ -271,6 +271,15 @@
    "read": 1,
    "report": 1,
    "role": "Accounts User"
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "report": 1,
+   "role": "Customer",
+   "select": 1,
+   "share": 1
   }
  ],
  "search_fields": "parent_item_group",
diff --git a/erpnext/setup/doctype/territory/territory.json b/erpnext/setup/doctype/territory/territory.json
index aa8e048..a25bda0 100644
--- a/erpnext/setup/doctype/territory/territory.json
+++ b/erpnext/setup/doctype/territory/territory.json
@@ -123,7 +123,7 @@
  "idx": 1,
  "is_tree": 1,
  "links": [],
- "modified": "2020-03-18 18:11:36.623555",
+ "modified": "2021-02-08 17:10:03.767426",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Territory",
@@ -166,6 +166,15 @@
   {
    "read": 1,
    "role": "Maintenance User"
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "report": 1,
+   "role": "Customer",
+   "select": 1,
+   "share": 1
   }
  ],
  "search_fields": "parent_territory,territory_manager",
diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py
index da9e798..36d446e 100644
--- a/erpnext/shopping_cart/product_query.py
+++ b/erpnext/shopping_cart/product_query.py
@@ -23,8 +23,10 @@
 		self.cart_settings = frappe.get_doc("Shopping Cart Settings")
 		self.page_length = self.settings.products_per_page or 20
 		self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants', 'item_group', 'image', 'web_long_description', 'description', 'route']
-		self.filters = [['show_in_website', '=', 1]]
-		self.or_filters = []
+		self.filters = []
+		self.or_filters = [['show_in_website', '=', 1]]
+		if not self.settings.get('hide_variants'):
+			self.or_filters.append(['show_variant_in_website', '=', 1])
 
 	def query(self, attributes=None, fields=None, search_term=None, start=0):
 		"""Summary
@@ -73,7 +75,8 @@
 
 		for item in result:
 			product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
-			item.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None
+			if product_info:
+				item.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None
 
 		return result
 
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 9dbb64c..95cb92b 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -132,7 +132,7 @@
 			var message = __("No Stock Available Currently");
 			this.content.find('.result').css('text-align', 'center');
 
-			$(`<div class='text-muted' style='margin: 20px 5px; font-weight: lighter;'>
+			$(`<div class='text-muted' style='margin: 20px 5px;'>
 				${message} </div>`).appendTo(this.result);
 		}
 	},
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index a5c303c..36d09ef 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -27,10 +27,11 @@
 
 	def validate(self):
 		self.flags.ignore_submit_comment = True
-		from erpnext.stock.utils import validate_warehouse_company
+		from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse
 		self.validate_mandatory()
 		self.validate_item()
 		self.validate_batch()
+		validate_disabled_warehouse(self.warehouse)
 		validate_warehouse_company(self.warehouse, self.company)
 		self.scrub_posting_time()
 		self.validate_and_set_fiscal_year()
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 46919c8..95f8c43 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -202,8 +202,7 @@
 			where
 				item_code = %(item_code)s
 				and warehouse = %(warehouse)s
-				and voucher_type = %(voucher_type)s
-				and voucher_no = %(voucher_no)s
+				and timestamp(posting_date, time_format(posting_time, '%H:%i:%s')) = timestamp(%(posting_date)s, time_format(%(posting_time)s, '%H:%i:%s'))
 			order by
 				creation ASC
 			for update
@@ -794,4 +793,4 @@
 			and qty_after_transaction + {0} < 0
 		order by timestamp(posting_date, posting_time) asc
 		limit 1
-	""".format(args.actual_qty), args, as_dict=1)
\ No newline at end of file
+	""".format(args.actual_qty), args, as_dict=1)
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 4ea7e4f..0af3d90 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -5,7 +5,7 @@
 import frappe, erpnext
 from frappe import _
 import json
-from frappe.utils import flt, cstr, nowdate, nowtime
+from frappe.utils import flt, cstr, nowdate, nowtime, get_link_to_form
 
 from six import string_types
 
@@ -284,6 +284,10 @@
 	if frappe.db.get_value("Warehouse", warehouse, "is_group"):
 		frappe.throw(_("Group node warehouse is not allowed to select for transactions"))
 
+def validate_disabled_warehouse(warehouse):
+	if frappe.db.get_value("Warehouse", warehouse, "disabled"):
+		frappe.throw(_("Disabled Warehouse {0} cannot be used for this transaction.").format(get_link_to_form('Warehouse', warehouse)))
+
 def update_included_uom_in_report(columns, result, include_uom, conversion_factors):
 	if not include_uom or not conversion_factors:
 		return
diff --git a/erpnext/telephony/doctype/call_log/call_log.js b/erpnext/telephony/doctype/call_log/call_log.js
index 977f86d..e7afa0b 100644
--- a/erpnext/telephony/doctype/call_log/call_log.js
+++ b/erpnext/telephony/doctype/call_log/call_log.js
@@ -2,7 +2,26 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Call Log', {
-	// refresh: function(frm) {
-
-	// }
+	refresh: function(frm) {
+		frm.events.setup_recording_audio_control(frm);
+		const incoming_call = frm.doc.type == 'Incoming';
+		frm.add_custom_button(incoming_call ? __('Callback'): __('Call Again'), () => {
+			const number = incoming_call ? frm.doc.from : frm.doc.to;
+			frappe.phone_call.handler(number, frm);
+		});
+	},
+	setup_recording_audio_control(frm) {
+		const recording_wrapper = frm.get_field('recording_html').$wrapper;
+		if (!frm.doc.recording_url || frm.doc.recording_url == 'null') {
+			recording_wrapper.empty();
+		} else {
+			recording_wrapper.addClass('input-max-width');
+			recording_wrapper.html(`
+				<audio
+					controls
+					src="${frm.doc.recording_url}">
+				</audio>
+			`);
+		}
+	}
 });
diff --git a/erpnext/telephony/doctype/call_log/call_log.json b/erpnext/telephony/doctype/call_log/call_log.json
index 1ecd884..1d6c39e 100644
--- a/erpnext/telephony/doctype/call_log/call_log.json
+++ b/erpnext/telephony/doctype/call_log/call_log.json
@@ -5,6 +5,7 @@
  "doctype": "DocType",
  "engine": "InnoDB",
  "field_order": [
+  "call_details_section",
   "id",
   "from",
   "to",
@@ -21,21 +22,10 @@
   "section_break_11",
   "summary",
   "section_break_19",
-  "links",
-  "column_break_3",
-  "section_break_5"
+  "links"
  ],
  "fields": [
   {
-   "fieldname": "column_break_3",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "section_break_5",
-   "fieldtype": "Section Break",
-   "label": "Call Details"
-  },
-  {
    "fieldname": "id",
    "fieldtype": "Data",
    "label": "ID",
@@ -75,6 +65,7 @@
   {
    "fieldname": "recording_url",
    "fieldtype": "Data",
+   "hidden": 1,
    "label": "Recording URL"
   },
   {
@@ -112,13 +103,13 @@
   },
   {
    "fieldname": "summary",
-   "fieldtype": "Small Text",
-   "label": "Call Summary"
+   "fieldtype": "Small Text"
   },
   {
    "fieldname": "section_break_11",
    "fieldtype": "Section Break",
-   "hide_border": 1
+   "hide_border": 1,
+   "label": "Call Summary"
   },
   {
    "fieldname": "start_time",
@@ -138,12 +129,17 @@
    "label": "Customer",
    "options": "Customer",
    "read_only": 1
+  },
+  {
+   "fieldname": "call_details_section",
+   "fieldtype": "Section Break",
+   "label": "Call Details"
   }
  ],
  "in_create": 1,
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2021-01-13 12:28:20.288985",
+ "modified": "2021-02-08 14:23:28.744844",
  "modified_by": "Administrator",
  "module": "Telephony",
  "name": "Call Log",
diff --git a/erpnext/telephony/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py
index a277a5f..4d553df 100644
--- a/erpnext/telephony/doctype/call_log/call_log.py
+++ b/erpnext/telephony/doctype/call_log/call_log.py
@@ -165,6 +165,8 @@
 	for log in logs:
 		log.show_call_button = 0
 		timeline_contents.append({
+			'icon': 'call',
+			'is_card': True,
 			'creation': log.creation,
 			'template': 'call_link',
 			'template_data': log
diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html
index 2cabf5a..ea34371 100644
--- a/erpnext/templates/pages/cart.html
+++ b/erpnext/templates/pages/cart.html
@@ -62,6 +62,9 @@
 				<div class="col-8">
 					{% if doc.items %}
 					<div class="place-order-container">
+						<a class="btn btn-primary-light mr-2" href="/all-products">
+							{{ _("Continue Shopping") }}
+						</a>
 						{% if cart_settings.enable_checkout %}
 							<button class="btn btn-primary btn-place-order" type="button">
 								{{ _("Place Order") }}