Merge branch 'develop' into debit-credit-opening-invoice-tool
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 4d3cbea..41f9ce0 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -40,7 +40,7 @@
  "fields": [
   {
    "default": "1",
-   "description": "If enabled, the system will post accounting entries for inventory automatically.",
+   "description": "If enabled, the system will post accounting entries for inventory automatically",
    "fieldname": "auto_accounting_for_stock",
    "fieldtype": "Check",
    "hidden": 1,
@@ -48,23 +48,23 @@
    "label": "Make Accounting Entry For Every Stock Movement"
   },
   {
-   "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
+   "description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below",
    "fieldname": "acc_frozen_upto",
    "fieldtype": "Date",
    "in_list_view": 1,
-   "label": "Accounts Frozen Upto"
+   "label": "Accounts Frozen Till Date"
   },
   {
    "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
    "fieldname": "frozen_accounts_modifier",
    "fieldtype": "Link",
    "in_list_view": 1,
-   "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
+   "label": "Role Allowed to Set Frozen Accounts and Edit Frozen Entries",
    "options": "Role"
   },
   {
    "default": "Billing Address",
-   "description": "Address used to determine Tax Category in transactions.",
+   "description": "Address used to determine Tax Category in transactions",
    "fieldname": "determine_address_tax_category_from",
    "fieldtype": "Select",
    "label": "Determine Address Tax Category From",
@@ -75,7 +75,7 @@
    "fieldtype": "Column Break"
   },
   {
-   "description": "Role that is allowed to submit transactions that exceed credit limits set.",
+   "description": "This role is allowed to submit transactions that exceed credit limits",
    "fieldname": "credit_controller",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -127,7 +127,7 @@
    "default": "0",
    "fieldname": "show_inclusive_tax_in_print",
    "fieldtype": "Check",
-   "label": "Show Inclusive Tax In Print"
+   "label": "Show Inclusive Tax in Print"
   },
   {
    "fieldname": "column_break_12",
@@ -165,7 +165,7 @@
   },
   {
    "default": "0",
-   "description": "Only select if you have setup Cash Flow Mapper documents",
+   "description": "Only select this if you have set up the Cash Flow Mapper documents",
    "fieldname": "use_custom_cash_flow",
    "fieldtype": "Check",
    "label": "Use Custom Cash Flow Format"
@@ -177,7 +177,7 @@
    "label": "Automatically Fetch Payment Terms"
   },
   {
-   "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.",
+   "description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
    "fieldname": "over_billing_allowance",
    "fieldtype": "Currency",
    "label": "Over Billing Allowance (%)"
@@ -199,7 +199,7 @@
   },
   {
    "default": "0",
-   "description": "If this is unchecked direct GL Entries will be created to book Deferred Revenue/Expense",
+   "description": "If this is unchecked, direct GL entries will be created to book deferred revenue or expense",
    "fieldname": "book_deferred_entries_via_journal_entry",
    "fieldtype": "Check",
    "label": "Book Deferred Entries Via Journal Entry"
@@ -214,7 +214,7 @@
   },
   {
    "default": "Days",
-   "description": "If \"Months\" is selected then fixed amount will be booked as deferred revenue or expense for each month irrespective of number of days in a month. Will be prorated if deferred revenue or expense is not booked for an entire month.",
+   "description": "If \"Months\" is selected, a fixed amount will be booked as deferred revenue or expense for each month irrespective of the number of days in a month. It will be prorated if deferred revenue or expense is not booked for an entire month",
    "fieldname": "book_deferred_entries_based_on",
    "fieldtype": "Select",
    "label": "Book Deferred Entries Based On",
@@ -226,7 +226,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-10-07 14:58:50.325577",
+ "modified": "2020-10-13 11:32:52.268826",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
@@ -254,4 +254,4 @@
  "sort_field": "modified",
  "sort_order": "ASC",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
index 6de62ee..7ad1d3a 100644
--- a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
+++ b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
@@ -23,13 +23,13 @@
 			where posting_date=%s and posting_time>=%s and posting_time<=%s and owner=%s
 		""", (self.date, self.from_time, self.time, self.user))
 		self.outstanding_amount = flt(values[0][0] if values else 0)
-			
+
 	def make_calculations(self):
 		total = 0.00
 		for i in self.payments:
 			total += flt(i.amount)
 
-		self.net_amount = total + self.outstanding_amount + self.expense - self.custody + self.returns
+		self.net_amount = total + self.outstanding_amount + flt(self.expense) - flt(self.custody) + flt(self.returns)
 
 	def validate_time(self):
 		if self.from_time >= self.time:
diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json
index 50fc3bb..51fc3f7 100644
--- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json
+++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "field:mode_of_payment",
@@ -28,7 +29,7 @@
    "fieldtype": "Select",
    "in_standard_filter": 1,
    "label": "Type",
-   "options": "Cash\nBank\nGeneral"
+   "options": "Cash\nBank\nGeneral\nPhone"
   },
   {
    "fieldname": "accounts",
@@ -45,7 +46,9 @@
  ],
  "icon": "fa fa-credit-card",
  "idx": 1,
- "modified": "2020-09-18 17:26:09.703215",
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-09-18 17:57:23.835236",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Mode of Payment",
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
index 699eb08..3ce5701 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
@@ -6,7 +6,7 @@
 		frm.set_query('party_type', 'invoices', function(doc, cdt, cdn) {
 			return {
 				filters: {
-					'name': ['in', 'Customer,Supplier']
+					'name': ['in', 'Customer, Supplier']
 				}
 			};
 		});
@@ -14,29 +14,46 @@
 		if (frm.doc.company) {
 			frm.trigger('setup_company_filters');
 		}
+
+		frappe.realtime.on('opening_invoice_creation_progress', data => {
+			if (!frm.doc.import_in_progress) {
+				frm.dashboard.reset();
+				frm.doc.import_in_progress = true;
+			}
+			if (data.user != frappe.session.user) return;
+			if (data.count == data.total) {
+				setTimeout((title) => {
+					frm.doc.import_in_progress = false;
+					frm.clear_table("invoices");
+					frm.refresh_fields();
+					frm.page.clear_indicator();
+					frm.dashboard.hide_progress(title);
+					frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
+				}, 1500, data.title);
+				return;
+			}
+
+			frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
+			frm.page.set_indicator(__('In Progress'), 'orange');
+		});
 	},
 
 	refresh: function(frm) {
 		frm.disable_save();
-		frm.trigger("make_dashboard");
+		!frm.doc.import_in_progress && frm.trigger("make_dashboard");
 		frm.page.set_primary_action(__('Create Invoices'), () => {
 			let btn_primary = frm.page.btn_primary.get(0);
 			return frm.call({
 				doc: frm.doc,
-				freeze: true,
 				btn: $(btn_primary),
 				method: "make_invoices",
-				freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
-				callback: (r) => {
-					if(!r.exc){
-						frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
-						frm.clear_table("invoices");
-						frm.refresh_fields();
-						frm.reload_doc();
-					}
-				}
+				freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type])
 			});
 		});
+
+		if (frm.doc.create_missing_party) {
+			frm.set_df_property("party", "fieldtype", "Data", frm.doc.name, "invoices");
+		}
 	},
 
 	setup_company_filters: function(frm) {
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index 3653a88..ee2092a 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -4,9 +4,12 @@
 
 from __future__ import unicode_literals
 import frappe
+import traceback
+from json import dumps
 from frappe import _, scrub
 from frappe.utils import flt, nowdate
 from frappe.model.document import Document
+from frappe.utils.background_jobs import enqueue
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
 
 
@@ -61,67 +64,48 @@
 			prepare_invoice_summary(doctype, invoices)
 
 		return invoices_summary, max_count
-
-	def make_invoices(self):
-		names = []
-		mandatory_error_msg = _("Row {0}: {1} is required to create the Opening {2} Invoices")
+	
+	def validate_company(self):
 		if not self.company:
 			frappe.throw(_("Please select the Company"))
+	
+	def set_missing_values(self, row):
+		row.qty = row.qty or 1.0
+		row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(self.company)
+		row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
+		row.item_name = row.item_name or _("Opening Invoice Item")
+		row.posting_date = row.posting_date or nowdate()
+		row.due_date = row.due_date or nowdate()
 
-		company_details = frappe.get_cached_value('Company', self.company,
-			["default_currency", "default_letter_head"], as_dict=1) or {}
+	def validate_mandatory_invoice_fields(self, row):
+		if not frappe.db.exists(row.party_type, row.party):
+			if self.create_missing_party:
+				self.add_party(row.party_type, row.party)
+			else:
+				frappe.throw(_("Row #{}: {} {} does not exist.").format(row.idx, frappe.bold(row.party_type), frappe.bold(row.party)))
 
+		mandatory_error_msg = _("Row #{0}: {1} is required to create the Opening {2} Invoices")
+		for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
+			if not row.get(scrub(d)):
+				frappe.throw(mandatory_error_msg.format(row.idx, d, self.invoice_type))
+
+	def get_invoices(self):
+		invoices = []
 		for row in self.invoices:
-			if not row.qty:
-				row.qty = 1.0
-
-			# always mandatory fields for the invoices
-			if not row.temporary_opening_account:
-				row.temporary_opening_account = get_temporary_opening_account(self.company)
-			row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
-
-			# Allow to create invoice even if no party present in customer or supplier.
-			if not frappe.db.exists(row.party_type, row.party):
-				if self.create_missing_party:
-					self.add_party(row.party_type, row.party)
-				else:
-					frappe.throw(_("{0} {1} does not exist.").format(frappe.bold(row.party_type), frappe.bold(row.party)))
-
-			if not row.item_name:
-				row.item_name = _("Opening Invoice Item")
-			if not row.posting_date:
-				row.posting_date = nowdate()
-			if not row.due_date:
-				row.due_date = nowdate()
-
-			for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
-				if not row.get(scrub(d)):
-					frappe.throw(mandatory_error_msg.format(row.idx, _(d), self.invoice_type))
-
-			args = self.get_invoice_dict(row=row)
-			if not args:
+			if not row:
 				continue
-
+			self.set_missing_values(row)
+			self.validate_mandatory_invoice_fields(row)
+			invoice = self.get_invoice_dict(row)
+			company_details = frappe.get_cached_value('Company', self.company, ["default_currency", "default_letter_head"], as_dict=1) or {}
 			if company_details:
-				args.update({
+				invoice.update({
 					"currency": company_details.get("default_currency"),
 					"letter_head": company_details.get("default_letter_head")
 				})
+			invoices.append(invoice)
 
-			doc = frappe.get_doc(args).insert()
-			doc.submit()
-			names.append(doc.name)
-
-			if len(self.invoices) > 5:
-				frappe.publish_realtime(
-					"progress", dict(
-						progress=[row.idx, len(self.invoices)],
-						title=_('Creating {0}').format(doc.doctype)
-					),
-					user=frappe.session.user
-				)
-
-		return names
+		return invoices
 
 	def add_party(self, party_type, party):
 		party_doc = frappe.new_doc(party_type)
@@ -140,14 +124,12 @@
 
 	def get_invoice_dict(self, row=None):
 		def get_item_dict():
-			default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
-			cost_center = row.get('cost_center') or frappe.get_cached_value('Company',
-				self.company,  "cost_center")
-
+			cost_center = row.get('cost_center') or frappe.get_cached_value('Company', self.company,  "cost_center")
 			if not cost_center:
-				frappe.throw(
-					_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company))
-				)
+				frappe.throw(_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company)))
+
+			income_expense_account_field = "income_account" if row.party_type == "Customer" else "expense_account"
+			default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
 			rate = flt(row.outstanding_amount) / flt(row.qty)
 
 			return frappe._dict({
@@ -161,18 +143,9 @@
 				"cost_center": cost_center
 			})
 
-		if not row:
-			return None
-
-		party_type = "Customer"
-		income_expense_account_field = "income_account"
-		if self.invoice_type == "Purchase":
-			party_type = "Supplier"
-			income_expense_account_field = "expense_account"
-
 		item = get_item_dict()
 
-		args = frappe._dict({
+		invoice = frappe._dict({
 			"items": [item],
 			"is_opening": "Yes",
 			"set_posting_time": 1,
@@ -180,22 +153,77 @@
 			"cost_center": self.cost_center,
 			"due_date": row.due_date,
 			"posting_date": row.posting_date,
-			frappe.scrub(party_type): row.party,
+			frappe.scrub(row.party_type): row.party,
+			"is_pos": 0,
 			"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
 			"update_stock": 0
 		})
 
 		accounting_dimension = get_accounting_dimensions()
-
 		for dimension in accounting_dimension:
-			args.update({
+			invoice.update({
 				dimension: item.get(dimension)
 			})
 
-		if self.invoice_type == "Sales":
-			args["is_pos"] = 0
+		return invoice
 
-		return args
+	def make_invoices(self):
+		self.validate_company()
+		invoices = self.get_invoices()
+		if len(invoices) < 50:
+			return start_import(invoices)
+		else:
+			from frappe.core.page.background_jobs.background_jobs import get_info
+			from frappe.utils.scheduler import is_scheduler_inactive
+
+			if is_scheduler_inactive() and not frappe.flags.in_test:
+				frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
+
+			enqueued_jobs = [d.get("job_name") for d in get_info()]
+			if self.name not in enqueued_jobs:
+				enqueue(
+					start_import,
+					queue="default",
+					timeout=6000,
+					event="opening_invoice_creation",
+					job_name=self.name,
+					invoices=invoices,
+					now=frappe.conf.developer_mode or frappe.flags.in_test
+				)
+
+def start_import(invoices):
+	errors = 0
+	names = []
+	for idx, d in enumerate(invoices):
+		try:
+			publish(idx, len(invoices), d.doctype)
+			doc = frappe.get_doc(d)
+			doc.insert()
+			doc.submit()
+			frappe.db.commit()
+			names.append(doc.name)
+		except Exception:
+			errors += 1
+			frappe.db.rollback()
+			message = "\n".join(["Data:", dumps(d, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
+			frappe.log_error(title="Error while creating Opening Invoice", message=message)
+			frappe.db.commit()
+	if errors:
+		frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details")
+			.format(errors, "<a href='#List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
+	return names
+
+def publish(index, total, doctype):
+	if total < 5: return
+	frappe.publish_realtime(
+		"opening_invoice_creation_progress",
+		dict(
+			title=_("Opening Invoice Creation In Progress"),
+			message=_('Creating {} out of {} {}').format(index + 1, total, doctype),
+			user=frappe.session.user,
+			count=index+1,
+			total=total
+		))
 
 @frappe.whitelist()
 def get_temporary_opening_account(company=None):
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
index 3bfc10d..54229f5 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
@@ -44,7 +44,7 @@
 			0: ["_Test Supplier", 300, "Overdue"],
 			1: ["_Test Supplier 1", 250, "Overdue"],
 		}
-		self.check_expected_values(invoices, expected_value, invoice_type="Purchase", )
+		self.check_expected_values(invoices, expected_value, "Purchase")
 
 def get_opening_invoice_creation_dict(**args):
 	party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
diff --git a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.json b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.json
index 8dc2628..12e6f5e 100644
--- a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.json
+++ b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.json
@@ -1,313 +1,98 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0,
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2015-12-23 21:31:52.699821", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
+ "actions": [],
+ "creation": "2015-12-23 21:31:52.699821",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "field_order": [
+  "payment_gateway",
+  "payment_channel",
+  "is_default",
+  "column_break_4",
+  "payment_account",
+  "currency",
+  "payment_request_message",
+  "message",
+  "message_examples"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0,
-   "fieldname": "payment_gateway", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0,
+   "fieldname": "payment_gateway",
+   "fieldtype": "Link",
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Payment Gateway", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Payment Gateway", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0,
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "label": "Payment Gateway",
+   "options": "Payment Gateway",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0,
-   "fieldname": "is_default", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0,
-   "in_list_view": 0, 
-   "in_standard_filter": 0,
-   "label": "Is Default", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0,
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "default": "0",
+   "fieldname": "is_default",
+   "fieldtype": "Check",
+   "label": "Is Default"
+  },
   {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0,
-   "fieldname": "column_break_4", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0,
-   "in_list_view": 0, 
-   "in_standard_filter": 0,
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0,
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0,
-   "fieldname": "payment_account", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0,
+   "fieldname": "payment_account",
+   "fieldtype": "Link",
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Payment Account", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Account", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0,
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "label": "Payment Account",
+   "options": "Account",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0,
    "fetch_from": "payment_account.account_currency",
    "fieldname": "currency",
-   "fieldtype": "Read Only", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0,
-   "in_list_view": 0, 
-   "in_standard_filter": 0,
-   "label": "Currency", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0,
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldtype": "Read Only",
+   "label": "Currency"
+  },
   {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0,
-   "fieldname": "payment_request_message", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0,
-   "in_list_view": 0, 
-   "in_standard_filter": 0,
-   "label": "", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0,
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "depends_on": "eval: doc.payment_channel !== \"Phone\"",
+   "fieldname": "payment_request_message",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0,
-   "default": "Please click on the link below to make your payment", 
-   "fieldname": "message", 
-   "fieldtype": "Small Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0,
-   "in_list_view": 0, 
-   "in_standard_filter": 0,
-   "label": "Default Payment Request Message", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0,
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "default": "Please click on the link below to make your payment",
+   "fieldname": "message",
+   "fieldtype": "Small Text",
+   "label": "Default Payment Request Message"
+  },
   {
-   "allow_bulk_edit": 0,
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0,
-   "fieldname": "message_examples", 
-   "fieldtype": "HTML", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0,
-   "in_list_view": 0, 
-   "in_standard_filter": 0,
-   "label": "Message Examples", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "<pre><h5>Message Example</h5>\n\n&lt;p&gt; Thank You for being a part of {{ doc.company }}! We hope you are enjoying the service.&lt;/p&gt;\n\n&lt;p&gt; Please find enclosed the E Bill statement. The outstanding amount is {{ doc.grand_total }}.&lt;/p&gt;\n\n&lt;p&gt; We don't want you to be spending time running around in order to pay for your Bill.<br>After all, life is beautiful and the time you have in hand should be spent to enjoy it!<br>So here are our little ways to help you get more time for life! &lt;/p&gt;\n\n&lt;a href=\"{{ payment_url }}\"&gt; click here to pay &lt;/a&gt;\n\n</pre>\n", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0,
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
+   "fieldname": "message_examples",
+   "fieldtype": "HTML",
+   "label": "Message Examples",
+   "options": "<pre><h5>Message Example</h5>\n\n&lt;p&gt; Thank You for being a part of {{ doc.company }}! We hope you are enjoying the service.&lt;/p&gt;\n\n&lt;p&gt; Please find enclosed the E Bill statement. The outstanding amount is {{ doc.grand_total }}.&lt;/p&gt;\n\n&lt;p&gt; We don't want you to be spending time running around in order to pay for your Bill.<br>After all, life is beautiful and the time you have in hand should be spent to enjoy it!<br>So here are our little ways to help you get more time for life! &lt;/p&gt;\n\n&lt;a href=\"{{ payment_url }}\"&gt; click here to pay &lt;/a&gt;\n\n</pre>\n"
+  },
+  {
+   "default": "Email",
+   "fieldname": "payment_channel",
+   "fieldtype": "Select",
+   "label": "Payment Channel",
+   "options": "\nEmail\nPhone"
   }
- ], 
- "has_web_view": 0,
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2018-05-16 22:43:34.970491",
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Payment Gateway Account", 
- "name_case": "", 
- "owner": "Administrator", 
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-09-20 13:30:27.722852",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Gateway Account",
+ "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Accounts Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
    "write": 1
   }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0,
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js
index e1e4314..901ef19 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.js
+++ b/erpnext/accounts/doctype/payment_request/payment_request.js
@@ -25,7 +25,7 @@
 })
 
 frappe.ui.form.on("Payment Request", "refresh", function(frm) {
-	if(frm.doc.payment_request_type == 'Inward' &&
+	if(frm.doc.payment_request_type == 'Inward' && frm.doc.payment_channel !== "Phone" &&
 		!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
 		frm.add_custom_button(__('Resend Payment Email'), function(){
 			frappe.call({
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 8eadfd0..2ee356a 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -48,6 +48,7 @@
   "section_break_7",
   "payment_gateway",
   "payment_account",
+  "payment_channel",
   "payment_order",
   "amended_from"
  ],
@@ -230,6 +231,7 @@
    "label": "Recipient Message And Payment Details"
   },
   {
+   "depends_on": "eval: doc.payment_channel != \"Phone\"",
    "fieldname": "print_format",
    "fieldtype": "Select",
    "label": "Print Format"
@@ -241,6 +243,7 @@
    "label": "To"
   },
   {
+   "depends_on": "eval: doc.payment_channel != \"Phone\"",
    "fieldname": "subject",
    "fieldtype": "Data",
    "in_global_search": 1,
@@ -277,16 +280,18 @@
    "read_only": 1
   },
   {
-   "depends_on": "eval: doc.payment_request_type == 'Inward'",
+   "depends_on": "eval: doc.payment_request_type == 'Inward' || doc.payment_channel != \"Phone\"",
    "fieldname": "section_break_10",
    "fieldtype": "Section Break"
   },
   {
+   "depends_on": "eval: doc.payment_channel != \"Phone\"",
    "fieldname": "message",
    "fieldtype": "Text",
    "label": "Message"
   },
   {
+   "depends_on": "eval: doc.payment_channel != \"Phone\"",
    "fieldname": "message_examples",
    "fieldtype": "HTML",
    "label": "Message Examples",
@@ -347,12 +352,21 @@
    "options": "Payment Request",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "fetch_from": "payment_gateway_account.payment_channel",
+   "fieldname": "payment_channel",
+   "fieldtype": "Select",
+   "label": "Payment Channel",
+   "options": "\nEmail\nPhone",
+   "read_only": 1
   }
  ],
  "in_create": 1,
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-07-17 14:06:42.185763",
+ "modified": "2020-09-18 12:24:14.178853",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Request",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index e93ec95..1b97050 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -36,7 +36,7 @@
 			ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
 			if (hasattr(ref_doc, "order_type") \
 					and getattr(ref_doc, "order_type") != "Shopping Cart"):
-				ref_amount = get_amount(ref_doc)
+				ref_amount = get_amount(ref_doc, self.payment_account)
 
 				if existing_payment_request_amount + flt(self.grand_total)> ref_amount:
 					frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount")
@@ -76,11 +76,25 @@
 			or self.flags.mute_email:
 			send_mail = False
 
-		if send_mail:
+		if send_mail and self.payment_channel != "Phone":
 			self.set_payment_request_url()
 			self.send_email()
 			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)
+
 	def on_cancel(self):
 		self.check_if_payment_entry_exists()
 		self.set_as_cancelled()
@@ -105,13 +119,14 @@
 			return False
 
 	def set_payment_request_url(self):
-		if self.payment_account:
+		if self.payment_account and self.payment_channel != "Phone":
 			self.payment_url = self.get_payment_url()
 
 		if self.payment_url:
 			self.db_set('payment_url', self.payment_url)
 
-		if self.payment_url or not self.payment_gateway_account:
+		if self.payment_url or not self.payment_gateway_account \
+			or (self.payment_gateway_account and self.payment_channel == "Phone"):
 			self.db_set('status', 'Initiated')
 
 	def get_payment_url(self):
@@ -140,10 +155,14 @@
 		})
 
 	def set_as_paid(self):
-		payment_entry = self.create_payment_entry()
-		self.make_invoice()
+		if self.payment_channel == "Phone":
+			self.db_set("status", "Paid")
 
-		return payment_entry
+		else:
+			payment_entry = self.create_payment_entry()
+			self.make_invoice()
+
+			return payment_entry
 
 	def create_payment_entry(self, submit=True):
 		"""create entry"""
@@ -151,7 +170,7 @@
 
 		ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
 
-		if self.reference_doctype == "Sales Invoice":
+		if self.reference_doctype in ["Sales Invoice", "POS Invoice"]:
 			party_account = ref_doc.debit_to
 		elif self.reference_doctype == "Purchase Invoice":
 			party_account = ref_doc.credit_to
@@ -166,8 +185,8 @@
 		else:
 			party_amount = self.grand_total
 
-		payment_entry = get_payment_entry(self.reference_doctype, self.reference_name,
-			party_amount=party_amount, bank_account=self.payment_account, bank_amount=bank_amount)
+		payment_entry = get_payment_entry(self.reference_doctype, self.reference_name, party_amount=party_amount,
+			bank_account=self.payment_account, bank_amount=bank_amount)
 
 		payment_entry.update({
 			"reference_no": self.name,
@@ -255,7 +274,7 @@
 
 			# if shopping cart enabled and in session
 			if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
-				and frappe.local.session.user != "Guest"):
+				and frappe.local.session.user != "Guest") and self.payment_channel != "Phone":
 
 				success_url = shopping_cart_settings.payment_success_url
 				if success_url:
@@ -280,7 +299,9 @@
 	args = frappe._dict(args)
 
 	ref_doc = frappe.get_doc(args.dt, args.dn)
-	grand_total = get_amount(ref_doc)
+	gateway_account = get_gateway_details(args) or frappe._dict()
+
+	grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
 	if args.loyalty_points and args.dt == "Sales Order":
 		from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
 		loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points))
@@ -288,8 +309,6 @@
 		frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
 		grand_total = grand_total - loyalty_amount
 
-	gateway_account = get_gateway_details(args) or frappe._dict()
-
 	bank_account = (get_party_bank_account(args.get('party_type'), args.get('party'))
 		if args.get('party_type') else '')
 
@@ -314,9 +333,11 @@
 			"payment_gateway_account": gateway_account.get("name"),
 			"payment_gateway": gateway_account.get("payment_gateway"),
 			"payment_account": gateway_account.get("payment_account"),
+			"payment_channel": gateway_account.get("payment_channel"),
 			"payment_request_type": args.get("payment_request_type"),
 			"currency": ref_doc.currency,
 			"grand_total": grand_total,
+			"mode_of_payment": args.mode_of_payment,
 			"email_to": args.recipient_id or ref_doc.owner,
 			"subject": _("Payment Request for {0}").format(args.dn),
 			"message": gateway_account.get("message") or get_dummy_message(ref_doc),
@@ -344,7 +365,7 @@
 
 	return pr.as_dict()
 
-def get_amount(ref_doc):
+def get_amount(ref_doc, payment_account=None):
 	"""get amount based on doctype"""
 	dt = ref_doc.doctype
 	if dt in ["Sales Order", "Purchase Order"]:
@@ -356,6 +377,12 @@
 		else:
 			grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
 
+	elif dt == "POS Invoice":
+		for pay in ref_doc.payments:
+			if pay.type == "Phone" and pay.account == payment_account:
+				grand_total = pay.amount
+				break
+
 	elif dt == "Fees":
 		grand_total = ref_doc.outstanding_amount
 
@@ -366,6 +393,10 @@
 		frappe.throw(_("Payment Entry is already created"))
 
 def get_existing_payment_request_amount(ref_dt, ref_dn):
+	"""
+	Get the existing payment request which are unpaid or partially paid for payment channel other than Phone
+	and get the summation of existing paid payment request for Phone payment channel.
+	"""
 	existing_payment_request_amount = frappe.db.sql("""
 		select sum(grand_total)
 		from `tabPayment Request`
@@ -373,7 +404,9 @@
 			reference_doctype = %s
 			and reference_name = %s
 			and docstatus = 1
-			and status != 'Paid'
+			and (status != 'Paid'
+			or (payment_channel = 'Phone'
+				and status = 'Paid'))
 	""", (ref_dt, ref_dn))
 	return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
 
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index 3be4304..c43cb79 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -201,5 +201,22 @@
 			}
 			frm.set_value("loyalty_amount", loyalty_amount);
 		}
+	},
+
+	request_for_payment: function (frm) {
+		frm.save().then(() => {
+			frappe.dom.freeze();
+			frappe.call({
+				method: 'create_payment_request',
+				doc: frm.doc,
+			})
+				.fail(() => {
+					frappe.dom.unfreeze();
+					frappe.msgprint('Payment request failed');
+				})
+				.then(() => {
+					frappe.msgprint('Payment request sent successfully');
+				});
+		});
 	}
 });
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index 4780688..1cff3c6 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -279,8 +279,7 @@
    "fieldtype": "Check",
    "label": "Is Return (Credit Note)",
    "no_copy": 1,
-   "print_hide": 1,
-   "set_only_once": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break1",
@@ -461,7 +460,7 @@
   },
   {
    "fieldname": "contact_mobile",
-   "fieldtype": "Small Text",
+   "fieldtype": "Data",
    "hidden": 1,
    "label": "Mobile No",
    "read_only": 1
@@ -1579,10 +1578,9 @@
   }
  ],
  "icon": "fa fa-file-text",
- "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-09-07 12:43:09.138720",
+ "modified": "2020-09-28 16:51:24.641755",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 7229aff..5b0822e 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -15,6 +15,7 @@
 
 from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option
 from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
+from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
 
 from six import iteritems
 
@@ -57,6 +58,7 @@
 			against_psi_doc.make_loyalty_point_entry()
 		if self.redeem_loyalty_points and self.loyalty_points:
 			self.apply_loyalty_points()
+		self.check_phone_payments()
 		self.set_status(update=True)
 
 	def on_cancel(self):
@@ -69,6 +71,18 @@
 			against_psi_doc.delete_loyalty_point_entry()
 			against_psi_doc.make_loyalty_point_entry()
 
+	def check_phone_payments(self):
+		for pay in self.payments:
+			if pay.type == "Phone" and pay.amount >= 0:
+				paid_amt = frappe.db.get_value("Payment Request",
+					filters=dict(
+						reference_doctype="POS Invoice", reference_name=self.name,
+						mode_of_payment=pay.mode_of_payment, status="Paid"),
+					fieldname="grand_total")
+
+				if pay.amount != paid_amt:
+					return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
+
 	def validate_stock_availablility(self):
 		allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
 
@@ -312,6 +326,32 @@
 			if not pay.account:
 				pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
 
+	def create_payment_request(self):
+		for pay in self.payments:
+			if pay.type == "Phone":
+				if pay.amount <= 0:
+					frappe.throw(_("Payment amount cannot be less than or equal to 0"))
+
+				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
+				}
+
+				return make_payment_request(**record)
+
 @frappe.whitelist()
 def get_stock_availability(item_code, warehouse):
 	latest_sle = frappe.db.sql("""select qty_after_transaction
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
index a168cd1..8abe20c 100644
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
+++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
@@ -243,7 +243,11 @@
 						continue
 
 				if "reference_no" in am_match and "reference_no" in des_match:
-					if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]).ratio() > 70:
+					# Sequence Matcher does not handle None as input
+					am_reference = am_match["reference_no"] or ""
+					des_reference = des_match["reference_no"] or ""
+
+					if difflib.SequenceMatcher(lambda x: x == " ", am_reference, des_reference).ratio() > 70:
 						if am_match not in result:
 							result.append(am_match)
 		if result:
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 008f6e8..53677cd 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -796,7 +796,7 @@
 
 	return acc
 
-def create_payment_gateway_account(gateway):
+def create_payment_gateway_account(gateway, payment_channel="Email"):
 	from erpnext.setup.setup_wizard.operations.company_setup import create_bank_account
 
 	company = frappe.db.get_value("Global Defaults", None, "default_company")
@@ -831,7 +831,8 @@
 			"is_default": 1,
 			"payment_gateway": gateway,
 			"payment_account": bank_account.name,
-			"currency": bank_account.account_currency
+			"currency": bank_account.account_currency,
+			"payment_channel": payment_channel
 		}).insert(ignore_permissions=True)
 
 	except frappe.DuplicateEntryError:
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index a0ab2a0..618212d 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -46,26 +46,26 @@
   {
    "fieldname": "po_required",
    "fieldtype": "Select",
-   "label": "Purchase Order Required for Purchase Invoice & Receipt Creation",
+   "label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
    "options": "No\nYes"
   },
   {
    "fieldname": "pr_required",
    "fieldtype": "Select",
-   "label": "Purchase Receipt Required for Purchase Invoice Creation",
+   "label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
    "options": "No\nYes"
   },
   {
    "default": "0",
    "fieldname": "maintain_same_rate",
    "fieldtype": "Check",
-   "label": "Maintain same rate throughout purchase cycle"
+   "label": "Maintain Same Rate Throughout the Purchase Cycle"
   },
   {
    "default": "0",
    "fieldname": "allow_multiple_items",
    "fieldtype": "Check",
-   "label": "Allow Item to be added multiple times in a transaction"
+   "label": "Allow Item To Be Added Multiple Times in a Transaction"
   },
   {
    "fieldname": "subcontract",
@@ -93,9 +93,10 @@
  ],
  "icon": "fa fa-cog",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-05-15 14:49:32.513611",
+ "modified": "2020-10-13 12:00:23.276329",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
@@ -113,4 +114,4 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index 0ab39b6..f56c9b4 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -29,14 +29,12 @@
 
 	refresh: function(frm, cdt, cdn) {
 		if (frm.doc.docstatus === 1) {
-			frm.add_custom_button(__('Create'),
-				function(){ frm.trigger("make_suppplier_quotation") }, __("Supplier Quotation"));
 
-			frm.add_custom_button(__("View"),
-				function(){ frappe.set_route('List', 'Supplier Quotation',
-					{'request_for_quotation': frm.doc.name}) }, __("Supplier Quotation"));
+			frm.add_custom_button(__('Supplier Quotation'),
+				function(){ frm.trigger("make_suppplier_quotation") }, __("Create"));
 
-			frm.add_custom_button(__("Send Supplier Emails"), function() {
+
+			frm.add_custom_button(__("Send Emails to Suppliers"), function() {
 				frappe.call({
 					method: 'erpnext.buying.doctype.request_for_quotation.request_for_quotation.send_supplier_emails',
 					freeze: true,
@@ -47,150 +45,82 @@
 						frm.reload_doc();
 					}
 				});
-			});
+			}, __("Tools"));
+
+			frm.add_custom_button(__('Download PDF'), () => {
+				var suppliers = [];
+				const fields = [{
+					fieldtype: 'Link',
+					label: __('Select a Supplier'),
+					fieldname: 'supplier',
+					options: 'Supplier',
+					reqd: 1,
+					get_query: () => {
+						return {
+							filters: [
+								["Supplier", "name", "in", frm.doc.suppliers.map((row) => {return row.supplier;})]
+							]
+						}
+					}
+				}];
+
+				frappe.prompt(fields, data => {
+					var child = locals[cdt][cdn]
+
+					var w = window.open(
+						frappe.urllib.get_full_url("/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?"
+						+"doctype="+encodeURIComponent(frm.doc.doctype)
+						+"&name="+encodeURIComponent(frm.doc.name)
+						+"&supplier="+encodeURIComponent(data.supplier)
+						+"&no_letterhead=0"));
+					if(!w) {
+						frappe.msgprint(__("Please enable pop-ups")); return;
+					}
+				},
+				'Download PDF for Supplier',
+				'Download');
+			},
+			__("Tools"));
+
+			frm.page.set_inner_btn_group_as_primary(__('Create'));
 		}
 
 	},
 
-	get_suppliers_button: function (frm) {
-		var doc = frm.doc;
-		var dialog = new frappe.ui.Dialog({
-			title: __("Get Suppliers"),
-			fields: [
-				{
-					"fieldtype": "Select", "label": __("Get Suppliers By"),
-					"fieldname": "search_type",
-					"options": ["Tag","Supplier Group"],
-					"reqd": 1,
-					onchange() {
-						if(dialog.get_value('search_type') == 'Tag'){
-							frappe.call({
-								method: 'erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_tag',
-							}).then(r => {
-								dialog.set_df_property("tag", "options", r.message)
-						});
-						}
-					}
-				},
-				{
-					"fieldtype": "Link", "label": __("Supplier Group"),
-					"fieldname": "supplier_group",
-					"options": "Supplier Group",
-					"reqd": 0,
-					"depends_on": "eval:doc.search_type == 'Supplier Group'"
-				},
-				{
-					"fieldtype": "Select", "label": __("Tag"),
-					"fieldname": "tag",
-					"reqd": 0,
-					"depends_on": "eval:doc.search_type == 'Tag'",
-				},
-				{
-					"fieldtype": "Button", "label": __("Add All Suppliers"),
-					"fieldname": "add_suppliers"
-				},
-			]
-		});
-
-		dialog.fields_dict.add_suppliers.$input.click(function() {
-			var args = dialog.get_values();
-			if(!args) return;
-			dialog.hide();
-
-			//Remove blanks
-			for (var j = 0; j < frm.doc.suppliers.length; j++) {
-				if(!frm.doc.suppliers[j].hasOwnProperty("supplier")) {
-					frm.get_field("suppliers").grid.grid_rows[j].remove();
-				}
-			}
-
-			 function load_suppliers(r) {
-				if(r.message) {
-					for (var i = 0; i < r.message.length; i++) {
-						var exists = false;
-						if (r.message[i].constructor === Array){
-							var supplier = r.message[i][0];
-						} else {
-							var supplier = r.message[i].name;
-						}
-
-						for (var j = 0; j < doc.suppliers.length;j++) {
-							if (supplier === doc.suppliers[j].supplier) {
-								exists = true;
-							}
-						}
-						if(!exists) {
-							var d = frm.add_child('suppliers');
-							d.supplier = supplier;
-							frm.script_manager.trigger("supplier", d.doctype, d.name);
-						}
-					}
-				}
-				frm.refresh_field("suppliers");
-			}
-
-			if (args.search_type === "Tag" && args.tag) {
-				return frappe.call({
-					type: "GET",
-					method: "frappe.desk.doctype.tag.tag.get_tagged_docs",
-					args: {
-						"doctype": "Supplier",
-						"tag": args.tag
-					},
-					callback: load_suppliers
-				});
-			} else if (args.supplier_group) {
-				return frappe.call({
-					method: "frappe.client.get_list",
-					args: {
-						doctype: "Supplier",
-						order_by: "name",
-						fields: ["name"],
-						filters: [["Supplier", "supplier_group", "=", args.supplier_group]]
-
-					},
-					callback: load_suppliers
-				});
-			}
-		});
-		dialog.show();
-
-	},
 	make_suppplier_quotation: function(frm) {
 		var doc = frm.doc;
 		var dialog = new frappe.ui.Dialog({
-			title: __("For Supplier"),
+			title: __("Create Supplier Quotation"),
 			fields: [
 				{	"fieldtype": "Select", "label": __("Supplier"),
 					"fieldname": "supplier",
 					"options": doc.suppliers.map(d => d.supplier),
 					"reqd": 1,
 					"default": doc.suppliers.length === 1 ? doc.suppliers[0].supplier_name : "" },
-				{	"fieldtype": "Button", "label": __('Create Supplier Quotation'),
-					"fieldname": "make_supplier_quotation", "cssClass": "btn-primary" },
-			]
+			],
+			primary_action_label: __("Create"),
+			primary_action: (args) => {
+				if(!args) return;
+				dialog.hide();
+
+				return frappe.call({
+					type: "GET",
+					method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
+					args: {
+						"source_name": doc.name,
+						"for_supplier": args.supplier
+					},
+					freeze: true,
+					callback: function(r) {
+						if(!r.exc) {
+							var doc = frappe.model.sync(r.message);
+							frappe.set_route("Form", r.message.doctype, r.message.name);
+						}
+					}
+				});
+			}
 		});
 
-		dialog.fields_dict.make_supplier_quotation.$input.click(function() {
-			var args = dialog.get_values();
-			if(!args) return;
-			dialog.hide();
-			return frappe.call({
-				type: "GET",
-				method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
-				args: {
-					"source_name": doc.name,
-					"for_supplier": args.supplier
-				},
-				freeze: true,
-				callback: function(r) {
-					if(!r.exc) {
-						var doc = frappe.model.sync(r.message);
-						frappe.set_route("Form", r.message.doctype, r.message.name);
-					}
-				}
-			});
-		});
 		dialog.show()
 	},
 
@@ -273,42 +203,6 @@
 		})
 	},
 
-	download_pdf: function(frm, cdt, cdn) {
-		var child = locals[cdt][cdn]
-
-		var w = window.open(
-			frappe.urllib.get_full_url("/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?"
-			+"doctype="+encodeURIComponent(frm.doc.doctype)
-			+"&name="+encodeURIComponent(frm.doc.name)
-			+"&supplier_idx="+encodeURIComponent(child.idx)
-			+"&no_letterhead=0"));
-		if(!w) {
-			frappe.msgprint(__("Please enable pop-ups")); return;
-		}
-	},
-	no_quote: function(frm, cdt, cdn) {
-		var d = locals[cdt][cdn];
-		if (d.no_quote) {
-			if (d.quote_status != __('Received')) {
-				frappe.model.set_value(cdt, cdn, 'quote_status', 'No Quote');
-			} else {
-				frappe.msgprint(__("Cannot set a received RFQ to No Quote"));
-				frappe.model.set_value(cdt, cdn, 'no_quote', 0);
-			}
-		} else {
-			d.quote_status = __('Pending');
-			frm.call({
-				method:"update_rfq_supplier_status",
-				doc: frm.doc,
-				args: {
-					sup_name: d.supplier
-				},
-				callback: function(r) {
-					frm.refresh_field("suppliers");
-				}
-			});
-		}
-	}
 })
 
 erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.extend({
@@ -332,7 +226,8 @@
 							per_ordered: ["<", 99.99]
 						}
 					})
-				}, __("Get items from"));
+				}, __("Get Items From"));
+
 			// Get items from Opportunity
 			this.frm.add_custom_button(__('Opportunity'),
 				function() {
@@ -344,7 +239,8 @@
 							company: me.frm.doc.company
 						},
 					})
-				}, __("Get items from"));
+				}, __("Get Items From"));
+
 			// Get items from open Material Requests based on supplier
 			this.frm.add_custom_button(__('Possible Supplier'), function() {
 				// Create a dialog window for the user to pick their supplier
@@ -382,8 +278,13 @@
 					}
 				}
 				d.show();
-			}, __("Get items from"));
+			}, __("Get Items From"));
 
+			// Get Suppliers
+			this.frm.add_custom_button(__('Get Suppliers'),
+				function() {
+					me.get_suppliers_button(me.frm);
+				});
 		}
 	},
 
@@ -393,9 +294,108 @@
 
 	tc_name: function() {
 		this.get_terms();
-	}
-});
+	},
 
+	get_suppliers_button: function (frm) {
+		var doc = frm.doc;
+		var dialog = new frappe.ui.Dialog({
+			title: __("Get Suppliers"),
+			fields: [
+				{
+					"fieldtype": "Select", "label": __("Get Suppliers By"),
+					"fieldname": "search_type",
+					"options": ["Tag","Supplier Group"],
+					"reqd": 1,
+					onchange() {
+						if(dialog.get_value('search_type') == 'Tag'){
+							frappe.call({
+								method: 'erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_tag',
+							}).then(r => {
+								dialog.set_df_property("tag", "options", r.message)
+						});
+						}
+					}
+				},
+				{
+					"fieldtype": "Link", "label": __("Supplier Group"),
+					"fieldname": "supplier_group",
+					"options": "Supplier Group",
+					"reqd": 0,
+					"depends_on": "eval:doc.search_type == 'Supplier Group'"
+				},
+				{
+					"fieldtype": "Select", "label": __("Tag"),
+					"fieldname": "tag",
+					"reqd": 0,
+					"depends_on": "eval:doc.search_type == 'Tag'",
+				}
+			],
+			primary_action_label: __("Add Suppliers"),
+			primary_action : (args) => {
+				if(!args) return;
+				dialog.hide();
+
+				//Remove blanks
+				for (var j = 0; j < frm.doc.suppliers.length; j++) {
+					if(!frm.doc.suppliers[j].hasOwnProperty("supplier")) {
+						frm.get_field("suppliers").grid.grid_rows[j].remove();
+					}
+				}
+
+				function load_suppliers(r) {
+					if(r.message) {
+						for (var i = 0; i < r.message.length; i++) {
+							var exists = false;
+							if (r.message[i].constructor === Array){
+								var supplier = r.message[i][0];
+							} else {
+								var supplier = r.message[i].name;
+							}
+
+							for (var j = 0; j < doc.suppliers.length;j++) {
+								if (supplier === doc.suppliers[j].supplier) {
+									exists = true;
+								}
+							}
+							if(!exists) {
+								var d = frm.add_child('suppliers');
+								d.supplier = supplier;
+								frm.script_manager.trigger("supplier", d.doctype, d.name);
+							}
+						}
+					}
+					frm.refresh_field("suppliers");
+				}
+
+				if (args.search_type === "Tag" && args.tag) {
+					return frappe.call({
+						type: "GET",
+						method: "frappe.desk.doctype.tag.tag.get_tagged_docs",
+						args: {
+							"doctype": "Supplier",
+							"tag": args.tag
+						},
+						callback: load_suppliers
+					});
+				} else if (args.supplier_group) {
+					return frappe.call({
+						method: "frappe.client.get_list",
+						args: {
+							doctype: "Supplier",
+							order_by: "name",
+							fields: ["name"],
+							filters: [["Supplier", "supplier_group", "=", args.supplier_group]]
+
+						},
+						callback: load_suppliers
+					});
+				}
+			}
+		});
+
+		dialog.show();
+	},
+});
 
 // for backward compatibility: combine new and previous states
 $.extend(cur_frm.cscript, new erpnext.buying.RequestforQuotationController({frm: cur_frm}));
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index 5f01f6e..4e09a7e 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -12,9 +12,10 @@
   "vendor",
   "column_break1",
   "transaction_date",
+  "status",
+  "amended_from",
   "suppliers_section",
   "suppliers",
-  "get_suppliers_button",
   "items_section",
   "items",
   "link_to_mrs",
@@ -31,11 +32,7 @@
   "terms",
   "printing_settings",
   "select_print_heading",
-  "letter_head",
-  "more_info",
-  "status",
-  "column_break3",
-  "amended_from"
+  "letter_head"
  ],
  "fields": [
   {
@@ -83,6 +80,7 @@
    "width": "50%"
   },
   {
+   "default": "Today",
    "fieldname": "transaction_date",
    "fieldtype": "Date",
    "in_list_view": 1,
@@ -99,17 +97,12 @@
   {
    "fieldname": "suppliers",
    "fieldtype": "Table",
-   "label": "Supplier Detail",
+   "label": "Suppliers",
    "options": "Request for Quotation Supplier",
    "print_hide": 1,
    "reqd": 1
   },
   {
-   "fieldname": "get_suppliers_button",
-   "fieldtype": "Button",
-   "label": "Get Suppliers"
-  },
-  {
    "fieldname": "items_section",
    "fieldtype": "Section Break",
    "oldfieldtype": "Section Break",
@@ -144,6 +137,7 @@
    "print_hide": 1
   },
   {
+   "allow_on_submit": 1,
    "fetch_from": "email_template.response",
    "fetch_if_empty": 1,
    "fieldname": "message_for_supplier",
@@ -207,14 +201,6 @@
    "print_hide": 1
   },
   {
-   "collapsible": 1,
-   "fieldname": "more_info",
-   "fieldtype": "Section Break",
-   "label": "More Information",
-   "oldfieldtype": "Section Break",
-   "options": "fa fa-file-text"
-  },
-  {
    "fieldname": "status",
    "fieldtype": "Select",
    "label": "Status",
@@ -228,10 +214,6 @@
    "search_index": 1
   },
   {
-   "fieldname": "column_break3",
-   "fieldtype": "Column Break"
-  },
-  {
    "fieldname": "amended_from",
    "fieldtype": "Link",
    "label": "Amended From",
@@ -275,9 +257,10 @@
   }
  ],
  "icon": "fa fa-shopping-cart",
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-01 14:54:50.888729",
+ "modified": "2020-10-16 17:49:09.561929",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Request for Quotation",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 7784a7b..a51498e 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -28,6 +28,10 @@
 		super(RequestforQuotation, self).set_qty_as_per_stock_uom()
 		self.update_email_id()
 
+		if self.docstatus < 1:
+			# after amend and save, status still shows as cancelled, until submit
+			frappe.db.set(self, 'status', 'Draft')
+
 	def validate_duplicate_supplier(self):
 		supplier_list = [d.supplier for d in self.suppliers]
 		if len(supplier_list) != len(set(supplier_list)):
@@ -82,7 +86,7 @@
 				# make new user if required
 				update_password_link, contact = self.update_supplier_contact(rfq_supplier, self.get_link())
 
-				self.update_supplier_part_no(rfq_supplier)
+				self.update_supplier_part_no(rfq_supplier.supplier)
 				self.supplier_rfq_mail(rfq_supplier, update_password_link, self.get_link())
 				rfq_supplier.email_sent = 1
 				if not rfq_supplier.contact:
@@ -93,11 +97,11 @@
 		# RFQ link for supplier portal
 		return get_url("/rfq/" + self.name)
 
-	def update_supplier_part_no(self, args):
-		self.vendor = args.supplier
+	def update_supplier_part_no(self, supplier):
+		self.vendor = supplier
 		for item in self.items:
 			item.supplier_part_no = frappe.db.get_value('Item Supplier',
-				{'parent': item.item_code, 'supplier': args.supplier}, 'supplier_part_no')
+				{'parent': item.item_code, 'supplier': supplier}, 'supplier_part_no')
 
 	def update_supplier_contact(self, rfq_supplier, link):
 		'''Create a new user for the supplier if not set in contact'''
@@ -197,23 +201,22 @@
 	def update_rfq_supplier_status(self, sup_name=None):
 		for supplier in self.suppliers:
 			if sup_name == None or supplier.supplier == sup_name:
-				if supplier.quote_status != _('No Quote'):
-					quote_status = _('Received')
-					for item in self.items:
-						sqi_count = frappe.db.sql("""
-							SELECT
-								COUNT(sqi.name) as count
-							FROM
-								`tabSupplier Quotation Item` as sqi,
-								`tabSupplier Quotation` as sq
-							WHERE sq.supplier = %(supplier)s
-								AND sqi.docstatus = 1
-								AND sqi.request_for_quotation_item = %(rqi)s
-								AND sqi.parent = sq.name""",
-							{"supplier": supplier.supplier, "rqi": item.name}, as_dict=1)[0]
-						if (sqi_count.count) == 0:
-							quote_status = _('Pending')
-					supplier.quote_status = quote_status
+				quote_status = _('Received')
+				for item in self.items:
+					sqi_count = frappe.db.sql("""
+						SELECT
+							COUNT(sqi.name) as count
+						FROM
+							`tabSupplier Quotation Item` as sqi,
+							`tabSupplier Quotation` as sq
+						WHERE sq.supplier = %(supplier)s
+							AND sqi.docstatus = 1
+							AND sqi.request_for_quotation_item = %(rqi)s
+							AND sqi.parent = sq.name""",
+						{"supplier": supplier.supplier, "rqi": item.name}, as_dict=1)[0]
+					if (sqi_count.count) == 0:
+						quote_status = _('Pending')
+				supplier.quote_status = quote_status
 
 
 @frappe.whitelist()
@@ -322,16 +325,15 @@
 	})
 
 @frappe.whitelist()
-def get_pdf(doctype, name, supplier_idx):
-	doc = get_rfq_doc(doctype, name, supplier_idx)
+def get_pdf(doctype, name, supplier):
+	doc = get_rfq_doc(doctype, name, supplier)
 	if doc:
 		download_pdf(doctype, name, doc=doc)
 
-def get_rfq_doc(doctype, name, supplier_idx):
-	if cint(supplier_idx):
+def get_rfq_doc(doctype, name, supplier):
+	if supplier:
 		doc = frappe.get_doc(doctype, name)
-		args = doc.get('suppliers')[cint(supplier_idx) - 1]
-		doc.update_supplier_part_no(args)
+		doc.update_supplier_part_no(supplier)
 		return doc
 
 @frappe.whitelist()
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index ea38129..36f87b0 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -25,14 +25,10 @@
 		sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier)
 		sq.submit()
 
-		# No Quote first supplier quotation
-		rfq.get('suppliers')[1].no_quote = 1
-		rfq.get('suppliers')[1].quote_status = 'No Quote'
-
 		rfq.update_rfq_supplier_status() #rfq.get('suppliers')[1].supplier)
 
 		self.assertEqual(rfq.get('suppliers')[0].quote_status, 'Received')
-		self.assertEqual(rfq.get('suppliers')[1].quote_status, 'No Quote')
+		self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending')
 
 	def test_make_supplier_quotation(self):
 		rfq = make_request_for_quotation()
diff --git a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js
index 1a9cd35..2e1652d 100644
--- a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js
+++ b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js
@@ -84,9 +84,6 @@
 			cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
 		},
 		() => frappe.timeout(1),
-		() => {
-			frappe.click_check('No Quote');
-		},
 		() => frappe.timeout(1),
 		() => {
 			cur_frm.cur_grid.toggle_view();
@@ -125,7 +122,6 @@
 		() => frappe.timeout(1),
 		() => {
 			assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.quote_status == "Received");
-			assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.no_quote == 1);
 		},
 		() => done()
 	]);
diff --git a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
index 408f49f..e07f462 100644
--- a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
+++ b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
@@ -27,10 +27,11 @@
   "stock_qty",
   "warehouse_and_reference",
   "warehouse",
-  "project_name",
   "col_break4",
   "material_request",
   "material_request_item",
+  "section_break_24",
+  "project_name",
   "section_break_23",
   "page_break"
  ],
@@ -161,7 +162,7 @@
   {
    "fieldname": "project_name",
    "fieldtype": "Link",
-   "label": "Project Name",
+   "label": "Project",
    "options": "Project",
    "print_hide": 1
   },
@@ -249,11 +250,18 @@
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "section_break_24",
+   "fieldtype": "Section Break",
+   "label": "Accounting Dimensions"
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-06-12 19:10:36.333441",
+ "modified": "2020-09-24 17:26:46.276934",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Request for Quotation Item",
diff --git a/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json b/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json
index ce9316f..96d7e2d 100644
--- a/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json
+++ b/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json
@@ -9,19 +9,19 @@
   "email_sent",
   "supplier",
   "contact",
-  "no_quote",
   "quote_status",
   "column_break_3",
   "supplier_name",
-  "email_id",
-  "download_pdf"
+  "email_id"
  ],
  "fields": [
   {
    "allow_on_submit": 1,
+   "columns": 2,
    "default": "1",
    "fieldname": "send_email",
    "fieldtype": "Check",
+   "in_list_view": 1,
    "label": "Send Email"
   },
   {
@@ -35,7 +35,7 @@
    "read_only": 1
   },
   {
-   "columns": 4,
+   "columns": 2,
    "fieldname": "supplier",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -45,7 +45,7 @@
   },
   {
    "allow_on_submit": 1,
-   "columns": 3,
+   "columns": 2,
    "fieldname": "contact",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -55,19 +55,11 @@
   },
   {
    "allow_on_submit": 1,
-   "default": "0",
-   "depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'",
-   "fieldname": "no_quote",
-   "fieldtype": "Check",
-   "label": "No Quote"
-  },
-  {
-   "allow_on_submit": 1,
-   "depends_on": "eval:doc.docstatus >= 1 && !doc.no_quote",
+   "depends_on": "eval:doc.docstatus >= 1",
    "fieldname": "quote_status",
    "fieldtype": "Select",
    "label": "Quote Status",
-   "options": "Pending\nReceived\nNo Quote",
+   "options": "Pending\nReceived",
    "read_only": 1
   },
   {
@@ -90,17 +82,12 @@
    "in_list_view": 1,
    "label": "Email Id",
    "no_copy": 1
-  },
-  {
-   "allow_on_submit": 1,
-   "fieldname": "download_pdf",
-   "fieldtype": "Button",
-   "label": "Download PDF"
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-28 19:31:11.855588",
+ "modified": "2020-10-16 12:23:41.769820",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Request for Quotation Supplier",
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index baf2457..ae5611f 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -91,12 +91,7 @@
 					for my_item in self.items) if include_me else 0
 				if (sqi_count.count + self_count) == 0:
 					quote_status = _('Pending')
-			if quote_status == _('Received') and doc_sup.quote_status == _('No Quote'):
-				frappe.msgprint(_("{0} indicates that {1} will not provide a quotation, but all items \
-					have been quoted. Updating the RFQ quote status.").format(doc.name, self.supplier))
-				frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
-				frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'no_quote', 0)
-			elif doc_sup.quote_status != _('No Quote'):
+
 				frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
 
 def get_list_context(context=None):
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/__init__.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/__init__.py
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html b/erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html
new file mode 100644
index 0000000..2c4d4bb
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html
@@ -0,0 +1,28 @@
+
+{% if not jQuery.isEmptyObject(data) %}
+<h5 style="margin-top: 20px;"> {{ __("Balance Details") }} </h5>
+<table class="table table-bordered small">
+	<thead>
+		<tr>
+			<th style="width: 20%">{{ __("Account Type") }}</th>
+			<th style="width: 20%" class="text-right">{{ __("Current Balance") }}</th>
+			<th style="width: 20%" class="text-right">{{ __("Available Balance") }}</th>
+			<th style="width: 20%" class="text-right">{{ __("Reserved Balance") }}</th>
+			<th style="width: 20%" class="text-right">{{ __("Uncleared Balance") }}</th>
+		</tr>
+	</thead>
+	<tbody>
+		{% for(const [key, value] of Object.entries(data)) { %}
+			<tr>
+				<td> {%= key %} </td>
+				<td class="text-right"> {%= value["current_balance"] %} </td>
+				<td class="text-right"> {%= value["available_balance"] %} </td>
+				<td class="text-right"> {%= value["reserved_balance"] %} </td>
+				<td class="text-right"> {%= value["uncleared_balance"] %} </td>
+			</tr>
+		{% } %}
+	</tbody>
+</table>
+{% else %}
+<p style="margin-top: 30px;"> Account Balance Information Not Available. </p>
+{% endif %}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py
new file mode 100644
index 0000000..d33b0a7
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py
@@ -0,0 +1,118 @@
+import base64
+import requests
+from requests.auth import HTTPBasicAuth
+import datetime
+
+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"):
+		"""Setup configuration for Mpesa connector and generate new access token."""
+		self.env = env
+		self.app_key = app_key
+		self.app_secret = app_secret
+		if env == "sandbox":
+			self.base_url = sandbox_url
+		else:
+			self.base_url = live_url
+		self.authenticate()
+
+	def authenticate(self):
+		"""
+		This method is used to fetch the access token required by Mpesa.
+
+		Returns:
+			access_token (str): This token is to be used with the Bearer header for further API calls to Mpesa.
+		"""
+		authenticate_uri = "/oauth/v1/generate?grant_type=client_credentials"
+		authenticate_url = "{0}{1}".format(self.base_url, authenticate_uri)
+		r = requests.get(
+			authenticate_url,
+			auth=HTTPBasicAuth(self.app_key, self.app_secret)
+		)
+		self.authentication_token = r.json()['access_token']
+		return r.json()['access_token']
+
+	def get_balance(self, initiator=None, security_credential=None, party_a=None, identifier_type=None,
+					remarks=None, queue_timeout_url=None,result_url=None):
+		"""
+		This method uses Mpesa's Account Balance API to to enquire the balance on a M-Pesa BuyGoods (Till Number).
+
+		Args:
+			initiator (str): Username used to authenticate the transaction.
+			security_credential (str): Generate from developer portal.
+			command_id (str): AccountBalance.
+			party_a (int): Till number being queried.
+			identifier_type (int): Type of organization receiving the transaction. (MSISDN/Till Number/Organization short code)
+			remarks (str): Comments that are sent along with the transaction(maximum 100 characters).
+			queue_timeout_url (str): The url that handles information of timed out transactions.
+			result_url (str): The url that receives results from M-Pesa api call.
+
+		Returns:
+			OriginatorConverstionID (str): The unique request ID for tracking a transaction.
+			ConversationID (str): The unique request ID returned by mpesa for each request made
+			ResponseDescription (str): Response Description message
+		"""
+
+		payload = {
+			"Initiator": initiator,
+			"SecurityCredential": security_credential,
+			"CommandID": "AccountBalance",
+			"PartyA": party_a,
+			"IdentifierType": identifier_type,
+			"Remarks": remarks,
+			"QueueTimeOutURL": queue_timeout_url,
+			"ResultURL": result_url
+		}
+		headers = {'Authorization': 'Bearer {0}'.format(self.authentication_token), 'Content-Type': "application/json"}
+		saf_url = "{0}{1}".format(self.base_url, "/mpesa/accountbalance/v1/query")
+		r = requests.post(saf_url, headers=headers, json=payload)
+		return r.json()
+
+	def stk_push(self, business_shortcode=None, passcode=None, amount=None, callback_url=None, reference_code=None,
+				 phone_number=None, description=None):
+		"""
+		This method uses Mpesa's Express API to initiate online payment on behalf of a customer.
+
+		Args:
+			business_shortcode (int): The short code of the organization.
+			passcode (str): Get from developer portal
+			amount (int): The amount being transacted
+			callback_url (str): A CallBack URL is a valid secure URL that is used to receive notifications from M-Pesa API.
+			reference_code(str): Account Reference: This is an Alpha-Numeric parameter that is defined by your system as an Identifier of the transaction for CustomerPayBillOnline transaction type.
+			phone_number(int): The Mobile Number to receive the STK Pin Prompt.
+			description(str): This is any additional information/comment that can be sent along with the request from your system. MAX 13 characters
+
+		Success Response:
+			CustomerMessage(str): Messages that customers can understand.
+			CheckoutRequestID(str): This is a global unique identifier of the processed checkout transaction request.
+			ResponseDescription(str): Describes Success or failure
+			MerchantRequestID(str): This is a global unique Identifier for any submitted payment request.
+			ResponseCode(int): 0 means success all others are error codes. e.g.404.001.03
+
+		Error Reponse:
+			requestId(str): This is a unique requestID for the payment request
+			errorCode(str): This is a predefined code that indicates the reason for request failure.
+			errorMessage(str): This is a predefined code that indicates the reason for request failure.
+		"""
+
+		time = str(datetime.datetime.now()).split(".")[0].replace("-", "").replace(" ", "").replace(":", "")
+		password = "{0}{1}{2}".format(str(business_shortcode), str(passcode), time)
+		encoded = base64.b64encode(bytes(password, encoding='utf8'))
+		payload = {
+			"BusinessShortCode": business_shortcode,
+			"Password": encoded.decode("utf-8"),
+			"Timestamp": time,
+			"TransactionType": "CustomerPayBillOnline",
+			"Amount": amount,
+			"PartyA": int(phone_number),
+			"PartyB": business_shortcode,
+			"PhoneNumber": int(phone_number),
+			"CallBackURL": callback_url,
+			"AccountReference": reference_code,
+			"TransactionDesc": description
+		}
+		headers = {'Authorization': 'Bearer {0}'.format(self.authentication_token), 'Content-Type': "application/json"}
+
+		saf_url = "{0}{1}".format(self.base_url, "/mpesa/stkpush/v1/processrequest")
+		r = requests.post(saf_url, headers=headers, json=payload)
+		return r.json()
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py
new file mode 100644
index 0000000..0499e88
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py
@@ -0,0 +1,53 @@
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+def create_custom_pos_fields():
+	"""Create custom fields corresponding to POS Settings and POS Invoice."""
+	pos_field = {
+		"POS Invoice": [
+			{
+				"fieldname": "request_for_payment",
+				"label": "Request for Payment",
+				"fieldtype": "Button",
+				"hidden": 1,
+				"insert_after": "contact_email"
+			},
+			{
+				"fieldname": "mpesa_receipt_number",
+				"label": "Mpesa Receipt Number",
+				"fieldtype": "Data",
+				"read_only": 1,
+				"insert_after": "company"
+			}
+		]
+	}
+	if not frappe.get_meta("POS Invoice").has_field("request_for_payment"):
+		create_custom_fields(pos_field)
+
+	record_dict = [{
+			"doctype": "POS Field",
+			"fieldname": "contact_mobile",
+			"label": "Mobile No",
+			"fieldtype": "Data",
+			"options": "Phone",
+			"parenttype": "POS Settings",
+			"parent": "POS Settings",
+			"parentfield": "invoice_fields"
+		},
+		{
+			"doctype": "POS Field",
+			"fieldname": "request_for_payment",
+			"label": "Request for Payment",
+			"fieldtype": "Button",
+			"parenttype": "POS Settings",
+			"parent": "POS Settings",
+			"parentfield": "invoice_fields"
+		}
+	]
+	create_pos_settings(record_dict)
+
+def create_pos_settings(record_dict):
+	for record in record_dict:
+		if frappe.db.exists("POS Field", {"fieldname": record.get("fieldname")}):
+			continue
+		frappe.get_doc(record).insert()
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js
new file mode 100644
index 0000000..636aa99
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js
@@ -0,0 +1,36 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Mpesa Settings', {
+	onload_post_render: function(frm) {
+		frm.events.setup_account_balance_html(frm);
+	},
+
+	refresh: function(frm) {
+		frappe.realtime.on("refresh_mpesa_dashboard", function(){
+			frm.reload_doc();
+		});
+	},
+
+	get_account_balance: function(frm) {
+		if (!frm.initiator_name && !frm.security_credentials) {
+			frappe.throw(__("Please set the initiator name and the security credential"));
+		}
+		frappe.call({
+			method: "get_account_balance_info",
+			doc: frm.doc
+		});
+	},
+
+	setup_account_balance_html: function(frm) {
+		if (!frm.doc.account_balance) return;
+		$("div").remove(".form-dashboard-section.custom");
+		frm.dashboard.add_section(
+			frappe.render_template('account_balance', {
+				data: JSON.parse(frm.doc.account_balance)
+			})
+		);
+		frm.dashboard.show();
+	}
+
+});
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json
new file mode 100644
index 0000000..fc7b310
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json
@@ -0,0 +1,135 @@
+{
+ "actions": [],
+ "autoname": "field:payment_gateway_name",
+ "creation": "2020-09-10 13:21:27.398088",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "payment_gateway_name",
+  "consumer_key",
+  "consumer_secret",
+  "initiator_name",
+  "till_number",
+  "sandbox",
+  "column_break_4",
+  "online_passkey",
+  "security_credential",
+  "get_account_balance",
+  "account_balance"
+ ],
+ "fields": [
+  {
+   "fieldname": "payment_gateway_name",
+   "fieldtype": "Data",
+   "label": "Payment Gateway Name",
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "consumer_key",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Consumer Key",
+   "reqd": 1
+  },
+  {
+   "fieldname": "consumer_secret",
+   "fieldtype": "Password",
+   "in_list_view": 1,
+   "label": "Consumer Secret",
+   "reqd": 1
+  },
+  {
+   "fieldname": "till_number",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Till Number",
+   "reqd": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "sandbox",
+   "fieldtype": "Check",
+   "label": "Sandbox"
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "online_passkey",
+   "fieldtype": "Password",
+   "label": " Online PassKey",
+   "reqd": 1
+  },
+  {
+   "fieldname": "initiator_name",
+   "fieldtype": "Data",
+   "label": "Initiator Name"
+  },
+  {
+   "fieldname": "security_credential",
+   "fieldtype": "Small Text",
+   "label": "Security Credential"
+  },
+  {
+   "fieldname": "account_balance",
+   "fieldtype": "Long Text",
+   "hidden": 1,
+   "label": "Account Balance",
+   "read_only": 1
+  },
+  {
+   "fieldname": "get_account_balance",
+   "fieldtype": "Button",
+   "label": "Get Account Balance"
+  }
+ ],
+ "links": [],
+ "modified": "2020-09-25 20:21:38.215494",
+ "modified_by": "Administrator",
+ "module": "ERPNext Integrations",
+ "name": "Mpesa Settings",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "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
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
new file mode 100644
index 0000000..dea4d81
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+
+from __future__ import unicode_literals
+from json import loads, dumps
+
+import frappe
+from frappe.model.document import Document
+from frappe import _
+from frappe.utils import call_hook_method, fmt_money
+from frappe.integrations.utils import create_request_log, create_payment_gateway
+from frappe.utils import get_request_site_address
+from erpnext.erpnext_integrations.utils import create_mode_of_payment
+from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_connector import MpesaConnector
+from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_custom_fields import create_custom_pos_fields
+
+class MpesaSettings(Document):
+	supported_currencies = ["KES"]
+
+	def validate_transaction_currency(self, currency):
+		if currency not in self.supported_currencies:
+			frappe.throw(_("Please select another payment method. Mpesa does not support transactions in currency '{0}'").format(currency))
+
+	def on_update(self):
+		create_custom_pos_fields()
+		create_payment_gateway('Mpesa-' + self.payment_gateway_name, settings='Mpesa Settings', controller=self.payment_gateway_name)
+		call_hook_method('payment_gateway_enabled', gateway='Mpesa-' + self.payment_gateway_name, payment_channel="Phone")
+
+		# required to fetch the bank account details from the payment gateway account
+		frappe.db.commit()
+		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))
+
+		self.handle_api_response("CheckoutRequestID", kwargs, response)
+
+	def get_account_balance_info(self):
+		payload = dict(
+			reference_doctype="Mpesa Settings",
+			reference_docname=self.name,
+			doc_details=vars(self)
+		)
+
+		if frappe.flags.in_test:
+			from erpnext.erpnext_integrations.doctype.mpesa_settings.test_mpesa_settings import get_test_account_balance_response
+			response = frappe._dict(get_test_account_balance_response())
+		else:
+			response = frappe._dict(get_account_balance(payload))
+
+		self.handle_api_response("ConversationID", payload, response)
+
+	def handle_api_response(self, global_id, request_dict, response):
+		"""Response received from API calls returns a global identifier for each transaction, this code is returned during the callback."""
+		# check error response
+		if getattr(response, "requestId"):
+			req_name = getattr(response, "requestId")
+			error = response
+		else:
+			# global checkout id used as request name
+			req_name = getattr(response, global_id)
+			error = None
+
+		create_request_log(request_dict, "Host", "Mpesa", req_name, error)
+
+		if error:
+			frappe.throw(_(getattr(response, "errorMessage")), title=_("Transaction Error"))
+
+def generate_stk_push(**kwargs):
+	"""Generate stk push by making a API call to the stk push API."""
+	args = frappe._dict(kwargs)
+	try:
+		callback_url = get_request_site_address(True) + "/api/method/erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings.verify_transaction"
+
+		mpesa_settings = frappe.get_doc("Mpesa Settings", args.payment_gateway[6:])
+		env = "production" if not mpesa_settings.sandbox else "sandbox"
+
+		connector = MpesaConnector(env=env,
+			app_key=mpesa_settings.consumer_key,
+			app_secret=mpesa_settings.get_password("consumer_secret"))
+
+		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,
+			callback_url=callback_url, reference_code=mpesa_settings.till_number,
+			phone_number=mobile_number, description="POS Payment")
+
+		return response
+
+	except Exception:
+		frappe.log_error(title=_("Mpesa Express Transaction Error"))
+		frappe.throw(_("Issue detected with Mpesa configuration, check the error logs for more details"), title=_("Mpesa Express Error"))
+
+def sanitize_mobile_number(number):
+	"""Add country code and strip leading zeroes from the phone number."""
+	return "254" + str(number).lstrip("0")
+
+@frappe.whitelist(allow_guest=True)
+def verify_transaction(**kwargs):
+	"""Verify the transaction result received via callback from stk."""
+	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))
+
+	if transaction_response['ResultCode'] == 0:
+		if request.reference_doctype and 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"]
+				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)
+			except Exception:
+				request.handle_failure(transaction_response)
+				frappe.log_error(frappe.get_traceback())
+
+	else:
+		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)
+
+def get_account_balance(request_payload):
+	"""Call account balance API to send the request to the Mpesa Servers."""
+	try:
+		mpesa_settings = frappe.get_doc("Mpesa Settings", request_payload.get("reference_docname"))
+		env = "production" if not mpesa_settings.sandbox else "sandbox"
+		connector = MpesaConnector(env=env,
+			app_key=mpesa_settings.consumer_key,
+			app_secret=mpesa_settings.get_password("consumer_secret"))
+
+		callback_url = get_request_site_address(True) + "/api/method/erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings.process_balance_info"
+
+		response = connector.get_balance(mpesa_settings.initiator_name, mpesa_settings.security_credential, mpesa_settings.till_number, 4, mpesa_settings.name, callback_url, callback_url)
+		return response
+	except Exception:
+		frappe.log_error(title=_("Account Balance Processing Error"))
+		frappe.throw(title=_("Error"), message=_("Please check your configuration and try again"))
+
+@frappe.whitelist(allow_guest=True)
+def process_balance_info(**kwargs):
+	"""Process and store account balance information received via callback from the account balance API call."""
+	account_balance_response = frappe._dict(kwargs["Result"])
+
+	conversation_id = getattr(account_balance_response, "ConversationID", "")
+	request = frappe.get_doc("Integration Request", conversation_id)
+
+	if request.status == "Completed":
+		return
+
+	transaction_data = frappe._dict(loads(request.data))
+
+	if account_balance_response["ResultCode"] == 0:
+		try:
+			result_params = account_balance_response["ResultParameters"]["ResultParameter"]
+
+			balance_info = fetch_param_value(result_params, "AccountBalance", "Key")
+			balance_info = format_string_to_json(balance_info)
+
+			ref_doc = frappe.get_doc(transaction_data.reference_doctype, transaction_data.reference_docname)
+			ref_doc.db_set("account_balance", balance_info)
+
+			request.handle_success(account_balance_response)
+			frappe.publish_realtime("refresh_mpesa_dashboard")
+		except Exception:
+			request.handle_failure(account_balance_response)
+			frappe.log_error(title=_("Mpesa Account Balance Processing Error"), message=account_balance_response)
+	else:
+		request.handle_failure(account_balance_response)
+
+def format_string_to_json(balance_info):
+	"""
+	Format string to json.
+
+	e.g: '''Working Account|KES|481000.00|481000.00|0.00|0.00'''
+	=> {'Working Account': {'current_balance': '481000.00',
+		'available_balance': '481000.00',
+		'reserved_balance': '0.00',
+		'uncleared_balance': '0.00'}}
+	"""
+	balance_dict = frappe._dict()
+	for account_info in balance_info.split("&"):
+		account_info = account_info.split('|')
+		balance_dict[account_info[0]] = dict(
+			current_balance=fmt_money(account_info[2], currency="KES"),
+			available_balance=fmt_money(account_info[3], currency="KES"),
+			reserved_balance=fmt_money(account_info[4], currency="KES"),
+			uncleared_balance=fmt_money(account_info[5], currency="KES")
+		)
+	return dumps(balance_dict)
+
+def fetch_param_value(response, key, key_field):
+	"""Fetch the specified key from list of dictionary. Key is identified via the key field."""
+	for param in response:
+		if param[key_field] == key:
+			return param["Value"]
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
new file mode 100644
index 0000000..4e86d36
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
@@ -0,0 +1,240 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+from json import dumps
+import frappe
+import unittest
+from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings import process_balance_info, verify_transaction
+from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
+
+class TestMpesaSettings(unittest.TestCase):
+	def test_creation_of_payment_gateway(self):
+		create_mpesa_settings(payment_gateway_name="_Test")
+
+		mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test")
+		self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"}))
+		self.assertTrue(mode_of_payment.name)
+		self.assertEquals(mode_of_payment.type, "Phone")
+
+	def test_processing_of_account_balance(self):
+		mpesa_doc = create_mpesa_settings(payment_gateway_name="_Account Balance")
+		mpesa_doc.get_account_balance_info()
+
+		callback_response = get_account_balance_callback_payload()
+		process_balance_info(**callback_response)
+		integration_request = frappe.get_doc("Integration Request", "AG_20200927_00007cdb1f9fb6494315")
+
+		# test integration request creation and successful update of the status on receiving callback response
+		self.assertTrue(integration_request)
+		self.assertEquals(integration_request.status, "Completed")
+
+		# test formatting of account balance received as string to json with appropriate currency symbol
+		mpesa_doc.reload()
+		self.assertEquals(mpesa_doc.account_balance, dumps({
+			"Working Account": {
+				"current_balance": "Sh 481,000.00",
+				"available_balance": "Sh 481,000.00",
+				"reserved_balance": "Sh 0.00",
+				"uncleared_balance": "Sh 0.00"
+			}
+		}))
+
+	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")
+		frappe.db.set_value("Account", mpesa_account, "account_currency", "KES")
+
+		pos_invoice = create_pos_invoice(do_not_submit=1)
+		pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 500})
+		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")
+
+		callback_response = get_payment_callback_payload()
+		verify_transaction(**callback_response)
+		# test creation of integration request
+		integration_request = frappe.get_doc("Integration Request", "ws_CO_061020201133231972")
+
+		# test integration request creation and successful update of the status on receiving callback response
+		self.assertTrue(integration_request)
+		self.assertEquals(integration_request.status, "Completed")
+
+		pos_invoice.reload()
+		integration_request.reload()
+		self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
+		self.assertEquals(integration_request.status, "Completed")
+
+def create_mpesa_settings(payment_gateway_name="Express"):
+	if frappe.db.exists("Mpesa Settings", payment_gateway_name):
+		return frappe.get_doc("Mpesa Settings", payment_gateway_name)
+
+	doc = frappe.get_doc(dict( #nosec
+		doctype="Mpesa Settings",
+		payment_gateway_name=payment_gateway_name,
+		consumer_key="5sMu9LVI1oS3oBGPJfh3JyvLHwZOdTKn",
+		consumer_secret="VI1oS3oBGPJfh3JyvLHw",
+		online_passkey="LVI1oS3oBGPJfh3JyvLHwZOd",
+		till_number="174379"
+	))
+
+	doc.insert(ignore_permissions=True)
+	return doc
+
+def get_test_account_balance_response():
+	"""Response received after calling the account balance API."""
+	return {
+		"ResultType":0,
+		"ResultCode":0,
+		"ResultDesc":"The service request has been accepted successfully.",
+		"OriginatorConversationID":"10816-694520-2",
+		"ConversationID":"AG_20200927_00007cdb1f9fb6494315",
+		"TransactionID":"LGR0000000",
+		"ResultParameters":{
+		"ResultParameter":[
+			{
+			"Key":"ReceiptNo",
+			"Value":"LGR919G2AV"
+			},
+			{
+			"Key":"Conversation ID",
+			"Value":"AG_20170727_00004492b1b6d0078fbe"
+			},
+			{
+			"Key":"FinalisedTime",
+			"Value":20170727101415
+			},
+			{
+			"Key":"Amount",
+			"Value":10
+			},
+			{
+			"Key":"TransactionStatus",
+			"Value":"Completed"
+			},
+			{
+			"Key":"ReasonType",
+			"Value":"Salary Payment via API"
+			},
+			{
+			"Key":"TransactionReason"
+			},
+			{
+			"Key":"DebitPartyCharges",
+			"Value":"Fee For B2C Payment|KES|33.00"
+			},
+			{
+			"Key":"DebitAccountType",
+			"Value":"Utility Account"
+			},
+			{
+			"Key":"InitiatedTime",
+			"Value":20170727101415
+			},
+			{
+			"Key":"Originator Conversation ID",
+			"Value":"19455-773836-1"
+			},
+			{
+			"Key":"CreditPartyName",
+			"Value":"254708374149 - John Doe"
+			},
+			{
+			"Key":"DebitPartyName",
+			"Value":"600134 - Safaricom157"
+			}
+		]
+	},
+	"ReferenceData":{
+	"ReferenceItem":{
+		"Key":"Occasion",
+		"Value":"aaaa"
+	}
+	}
+		}
+
+def get_payment_request_response_payload():
+	"""Response received after successfully calling the stk push process request API."""
+	return {
+		"MerchantRequestID": "8071-27184008-1",
+		"CheckoutRequestID": "ws_CO_061020201133231972",
+		"ResultCode": 0,
+		"ResultDesc": "The service request is processed successfully.",
+		"CallbackMetadata": {
+			"Item": [
+				{ "Name": "Amount", "Value": 500.0 },
+				{ "Name": "MpesaReceiptNumber", "Value": "LGR7OWQX0R" },
+				{ "Name": "TransactionDate", "Value": 20201006113336 },
+				{ "Name": "PhoneNumber", "Value": 254723575670 }
+			]
+		}
+	}
+
+
+def get_payment_callback_payload():
+	"""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
+				}
+			]
+			}
+		}
+		}
+	}
+
+def get_account_balance_callback_payload():
+	"""Response received from the server as callback after calling the account balance API."""
+	return {
+		"Result":{
+			"ResultType": 0,
+			"ResultCode": 0,
+			"ResultDesc": "The service request is processed successfully.",
+			"OriginatorConversationID": "16470-170099139-1",
+			"ConversationID": "AG_20200927_00007cdb1f9fb6494315",
+			"TransactionID": "OIR0000000",
+			"ResultParameters": {
+				"ResultParameter": [
+					{
+						"Key": "AccountBalance",
+						"Value": "Working Account|KES|481000.00|481000.00|0.00|0.00"
+					},
+					{ "Key": "BOCompletedTime", "Value": 20200927234123 }
+				]
+			},
+			"ReferenceData": {
+				"ReferenceItem": {
+					"Key": "QueueTimeoutURL",
+					"Value": "https://internalsandbox.safaricom.co.ke/mpesa/abresults/v1/submit"
+				}
+			}
+		}
+	}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py
index 84f7f5a..e278fd7 100644
--- a/erpnext/erpnext_integrations/utils.py
+++ b/erpnext/erpnext_integrations/utils.py
@@ -3,6 +3,7 @@
 from frappe import _
 import base64, hashlib, hmac
 from six.moves.urllib.parse import urlparse
+from erpnext import get_default_company
 
 def validate_webhooks_request(doctype,  hmac_key, secret_key='secret'):
 	def innerfn(fn):
@@ -41,3 +42,22 @@
 	server_url = '{uri.scheme}://{uri.netloc}/api/method/{endpoint}'.format(uri=urlparse(url), endpoint=endpoint)
 
 	return server_url
+
+def create_mode_of_payment(gateway, payment_type="General"):
+	payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
+			"payment_gateway": gateway
+		}, ['payment_account'])
+
+	if not frappe.db.exists("Mode of Payment", gateway) and payment_gateway_account:
+		mode_of_payment = frappe.get_doc({
+			"doctype": "Mode of Payment",
+			"mode_of_payment": gateway,
+			"enabled": 1,
+			"type": payment_type,
+			"accounts": [{
+				"doctype": "Mode of Payment Account",
+				"company": get_default_company(),
+				"default_account": payment_gateway_account
+			}]
+		})
+		mode_of_payment.insert(ignore_permissions=True)
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 85eaa5e..dfc600c 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -56,7 +56,7 @@
 			if existing_user_id:
 				remove_user_permission(
 					"Employee", self.name, existing_user_id)
-	
+
 	def after_rename(self, old, new, merge):
 		self.db_set("employee", new)
 
@@ -181,8 +181,11 @@
 			)
 			if reports_to:
 				link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name, label=employee.employee_name) for employee in reports_to]
-				throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee:&nbsp;")
-					+ ', '.join(link_to_employees), EmployeeLeftValidationError)
+				message = _("The following employees are currently still reporting to {0}:").format(frappe.bold(self.employee_name))
+				message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
+				message += "</li></ul><br>"
+				message += _("Please make sure the employees above report to another Active employee.")
+				throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee"))
 			if not self.relieving_date:
 				throw(_("Please enter relieving date."))
 
@@ -215,7 +218,7 @@
 
 	def validate_preferred_email(self):
 		if self.prefered_contact_email and not self.get(scrub(self.prefered_contact_email)):
-			frappe.msgprint(_("Please enter " + self.prefered_contact_email))
+			frappe.msgprint(_("Please enter {0}").format(self.prefered_contact_email))
 
 	def validate_onboarding_process(self):
 		employee_onboarding = frappe.get_all("Employee Onboarding",
@@ -417,9 +420,9 @@
 @frappe.whitelist()
 def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False):
 
-	filters = []
+	filters = [['status', '!=', 'Left']]
 	if company and company != 'All Companies':
-		filters = [['company', '=', company]]
+		filters.append(['company', '=', company])
 
 	fields = ['name as value', 'employee_name as title']
 
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index c42e1d7..4374d29 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -28,144 +28,110 @@
   {
    "fieldname": "employee_settings",
    "fieldtype": "Section Break",
-   "label": "Employee Settings",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Employee Settings"
   },
   {
    "description": "Enter retirement age in years",
    "fieldname": "retirement_age",
    "fieldtype": "Data",
-   "label": "Retirement Age",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Retirement Age"
   },
   {
    "default": "Naming Series",
-   "description": "Employee record is created using selected field. ",
+   "description": "Employee records are created using the selected field",
    "fieldname": "emp_created_by",
    "fieldtype": "Select",
-   "label": "Employee Records to be created by",
-   "options": "Naming Series\nEmployee Number\nFull Name",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Employee Records to Be Created By",
+   "options": "Naming Series\nEmployee Number\nFull Name"
   },
   {
    "fieldname": "column_break_4",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "default": "0",
-   "description": "Don't send Employee Birthday Reminders",
+   "description": "Don't send employee birthday reminders",
    "fieldname": "stop_birthday_reminders",
    "fieldtype": "Check",
-   "label": "Stop Birthday Reminders",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Stop Birthday Reminders"
   },
   {
    "default": "1",
    "fieldname": "expense_approver_mandatory_in_expense_claim",
    "fieldtype": "Check",
-   "label": "Expense Approver Mandatory In Expense Claim",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Expense Approver Mandatory In Expense Claim"
   },
   {
    "collapsible": 1,
    "fieldname": "leave_settings",
    "fieldtype": "Section Break",
-   "label": "Leave Settings",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Leave Settings"
   },
   {
    "fieldname": "leave_approval_notification_template",
    "fieldtype": "Link",
    "label": "Leave Approval Notification Template",
-   "options": "Email Template",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Email Template"
   },
   {
    "fieldname": "leave_status_notification_template",
    "fieldtype": "Link",
    "label": "Leave Status Notification Template",
-   "options": "Email Template",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Email Template"
   },
   {
    "fieldname": "column_break_18",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "default": "1",
    "fieldname": "leave_approver_mandatory_in_leave_application",
    "fieldtype": "Check",
-   "label": "Leave Approver Mandatory In Leave Application",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Leave Approver Mandatory In Leave Application"
   },
   {
    "default": "0",
    "fieldname": "show_leaves_of_all_department_members_in_calendar",
    "fieldtype": "Check",
-   "label": "Show Leaves Of All Department Members In Calendar",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Show Leaves Of All Department Members In Calendar"
   },
   {
    "collapsible": 1,
    "fieldname": "hiring_settings",
    "fieldtype": "Section Break",
-   "label": "Hiring Settings",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Hiring Settings"
   },
   {
    "default": "0",
    "fieldname": "check_vacancies",
    "fieldtype": "Check",
-   "label": "Check Vacancies On Job Offer Creation",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Check Vacancies On Job Offer Creation"
   },
   {
    "default": "0",
    "fieldname": "auto_leave_encashment",
    "fieldtype": "Check",
-   "label": "Auto Leave Encashment",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Auto Leave Encashment"
   },
   {
    "default": "0",
    "fieldname": "restrict_backdated_leave_application",
    "fieldtype": "Check",
-   "label": "Restrict Backdated Leave Application",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Restrict Backdated Leave Applications"
   },
   {
    "depends_on": "eval:doc.restrict_backdated_leave_application == 1",
    "fieldname": "role_allowed_to_create_backdated_leave_application",
    "fieldtype": "Link",
    "label": "Role Allowed to Create Backdated Leave Application",
-   "options": "Role",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Role"
   }
  ],
  "icon": "fa fa-cog",
  "idx": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-06-04 15:15:09.865476",
+ "modified": "2020-10-13 11:49:46.168027",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "HR Settings",
@@ -183,4 +149,4 @@
  ],
  "sort_field": "modified",
  "sort_order": "ASC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 71d49a9..2ab1b98 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -55,10 +55,11 @@
 			conflicting_bom = frappe.get_doc("BOM", name)
 
 			if conflicting_bom.item != self.item:
+				msg = (_("A BOM with name {0} already exists for item {1}.")
+					.format(frappe.bold(name), frappe.bold(conflicting_bom.item)))
 
-				frappe.throw(_("""A BOM with name {0} already exists for item {1}.
-					<br> Did you rename the item? Please contact Administrator / Tech support
-				""").format(frappe.bold(name), frappe.bold(conflicting_bom.item)))
+				frappe.throw(_("{0}{1} Did you rename the item? Please contact Administrator / Tech support")
+					.format(msg, "<br>"))
 
 		self.name = name
 
@@ -72,6 +73,7 @@
 		self.validate_uom_is_interger()
 		self.set_bom_material_details()
 		self.validate_materials()
+		self.set_routing_operations()
 		self.validate_operations()
 		self.calculate_cost()
 		self.update_cost(update_parent=False, from_child_bom=True, save=False)
@@ -111,18 +113,13 @@
 	def get_routing(self):
 		if self.routing:
 			self.set("operations", [])
-			for d in frappe.get_all("BOM Operation", fields = ["*"],
-				filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="idx"):
-				child = self.append('operations', {
-					"operation": d.operation,
-					"workstation": d.workstation,
-					"description": d.description,
-					"time_in_mins": d.time_in_mins,
-					"batch_size": d.batch_size,
-					"operating_cost": d.operating_cost,
-					"idx": d.idx
-				})
-				child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
+			fields = ["sequence_id", "operation", "workstation", "description",
+				"time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate"]
+
+			for row in frappe.get_all("BOM Operation", fields = fields,
+				filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="sequence_id, idx"):
+				child = self.append('operations', row)
+				child.hour_rate = flt(row.hour_rate / self.conversion_rate, 2)
 
 	def set_bom_material_details(self):
 		for item in self.get("items"):
@@ -571,6 +568,10 @@
 			if act_pbom and act_pbom[0][0]:
 				frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
 
+	def set_routing_operations(self):
+		if self.routing and self.with_operations and not self.operations:
+			self.get_routing()
+
 	def validate_operations(self):
 		if self.with_operations and not self.get('operations'):
 			frappe.throw(_("Operations cannot be left blank"))
diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
index 0350e2c..07464e3 100644
--- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
+++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
@@ -1,10 +1,12 @@
 {
+ "actions": [],
  "creation": "2013-02-22 01:27:49",
  "doctype": "DocType",
  "document_type": "Setup",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
+  "sequence_id",
   "operation",
   "workstation",
   "description",
@@ -106,11 +108,19 @@
    "fieldname": "batch_size",
    "fieldtype": "Int",
    "label": "Batch Size"
+  },
+  {
+   "depends_on": "eval:doc.parenttype == \"Routing\"",
+   "fieldname": "sequence_id",
+   "fieldtype": "Int",
+   "label": "Sequence ID"
   }
  ],
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "istable": 1,
- "modified": "2020-06-16 17:01:11.128420",
+ "links": [],
+ "modified": "2020-10-13 18:14:10.018774",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Operation",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 087ab6b..575e719 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -36,6 +36,7 @@
   "items",
   "more_information",
   "operation_id",
+  "sequence_id",
   "transferred_qty",
   "requested_qty",
   "column_break_20",
@@ -297,10 +298,18 @@
    "fieldname": "operation_row_number",
    "fieldtype": "Select",
    "label": "Operation Row Number"
+  },
+  {
+   "fieldname": "sequence_id",
+   "fieldtype": "Int",
+   "label": "Sequence Id",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "is_submittable": 1,
- "modified": "2020-08-24 15:21:21.398267",
+ "links": [],
+ "modified": "2020-10-14 12:58:25.327897",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Job Card",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 8855e0a..4dfa78b 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe
 import datetime
-from frappe import _
+from frappe import _, bold
 from frappe.model.mapper import get_mapped_doc
 from frappe.model.document import Document
 from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
@@ -16,12 +16,14 @@
 class OverlapError(frappe.ValidationError): pass
 
 class OperationMismatchError(frappe.ValidationError): pass
+class OperationSequenceError(frappe.ValidationError): pass
 
 class JobCard(Document):
 	def validate(self):
 		self.validate_time_logs()
 		self.set_status()
 		self.validate_operation_id()
+		self.validate_sequence_id()
 
 	def validate_time_logs(self):
 		self.total_completed_qty = 0.0
@@ -196,14 +198,14 @@
 	def validate_job_card(self):
 		if not self.time_logs:
 			frappe.throw(_("Time logs are required for {0} {1}")
-				.format(frappe.bold("Job Card"), get_link_to_form("Job Card", self.name)))
+				.format(bold("Job Card"), get_link_to_form("Job Card", self.name)))
 
 		if self.for_quantity and self.total_completed_qty != self.for_quantity:
-			total_completed_qty = frappe.bold(_("Total Completed Qty"))
-			qty_to_manufacture = frappe.bold(_("Qty to Manufacture"))
+			total_completed_qty = bold(_("Total Completed Qty"))
+			qty_to_manufacture = bold(_("Qty to Manufacture"))
 
-			frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})"
-				.format(total_completed_qty, frappe.bold(self.total_completed_qty), qty_to_manufacture,frappe.bold(self.for_quantity))))
+			frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})")
+				.format(total_completed_qty, bold(self.total_completed_qty), qty_to_manufacture,bold(self.for_quantity)))
 
 	def update_work_order(self):
 		if not self.work_order:
@@ -213,10 +215,7 @@
 		from_time_list, to_time_list = [], []
 
 		field = "operation_id"
-		data = frappe.get_all('Job Card',
-			fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
-			filters = {"docstatus": 1, "work_order": self.work_order, field: self.get(field)})
-
+		data = self.get_current_operation_data()
 		if data and len(data) > 0:
 			for_quantity = data[0].completed_qty
 			time_in_mins = data[0].time_in_mins
@@ -246,6 +245,11 @@
 			wo.set_actual_dates()
 			wo.save()
 
+	def get_current_operation_data(self):
+		return frappe.get_all('Job Card',
+			fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
+			filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id})
+
 	def set_transferred_qty(self, update_status=False):
 		if not self.items:
 			self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
@@ -310,9 +314,32 @@
 	def validate_operation_id(self):
 		if (self.get("operation_id") and self.get("operation_row_number") and self.operation and self.work_order and
 			frappe.get_cached_value("Work Order Operation", self.operation_row_number, "name") != self.operation_id):
-			work_order = frappe.bold(get_link_to_form("Work Order", self.work_order))
+			work_order = bold(get_link_to_form("Work Order", self.work_order))
 			frappe.throw(_("Operation {0} does not belong to the work order {1}")
-				.format(frappe.bold(self.operation), work_order), OperationMismatchError)
+				.format(bold(self.operation), work_order), OperationMismatchError)
+
+	def validate_sequence_id(self):
+		if not (self.work_order and self.sequence_id): return
+
+		current_operation_qty = 0.0
+		data = self.get_current_operation_data()
+		if data and len(data) > 0:
+			current_operation_qty = flt(data[0].completed_qty)
+
+		current_operation_qty += flt(self.total_completed_qty)
+
+		data = frappe.get_all("Work Order Operation",
+			fields = ["operation", "status", "completed_qty"],
+			filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ('<', self.sequence_id)},
+			order_by = "sequence_id, idx")
+
+		message = "Job Card {0}: As per the sequence of the operations in the work order {1}".format(bold(self.name),
+			bold(get_link_to_form("Work Order", self.work_order)))
+
+		for row in data:
+			if row.status != "Completed" and row.completed_qty < current_operation_qty:
+				frappe.throw(_("{0}, complete the operation {1} before the operation {2}.")
+					.format(message, bold(row.operation), bold(self.operation)), OperationSequenceError)
 
 @frappe.whitelist()
 def get_operation_details(work_order, operation):
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
index 86fa7a8..b7634da 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2014-11-27 14:12:07.542534",
  "doctype": "DocType",
  "document_type": "Document",
@@ -36,7 +37,7 @@
   {
    "default": "0",
    "depends_on": "eval:!doc.disable_capacity_planning",
-   "description": "Plan time logs outside Workstation Working Hours.",
+   "description": "Plan time logs outside Workstation working hours",
    "fieldname": "allow_overtime",
    "fieldtype": "Check",
    "label": "Allow Overtime"
@@ -56,17 +57,17 @@
   {
    "default": "30",
    "depends_on": "eval:!doc.disable_capacity_planning",
-   "description": "Try planning operations for X days in advance.",
+   "description": "Plan operations X days in advance",
    "fieldname": "capacity_planning_for_days",
    "fieldtype": "Int",
    "label": "Capacity Planning For (Days)"
   },
   {
    "depends_on": "eval:!doc.disable_capacity_planning",
-   "description": "Default 10 mins",
+   "description": "Default: 10 mins",
    "fieldname": "mins_between_operations",
    "fieldtype": "Int",
-   "label": "Time Between Operations (in mins)"
+   "label": "Time Between Operations (Mins)"
   },
   {
    "fieldname": "section_break_6",
@@ -92,14 +93,14 @@
   },
   {
    "default": "0",
-   "description": "Allow multiple Material Consumption against a Work Order",
+   "description": "Allow multiple material consumptions against a Work Order",
    "fieldname": "material_consumption",
    "fieldtype": "Check",
    "label": "Allow Multiple Material Consumption"
   },
   {
    "default": "0",
-   "description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.",
+   "description": "Update BOM cost automatically via scheduler, based on the latest Valuation Rate/Price List Rate/Last Purchase Rate of raw materials",
    "fieldname": "update_bom_costs_automatically",
    "fieldtype": "Check",
    "label": "Update BOM Cost Automatically"
@@ -135,7 +136,7 @@
   {
    "fieldname": "over_production_for_sales_and_work_order_section",
    "fieldtype": "Section Break",
-   "label": "Over Production for Sales and Work Order"
+   "label": "Overproduction for Sales and Work Order"
   },
   {
    "fieldname": "raw_materials_consumption_section",
@@ -157,8 +158,10 @@
   }
  ],
  "icon": "icon-wrench",
+ "index_web_pages_for_search": 1,
  "issingle": 1,
- "modified": "2019-11-26 13:10:45.569341",
+ "links": [],
+ "modified": "2020-10-13 10:55:43.996581",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Manufacturing Settings",
@@ -175,4 +178,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/manufacturing/doctype/operation/test_operation.py b/erpnext/manufacturing/doctype/operation/test_operation.py
index 17d206a..0067231 100644
--- a/erpnext/manufacturing/doctype/operation/test_operation.py
+++ b/erpnext/manufacturing/doctype/operation/test_operation.py
@@ -9,3 +9,23 @@
 
 class TestOperation(unittest.TestCase):
 	pass
+
+def make_operation(*args, **kwargs):
+	args = args if args else kwargs
+	if isinstance(args, tuple):
+		args = args[0]
+
+	args = frappe._dict(args)
+
+	try:
+		doc = frappe.get_doc({
+			"doctype": "Operation",
+			"name": args.operation,
+			"workstation": args.workstation
+		})
+
+		doc.insert()
+
+		return doc
+	except frappe.DuplicateEntryError:
+		return frappe.get_doc("Operation", args.operation)
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index d020bc8..fa9d080 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -237,7 +237,9 @@
 		'item': args.item,
 		'currency': args.currency or 'USD',
 		'quantity': args.quantity or 1,
-		'company': args.company or '_Test Company'
+		'company': args.company or '_Test Company',
+		'routing': args.routing,
+		'with_operations': args.with_operations or 0
 	})
 
 	for item in args.raw_materials:
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
index d7589fa..741a3f0 100644
--- a/erpnext/manufacturing/doctype/routing/routing.js
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -2,6 +2,13 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Routing', {
+	setup: function(frm) {
+		frappe.meta.get_docfield("BOM Operation", "sequence_id",
+			frm.doc.name).in_list_view = true;
+
+		frm.fields_dict.operations.grid.refresh();
+	},
+
 	calculate_operating_cost: function(frm, child) {
 		const operating_cost = flt(flt(child.hour_rate) * flt(child.time_in_mins) / 60, 2);
 		frappe.model.set_value(child.doctype, child.name, "operating_cost", operating_cost);
diff --git a/erpnext/manufacturing/doctype/routing/routing.py b/erpnext/manufacturing/doctype/routing/routing.py
index ecd0ba8..8312d74 100644
--- a/erpnext/manufacturing/doctype/routing/routing.py
+++ b/erpnext/manufacturing/doctype/routing/routing.py
@@ -3,7 +3,22 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
+import frappe
+from frappe.utils import cint
+from frappe import _
 from frappe.model.document import Document
 
 class Routing(Document):
-	pass
+	def validate(self):
+		self.set_routing_id()
+
+	def set_routing_id(self):
+		sequence_id = 0
+		for row in self.operations:
+			if not row.sequence_id:
+				row.sequence_id = sequence_id + 1
+			elif sequence_id and row.sequence_id and cint(sequence_id) > cint(row.sequence_id):
+				frappe.throw(_("At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2}")
+					.format(row.idx, row.sequence_id, sequence_id))
+
+			sequence_id = row.sequence_id
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index 53ad152..73d05a6 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -4,6 +4,88 @@
 from __future__ import unicode_literals
 
 import unittest
+import frappe
+from frappe.test_runner import make_test_records
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.manufacturing.doctype.operation.test_operation import make_operation
+from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError
+from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
+from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
 
 class TestRouting(unittest.TestCase):
-	pass
+	def test_sequence_id(self):
+		item_code = "Test Routing Item - A"
+		operations = [{"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30},
+			{"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20}]
+
+		make_test_records("UOM")
+
+		setup_operations(operations)
+		routing_doc = create_routing(routing_name="Testing Route", operations=operations)
+		bom_doc = setup_bom(item_code=item_code, routing=routing_doc.name)
+		wo_doc = make_wo_order_test_record(production_item = item_code, bom_no=bom_doc.name)
+
+		for row in routing_doc.operations:
+			self.assertEqual(row.sequence_id, row.idx)
+
+		for data in frappe.get_all("Job Card",
+			filters={"work_order": wo_doc.name}, order_by="sequence_id desc"):
+			job_card_doc = frappe.get_doc("Job Card", data.name)
+			job_card_doc.time_logs[0].completed_qty = 10
+			if job_card_doc.sequence_id != 1:
+				self.assertRaises(OperationSequenceError, job_card_doc.save)
+			else:
+				job_card_doc.save()
+				self.assertEqual(job_card_doc.total_completed_qty, 10)
+
+		wo_doc.cancel()
+		wo_doc.delete()
+
+def setup_operations(rows):
+	for row in rows:
+		make_workstation(row)
+		make_operation(row)
+
+def create_routing(**args):
+	args = frappe._dict(args)
+
+	doc = frappe.new_doc("Routing")
+	doc.update(args)
+
+	if not args.do_not_save:
+		try:
+			for operation in args.operations:
+				doc.append("operations", operation)
+
+			doc.insert()
+		except frappe.DuplicateEntryError:
+			doc = frappe.get_doc("Routing", args.routing_name)
+
+	return doc
+
+def setup_bom(**args):
+	from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+	args = frappe._dict(args)
+
+	if not frappe.db.exists('Item', args.item_code):
+		make_item(args.item_code, {
+			'is_stock_item': 1
+		})
+
+	if not args.raw_materials:
+		if not frappe.db.exists('Item', "Test Extra Item 1"):
+			make_item("Test Extra Item N-1", {
+				'is_stock_item': 1,
+			})
+
+		args.raw_materials = ['Test Extra Item N-1']
+
+	name = frappe.db.get_value('BOM', {'item': args.item_code}, 'name')
+	if not name:
+		bom_doc = make_bom(item = args.item_code, raw_materials = args.get("raw_materials"),
+			routing = args.routing, with_operations=1)
+	else:
+		bom_doc = frappe.get_doc("BOM", name)
+
+	return bom_doc
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 7010f29..5f8a134 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -193,6 +193,42 @@
 		self.assertEqual(cint(bin1_on_end_production.projected_qty),
 			cint(bin1_on_end_production.projected_qty))
 
+	def test_backflush_qty_for_overpduction_manufacture(self):
+		cancel_stock_entry = []
+		allow_overproduction("overproduction_percentage_for_work_order", 30)
+		wo_order = make_wo_order_test_record(planned_start_date=now(), qty=100)
+		ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
+			target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0)
+		ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+			target="_Test Warehouse - _TC", qty=240, basic_rate=1000.0)
+
+		cancel_stock_entry.extend([ste1.name, ste2.name])
+
+		s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 60))
+		s.submit()
+		cancel_stock_entry.append(s.name)
+
+		s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 60))
+		s.submit()
+		cancel_stock_entry.append(s.name)
+
+		s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 60))
+		s.submit()
+		cancel_stock_entry.append(s.name)
+
+		s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 50))
+		s1.submit()
+		cancel_stock_entry.append(s1.name)
+
+		self.assertEqual(s1.items[0].qty, 50)
+		self.assertEqual(s1.items[1].qty, 100)
+		cancel_stock_entry.reverse()
+		for ste in cancel_stock_entry:
+			doc = frappe.get_doc("Stock Entry", ste)
+			doc.cancel()
+
+		allow_overproduction("overproduction_percentage_for_work_order", 0)
+
 	def test_reserved_qty_for_stopped_production(self):
 		test_stock_entry.make_stock_entry(item_code="_Test Item",
 			target= self.warehouse, qty=100, basic_rate=100)
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 7f8341f..cc93bf9 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -378,7 +378,7 @@
 			select
 				operation, description, workstation, idx,
 				base_hour_rate as hour_rate, time_in_mins,
-				"Pending" as status, parent as bom, batch_size
+				"Pending" as status, parent as bom, batch_size, sequence_id
 			from
 				`tabBOM Operation`
 			where
@@ -865,6 +865,7 @@
 		'bom_no': work_order.bom_no,
 		'project': work_order.project,
 		'company': work_order.company,
+		'sequence_id': row.get("sequence_id"),
 		'wip_warehouse': work_order.wip_warehouse
 	})
 
diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
index 3f5e18e..8c5cde9 100644
--- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
+++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
@@ -8,6 +8,7 @@
   "details",
   "operation",
   "bom",
+  "sequence_id",
   "description",
   "col_break1",
   "completed_qty",
@@ -187,11 +188,19 @@
    "fieldtype": "Int",
    "label": "Batch Size",
    "read_only": 1
+  },
+  {
+   "fieldname": "sequence_id",
+   "fieldtype": "Int",
+   "label": "Sequence ID",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2019-12-03 19:24:29.594189",
+ "modified": "2020-10-14 12:58:49.241252",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Work Order Operation",
diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py
index 8266cf7..c6699be 100644
--- a/erpnext/manufacturing/doctype/workstation/test_workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py
@@ -21,17 +21,22 @@
 		self.assertRaises(WorkstationHolidayError, check_if_within_operating_hours,
 			"_Test Workstation 1", "Operation 1", "2013-02-01 10:00:00", "2013-02-02 20:00:00")
 
-def make_workstation(**args):
+def make_workstation(*args, **kwargs):
+	args = args if args else kwargs
+	if isinstance(args, tuple):
+		args = args[0]
+
 	args = frappe._dict(args)
 
+	workstation_name = args.workstation_name or args.workstation
 	try:
 		doc = frappe.get_doc({
 			"doctype": "Workstation",
-			"workstation_name": args.workstation_name
+			"workstation_name": workstation_name
 		})
 
 		doc.insert()
 
 		return doc
 	except frappe.DuplicateEntryError:
-		return frappe.get_doc("Workstation", args.workstation_name)
\ No newline at end of file
+		return frappe.get_doc("Workstation", workstation_name)
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 77310cd..5b45c22 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -729,4 +729,5 @@
 erpnext.patches.v13_0.rename_issue_doctype_fields
 erpnext.patches.v13_0.change_default_pos_print_format
 erpnext.patches.v13_0.set_youtube_video_id
-erpnext.patches.v13_0.print_uom_after_quantity_patch
\ No newline at end of file
+erpnext.patches.v13_0.print_uom_after_quantity_patch
+erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
diff --git a/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py b/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py
new file mode 100644
index 0000000..edca238
--- /dev/null
+++ b/erpnext/patches/v13_0/set_payment_channel_in_payment_gateway_account.py
@@ -0,0 +1,17 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	"""Set the payment gateway account as Email for all the existing payment channel."""
+	doc_meta = frappe.get_meta("Payment Gateway Account")
+	if doc_meta.get_field("payment_channel"):
+		return
+
+	frappe.reload_doc("Accounts", "doctype", "Payment Gateway Account")
+	set_payment_channel_as_email()
+
+def set_payment_channel_as_email():
+	frappe.db.sql("""
+		UPDATE `tabPayment Gateway Account`
+		SET `payment_channel` = "Email"
+	""")
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 7b46fb6..989bd33 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -162,7 +162,7 @@
 
 					// sales invoice
 					if(flt(doc.per_billed, 6) < 100) {
-						this.frm.add_custom_button(__('Invoice'), () => me.make_sales_invoice(), __('Create'));
+						this.frm.add_custom_button(__('Sales Invoice'), () => me.make_sales_invoice(), __('Create'));
 					}
 
 					// material request
@@ -554,19 +554,32 @@
 	},
 
 	make_purchase_order: function(){
+		let pending_items = this.frm.doc.items.some((item) =>{
+			let pending_qty = flt(item.stock_qty) - flt(item.ordered_qty);
+			return pending_qty > 0;
+		})
+		if(!pending_items){
+			frappe.throw({message: __("Purchase Order already created for all Sales Order items"), title: __("Note")});
+		}
+
 		var me = this;
 		var dialog = new frappe.ui.Dialog({
-			title: __("For Supplier"),
+			title: __("Select Items"),
 			fields: [
-				{"fieldtype": "Link", "label": __("Supplier"), "fieldname": "supplier", "options":"Supplier",
-				 "description": __("Leave the field empty to make purchase orders for all suppliers"),
-					"get_query": function () {
-						return {
-							query:"erpnext.selling.doctype.sales_order.sales_order.get_supplier",
-							filters: {'parent': me.frm.doc.name}
-						}
-					}},
-					{fieldname: 'items_for_po', fieldtype: 'Table', label: 'Select Items',
+				{
+					"fieldtype": "Check",
+					"label": __("Against Default Supplier"),
+					"fieldname": "against_default_supplier",
+					"default": 0
+				},
+				{
+					"fieldtype": "Section Break",
+					"label": "",
+					"fieldname": "sec_break_dialog",
+					"hide_border": 1
+				},
+				{
+					fieldname: 'items_for_po', fieldtype: 'Table', label: 'Select Items',
 					fields: [
 						{
 							fieldtype:'Data',
@@ -584,8 +597,8 @@
 						},
 						{
 							fieldtype:'Float',
-							fieldname:'qty',
-							label: __('Quantity'),
+							fieldname:'pending_qty',
+							label: __('Pending Qty'),
 							read_only: 1,
 							in_list_view:1
 						},
@@ -594,60 +607,86 @@
 							read_only:1,
 							fieldname:'uom',
 							label: __('UOM'),
+							in_list_view:1,
+						},
+						{
+							fieldtype:'Data',
+							fieldname:'supplier',
+							label: __('Supplier'),
+							read_only:1,
 							in_list_view:1
-						}
+						},
 					],
-					data: cur_frm.doc.items,
-					get_data: function() {
-						return cur_frm.doc.items
-					}
-				},
-
-				{"fieldtype": "Button", "label": __('Create Purchase Order'), "fieldname": "make_purchase_order", "cssClass": "btn-primary"},
-			]
-		});
-
-		dialog.fields_dict.make_purchase_order.$input.click(function() {
-			var args = dialog.get_values();
-			let selected_items = dialog.fields_dict.items_for_po.grid.get_selected_children()
-			if(selected_items.length == 0) {
-				frappe.throw({message: 'Please select Item form Table', title: __('Message'), indicator:'blue'})
-			}
-			let selected_items_list = []
-			for(let i in selected_items){
-				selected_items_list.push(selected_items[i].item_code)
-			}
-			dialog.hide();
-			return frappe.call({
-				type: "GET",
-				method: "erpnext.selling.doctype.sales_order.sales_order.make_purchase_order",
-				args: {
-					"source_name": me.frm.doc.name,
-					"for_supplier": args.supplier,
-					"selected_items": selected_items_list
-				},
-				freeze: true,
-				callback: function(r) {
-					if(!r.exc) {
-						// var args = dialog.get_values();
-						if (args.supplier){
-							var doc = frappe.model.sync(r.message);
-							frappe.set_route("Form", r.message.doctype, r.message.name);
-						}
-						else{
-							frappe.route_options = {
-								"sales_order": me.frm.doc.name
-							}
-							frappe.set_route("List", "Purchase Order");
-						}
-					}
+					data: me.frm.doc.items.map((item) =>{
+						item.pending_qty = (flt(item.stock_qty) - flt(item.ordered_qty)) / flt(item.conversion_factor);
+						return item;
+					}).filter((item) => {return item.pending_qty > 0;})
 				}
-			})
+			],
+			primary_action_label: 'Create Purchase Order',
+			primary_action (args) {
+				if (!args) return;
+				let selected_items = dialog.fields_dict.items_for_po.grid.get_selected_children();
+				if(selected_items.length == 0) {
+					frappe.throw({message: 'Please select Items from the Table', title: __('Items Required'), indicator:'blue'})
+				}
+
+				dialog.hide();
+
+				var method = args.against_default_supplier ? "make_purchase_order_for_default_supplier" : "make_purchase_order"
+				return frappe.call({
+					type: "GET",
+					method: "erpnext.selling.doctype.sales_order.sales_order." + method,
+					args: {
+						"source_name": me.frm.doc.name,
+						"selected_items": selected_items
+					},
+					freeze: true,
+					callback: function(r) {
+						if(!r.exc) {
+							if (!args.against_default_supplier) {
+								frappe.model.sync(r.message);
+								frappe.set_route("Form", r.message.doctype, r.message.name);
+							}
+							else {
+								frappe.route_options = {
+									"sales_order": me.frm.doc.name
+								}
+								frappe.set_route("List", "Purchase Order");
+							}
+						}
+					}
+				})
+			}
 		});
-		dialog.get_field("items_for_po").grid.only_sortable()
-		dialog.get_field("items_for_po").refresh()
+
+		dialog.fields_dict["against_default_supplier"].df.onchange = () => {
+			console.log("yo");
+			var against_default_supplier = dialog.get_value("against_default_supplier");
+			var items_for_po = dialog.get_value("items_for_po");
+
+			if (against_default_supplier) {
+				let items_with_supplier = items_for_po.filter((item) => item.supplier)
+
+				dialog.fields_dict["items_for_po"].df.data = items_with_supplier;
+				dialog.get_field("items_for_po").refresh();
+			} else {
+				let pending_items = me.frm.doc.items.map((item) =>{
+					item.pending_qty = (flt(item.stock_qty) - flt(item.ordered_qty)) / flt(item.conversion_factor);
+					return item;
+					}).filter((item) => {return item.pending_qty > 0;});
+
+				dialog.fields_dict["items_for_po"].df.data = pending_items;
+				dialog.get_field("items_for_po").refresh();
+			}
+		}
+
+		dialog.get_field("items_for_po").grid.only_sortable();
+		dialog.get_field("items_for_po").refresh();
+		dialog.wrapper.find('.grid-heading-row .grid-row-check').click();
 		dialog.show();
 	},
+
 	hold_sales_order: function(){
 		var me = this;
 		var d = new frappe.ui.Dialog({
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index fe3fa82..ae227e0 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -443,25 +443,19 @@
 		for item in self.items:
 			if item.ensure_delivery_based_on_produced_serial_no:
 				if item.item_code in normal_items:
-					frappe.throw(_("Cannot ensure delivery by Serial No as \
-					Item {0} is added with and without Ensure Delivery by \
-					Serial No.").format(item.item_code))
+					frappe.throw(_("Cannot ensure delivery by Serial No as Item {0} is added with and without Ensure Delivery by Serial No.").format(item.item_code))
 				if item.item_code not in reserved_items:
 					if not frappe.get_cached_value("Item", item.item_code, "has_serial_no"):
-						frappe.throw(_("Item {0} has no Serial No. Only serilialized items \
-						can have delivery based on Serial No").format(item.item_code))
+						frappe.throw(_("Item {0} has no Serial No. Only serilialized items can have delivery based on Serial No").format(item.item_code))
 					if not frappe.db.exists("BOM", {"item": item.item_code, "is_active": 1}):
-						frappe.throw(_("No active BOM found for item {0}. Delivery by \
-						Serial No cannot be ensured").format(item.item_code))
+						frappe.throw(_("No active BOM found for item {0}. Delivery by Serial No cannot be ensured").format(item.item_code))
 				reserved_items.append(item.item_code)
 			else:
 				normal_items.append(item.item_code)
 
 			if not item.ensure_delivery_based_on_produced_serial_no and \
 				item.item_code in reserved_items:
-				frappe.throw(_("Cannot ensure delivery by Serial No as \
-				Item {0} is added with and without Ensure Delivery by \
-				Serial No.").format(item.item_code))
+				frappe.throw(_("Cannot ensure delivery by Serial No as Item {0} is added with and without Ensure Delivery by Serial No.").format(item.item_code))
 
 def get_list_context(context=None):
 	from erpnext.controllers.website_list_for_contact import get_list_context
@@ -785,7 +779,7 @@
 	return data
 
 @frappe.whitelist()
-def make_purchase_order(source_name, for_supplier=None, selected_items=[], target_doc=None):
+def make_purchase_order_for_default_supplier(source_name, selected_items=[], target_doc=None):
 	if isinstance(selected_items, string_types):
 		selected_items = json.loads(selected_items)
 
@@ -822,24 +816,21 @@
 
 	def update_item(source, target, source_parent):
 		target.schedule_date = source.delivery_date
-		target.qty = flt(source.qty) - flt(source.ordered_qty)
-		target.stock_qty = (flt(source.qty) - flt(source.ordered_qty)) * flt(source.conversion_factor)
+		target.qty = flt(source.qty) - (flt(source.ordered_qty) / flt(source.conversion_factor))
+		target.stock_qty = (flt(source.stock_qty) - flt(source.ordered_qty))
 		target.project = source_parent.project
 
-	suppliers =[]
-	if for_supplier:
-		suppliers.append(for_supplier)
-	else:
-		sales_order = frappe.get_doc("Sales Order", source_name)
-		for item in sales_order.items:
-			if item.supplier and item.supplier not in suppliers:
-				suppliers.append(item.supplier)
+	suppliers = [item.get('supplier') for item in selected_items if item.get('supplier') and item.get('supplier')]
+	suppliers = list(set(suppliers))
+
+	items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code') and item.get('item_code')]
+	items_to_map = list(set(items_to_map))
 
 	if not suppliers:
 		frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order."))
 
 	for supplier in suppliers:
-		po =frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")})
+		po = frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")})
 		if len(po) == 0:
 			doc = get_mapped_doc("Sales Order", source_name, {
 				"Sales Order": {
@@ -850,7 +841,8 @@
 						"contact_mobile",
 						"contact_email",
 						"contact_person",
-						"taxes_and_charges"
+						"taxes_and_charges",
+						"shipping_address"
 					],
 					"validation": {
 						"docstatus": ["=", 1]
@@ -872,52 +864,82 @@
 						"item_tax_template"
 					],
 					"postprocess": update_item,
-					"condition": lambda doc: doc.ordered_qty < doc.qty and doc.supplier == supplier and doc.item_code in selected_items
+					"condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.supplier == supplier and doc.item_code in items_to_map
 				}
 			}, target_doc, set_missing_values)
-			if not for_supplier:
-				doc.insert()
+
+			doc.insert()
 		else:
 			suppliers =[]
 	if suppliers:
-		if not for_supplier:
-			frappe.db.commit()
+		frappe.db.commit()
 		return doc
 	else:
-		frappe.msgprint(_("PO already created for all sales order items"))
-
+		frappe.msgprint(_("Purchase Order already created for all Sales Order items"))
 
 @frappe.whitelist()
-@frappe.validate_and_sanitize_search_inputs
-def get_supplier(doctype, txt, searchfield, start, page_len, filters):
-	supp_master_name = frappe.defaults.get_user_default("supp_master_name")
-	if supp_master_name == "Supplier Name":
-		fields = ["name", "supplier_group"]
-	else:
-		fields = ["name", "supplier_name", "supplier_group"]
-	fields = ", ".join(fields)
+def make_purchase_order(source_name, selected_items=[], target_doc=None):
+	if isinstance(selected_items, string_types):
+		selected_items = json.loads(selected_items)
 
-	return frappe.db.sql("""select {field} from `tabSupplier`
-		where docstatus < 2
-			and ({key} like %(txt)s
-				or supplier_name like %(txt)s)
-			and name in (select supplier from `tabSales Order Item` where parent = %(parent)s)
-			and name not in (select supplier from `tabPurchase Order` po inner join `tabPurchase Order Item` poi
-			     on po.name=poi.parent where po.docstatus<2 and poi.sales_order=%(parent)s)
-		order by
-			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
-			if(locate(%(_txt)s, supplier_name), locate(%(_txt)s, supplier_name), 99999),
-			name, supplier_name
-		limit %(start)s, %(page_len)s """.format(**{
-			'field': fields,
-			'key': frappe.db.escape(searchfield)
-		}), {
-			'txt': "%%%s%%" % txt,
-			'_txt': txt.replace("%", ""),
-			'start': start,
-			'page_len': page_len,
-			'parent': filters.get('parent')
-		})
+	items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code') and item.get('item_code')]
+	items_to_map = list(set(items_to_map))
+
+	def set_missing_values(source, target):
+		target.supplier = ""
+		target.apply_discount_on = ""
+		target.additional_discount_percentage = 0.0
+		target.discount_amount = 0.0
+		target.inter_company_order_reference = ""
+		target.customer = ""
+		target.customer_name = ""
+		target.run_method("set_missing_values")
+		target.run_method("calculate_taxes_and_totals")
+
+	def update_item(source, target, source_parent):
+		target.schedule_date = source.delivery_date
+		target.qty = flt(source.qty) - (flt(source.ordered_qty) / flt(source.conversion_factor))
+		target.stock_qty = (flt(source.stock_qty) - flt(source.ordered_qty))
+		target.project = source_parent.project
+
+	# po = frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")})
+	doc = get_mapped_doc("Sales Order", source_name, {
+		"Sales Order": {
+			"doctype": "Purchase Order",
+			"field_no_map": [
+				"address_display",
+				"contact_display",
+				"contact_mobile",
+				"contact_email",
+				"contact_person",
+				"taxes_and_charges",
+				"shipping_address"
+			],
+			"validation": {
+				"docstatus": ["=", 1]
+			}
+		},
+		"Sales Order Item": {
+			"doctype": "Purchase Order Item",
+			"field_map":  [
+				["name", "sales_order_item"],
+				["parent", "sales_order"],
+				["stock_uom", "stock_uom"],
+				["uom", "uom"],
+				["conversion_factor", "conversion_factor"],
+				["delivery_date", "schedule_date"]
+			],
+			"field_no_map": [
+				"rate",
+				"price_list_rate",
+				"item_tax_template",
+				"supplier"
+			],
+			"postprocess": update_item,
+			"condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.item_code in items_to_map
+		}
+	}, target_doc, set_missing_values)
+	return doc
 
 @frappe.whitelist()
 def make_work_orders(items, sales_order, company, project=None):
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 2f5f979..9e25ed0 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -688,12 +688,12 @@
 		frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1)
 
 	def test_drop_shipping(self):
-		from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order
+		from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier, \
+			update_status as so_update_status
 		from erpnext.buying.doctype.purchase_order.purchase_order import update_status
 
-		make_stock_entry(target="_Test Warehouse - _TC", qty=10, rate=100)
+		# make items
 		po_item = make_item("_Test Item for Drop Shipping", {"is_stock_item": 1, "delivered_by_supplier": 1})
-
 		dn_item = make_item("_Test Regular Item", {"is_stock_item": 1})
 
 		so_items = [
@@ -715,80 +715,61 @@
 		]
 
 		if frappe.db.get_value("Item", "_Test Regular Item", "is_stock_item")==1:
-			make_stock_entry(item="_Test Regular Item", target="_Test Warehouse - _TC", qty=10, rate=100)
+			make_stock_entry(item="_Test Regular Item", target="_Test Warehouse - _TC", qty=2, rate=100)
 
-		#setuo existing qty from bin
-		bin = frappe.get_all("Bin", filters={"item_code": po_item.item_code, "warehouse": "_Test Warehouse - _TC"},
-			fields=["ordered_qty", "reserved_qty"])
-
-		existing_ordered_qty = bin[0].ordered_qty if bin else 0.0
-		existing_reserved_qty = bin[0].reserved_qty if bin else 0.0
-
-		bin = frappe.get_all("Bin", filters={"item_code": dn_item.item_code,
-			"warehouse": "_Test Warehouse - _TC"}, fields=["reserved_qty"])
-
-		existing_reserved_qty_for_dn_item = bin[0].reserved_qty if bin else 0.0
-
-		#create so, po and partial dn
+		#create so, po and dn
 		so = make_sales_order(item_list=so_items, do_not_submit=True)
 		so.submit()
 
-		po = make_purchase_order(so.name, '_Test Supplier', selected_items=[so_items[0]['item_code']])
+		po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
 		po.submit()
 
-		dn = create_dn_against_so(so.name, delivered_qty=1)
+		dn = create_dn_against_so(so.name, delivered_qty=2)
 
 		self.assertEqual(so.customer, po.customer)
 		self.assertEqual(po.items[0].sales_order, so.name)
 		self.assertEqual(po.items[0].item_code, po_item.item_code)
 		self.assertEqual(dn.items[0].item_code, dn_item.item_code)
-
-		#test ordered_qty and reserved_qty
-		bin = frappe.get_all("Bin", filters={"item_code": po_item.item_code, "warehouse": "_Test Warehouse - _TC"},
-			fields=["ordered_qty", "reserved_qty"])
-
-		ordered_qty = bin[0].ordered_qty if bin else 0.0
-		reserved_qty = bin[0].reserved_qty if bin else 0.0
-
-		self.assertEqual(abs(flt(ordered_qty)), existing_ordered_qty)
-		self.assertEqual(abs(flt(reserved_qty)), existing_reserved_qty)
-
-		reserved_qty = frappe.db.get_value("Bin",
-					{"item_code": dn_item.item_code, "warehouse": "_Test Warehouse - _TC"}, "reserved_qty")
-
-		self.assertEqual(abs(flt(reserved_qty)), existing_reserved_qty_for_dn_item + 1)
-
 		#test po_item length
 		self.assertEqual(len(po.items), 1)
 
-		#test per_delivered status
+		# test ordered_qty and reserved_qty for drop ship item
+		bin_po_item = frappe.get_all("Bin", filters={"item_code": po_item.item_code, "warehouse": "_Test Warehouse - _TC"},
+			fields=["ordered_qty", "reserved_qty"])
+
+		ordered_qty = bin_po_item[0].ordered_qty if bin_po_item else 0.0
+		reserved_qty = bin_po_item[0].reserved_qty if bin_po_item else 0.0
+
+		# drop ship PO should not impact bin, test the same
+		self.assertEqual(abs(flt(ordered_qty)), 0)
+		self.assertEqual(abs(flt(reserved_qty)), 0)
+
+		# test per_delivered status
 		update_status("Delivered", po.name)
-		self.assertEqual(flt(frappe.db.get_value("Sales Order", so.name, "per_delivered"), 2), 75.00)
+		self.assertEqual(flt(frappe.db.get_value("Sales Order", so.name, "per_delivered"), 2), 100.00)
+		po.load_from_db()
 
-		#test reserved qty after complete delivery
-		dn = create_dn_against_so(so.name, delivered_qty=1)
-		reserved_qty = frappe.db.get_value("Bin",
-			{"item_code": dn_item.item_code, "warehouse": "_Test Warehouse - _TC"}, "reserved_qty")
-
-		self.assertEqual(abs(flt(reserved_qty)), existing_reserved_qty_for_dn_item)
-
-		#test after closing so
+		# test after closing so
 		so.db_set('status', "Closed")
 		so.update_reserved_qty()
 
-		bin = frappe.get_all("Bin", filters={"item_code": po_item.item_code, "warehouse": "_Test Warehouse - _TC"},
+		# test ordered_qty and reserved_qty for drop ship item after closing so
+		bin_po_item = frappe.get_all("Bin", filters={"item_code": po_item.item_code, "warehouse": "_Test Warehouse - _TC"},
 			fields=["ordered_qty", "reserved_qty"])
 
-		ordered_qty = bin[0].ordered_qty if bin else 0.0
-		reserved_qty = bin[0].reserved_qty if bin else 0.0
+		ordered_qty = bin_po_item[0].ordered_qty if bin_po_item else 0.0
+		reserved_qty = bin_po_item[0].reserved_qty if bin_po_item else 0.0
 
-		self.assertEqual(abs(flt(ordered_qty)), existing_ordered_qty)
-		self.assertEqual(abs(flt(reserved_qty)), existing_reserved_qty)
+		self.assertEqual(abs(flt(ordered_qty)), 0)
+		self.assertEqual(abs(flt(reserved_qty)), 0)
 
-		reserved_qty = frappe.db.get_value("Bin",
-			{"item_code": dn_item.item_code, "warehouse": "_Test Warehouse - _TC"}, "reserved_qty")
-
-		self.assertEqual(abs(flt(reserved_qty)), existing_reserved_qty_for_dn_item)
+		# teardown
+		so_update_status("Draft", so.name)
+		dn.load_from_db()
+		dn.cancel()
+		po.cancel()
+		so.load_from_db()
+		so.cancel()
 
 	def test_reserved_qty_for_closing_so(self):
 		bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index dcbc074..4044f09 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -63,7 +63,7 @@
   },
   {
    "default": "15",
-   "description": "Auto close Opportunity after 15 days",
+   "description": "Auto close Opportunity after the no. of days mentioned above",
    "fieldname": "close_opportunity_after_days",
    "fieldtype": "Int",
    "label": "Close Opportunity After Days"
@@ -80,18 +80,18 @@
   {
    "fieldname": "so_required",
    "fieldtype": "Select",
-   "label": "Sales Order Required for Sales Invoice & Delivery Note Creation",
+   "label": "Is Sales Order Required for Sales Invoice & Delivery Note Creation?",
    "options": "No\nYes"
   },
   {
    "fieldname": "dn_required",
    "fieldtype": "Select",
-   "label": "Delivery Note Required for Sales Invoice Creation",
+   "label": "Is Delivery Note Required for Sales Invoice Creation?",
    "options": "No\nYes"
   },
   {
    "default": "Each Transaction",
-   "description": "How often should project and company be updated based on Sales Transactions.",
+   "description": "How often should Project and Company be updated based on Sales Transactions?",
    "fieldname": "sales_update_frequency",
    "fieldtype": "Select",
    "label": "Sales Update Frequency",
@@ -108,38 +108,39 @@
    "default": "0",
    "fieldname": "editable_price_list_rate",
    "fieldtype": "Check",
-   "label": "Allow user to edit Price List Rate in transactions"
+   "label": "Allow User to Edit Price List Rate in Transactions"
   },
   {
    "default": "0",
    "fieldname": "allow_multiple_items",
    "fieldtype": "Check",
-   "label": "Allow Item to be added multiple times in a transaction"
+   "label": "Allow Item to Be Added Multiple Times in a Transaction"
   },
   {
    "default": "0",
    "fieldname": "allow_against_multiple_purchase_orders",
    "fieldtype": "Check",
-   "label": "Allow multiple Sales Orders against a Customer's Purchase Order"
+   "label": "Allow Multiple Sales Orders Against a Customer's Purchase Order"
   },
   {
    "default": "0",
    "fieldname": "validate_selling_price",
    "fieldtype": "Check",
-   "label": "Validate Selling Price for Item against Purchase Rate or Valuation Rate"
+   "label": "Validate Selling Price for Item Against Purchase Rate or Valuation Rate"
   },
   {
    "default": "0",
    "fieldname": "hide_tax_id",
    "fieldtype": "Check",
-   "label": "Hide Customer's Tax Id from Sales Transactions"
+   "label": "Hide Customer's Tax ID from Sales Transactions"
   }
  ],
  "icon": "fa fa-cog",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-06-01 13:58:35.637858",
+ "modified": "2020-10-13 12:12:56.784014",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling Settings",
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index 7f0cabe..ec886d7 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -9,8 +9,8 @@
 	}
 
 	init_component() {
-        this.prepare_dom();
-        this.initialize_numpad();
+		this.prepare_dom();
+		this.initialize_numpad();
 		this.bind_events();
 		this.attach_shortcuts();
 		
@@ -18,32 +18,32 @@
 
 	prepare_dom() {
 		this.wrapper.append(
-            `<section class="col-span-6 flex shadow rounded payment-section bg-white mx-h-70 h-100 d-none">
+			`<section class="col-span-6 flex shadow rounded payment-section bg-white mx-h-70 h-100 d-none">
 				<div class="flex flex-col p-16 pt-8 pb-8 w-full">
 					<div class="text-grey mb-6 payment-section no-select pointer">
 						PAYMENT METHOD<span class="octicon octicon-chevron-down collapse-indicator"></span>
 					</div>
 					<div class="payment-modes flex flex-wrap"></div>
 					<div class="invoice-details-section"></div>
-                    <div class="flex mt-auto justify-center w-full">
-                        <div class="flex flex-col justify-center flex-1 ml-4">
-                            <div class="flex w-full">
-                                <div class="totals-remarks items-end justify-end flex flex-1">
-                                    <div class="remarks text-md-0 text-grey mr-auto"></div>
-                                    <div class="totals flex justify-end pt-4"></div>
-                                </div>
-                                <div class="number-pad w-40 mb-4 ml-8 d-none"></div>
-                            </div>
-                            <div class="flex items-center justify-center mt-4 submit-order h-16 w-full rounded bg-primary text-md text-white no-select pointer text-bold">
-                                Complete Order
+					<div class="flex mt-auto justify-center w-full">
+							<div class="flex flex-col justify-center flex-1 ml-4">
+							<div class="flex w-full">
+								<div class="totals-remarks items-end justify-end flex flex-1">
+									<div class="remarks text-md-0 text-grey mr-auto"></div>
+									<div class="totals flex justify-end pt-4"></div>
+								</div>
+								<div class="number-pad w-40 mb-4 ml-8 d-none"></div>
+							</div>
+							<div class="flex items-center justify-center mt-4 submit-order h-16 w-full rounded bg-primary text-md text-white no-select pointer text-bold">
+								Complete Order
 							</div>
 							<div class="order-time flex items-center justify-end mt-2 pt-2 pb-2 w-full text-md-0 text-grey no-select pointer d-none"></div>
-                        </div>
-                    </div>
-                </div>
-            </section>`
-        )
-        this.$component = this.wrapper.find('.payment-section');
+						</div>
+					</div>
+				</div>
+			</section>`
+		)
+		this.$component = this.wrapper.find('.payment-section');
 		this.$payment_modes = this.$component.find('.payment-modes');
 		this.$totals_remarks = this.$component.find('.totals-remarks');
 		this.$totals = this.$component.find('.totals');
@@ -174,6 +174,24 @@
 			}
 		})
 
+		frappe.realtime.on("process_phone_payment", function(data) {
+			frappe.dom.unfreeze();
+			cur_frm.reload_doc();
+			let message = data["ResultDesc"];
+			let title = __("Payment Failed");
+
+			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.$payment_modes.on('click', '.shortcut', function(e) {
 			const value = $(this).attr('data-value');
 			me.selected_mode.set_value(value);
@@ -509,5 +527,5 @@
 
 	toggle_component(show) {
 		show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
-    }
+	}
  }
\ No newline at end of file
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
index 21fa4c3..20c6342 100644
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
+++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
@@ -7,6 +7,10 @@
 			frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series;
 			frm.refresh_field("quotation_series");
 		}
+
+		frm.set_query('payment_gateway_account', function() {
+			return { 'filters': { 'payment_channel': "Email" } };
+		});
 	},
 	enabled: function(frm) {
 		if (frm.doc.enabled === 1) {
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 4b04a0a..0168613 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -57,7 +57,7 @@
 
 		sle = frappe.get_doc("Stock Ledger Entry", {"voucher_type": "Delivery Note", "voucher_no": dn.name})
 
-		self.assertEqual(sle.stock_value_difference, -1*stock_queue[0][1])
+		self.assertEqual(sle.stock_value_difference, flt(-1*stock_queue[0][1], 2))
 
 		self.assertFalse(get_gl_entries("Delivery Note", dn.name))
 
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 2a54169..9ad3694 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1117,7 +1117,10 @@
 				for d in backflushed_materials.get(item.item_code):
 					if d.get(item.warehouse):
 						if (qty > req_qty):
-							qty-= d.get(item.warehouse)
+							qty = (qty/trans_qty) * flt(self.fg_completed_qty)
+
+			if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
+				qty = frappe.utils.ceil(qty)
 
 			if qty > 0:
 				self.add_to_stock_entry_detail({
@@ -1320,8 +1323,10 @@
 				for sr in get_serial_nos(item.serial_no):
 					sales_order = frappe.db.get_value("Serial No", sr, "sales_order")
 					if sales_order:
-						frappe.throw(_("Item {0} (Serial No: {1}) cannot be consumed as is reserverd\
-						 to fullfill Sales Order {2}.").format(item.item_code, sr, sales_order))
+						msg = (_("(Serial No: {0}) cannot be consumed as it's reserverd to fullfill Sales Order {1}.")
+							.format(sr, sales_order))
+
+						frappe.throw(_("Item {0} {1}").format(item.item_code, msg))
 
 	def update_transferred_qty(self):
 		if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 9c5d3d8..067659f 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -82,7 +82,7 @@
    "options": "FIFO\nMoving Average"
   },
   {
-   "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.",
+   "description": "The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.",
    "fieldname": "over_delivery_receipt_allowance",
    "fieldtype": "Float",
    "label": "Over Delivery/Receipt Allowance (%)"
@@ -91,7 +91,7 @@
    "default": "Stop",
    "fieldname": "action_if_quality_inspection_is_not_submitted",
    "fieldtype": "Select",
-   "label": "Action if Quality inspection is not submitted",
+   "label": "Action If Quality Inspection Is Not Submitted",
    "options": "Stop\nWarn"
   },
   {
@@ -114,7 +114,7 @@
    "default": "0",
    "fieldname": "auto_insert_price_list_rate_if_missing",
    "fieldtype": "Check",
-   "label": "Auto insert Price List rate if missing"
+   "label": "Auto Insert Price List Rate If Missing"
   },
   {
    "default": "0",
@@ -130,13 +130,13 @@
    "default": "1",
    "fieldname": "automatically_set_serial_nos_based_on_fifo",
    "fieldtype": "Check",
-   "label": "Automatically Set Serial Nos based on FIFO"
+   "label": "Automatically Set Serial Nos Based on FIFO"
   },
   {
    "default": "1",
    "fieldname": "set_qty_in_transactions_based_on_serial_no_input",
    "fieldtype": "Check",
-   "label": "Set Qty in Transactions based on Serial No Input"
+   "label": "Set Qty in Transactions Based on Serial No Input"
   },
   {
    "fieldname": "auto_material_request",
@@ -147,13 +147,13 @@
    "default": "0",
    "fieldname": "auto_indent",
    "fieldtype": "Check",
-   "label": "Raise Material Request when stock reaches re-order level"
+   "label": "Raise Material Request When Stock Reaches Re-order Level"
   },
   {
    "default": "0",
    "fieldname": "reorder_email_notify",
    "fieldtype": "Check",
-   "label": "Notify by Email on creation of automatic Material Request"
+   "label": "Notify by Email on Creation of Automatic Material Request"
   },
   {
    "fieldname": "freeze_stock_entries",
@@ -168,12 +168,12 @@
   {
    "fieldname": "stock_frozen_upto_days",
    "fieldtype": "Int",
-   "label": "Freeze Stocks Older Than [Days]"
+   "label": "Freeze Stocks Older Than (Days)"
   },
   {
    "fieldname": "stock_auth_role",
    "fieldtype": "Link",
-   "label": "Role Allowed to edit frozen stock",
+   "label": "Role Allowed to Edit Frozen Stock",
    "options": "Role"
   },
   {
@@ -203,20 +203,21 @@
    "default": "0",
    "fieldname": "allow_from_dn",
    "fieldtype": "Check",
-   "label": "Allow Material Transfer From Delivery Note and Sales Invoice"
+   "label": "Allow Material Transfer from Delivery Note to Sales Invoice"
   },
   {
    "default": "0",
    "fieldname": "allow_from_pr",
    "fieldtype": "Check",
-   "label": "Allow Material Transfer From Purchase Receipt and Purchase Invoice"
+   "label": "Allow Material Transfer from Purchase Receipt to Purchase Invoice"
   }
  ],
  "icon": "icon-cog",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-06-20 11:39:15.344112",
+ "modified": "2020-10-13 10:33:29.147682",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Settings",