Merge branch 'staging' into develop
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index e61c0d1..699dcf3 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
 from erpnext.hooks import regional_overrides
 from frappe.utils import getdate
 
-__version__ = '10.1.60'
+__version__ = '10.1.61'
 
 def get_default_company(user=None):
 	'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index f3e85b2..014efd9 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -123,7 +123,7 @@
 			"reference_doctype": "Payment Request",
 			"reference_docname": self.name,
 			"payer_email": self.email_to or frappe.session.user,
-			"payer_name": frappe.safe_decode(data.customer_name),
+			"payer_name": frappe.safe_encode(data.customer_name),
 			"order_id": self.name,
 			"currency": self.currency
 		})
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 7219d87..b14597d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -123,6 +123,7 @@
 	def set_missing_values(self, for_validate=False):
 		if not self.credit_to:
 			self.credit_to = get_party_account("Supplier", self.supplier, self.company)
+			self.party_account_currency = frappe.db.get_value("Account", self.credit_to, "account_currency", cache=True)
 		if not self.due_date:
 			self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company,  self.bill_date)
 
@@ -208,7 +209,8 @@
 		if self.update_stock:
 			self.validate_item_code()
 			self.validate_warehouse()
-			warehouse_account = get_warehouse_account_map()
+			if auto_accounting_for_stock:
+				warehouse_account = get_warehouse_account_map()
 
 		for item in self.get("items"):
 			# in case of auto inventory accounting,
@@ -377,7 +379,10 @@
 		return gl_entries
 
 	def make_supplier_gl_entry(self, gl_entries):
-		grand_total = self.rounded_total or self.grand_total
+		# Checked both rounding_adjustment and rounded_total 
+		# because rounded_total had value even before introcution of posting GLE based on rounded total
+		grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
+
 		if grand_total:
 			# Didnot use base_grand_total to book rounding loss gle
 			grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
@@ -401,7 +406,8 @@
 		# item gl entries
 		stock_items = self.get_stock_items()
 		expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
-		warehouse_account = get_warehouse_account_map()
+		if self.update_stock and self.auto_accounting_for_stock:
+			warehouse_account = get_warehouse_account_map()
 
 		voucher_wise_stock_value = {}
 		if self.update_stock:
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index d730e88..607051e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -310,6 +310,7 @@
 
 		if not self.debit_to:
 			self.debit_to = get_party_account("Customer", self.customer, self.company)
+			self.party_account_currency = frappe.db.get_value("Account", self.debit_to, "account_currency", cache=True)
 		if not self.due_date and self.customer:
 			self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
 
@@ -715,7 +716,9 @@
 		return gl_entries
 
 	def make_customer_gl_entry(self, gl_entries):
-		grand_total = self.rounded_total or self.grand_total
+		# Checked both rounding_adjustment and rounded_total 
+		# because rounded_total had value even before introcution of posting GLE based on rounded total
+		grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
 		if grand_total:
 			# Didnot use base_grand_total to book rounding loss gle
 			grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index bf1c139..6985c80 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -70,6 +70,7 @@
 						if item.rate_with_margin > 0 else item.rate
 
 				item.net_rate = item.rate
+				item.discount_amount = item.price_list_rate - item.rate
 				item.amount = flt(item.rate * item.qty,	item.precision("amount"))
 				item.net_amount = item.amount
 
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index 4070091..b0be2a9 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -287,6 +287,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "eval:!doc.organization_lead",
    "fieldname": "gender", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -1521,4 +1522,4 @@
  "track_changes": 0, 
  "track_seen": 0, 
  "track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/__init__.py b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/__init__.py
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.js b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.js
new file mode 100644
index 0000000..bfffcc5
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.js
@@ -0,0 +1,71 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('QuickBooks Migrator', {
+	connect: function(frm) {
+		// OAuth requires user intervention to provide application access permissionsto requested scope
+		// Here we open a new window and redirect user to the authorization url.
+		// After user grants us permission to access. We will set authorization details on this doc which will force refresh.
+		window.open(frm.doc.authorization_url)
+	},
+	fetch_data: function(frm) {
+		frm.call("migrate")
+	},
+	onload: function(frm) {
+		frm.trigger("set_indicator")
+		var domain = frappe.urllib.get_base_url()
+		var redirect_url = `${domain}/api/method/erpnext.erpnext_integrations.doctype.quickbooks_migrator.quickbooks_migrator.callback`
+		if (frm.doc.redirect_url != redirect_url) {
+			frm.set_value("redirect_url", redirect_url)
+		}
+		// Instead of changing percentage width and message of single progress bar
+		// Show a different porgress bar for every action after some time remove the finished progress bar
+ 		// Former approach causes the progress bar to dance back and forth.
+		frm.trigger("set_indicator")
+ 		frappe.realtime.on("quickbooks_progress_update", function (data) {
+			frm.dashboard.show_progress(data.message, (data.count / data.total) * 100, data.message)
+			if (data.count == data.total) {
+				window.setTimeout( function(message) {frm.dashboard.hide_progress(message)}, 1500, data.messsage)
+			}
+		})
+	},
+	refresh: function(frm) {
+		frm.trigger("set_indicator")
+		if (!frm.doc.access_token) {
+			// Unset access_token signifies that we don't have enough information to connect to quickbooks api and fetch data
+			if (frm.doc.authorization_url) {
+				frm.add_custom_button(__("Connect to Quickbooks"), function () {
+					frm.trigger("connect")
+				});
+			}
+		}
+		if (frm.doc.access_token) {
+			// If we have access_token that means we also have refresh_token we don't need user intervention anymore
+			// All we need now is a Company from erpnext
+			frm.remove_custom_button(__("Connect to Quickbooks"))
+
+			frm.toggle_display("company_settings", 1)
+			frm.set_df_property("company", "reqd", 1)
+			if (frm.doc.company) {
+					frm.add_custom_button(__("Fetch Data"), function () {
+					frm.trigger("fetch_data")
+				});
+			}
+		}
+	},
+	set_indicator: function(frm) {
+		var indicator_map = {
+			"Connecting to QuickBooks": [__("Connecting to QuickBooks"), "orange"],
+			"Connected to QuickBooks": [__("Connected to QuickBooks"), "green"],
+			"In Progress": [__("In Progress"), "orange"],
+			"Complete": [__("Complete"), "green"],
+			"Failed": [__("Failed"), "red"],
+		}
+		if (frm.doc.status) {
+			var indicator = indicator_map[frm.doc.status]
+			var label = indicator[0]
+			var color = indicator[1]
+			frm.page.set_indicator(label, color)
+		}
+	},
+});
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.json b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.json
new file mode 100644
index 0000000..b156006
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.json
@@ -0,0 +1,843 @@
+{
+ "allow_copy": 0,
+ "allow_events_in_timeline": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 1,
+ "creation": "2018-07-10 14:48:16.757030",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "hidden": 1,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Status",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Connecting to QuickBooks\nConnected to QuickBooks\nIn Progress\nComplete\nFailed",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 1,
+   "collapsible_depends_on": "eval:doc.client_id && doc.client_secret && doc.redirect_url",
+   "columns": 0,
+   "fieldname": "application_settings",
+   "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": "Application Settings",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "default": "",
+   "fieldname": "client_id",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 1,
+   "in_standard_filter": 0,
+   "label": "Client ID",
+   "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": 1,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "default": "",
+   "fieldname": "redirect_url",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 1,
+   "in_standard_filter": 0,
+   "label": "Redirect URL",
+   "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": 1,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "default": "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
+   "fieldname": "token_endpoint",
+   "fieldtype": "Data",
+   "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": "Token Endpoint",
+   "length": 0,
+   "no_copy": 0,
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 1,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 1,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "application_column_break",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "default": "",
+   "fieldname": "client_secret",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 1,
+   "in_standard_filter": 0,
+   "label": "Client Secret",
+   "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": 1,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "default": "com.intuit.quickbooks.accounting",
+   "fieldname": "scope",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 1,
+   "in_standard_filter": 0,
+   "label": "Scope",
+   "length": 0,
+   "no_copy": 0,
+   "options": "",
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 1,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 1,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "default": "https://quickbooks.api.intuit.com/v3",
+   "fieldname": "api_endpoint",
+   "fieldtype": "Data",
+   "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": "API Endpoint",
+   "length": 0,
+   "no_copy": 0,
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 1,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 1,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 1,
+   "columns": 0,
+   "fieldname": "authorization_settings",
+   "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": "Authorization Settings",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "default": "https://appcenter.intuit.com/connect/oauth2",
+   "fieldname": "authorization_endpoint",
+   "fieldtype": "Data",
+   "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": "Authorization Endpoint",
+   "length": 0,
+   "no_copy": 0,
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 1,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 1,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "refresh_token",
+   "fieldtype": "Small Text",
+   "hidden": 1,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Refresh Token",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "code",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Code",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "authorization_column_break",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "authorization_url",
+   "fieldtype": "Data",
+   "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": "Authorization URL",
+   "length": 0,
+   "no_copy": 0,
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 1,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 1,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "access_token",
+   "fieldtype": "Small Text",
+   "hidden": 1,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Access Token",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "quickbooks_company_id",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Quickbooks Company ID",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "company_settings",
+   "fieldtype": "Section Break",
+   "hidden": 1,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Company Settings",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "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": "Company",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Company",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "default_shipping_account",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "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 Shipping 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": 0,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "default_warehouse",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "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 Warehouse",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Warehouse",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "company_column_break",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "default_cost_center",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "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 Cost Center",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Cost Center",
+   "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
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "undeposited_funds_account",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Undeposited Funds 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": 0,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 1,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2018-10-17 03:12:53.506229",
+ "modified_by": "Administrator",
+ "module": "ERPNext Integrations",
+ "name": "QuickBooks Migrator",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "amend": 0,
+   "cancel": 0,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 0,
+   "if_owner": 0,
+   "import": 0,
+   "permlevel": 0,
+   "print": 1,
+   "read": 1,
+   "report": 0,
+   "role": "System Manager",
+   "set_user_permissions": 0,
+   "share": 1,
+   "submit": 0,
+   "write": 1
+  }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 0,
+ "track_seen": 0,
+ "track_views": 0
+}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
new file mode 100644
index 0000000..29a1a2b
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
@@ -0,0 +1,1274 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from requests_oauthlib import OAuth2Session
+import json, requests
+from erpnext import encode_company_abbr
+
+# QuickBooks requires a redirect URL, User will be redirect to this URL
+# This will be a GET request
+# Request parameters will have two parameters `code` and `realmId`
+# `code` is required to acquire refresh_token and access_token
+# `realmId` is the QuickBooks Company ID. It is Needed to actually fetch data.
+@frappe.whitelist()
+def callback(*args, **kwargs):
+	migrator = frappe.get_doc("QuickBooks Migrator")
+	migrator.set_indicator("Connecting to QuickBooks")
+	migrator.code = kwargs.get("code")
+	migrator.quickbooks_company_id = kwargs.get("realmId")
+	migrator.save()
+	migrator.get_tokens()
+	frappe.db.commit()
+	migrator.set_indicator("Connected to QuickBooks")
+	# We need this page to automatically close afterwards
+	frappe.respond_as_web_page("Quickbooks Authentication", html="<script>window.close()</script>")
+
+
+class QuickBooksMigrator(Document):
+	def __init__(self, *args, **kwargs):
+		super(QuickBooksMigrator, self).__init__(*args, **kwargs)
+		from pprint import pprint
+		self.oauth = OAuth2Session(
+			client_id=self.client_id,
+			redirect_uri=self.redirect_url,
+			scope=self.scope
+		)
+		if not self.authorization_url and self.authorization_endpoint:
+			self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
+
+
+	def on_update(self):
+		if self.company:
+			# We need a Cost Center corresponding to the selected erpnext Company
+			self.default_cost_center = frappe.db.get_value('Company', self.company, 'cost_center')
+			self.default_warehouse = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0})[0]["name"]
+		if self.authorization_endpoint:
+			self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
+
+
+	def migrate(self):
+		frappe.enqueue_doc("QuickBooks Migrator", "QuickBooks Migrator", "_migrate", queue="long")
+
+
+	def _migrate(self):
+		try:
+			self.set_indicator("In Progress")
+			# Add quickbooks_id field to every document so that we can lookup by Id reference
+			# provided by documents in API responses.
+			# Also add a company field to Customer Supplier and Item
+			self._make_custom_fields()
+
+			self._migrate_accounts()
+
+			# Some Quickbooks Entities like Advance Payment, Payment aren't available firectly from API
+			# Sales Invoice also sometimes needs to be saved as a Journal Entry
+			# (When Item table is not present, This appens when Invoice is attached with a "StatementCharge" "ReimburseCharge
+			# Details of both of these cannot be fetched from API)
+			# Their GL entries need to be generated from GeneralLedger Report.
+			self._fetch_general_ledger()
+
+			# QuickBooks data can have transactions that do not fall in existing fiscal years in ERPNext
+			self._create_fiscal_years()
+
+			self._allow_fraction_in_unit()
+
+			# Following entities are directly available from API
+			# Invoice can be an exception sometimes though (as explained above).
+			entities_for_normal_transform = [
+				"Customer", "Item", "Vendor",
+				"Preferences",
+				"JournalEntry", "Purchase", "Deposit",
+				"Invoice", "CreditMemo", "SalesReceipt", "RefundReceipt",
+				"Bill", "VendorCredit",
+				"Payment", "BillPayment",
+			]
+			for entity in entities_for_normal_transform:
+				self._migrate_entries(entity)
+
+			# Following entries are not available directly from API, Need to be regenrated from GeneralLedger Report
+			entities_for_gl_transform = ["Advance Payment", "Tax Payment", "Sales Tax Payment", "Purchase Tax Payment", "Inventory Qty Adjust"]
+			for entity in entities_for_gl_transform:
+				self._migrate_entries_from_gl(entity)
+			self.set_indicator("Complete")
+		except Exception as e:
+			self.set_indicator("Failed")
+			self._log_error(e)
+
+		frappe.db.commit()
+
+
+	def get_tokens(self):
+		token = self.oauth.fetch_token(
+			token_url=self.token_endpoint,
+			client_secret=self.client_secret,
+			code=self.code
+		)
+		self.access_token = token["access_token"]
+		self.refresh_token = token["refresh_token"]
+		self.save()
+
+
+	def _refresh_tokens(self):
+		token = self.oauth.refresh_token(
+			token_url=self.token_endpoint,
+			client_id=self.client_id,
+			refresh_token=self.refresh_token,
+			client_secret=self.client_secret,
+			code=self.code
+		)
+		self.access_token = token["access_token"]
+		self.refresh_token = token["refresh_token"]
+		self.save()
+
+
+	def _make_custom_fields(self):
+		doctypes_for_quickbooks_id_field = ["Account", "Customer", "Address", "Item", "Supplier", "Sales Invoice", "Journal Entry", "Purchase Invoice"]
+		for doctype in doctypes_for_quickbooks_id_field:
+			self._make_custom_quickbooks_id_field(doctype)
+
+		doctypes_for_company_field = ["Customer", "Item", "Supplier"]
+		for doctype in doctypes_for_company_field:
+			self._make_custom_company_field(doctype)
+
+		frappe.db.commit()
+
+
+	def _make_custom_quickbooks_id_field(self, doctype):
+		if not frappe.get_meta(doctype).has_field("quickbooks_id"):
+			frappe.get_doc({
+				"doctype": "Custom Field",
+				"label": "QuickBooks ID",
+				"dt": doctype,
+				"fieldname": "quickbooks_id",
+				"fieldtype": "Data",
+			}).insert()
+
+
+	def _make_custom_company_field(self, doctype):
+		if not frappe.get_meta(doctype).has_field("company"):
+			frappe.get_doc({
+				"doctype": "Custom Field",
+				"label": "Company",
+				"dt": doctype,
+				"fieldname": "company",
+				"fieldtype": "Link",
+				"options": "Company",
+			}).insert()
+
+
+	def _migrate_accounts(self):
+		self._make_root_accounts()
+		for entity in ["Account", "TaxRate", "TaxCode"]:
+			self._migrate_entries(entity)
+
+
+	def _make_root_accounts(self):
+		roots = ["Asset", "Equity", "Expense", "Liability", "Income"]
+		for root in roots:
+			try:
+				if not frappe.db.exists({"doctype": "Account", "name": encode_company_abbr("{} - QB".format(root), self.company), "company": self.company}):
+					frappe.get_doc({
+						"doctype": "Account",
+						"account_name": "{} - QB".format(root),
+						"root_type": root,
+						"is_group": "1",
+						"company": self.company,
+					}).insert(ignore_mandatory=True)
+			except Exception as e:
+				self._log_error(e, root)
+		frappe.db.commit()
+
+
+	def _migrate_entries(self, entity):
+		try:
+			query_uri = "{}/company/{}/query".format(
+				self.api_endpoint,
+				self.quickbooks_company_id,
+			)
+			max_result_count = 1000
+			# Count number of entries
+			response = self._get(query_uri,
+				params={
+					"query": """SELECT COUNT(*) FROM {}""".format(entity)
+				}
+			)
+			entry_count = response.json()["QueryResponse"]["totalCount"]
+
+			# fetch pages and accumulate
+			entries = []
+			for start_position in range(1, entry_count + 1, max_result_count):
+				response = self._get(query_uri,
+					params={
+						"query": """SELECT * FROM {} STARTPOSITION {} MAXRESULTS {}""".format(
+							entity, start_position, max_result_count
+						)
+					}
+				)
+				entries.extend(response.json()["QueryResponse"][entity])
+			entries = self._preprocess_entries(entity, entries)
+			self._save_entries(entity, entries)
+		except Exception as e:
+			self._log_error(e, response.text)
+
+
+	def _fetch_general_ledger(self):
+		try:
+			query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint ,self.quickbooks_company_id)
+			response = self._get(query_uri,
+				params={
+					"columns": ",".join(["tx_date", "txn_type", "credit_amt", "debt_amt"]),
+					"date_macro": "All",
+					"minorversion": 3,
+				}
+			)
+			self.gl_entries = {}
+			for section in response.json()["Rows"]["Row"]:
+				if section["type"] == "Section":
+					self._get_gl_entries_from_section(section)
+			self.general_ledger = {}
+			for account in self.gl_entries.values():
+				for line in account:
+					type_dict = self.general_ledger.setdefault(line["type"], {})
+					if line["id"] not in type_dict:
+						type_dict[line["id"]] = {
+							"id": line["id"],
+							"date": line["date"],
+							"lines": [],
+						}
+					type_dict[line["id"]]["lines"].append(line)
+		except Exception as e:
+			self._log_error(e, response.text)
+
+
+	def _create_fiscal_years(self):
+		try:
+			# Assumes that exactly one fiscal year has been created so far
+			# Creates fiscal years till oldest ledger entry date is covered
+			from frappe.utils.data import add_years, getdate
+			from itertools import chain
+			smallest_ledger_entry_date = getdate(min(entry["date"] for entry in chain(*self.gl_entries.values()) if entry["date"]))
+			oldest_fiscal_year = frappe.get_all("Fiscal Year",
+				fields=["year_start_date", "year_end_date"],
+				order_by="year_start_date"
+			)[0]
+			# Keep on creating fiscal years
+			# until smallest_ledger_entry_date is no longer smaller than the oldest fiscal year's start date
+			while smallest_ledger_entry_date < oldest_fiscal_year.year_start_date:
+				new_fiscal_year = frappe.get_doc({"doctype": "Fiscal Year"})
+				new_fiscal_year.year_start_date = add_years(oldest_fiscal_year.year_start_date, -1)
+				new_fiscal_year.year_end_date = add_years(oldest_fiscal_year.year_end_date, -1)
+				if new_fiscal_year.year_start_date.year == new_fiscal_year.year_end_date.year:
+					new_fiscal_year.year = new_fiscal_year.year_start_date.year
+				else:
+					new_fiscal_year.year = "{}-{}".format(new_fiscal_year.year_start_date.year, new_fiscal_year.year_end_date.year)
+				new_fiscal_year.save()
+				oldest_fiscal_year = new_fiscal_year
+
+			frappe.db.commit()
+		except Exception as e:
+			self._log_error(e)
+
+
+	def _migrate_entries_from_gl(self, entity):
+		if entity in self.general_ledger:
+			self._save_entries(entity, self.general_ledger[entity].values())
+
+
+	def _save_entries(self, entity, entries):
+		entity_method_map = {
+			"Account": self._save_account,
+			"TaxRate": self._save_tax_rate,
+			"TaxCode": self._save_tax_code,
+
+			"Preferences": self._save_preference,
+
+			"Customer": self._save_customer,
+			"Item": self._save_item,
+			"Vendor": self._save_vendor,
+
+			"Invoice": self._save_invoice,
+			"CreditMemo": self._save_credit_memo,
+			"SalesReceipt": self._save_sales_receipt,
+			"RefundReceipt": self._save_refund_receipt,
+
+			"JournalEntry": self._save_journal_entry,
+
+			"Bill": self._save_bill,
+			"VendorCredit": self._save_vendor_credit,
+
+			"Payment": self._save_payment,
+			"BillPayment": self._save_bill_payment,
+
+			"Purchase": self._save_purchase,
+			"Deposit": self._save_deposit,
+
+			"Advance Payment": self._save_advance_payment,
+			"Tax Payment": self._save_tax_payment,
+			"Sales Tax Payment": self._save_tax_payment,
+			"Purchase Tax Payment": self._save_tax_payment,
+			"Inventory Qty Adjust": self._save_inventory_qty_adjust,
+		}
+		total = len(entries)
+		for index, entry in enumerate(entries, start=1):
+			self._publish({"event": "progress", "message": _("Saving {0}").format(entity), "count": index, "total": total})
+			entity_method_map[entity](entry)
+		frappe.db.commit()
+
+
+	def _preprocess_entries(self, entity, entries):
+		entity_method_map = {
+			"Account": self._preprocess_accounts,
+			"TaxRate": self._preprocess_tax_rates,
+			"TaxCode": self._preprocess_tax_codes,
+		}
+		preprocessor = entity_method_map.get(entity)
+		if preprocessor:
+			entries = preprocessor(entries)
+		return entries
+
+
+	def _get_gl_entries_from_section(self, section, account=None):
+		if "Header" in section:
+			if "id" in section["Header"]["ColData"][0]:
+				account = self._get_account_name_by_id(section["Header"]["ColData"][0]["id"])
+			elif "value" in section["Header"]["ColData"][0] and section["Header"]["ColData"][0]["value"]:
+				# For some reason during migrating UK company, account id is not available.
+				# preprocess_accounts retains name:account mapping in self.accounts
+				# This mapping can then be used to obtain quickbooks_id for correspondong account
+				# Rest is trivial
+
+				# Some Lines in General Leder Report are shown under Not Specified
+				# These should be skipped
+				if section["Header"]["ColData"][0]["value"] == "Not Specified":
+					return
+				account_id = self.accounts[section["Header"]["ColData"][0]["value"]]["Id"]
+				account = self._get_account_name_by_id(account_id)
+		entries = []
+		for row in section["Rows"]["Row"]:
+			if row["type"] == "Data":
+				data = row["ColData"]
+				entries.append({
+					"account": account,
+					"date": data[0]["value"],
+					"type": data[1]["value"],
+					"id": data[1].get("id"),
+					"credit": frappe.utils.flt(data[2]["value"]),
+					"debit": frappe.utils.flt(data[3]["value"]),
+				})
+			if row["type"] == "Section":
+				self._get_gl_entries_from_section(row, account)
+		self.gl_entries.setdefault(account, []).extend(entries)
+
+
+	def _preprocess_accounts(self, accounts):
+		self.accounts = {account["Name"]: account for account in accounts}
+		for account in accounts:
+			if any(acc["SubAccount"] and acc["ParentRef"]["value"] == account["Id"] for acc in accounts):
+				account["is_group"] = 1
+			else:
+				account["is_group"] = 0
+		return sorted(accounts, key=lambda account: int(account["Id"]))
+
+
+	def _save_account(self, account):
+		mapping = {
+			"Bank": "Asset",
+			"Other Current Asset": "Asset",
+			"Fixed Asset": "Asset",
+			"Other Asset": "Asset",
+			"Accounts Receivable": "Asset",
+
+			"Equity": "Equity",
+
+			"Expense": "Expense",
+			"Other Expense": "Expense",
+			"Cost of Goods Sold": "Expense",
+
+			"Accounts Payable": "Liability",
+			"Credit Card": "Liability",
+			"Long Term Liability": "Liability",
+			"Other Current Liability": "Liability",
+
+			"Income": "Income",
+			"Other Income": "Income",
+		}
+		# Map Quickbooks Account Types to ERPNext root_accunts and and root_type
+		try:
+			if not frappe.db.exists({"doctype": "Account", "quickbooks_id": account["Id"], "company": self.company}):
+				is_child = account["SubAccount"]
+				is_group = account["is_group"]
+				# Create Two Accounts for every Group Account
+				if is_group:
+					account_id = "Group - {}".format(account["Id"])
+				else:
+					account_id = account["Id"]
+
+				if is_child:
+					parent_account = self._get_account_name_by_id("Group - {}".format(account["ParentRef"]["value"]))
+				else:
+					parent_account = encode_company_abbr("{} - QB".format(mapping[account["AccountType"]]), self.company)
+
+				frappe.get_doc({
+					"doctype": "Account",
+					"quickbooks_id": account_id,
+					"account_name": self._get_unique_account_name(account["Name"]),
+					"root_type": mapping[account["AccountType"]],
+					"account_type": self._get_account_type(account),
+					"account_currency": account["CurrencyRef"]["value"],
+					"parent_account": parent_account,
+					"is_group": is_group,
+					"company": self.company,
+				}).insert()
+
+				if is_group:
+					# Create a Leaf account corresponding to the group account
+					frappe.get_doc({
+						"doctype": "Account",
+						"quickbooks_id": account["Id"],
+						"account_name": self._get_unique_account_name(account["Name"]),
+						"root_type": mapping[account["AccountType"]],
+						"account_type": self._get_account_type(account),
+						"account_currency": account["CurrencyRef"]["value"],
+						"parent_account": self._get_account_name_by_id(account_id),
+						"is_group": 0,
+						"company": self.company,
+					}).insert()
+				if account.get("AccountSubType") == "UndepositedFunds":
+					self.undeposited_funds_account = self._get_account_name_by_id(account["Id"])
+					self.save()
+		except Exception as e:
+			self._log_error(e, account)
+
+
+	def _get_account_type(self, account):
+		account_subtype_mapping = {"UndepositedFunds": "Cash"}
+		account_type = account_subtype_mapping.get(account.get("AccountSubType"))
+		if account_type is None:
+			account_type_mapping = {"Accounts Payable": "Payable", "Accounts Receivable": "Receivable", "Bank": "Bank", "Credit Card": "Bank"}
+			account_type = account_type_mapping.get(account["AccountType"])
+		return account_type
+
+
+	def _preprocess_tax_rates(self, tax_rates):
+		self.tax_rates = {tax_rate["Id"]: tax_rate for tax_rate in tax_rates}
+		return tax_rates
+
+
+	def _save_tax_rate(self, tax_rate):
+		try:
+			if not frappe.db.exists({"doctype": "Account", "quickbooks_id": "TaxRate - {}".format(tax_rate["Id"]), "company": self.company}):
+				frappe.get_doc({
+					"doctype": "Account",
+					"quickbooks_id": "TaxRate - {}".format(tax_rate["Id"]),
+					"account_name": "{} - QB".format(tax_rate["Name"]),
+					"root_type": "Liability",
+					"parent_account": encode_company_abbr("{} - QB".format("Liability"), self.company),
+					"is_group": "0",
+					"company": self.company,
+				}).insert()
+		except Exception as e:
+			self._log_error(e, tax_rate)
+
+
+	def _preprocess_tax_codes(self, tax_codes):
+		self.tax_codes = {tax_code["Id"]: tax_code for tax_code in tax_codes}
+		return tax_codes
+
+
+	def _save_tax_code(self, tax_code):
+		pass
+
+
+	def _save_customer(self, customer):
+		try:
+			if not frappe.db.exists({"doctype": "Customer", "quickbooks_id": customer["Id"], "company": self.company}):
+				try:
+					receivable_account = frappe.get_all("Account", filters={
+						"account_type": "Receivable",
+						"account_currency": customer["CurrencyRef"]["value"],
+						"company": self.company,
+					})[0]["name"]
+				except Exception as e:
+					receivable_account = None
+				erpcustomer = frappe.get_doc({
+					"doctype": "Customer",
+					"quickbooks_id": customer["Id"],
+					"customer_name" : encode_company_abbr(customer["DisplayName"], self.company),
+					"customer_type" : "Individual",
+					"customer_group" : "Commercial",
+					"default_currency": customer["CurrencyRef"]["value"],
+					"accounts": [{"company": self.company, "account": receivable_account}],
+					"territory" : "All Territories",
+					"company": self.company,
+				}).insert()
+				if "BillAddr" in customer:
+					self._create_address(erpcustomer, "Customer", customer["BillAddr"], "Billing")
+				if "ShipAddr" in customer:
+					self._create_address(erpcustomer, "Customer", customer["ShipAddr"], "Shipping")
+		except Exception as e:
+			self._log_error(e, customer)
+
+
+	def _save_item(self, item):
+		try:
+			if not frappe.db.exists({"doctype": "Item", "quickbooks_id": item["Id"], "company": self.company}):
+				if item["Type"] in ("Service", "Inventory"):
+					item_dict = {
+						"doctype": "Item",
+						"quickbooks_id": item["Id"],
+						"item_code" : encode_company_abbr(item["Name"], self.company),
+						"stock_uom": "Unit",
+						"is_stock_item": 0,
+						"item_group": "All Item Groups",
+						"company": self.company,
+						"item_defaults": [{"company": self.company, "default_warehouse": self.default_warehouse}]
+					}
+					if "ExpenseAccountRef" in item:
+						expense_account = self._get_account_name_by_id(item["ExpenseAccountRef"]["value"])
+						item_dict["item_defaults"][0]["expense_account"] = expense_account
+					if "IncomeAccountRef" in item:
+						income_account = self._get_account_name_by_id(item["IncomeAccountRef"]["value"])
+						item_dict["item_defaults"][0]["income_account"] = income_account
+					frappe.get_doc(item_dict).insert()
+		except Exception as e:
+			self._log_error(e, item)
+
+
+	def _allow_fraction_in_unit(self):
+		frappe.db.set_value("UOM", "Unit", "must_be_whole_number", 0)
+
+
+	def _save_vendor(self, vendor):
+		try:
+			if not frappe.db.exists({"doctype": "Supplier", "quickbooks_id": vendor["Id"], "company": self.company}):
+				erpsupplier = frappe.get_doc({
+					"doctype": "Supplier",
+					"quickbooks_id": vendor["Id"],
+					"supplier_name" : encode_company_abbr(vendor["DisplayName"], self.company),
+					"supplier_group" : "All Supplier Groups",
+					"company": self.company,
+				}).insert()
+				if "BillAddr" in vendor:
+					self._create_address(erpsupplier, "Supplier", vendor["BillAddr"], "Billing")
+				if "ShipAddr" in vendor:
+					self._create_address(erpsupplier, "Supplier",vendor["ShipAddr"], "Shipping")
+		except Exception as e:
+			self._log_error(e)
+
+
+	def _save_preference(self, preference):
+		try:
+			if preference["SalesFormsPrefs"]["AllowShipping"]:
+				default_shipping_account_id = preference["SalesFormsPrefs"]["DefaultShippingAccount"]
+				self.default_shipping_account = self._get_account_name_by_id(self, default_shipping_account_id)
+				self.save()
+		except Exception as e:
+			self._log_error(e, preference)
+
+
+	def _save_invoice(self, invoice):
+		# Invoice can be Linked with Another Transactions
+		# If any of these transactions is a "StatementCharge" or "ReimburseCharge" then in the UI
+		# item list is populated from the corresponding transaction, these items are not shown in api response
+		# Also as of now there is no way of fetching the corresponding transaction from api
+		# We in order to correctly reflect account balance make an equivalent Journal Entry
+		quickbooks_id = "Invoice - {}".format(invoice["Id"])
+		if any(linked["TxnType"] in ("StatementCharge", "ReimburseCharge") for linked in invoice["LinkedTxn"]):
+			self._save_invoice_as_journal_entry(invoice, quickbooks_id)
+		else:
+			self._save_sales_invoice(invoice, quickbooks_id)
+
+
+	def _save_credit_memo(self, credit_memo):
+		# Credit Memo is equivalent to a return Sales Invoice
+		quickbooks_id = "Credit Memo - {}".format(credit_memo["Id"])
+		self._save_sales_invoice(credit_memo, quickbooks_id, is_return=True)
+
+
+	def _save_sales_receipt(self, sales_receipt):
+		# Sales Receipt is equivalent to a POS Sales Invoice
+		quickbooks_id = "Sales Receipt - {}".format(sales_receipt["Id"])
+		self._save_sales_invoice(sales_receipt, quickbooks_id, is_pos=True)
+
+
+	def _save_refund_receipt(self, refund_receipt):
+		# Refund Receipt is equivalent to a return POS Sales Invoice
+		quickbooks_id = "Refund Receipt - {}".format(refund_receipt["Id"])
+		self._save_sales_invoice(refund_receipt, quickbooks_id, is_return=True, is_pos=True)
+
+
+	def _save_sales_invoice(self, invoice, quickbooks_id, is_return=False, is_pos=False):
+		try:
+			if not frappe.db.exists({"doctype": "Sales Invoice", "quickbooks_id": quickbooks_id, "company": self.company}):
+				invoice_dict = {
+					"doctype": "Sales Invoice",
+					"quickbooks_id": quickbooks_id,
+
+					# Quickbooks uses ISO 4217 Code
+					# of course this gonna come back to bite me
+					"currency": invoice["CurrencyRef"]["value"],
+
+					# Exchange Rate is provided if multicurrency is enabled
+					# It is not provided if multicurrency is not enabled
+					"conversion_rate": invoice.get("ExchangeRate", 1),
+					"posting_date": invoice["TxnDate"],
+
+					# QuickBooks doesn't make Due Date a mandatory field this is a hack
+					"due_date": invoice.get("DueDate", invoice["TxnDate"]),
+					"customer": frappe.get_all("Customer",
+						filters={
+							"quickbooks_id": invoice["CustomerRef"]["value"],
+							"company": self.company,
+						})[0]["name"],
+					"items": self._get_si_items(invoice, is_return=is_return),
+					"taxes": self._get_taxes(invoice),
+
+					# Do not change posting_date upon submission
+					"set_posting_time": 1,
+
+					# QuickBooks doesn't round total
+					"disable_rounded_total": 1,
+					"is_return": is_return,
+					"is_pos": is_pos,
+					"payments": self._get_invoice_payments(invoice, is_return=is_return, is_pos=is_pos),
+					"company": self.company,
+				}
+				discount = self._get_discount(invoice["Line"])
+				if discount:
+					if invoice["ApplyTaxAfterDiscount"]:
+						invoice_dict["apply_discount_on"] = "Net Total"
+					else:
+						invoice_dict["apply_discount_on"] = "Grand Total"
+					invoice_dict["discount_amount"] = discount["Amount"]
+
+				invoice_doc = frappe.get_doc(invoice_dict)
+				invoice_doc.insert()
+				invoice_doc.submit()
+		except Exception as e:
+			self._log_error(e, [invoice, invoice_dict, json.loads(invoice_doc.as_json())])
+
+
+	def _get_si_items(self, invoice, is_return=False):
+		items = []
+		for line in invoice["Line"]:
+			if line["DetailType"] == "SalesItemLineDetail":
+				if line["SalesItemLineDetail"]["TaxCodeRef"]["value"] != "TAX":
+					tax_code = line["SalesItemLineDetail"]["TaxCodeRef"]["value"]
+				else:
+					if "TxnTaxCodeRef" in invoice["TxnTaxDetail"]:
+						tax_code = invoice["TxnTaxDetail"]["TxnTaxCodeRef"]["value"]
+					else:
+						tax_code = "NON"
+				if line["SalesItemLineDetail"]["ItemRef"]["value"] != "SHIPPING_ITEM_ID":
+					item = frappe.db.get_all("Item",
+						filters={
+							"quickbooks_id": line["SalesItemLineDetail"]["ItemRef"]["value"],
+							"company": self.company,
+						},
+						fields=["name", "stock_uom"]
+					)[0]
+					items.append({
+						"item_code": item["name"],
+						"conversion_factor": 1,
+						"uom": item["stock_uom"],
+						"description": line.get("Description", line["SalesItemLineDetail"]["ItemRef"]["name"]),
+						"qty": line["SalesItemLineDetail"]["Qty"],
+						"price_list_rate": line["SalesItemLineDetail"]["UnitPrice"],
+						"cost_center": self.default_cost_center,
+						"warehouse": self.default_warehouse,
+						"item_tax_rate": json.dumps(self._get_item_taxes(tax_code))
+					})
+				else:
+					items.append({
+						"item_name": "Shipping",
+						"conversion_factor": 1,
+						"expense_account": self._get_account_name_by_id("TaxRate - {}".format(line["SalesItemLineDetail"]["TaxCodeRef"]["value"])),
+						"uom": "Unit",
+						"description": "Shipping",
+						"income_account": self.default_shipping_account,
+						"qty": 1,
+						"price_list_rate": line["Amount"],
+						"cost_center": self.default_cost_center,
+						"warehouse": self.default_warehouse,
+						"item_tax_rate": json.dumps(self._get_item_taxes(tax_code))
+					})
+				if is_return:
+					items[-1]["qty"] *= -1
+			elif line["DetailType"] == "DescriptionOnly":
+				items[-1].update({
+					"margin_type": "Percentage",
+					"margin_rate_or_amount": int(line["Description"].split("%")[0]),
+				})
+		return items
+
+
+	def _get_item_taxes(self, tax_code):
+		tax_rates = self.tax_rates
+		item_taxes = {}
+		if tax_code != "NON":
+			tax_code = self.tax_codes[tax_code]
+			for rate_list_type in ("SalesTaxRateList", "PurchaseTaxRateList"):
+				if rate_list_type in tax_code:
+					for tax_rate_detail in tax_code[rate_list_type]["TaxRateDetail"]:
+						if tax_rate_detail["TaxTypeApplicable"] == "TaxOnAmount":
+							tax_head = self._get_account_name_by_id("TaxRate - {}".format(tax_rate_detail["TaxRateRef"]["value"]))
+							tax_rate = tax_rates[tax_rate_detail["TaxRateRef"]["value"]]
+							item_taxes[tax_head] = tax_rate["RateValue"]
+		return item_taxes
+
+
+	def _get_invoice_payments(self, invoice, is_return=False, is_pos=False):
+		if is_pos:
+			amount = invoice["TotalAmt"]
+			if is_return:
+				amount = -amount
+			return [{
+				"mode_of_payment": "Cash",
+				"account": self._get_account_name_by_id(invoice["DepositToAccountRef"]["value"]),
+				"amount": amount,
+			}]
+
+
+	def _get_discount(self, lines):
+		for line in lines:
+			if line["DetailType"] == "DiscountLineDetail" and "Amount" in line["DiscountLineDetail"]:
+				return line
+
+
+	def _save_invoice_as_journal_entry(self, invoice, quickbooks_id):
+		try:
+			accounts = []
+			for line in self.general_ledger["Invoice"][invoice["Id"]]["lines"]:
+				account_line = {"account": line["account"], "cost_center": self.default_cost_center}
+				if line["debit"]:
+					account_line["debit_in_account_currency"] = line["debit"]
+				elif line["credit"]:
+					account_line["credit_in_account_currency"] = line["credit"]
+				if frappe.db.get_value("Account", line["account"], "account_type") == "Receivable":
+					account_line["party_type"] = "Customer"
+					account_line["party"] = frappe.get_all("Customer",
+						filters={"quickbooks_id": invoice["CustomerRef"]["value"], "company": self.company}
+					)[0]["name"]
+
+				accounts.append(account_line)
+
+			posting_date = invoice["TxnDate"]
+			self.__save_journal_entry(quickbooks_id, accounts, posting_date)
+		except Exception as e:
+			self._log_error(e, [invoice, accounts])
+
+
+	def _save_journal_entry(self, journal_entry):
+		# JournalEntry is equivalent to a Journal Entry
+
+		def _get_je_accounts(lines):
+			# Converts JounalEntry lines to accounts list
+			posting_type_field_mapping = {
+				"Credit": "credit_in_account_currency",
+				"Debit": "debit_in_account_currency",
+			}
+			accounts = []
+			for line in lines:
+				if line["DetailType"] == "JournalEntryLineDetail":
+					account_name = self._get_account_name_by_id(line["JournalEntryLineDetail"]["AccountRef"]["value"])
+					posting_type = line["JournalEntryLineDetail"]["PostingType"]
+					accounts.append({
+						"account": account_name,
+						posting_type_field_mapping[posting_type]: line["Amount"],
+						"cost_center": self.default_cost_center,
+					})
+			return accounts
+
+		quickbooks_id = "Journal Entry - {}".format(journal_entry["Id"])
+		accounts = _get_je_accounts(journal_entry["Line"])
+		posting_date = journal_entry["TxnDate"]
+		self.__save_journal_entry(quickbooks_id, accounts, posting_date)
+
+
+	def __save_journal_entry(self, quickbooks_id, accounts, posting_date):
+		try:
+			if not frappe.db.exists({"doctype": "Journal Entry", "quickbooks_id": quickbooks_id, "company": self.company}):
+				je = frappe.get_doc({
+					"doctype": "Journal Entry",
+					"quickbooks_id": quickbooks_id,
+					"company": self.company,
+					"posting_date": posting_date,
+					"accounts": accounts,
+					"multi_currency": 1,
+				})
+				je.insert()
+				je.submit()
+		except Exception as e:
+			self._log_error(e, [accounts, json.loads(je.as_json())])
+
+
+	def _save_bill(self, bill):
+		# Bill is equivalent to a Purchase Invoice
+		quickbooks_id = "Bill - {}".format(bill["Id"])
+		self.__save_purchase_invoice(bill, quickbooks_id)
+
+
+	def _save_vendor_credit(self, vendor_credit):
+		# Vendor Credit is equivalent to a return Purchase Invoice
+		quickbooks_id = "Vendor Credit - {}".format(vendor_credit["Id"])
+		self.__save_purchase_invoice(vendor_credit, quickbooks_id, is_return=True)
+
+
+	def __save_purchase_invoice(self, invoice, quickbooks_id, is_return=False):
+		try:
+			if not frappe.db.exists({"doctype": "Purchase Invoice", "quickbooks_id": quickbooks_id, "company": self.company}):
+				credit_to_account = self._get_account_name_by_id(invoice["APAccountRef"]["value"])
+				invoice_dict = {
+					"doctype": "Purchase Invoice",
+					"quickbooks_id": quickbooks_id,
+					"currency": invoice["CurrencyRef"]["value"],
+					"conversion_rate": invoice.get("ExchangeRate", 1),
+					"posting_date": invoice["TxnDate"],
+					"due_date":  invoice.get("DueDate", invoice["TxnDate"]),
+					"credit_to": credit_to_account,
+					"supplier": frappe.get_all("Supplier",
+						filters={
+							"quickbooks_id": invoice["VendorRef"]["value"],
+							"company": self.company,
+						})[0]["name"],
+					"items": self._get_pi_items(invoice, is_return=is_return),
+					"taxes": self._get_taxes(invoice),
+					"set_posting_time": 1,
+					"disable_rounded_total": 1,
+					"is_return": is_return,
+					"udpate_stock": 0,
+					"company": self.company,
+				}
+				invoice_doc = frappe.get_doc(invoice_dict)
+				invoice_doc.insert()
+				invoice_doc.submit()
+		except Exception as e:
+			self._log_error(e, [invoice, invoice_dict, json.loads(invoice_doc.as_json())])
+
+
+	def _get_pi_items(self, purchase_invoice, is_return=False):
+		items = []
+		for line in purchase_invoice["Line"]:
+			if line["DetailType"] == "ItemBasedExpenseLineDetail":
+				if line["ItemBasedExpenseLineDetail"]["TaxCodeRef"]["value"] != "TAX":
+					tax_code = line["ItemBasedExpenseLineDetail"]["TaxCodeRef"]["value"]
+				else:
+					if "TxnTaxCodeRef" in purchase_invoice["TxnTaxDetail"]:
+						tax_code = purchase_invoice["TxnTaxDetail"]["TxnTaxCodeRef"]["value"]
+					else:
+						tax_code = "NON"
+				item = frappe.db.get_all("Item",
+					filters={
+						"quickbooks_id": line["ItemBasedExpenseLineDetail"]["ItemRef"]["value"],
+						"company": self.company
+					},
+					fields=["name", "stock_uom"]
+				)[0]
+				items.append({
+					"item_code": item["name"],
+					"conversion_factor": 1,
+					"uom": item["stock_uom"],
+					"description": line.get("Description", line["ItemBasedExpenseLineDetail"]["ItemRef"]["name"]),
+					"qty": line["ItemBasedExpenseLineDetail"]["Qty"],
+					"price_list_rate": line["ItemBasedExpenseLineDetail"]["UnitPrice"],
+					"warehouse": self.default_warehouse,
+					"cost_center": self.default_cost_center,
+					"item_tax_rate": json.dumps(self._get_item_taxes(tax_code)),
+				})
+			elif line["DetailType"] == "AccountBasedExpenseLineDetail":
+				if line["AccountBasedExpenseLineDetail"]["TaxCodeRef"]["value"] != "TAX":
+					tax_code = line["AccountBasedExpenseLineDetail"]["TaxCodeRef"]["value"]
+				else:
+					if "TxnTaxCodeRef" in purchase_invoice["TxnTaxDetail"]:
+						tax_code = purchase_invoice["TxnTaxDetail"]["TxnTaxCodeRef"]["value"]
+					else:
+						tax_code = "NON"
+				items.append({
+					"item_name": line.get("Description", line["AccountBasedExpenseLineDetail"]["AccountRef"]["name"]),
+					"conversion_factor": 1,
+					"expense_account": self._get_account_name_by_id(line["AccountBasedExpenseLineDetail"]["AccountRef"]["value"]),
+					"uom": "Unit",
+					"description": line.get("Description", line["AccountBasedExpenseLineDetail"]["AccountRef"]["name"]),
+					"qty": 1,
+					"price_list_rate": line["Amount"],
+					"warehouse": self.default_warehouse,
+					"cost_center": self.default_cost_center,
+					"item_tax_rate": json.dumps(self._get_item_taxes(tax_code)),
+				})
+			if is_return:
+				items[-1]["qty"] *= -1
+		return items
+
+
+	def _save_payment(self, payment):
+		try:
+			quickbooks_id = "Payment - {}".format(payment["Id"])
+			# If DepositToAccountRef is not set on payment that means it actually doesn't affect any accounts
+			# No need to record such payment
+			# Such payment record is created QuickBooks Payments API
+			if "DepositToAccountRef" not in payment:
+				return
+
+			# A Payment can be linked to multiple transactions
+			accounts = []
+			for line in payment["Line"]:
+				linked_transaction = line["LinkedTxn"][0]
+				if linked_transaction["TxnType"] == "Invoice":
+					si_quickbooks_id = "Invoice - {}".format(linked_transaction["TxnId"])
+					# Invoice could have been saved as a Sales Invoice or a Journal Entry
+					if frappe.db.exists({"doctype": "Sales Invoice", "quickbooks_id": si_quickbooks_id, "company": self.company}):
+						sales_invoice = frappe.get_all("Sales Invoice",
+							filters={
+								"quickbooks_id": si_quickbooks_id,
+								"company": self.company,
+							},
+							fields=["name", "customer", "debit_to"],
+						)[0]
+						reference_type = "Sales Invoice"
+						reference_name = sales_invoice["name"]
+						party = sales_invoice["customer"]
+						party_account = sales_invoice["debit_to"]
+
+					if frappe.db.exists({"doctype": "Journal Entry", "quickbooks_id": si_quickbooks_id, "company": self.company}):
+						journal_entry = frappe.get_doc("Journal Entry",
+							{
+								"quickbooks_id": si_quickbooks_id,
+								"company": self.company,
+							}
+						)
+						# Invoice saved as a Journal Entry must have party and party_type set on line containing Receivable Account
+						customer_account_line = list(filter(lambda acc: acc.party_type == "Customer", journal_entry.accounts))[0]
+
+						reference_type = "Journal Entry"
+						reference_name = journal_entry.name
+						party = customer_account_line.party
+						party_account = customer_account_line.account
+
+					accounts.append({
+						"party_type": "Customer",
+						"party": party,
+						"reference_type": reference_type,
+						"reference_name": reference_name,
+						"account": party_account,
+						"credit_in_account_currency": line["Amount"],
+						"cost_center": self.default_cost_center,
+					})
+
+			deposit_account = self._get_account_name_by_id(payment["DepositToAccountRef"]["value"])
+			accounts.append({
+				"account": deposit_account,
+				"debit_in_account_currency": payment["TotalAmt"],
+				"cost_center": self.default_cost_center,
+			})
+			posting_date = payment["TxnDate"]
+			self.__save_journal_entry(quickbooks_id, accounts, posting_date)
+		except Exception as e:
+			self._log_error(e, [payment, accounts])
+
+
+	def _save_bill_payment(self, bill_payment):
+		try:
+			quickbooks_id = "BillPayment - {}".format(bill_payment["Id"])
+			# A BillPayment can be linked to multiple transactions
+			accounts = []
+			for line in bill_payment["Line"]:
+				linked_transaction = line["LinkedTxn"][0]
+				if linked_transaction["TxnType"] == "Bill":
+					pi_quickbooks_id = "Bill - {}".format(linked_transaction["TxnId"])
+					if frappe.db.exists({"doctype": "Purchase Invoice", "quickbooks_id": pi_quickbooks_id, "company": self.company}):
+						purchase_invoice = frappe.get_all("Purchase Invoice",
+							filters={
+								"quickbooks_id": pi_quickbooks_id,
+								"company": self.company,
+							},
+							fields=["name", "supplier", "credit_to"],
+						)[0]
+						reference_type = "Purchase Invoice"
+						reference_name = purchase_invoice["name"]
+						party = purchase_invoice["supplier"]
+						party_account = purchase_invoice["credit_to"]
+					accounts.append({
+						"party_type": "Supplier",
+						"party": party,
+						"reference_type": reference_type,
+						"reference_name": reference_name,
+						"account": party_account,
+						"debit_in_account_currency": line["Amount"],
+						"cost_center": self.default_cost_center,
+					})
+
+			if bill_payment["PayType"] == "Check":
+				bank_account_id = bill_payment["CheckPayment"]["BankAccountRef"]["value"]
+			elif bill_payment["PayType"] == "CreditCard":
+				bank_account_id = bill_payment["CreditCardPayment"]["CCAccountRef"]["value"]
+
+			bank_account = self._get_account_name_by_id(bank_account_id)
+			accounts.append({
+				"account": bank_account,
+				"credit_in_account_currency": bill_payment["TotalAmt"],
+				"cost_center": self.default_cost_center,
+			})
+			posting_date = bill_payment["TxnDate"]
+			self.__save_journal_entry(quickbooks_id, accounts, posting_date)
+		except Exception as e:
+			self._log_error(e, [bill_payment, accounts])
+
+
+	def _save_purchase(self, purchase):
+		try:
+			quickbooks_id = "Purchase - {}".format(purchase["Id"])
+			# Credit Bank Account
+			accounts = [{
+				"account": self._get_account_name_by_id(purchase["AccountRef"]["value"]),
+				"credit_in_account_currency": purchase["TotalAmt"],
+				"cost_center": self.default_cost_center,
+			}]
+
+			# Debit Mentioned Accounts
+			for line in purchase["Line"]:
+				if line["DetailType"] == "AccountBasedExpenseLineDetail":
+					account = self._get_account_name_by_id(line["AccountBasedExpenseLineDetail"]["AccountRef"]["value"])
+				elif line["DetailType"] == "ItemBasedExpenseLineDetail":
+					account = frappe.get_doc("Item",
+						{"quickbooks_id": line["ItemBasedExpenseLineDetail"]["ItemRef"]["value"], "company": self.company}
+					).item_defaults[0].expense_account
+				accounts.append({
+					"account": account,
+					"debit_in_account_currency": line["Amount"],
+					"cost_center": self.default_cost_center,
+				})
+
+			# Debit Tax Accounts
+			if "TxnTaxDetail" in purchase:
+				for line in purchase["TxnTaxDetail"]["TaxLine"]:
+					accounts.append({
+						"account": self._get_account_name_by_id("TaxRate - {}".format(line["TaxLineDetail"]["TaxRateRef"]["value"])),
+						"debit_in_account_currency": line["Amount"],
+						"cost_center": self.default_cost_center,
+					})
+
+			# If purchase["Credit"] is set to be True then it represents a refund
+			if purchase.get("Credit"):
+				for account in accounts:
+					if "debit_in_account_currency" in account:
+						account["credit_in_account_currency"] = account["debit_in_account_currency"]
+						del account["debit_in_account_currency"]
+					else:
+						account["debit_in_account_currency"] = account["credit_in_account_currency"]
+						del account["credit_in_account_currency"]
+
+			posting_date = purchase["TxnDate"]
+			self.__save_journal_entry(quickbooks_id, accounts, posting_date)
+		except Exception as e:
+			self._log_error(e, [purchase, accounts])
+
+
+	def _save_deposit(self, deposit):
+		try:
+			quickbooks_id = "Deposit - {}".format(deposit["Id"])
+			# Debit Bank Account
+			accounts = [{
+				"account": self._get_account_name_by_id(deposit["DepositToAccountRef"]["value"]),
+				"debit_in_account_currency": deposit["TotalAmt"],
+				"cost_center": self.default_cost_center,
+			}]
+
+			# Credit Mentioned Accounts
+			for line in deposit["Line"]:
+				if "LinkedTxn" in line:
+					accounts.append({
+						"account": self.undeposited_funds_account,
+						"credit_in_account_currency": line["Amount"],
+						"cost_center": self.default_cost_center,
+					})
+				else:
+					accounts.append({
+						"account": self._get_account_name_by_id(line["DepositLineDetail"]["AccountRef"]["value"]),
+						"credit_in_account_currency": line["Amount"],
+						"cost_center": self.default_cost_center,
+					})
+
+			# Debit Cashback if mentioned
+			if "CashBack" in deposit:
+				accounts.append({
+					"account": self._get_account_name_by_id(deposit["CashBack"]["AccountRef"]["value"]),
+					"debit_in_account_currency": deposit["CashBack"]["Amount"],
+					"cost_center": self.default_cost_center,
+				})
+
+			posting_date = deposit["TxnDate"]
+			self.__save_journal_entry(quickbooks_id, accounts, posting_date)
+		except Exception as e:
+			self._log_error(e, [deposit, accounts])
+
+
+	def _save_advance_payment(self, advance_payment):
+		quickbooks_id = "Advance Payment - {}".format(advance_payment["id"])
+		self.__save_ledger_entry_as_je(advance_payment, quickbooks_id)
+
+
+	def _save_tax_payment(self, tax_payment):
+		quickbooks_id = "Tax Payment - {}".format(tax_payment["id"])
+		self.__save_ledger_entry_as_je(tax_payment, quickbooks_id)
+
+
+	def _save_inventory_qty_adjust(self, inventory_qty_adjust):
+		quickbooks_id = "Inventory Qty Adjust - {}".format(inventory_qty_adjust["id"])
+		self.__save_ledger_entry_as_je(inventory_qty_adjust, quickbooks_id)
+
+
+	def __save_ledger_entry_as_je(self, ledger_entry, quickbooks_id):
+		try:
+			accounts = []
+			for line in ledger_entry["lines"]:
+				account_line = {"account": line["account"], "cost_center": self.default_cost_center}
+				if line["credit"]:
+					account_line["credit_in_account_currency"] = line["credit"]
+				else:
+					account_line["debit_in_account_currency"] = line["debit"]
+				accounts.append(account_line)
+
+			posting_date = ledger_entry["date"]
+			self.__save_journal_entry(quickbooks_id, accounts, posting_date)
+		except Exception as e:
+			self._log_error(e, ledger_entry)
+
+
+	def _get_taxes(self, entry):
+		taxes = []
+		if "TxnTaxDetail" not in entry or "TaxLine" not in entry["TxnTaxDetail"]:
+			return taxes
+		for line in entry["TxnTaxDetail"]["TaxLine"]:
+			tax_rate = line["TaxLineDetail"]["TaxRateRef"]["value"]
+			account_head = self._get_account_name_by_id("TaxRate - {}".format(tax_rate))
+			tax_type_applicable = self._get_tax_type(tax_rate)
+			if tax_type_applicable == "TaxOnAmount":
+				taxes.append({
+					"charge_type": "On Net Total",
+					"account_head": account_head,
+					"description": account_head,
+					"cost_center": self.default_cost_center,
+					"rate": 0,
+				})
+			else:
+				parent_tax_rate = self._get_parent_tax_rate(tax_rate)
+				parent_row_id = self._get_parent_row_id(parent_tax_rate, taxes)
+				taxes.append({
+					"charge_type": "On Previous Row Amount",
+					"row_id": parent_row_id,
+					"account_head": account_head,
+					"description": account_head,
+					"cost_center": self.default_cost_center,
+					"rate": line["TaxLineDetail"]["TaxPercent"],
+				})
+		return taxes
+
+
+	def _get_tax_type(self, tax_rate):
+		for tax_code in self.tax_codes.values():
+			for rate_list_type in ("SalesTaxRateList", "PurchaseTaxRateList"):
+				if rate_list_type in tax_code:
+					for tax_rate_detail in tax_code[rate_list_type]["TaxRateDetail"]:
+						if tax_rate_detail["TaxRateRef"]["value"] == tax_rate:
+							return tax_rate_detail["TaxTypeApplicable"]
+
+
+	def _get_parent_tax_rate(self, tax_rate):
+		parent = None
+		for tax_code in self.tax_codes.values():
+			for rate_list_type in ("SalesTaxRateList", "PurchaseTaxRateList"):
+				if rate_list_type in tax_code:
+					for tax_rate_detail in tax_code[rate_list_type]["TaxRateDetail"]:
+						if tax_rate_detail["TaxRateRef"]["value"] == tax_rate:
+							parent = tax_rate_detail["TaxOnTaxOrder"]
+					if parent:
+						for tax_rate_detail in tax_code[rate_list_type]["TaxRateDetail"]:
+							if tax_rate_detail["TaxOrder"] == parent:
+								return tax_rate_detail["TaxRateRef"]["value"]
+
+
+	def _get_parent_row_id(self, tax_rate, taxes):
+		tax_account = self._get_account_name_by_id("TaxRate - {}".format(tax_rate))
+		for index, tax in enumerate(taxes):
+			if tax["account_head"] == tax_account:
+				return index + 1
+
+
+	def _create_address(self, entity, doctype, address, address_type):
+		try :
+			if not frappe.db.exists({"doctype": "Address", "quickbooks_id": address["Id"]}):
+				frappe.get_doc({
+					"doctype": "Address",
+					"quickbooks_address_id": address["Id"],
+					"address_title": entity.name,
+					"address_type": address_type,
+					"address_line1": address["Line1"],
+					"city": address["City"],
+					"links": [{"link_doctype": doctype, "link_name": entity.name}]
+				}).insert()
+		except Exception as e:
+			self._log_error(e, address)
+
+
+	def _get(self, *args, **kwargs):
+		kwargs["headers"] = {
+			"Accept": "application/json",
+			"Authorization": "Bearer {}".format(self.access_token)
+		}
+		response = requests.get(*args, **kwargs)
+		# HTTP Status code 401 here means that the access_token is expired
+		# We can refresh tokens and retry
+		# However limitless recursion does look dangerous
+		if response.status_code == 401:
+			self._refresh_tokens()
+			response = self._get(*args, **kwargs)
+		return response
+
+
+	def _get_account_name_by_id(self, quickbooks_id):
+		return frappe.get_all("Account", filters={"quickbooks_id": quickbooks_id, "company": self.company})[0]["name"]
+
+
+	def _publish(self, *args, **kwargs):
+		frappe.publish_realtime("quickbooks_progress_update", *args, **kwargs)
+
+
+	def _get_unique_account_name(self, quickbooks_name, number=0):
+		if number:
+			quickbooks_account_name = "{} - {} - QB".format(quickbooks_name, number)
+		else:
+			quickbooks_account_name = "{} - QB".format(quickbooks_name)
+		company_encoded_account_name = encode_company_abbr(quickbooks_account_name, self.company)
+		if frappe.db.exists({"doctype": "Account", "name": company_encoded_account_name, "company": self.company}):
+			unique_account_name = self._get_unique_account_name(quickbooks_name, number + 1)
+		else:
+			unique_account_name = quickbooks_account_name
+		return unique_account_name
+
+
+	def _log_error(self, execption, data=""):
+		import json, traceback
+		traceback.print_exc()
+		frappe.log_error(title="QuickBooks Migration Error",
+			message="\n".join([
+				"Data",
+				json.dumps(data,
+					sort_keys=True,
+					indent=4,
+					separators=(',', ': ')
+				),
+				"Exception",
+				traceback.format_exc()
+			])
+		)
+
+
+	def set_indicator(self, status):
+		self.status = status
+		self.save()
+		frappe.db.commit()
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js
new file mode 100644
index 0000000..b71d704
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: QuickBooks Migrator", function (assert) {
+	let done = assert.async();
+
+	// number of asserts
+	assert.expect(1);
+
+	frappe.run_serially([
+		// insert a new QuickBooks Migrator
+		() => frappe.tests.make('QuickBooks Migrator', [
+			// values to be set
+			{key: 'value'}
+		]),
+		() => {
+			assert.equal(cur_frm.doc.key, 'value');
+		},
+		() => done()
+	]);
+
+});
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.py b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.py
new file mode 100644
index 0000000..6ce7c92
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestQuickBooksMigrator(unittest.TestCase):
+	pass
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 226a766..f242894 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -12,7 +12,7 @@
 source_link = "https://github.com/frappe/erpnext"
 
 develop_version = '12.x.x-develop'
-staging_version = '11.0.3-beta.12'
+staging_version = '11.0.3-beta.13'
 
 error_report_email = "support@erpnext.com"
 
diff --git a/erpnext/manufacturing/report/production_analytics/__init__.py b/erpnext/manufacturing/report/production_analytics/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/production_analytics/__init__.py
diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.js b/erpnext/manufacturing/report/production_analytics/production_analytics.js
new file mode 100644
index 0000000..b7b8f05
--- /dev/null
+++ b/erpnext/manufacturing/report/production_analytics/production_analytics.js
@@ -0,0 +1,46 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Production Analytics"] = {
+	"filters": [
+		{
+			fieldname: "company",
+			label: __("Company"),
+			fieldtype: "Link",
+			options: "Company",
+			default: frappe.defaults.get_user_default("Company"),
+			reqd: 1
+		},
+		{
+			fieldname: "from_date",
+			label: __("From Date"),
+			fieldtype: "Date",
+			default: frappe.defaults.get_user_default("year_start_date"),
+			reqd: 1
+		},
+		{
+			fieldname:"to_date",
+			label: __("To Date"),
+			fieldtype: "Date",
+			default: frappe.defaults.get_user_default("year_end_date"),
+			reqd: 1
+		},
+		{
+			fieldname: "range",
+			label: __("Range"),
+			fieldtype: "Select",
+			options: [
+				{ "value": "Weekly", "label": __("Weekly") },
+				{ "value": "Monthly", "label": __("Monthly") },
+				{ "value": "Quarterly", "label": __("Quarterly") },
+				{ "value": "Yearly", "label": __("Yearly") }
+			],
+			default: "Monthly",
+			reqd: 1
+		}
+	],
+	"formatter": function(value, row, column, data) {
+		return value;
+	}
+}
diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.json b/erpnext/manufacturing/report/production_analytics/production_analytics.json
new file mode 100644
index 0000000..023e0a8
--- /dev/null
+++ b/erpnext/manufacturing/report/production_analytics/production_analytics.json
@@ -0,0 +1,27 @@
+{
+ "add_total_row": 0, 
+ "creation": "2018-10-11 19:28:37.085066", 
+ "disabled": 0, 
+ "docstatus": 0, 
+ "doctype": "Report", 
+ "idx": 0, 
+ "is_standard": "Yes", 
+ "letter_head": "", 
+ "modified": "2018-10-11 19:28:37.085066", 
+ "modified_by": "Administrator", 
+ "module": "Manufacturing", 
+ "name": "Production Analytics", 
+ "owner": "Administrator", 
+ "prepared_report": 0, 
+ "ref_doctype": "Work Order", 
+ "report_name": "Production Analytics", 
+ "report_type": "Script Report", 
+ "roles": [
+  {
+   "role": "Manufacturing User"
+  }, 
+  {
+   "role": "Stock User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py
new file mode 100644
index 0000000..1dc821c
--- /dev/null
+++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import getdate
+from erpnext.selling.report.sales_analytics.sales_analytics import (get_period_date_ranges,get_period)
+
+def execute(filters=None):
+	columns = get_columns(filters)
+	data, chart = get_data(filters,columns)
+	return columns, data,None ,chart
+
+def get_columns(filters):
+
+	columns =[
+		{
+			"label": _("Status"),
+			"fieldname": "Status",
+			"fieldtype": "Data",
+			"width": 140
+		}]
+
+	ranges = get_period_date_ranges(period=filters["range"], year_start_date = filters["from_date"],year_end_date=filters["to_date"])
+
+	for dummy, end_date in ranges:
+
+		label = field_name = get_period(end_date,filters["range"])
+
+		columns.append(
+			{
+			"label": _(label),
+			"field_name":field_name,
+			"fieldtype": "Float",
+			"width": 120
+		},
+		)
+
+	return columns
+
+def get_data_list(filters,entry):
+
+	data_list = {
+		"All Work Orders" : {},
+		"Not Started" : {},
+		"Overdue" : {},
+		"Pending" : {},
+		"Completed" : {}
+	}
+
+	ranges = get_period_date_ranges(period=filters["range"], year_start_date = filters["from_date"],year_end_date=filters["to_date"])
+
+	for from_date,end_date in ranges:
+		period = get_period(end_date,filters["range"])
+		for d in entry:
+			if getdate(d.creation) <= getdate(from_date) or getdate(d.creation) <= getdate(end_date) :
+				data_list = update_data_list(data_list,"All Work Orders",period)
+
+				if d.status == 'Completed':
+					if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(from_date):
+						data_list = update_data_list(data_list, "Completed",period)
+
+					elif getdate(d.actual_start_date) < getdate(from_date) :
+						data_list = update_data_list(data_list, "Pending", period)
+
+					elif getdate(d.planned_start_date) < getdate(from_date) :
+						data_list = update_data_list(data_list, "Overdue", period)
+						
+					else:
+						data_list = update_data_list(data_list, "Not Started", period)
+
+				elif d.status == 'In Process':
+					if getdate(d.actual_start_date) < getdate(from_date) :
+						data_list = update_data_list(data_list, "Pending", period)
+
+					elif getdate(d.planned_start_date) < getdate(from_date) :
+						data_list = update_data_list(data_list, "Overdue", period)
+
+					else:
+						data_list = update_data_list(data_list, "Not Started", period)
+
+				elif d.status == 'Not Started':
+					if getdate(d.planned_start_date) < getdate(from_date) :
+						data_list = update_data_list(data_list, "Overdue", period)
+
+					else:
+						data_list = update_data_list(data_list, "Not Started", period)
+	return data_list
+
+def update_data_list(data_list, status, period):
+	if data_list.get(status).get(period):
+		data_list[status][period] += 1
+	else:
+		data_list[status][period] = 1
+
+	return data_list
+
+def get_data(filters,columns):
+
+	data = []
+
+	entry = frappe.get_all("Work Order",
+		fields=["creation", "modified", "actual_start_date", "actual_end_date", "planned_start_date", "planned_end_date", "status"],
+		filters={"docstatus" : 1, "company" : filters["company"] })
+
+	data_list = get_data_list(filters,entry)
+
+	labels = ["All Work Orders", "Not Started", "Overdue", "Pending", "Completed"]
+
+	chart_data = get_chart_data(data_list,columns)
+
+	ranges = get_period_date_ranges(period=filters["range"], year_start_date = filters["from_date"],year_end_date=filters["to_date"])
+
+	for label in labels:
+		work = {}
+		work["Status"] = label
+		for dummy,end_date in ranges:
+			period = get_period(end_date,filters["range"])
+			if data_list.get(label).get(period):
+				work[period] = data_list.get(label).get(period)
+			else:
+				work[period] = 0.0
+		data.append(work)
+
+	return data, chart_data
+
+def get_chart_data(data_list,columns):
+
+	labels = [d.get("label") for d in columns[1:]]
+
+	all_data, not_start, overdue, pending, completed = [], [], [] , [], []
+	datasets = []
+
+	for d in labels:
+		all_data.append(data_list.get("All Work Orders").get(d))
+		not_start.append(data_list.get("Not Started").get(d))
+		overdue.append(data_list.get("Overdue").get(d))
+		pending.append(data_list.get("Pending").get(d))
+		completed.append(data_list.get("Completed").get(d))
+
+	datasets.append({'name':'All Work Orders', 'values': all_data})
+	datasets.append({'name':'Not Started', 'values': not_start})
+	datasets.append({'name':'Overdue', 'values': overdue})
+	datasets.append({'name':'Pending', 'values': pending})
+	datasets.append({'name':'Completed', 'values': completed})
+
+	chart = {
+		"data": {
+			'labels': labels,
+			'datasets':datasets
+		}
+	}
+
+	chart["type"] = "line"
+
+	return chart
+
+
+
+
+
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 5d6ff70..1f14e2c 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -159,7 +159,7 @@
 
 			if(cumulated_tax_fraction && !me.discount_amount_applied) {
 				item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction));
-				item.net_rate = flt(item.net_amount / item.qty, precision("net_rate", item));
+				item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0;
 
 				me.set_in_company_currency(item, ["net_rate", "net_amount"]);
 			}
@@ -519,7 +519,7 @@
 						item.net_amount = flt(item.net_amount + discount_amount_loss,
 							precision("net_amount", item));
 					}
-					item.net_rate = flt(item.net_amount / item.qty, precision("net_rate", item));
+					item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0;
 					me.set_in_company_currency(item, ["net_rate", "net_amount"]);
 				});
 
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index a27161f..3e27d56 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -286,6 +286,7 @@
 									me.frm.set_value("taxes", r.message.taxes);
 								}
 							},
+							() => me.set_dynamic_labels(),
 							() => me.calculate_taxes_and_totals()
 						]);
 					}
@@ -845,16 +846,10 @@
 		this.frm.toggle_reqd("plc_conversion_rate",
 			!!(this.frm.doc.price_list_name && this.frm.doc.price_list_currency));
 
-		if(this.frm.doc_currency!==this.frm.doc.currency
-			|| this.frm.doc_currency!==this.frm.doc.price_list_currency) {
-			// reset names only when the currency is different
-
-			var company_currency = this.get_company_currency();
-			this.change_form_labels(company_currency);
-			this.change_grid_labels(company_currency);
-			this.frm.refresh_fields();
-			this.frm.doc_currency = this.frm.doc.currency;
-		}
+		var company_currency = this.get_company_currency();
+		this.change_form_labels(company_currency);
+		this.change_grid_labels(company_currency);
+		this.frm.refresh_fields();
 	},
 
 	change_form_labels: function(company_currency) {
diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py
index 06f424e..ea3d103 100644
--- a/erpnext/stock/__init__.py
+++ b/erpnext/stock/__init__.py
@@ -53,9 +53,9 @@
 	if not account and warehouse.company:
 		account = get_company_default_inventory_account(warehouse.company)
 
-	if not account:
+	if not account and warehouse.company:
 		frappe.throw(_("Please set Account in Warehouse {0} or Default Inventory Account in Company {1}")
-			.format(warehouse, warehouse.company))
+			.format(warehouse.name, warehouse.company))
 	return account
 	
 def get_company_default_inventory_account(company):
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 850d648..ef63740 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -22,10 +22,11 @@
 
 	def onload(self):
 		'''load account name for General Ledger Report'''
-		account = self.account or get_warehouse_account(self)
+		if self.company and cint(frappe.db.get_value("Company", self.company, "enable_perpetual_inventory")):
+			account = self.account or get_warehouse_account(self)
 
-		if account:
-			self.set_onload('account', account)
+			if account:
+				self.set_onload('account', account)
 		load_address_and_contact(self)
 
 
diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js
index a563564..839ed7a 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.js
+++ b/erpnext/stock/report/stock_balance/stock_balance.js
@@ -52,6 +52,12 @@
 			"options": "Warehouse"
 		},
 		{
+			"fieldname":"include_uom",
+			"label": __("Include UOM"),
+			"fieldtype": "Link",
+			"options": "UOM"
+		},
+		{
 			"fieldname": "show_variant_attributes",
 			"label": __("Show Variant Attributes"),
 			"fieldtype": "Check"
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index b25a42a..cc1112c 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -5,6 +5,7 @@
 import frappe
 from frappe import _
 from frappe.utils import flt, cint, getdate, now
+from erpnext.stock.utils import update_included_uom_in_report
 from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
 
 from six import iteritems
@@ -14,6 +15,7 @@
 
 	validate_filters(filters)
 
+	include_uom = filters.get("include_uom")
 	columns = get_columns()
 	items = get_items(filters)
 	sle = get_stock_ledger_entries(filters, items)
@@ -27,6 +29,7 @@
 	item_reorder_detail_map = get_item_reorder_details(item_map.keys())
 
 	data = []
+	conversion_factors = []
 	for (company, item, warehouse) in sorted(iwb_map):
 		if item_map.get(item):
 			qty_dict = iwb_map[(company, item, warehouse)]
@@ -54,36 +57,40 @@
 				variants_attributes = get_variants_attributes()
 				report_data += [item_map[item].get(i) for i in variants_attributes]
 
+			if include_uom:
+				conversion_factors.append(item_map[item].conversion_factor)
+
 			data.append(report_data)
 
 	if filters.get('show_variant_attributes', 0) == 1:
 		columns += ["{}:Data:100".format(i) for i in get_variants_attributes()]
 
+	update_included_uom_in_report(columns, data, include_uom, conversion_factors)
 	return columns, data
 
 def get_columns():
 	"""return columns"""
 
 	columns = [
-		_("Item")+":Link/Item:100",
-		_("Item Name")+"::150",
-		_("Item Group")+":Link/Item Group:100",
-		_("Brand")+":Link/Brand:90",
-		_("Description")+"::140",
-		_("Warehouse")+":Link/Warehouse:100",
-		_("Stock UOM")+":Link/UOM:90",
-		_("Opening Qty")+":Float:100",
-		_("Opening Value")+":Float:110",
-		_("In Qty")+":Float:80",
-		_("In Value")+":Float:80",
-		_("Out Qty")+":Float:80",
-		_("Out Value")+":Float:80",
-		_("Balance Qty")+":Float:100",
-		_("Balance Value")+":Float:100",
-		_("Valuation Rate")+":Float:90",
-		_("Reorder Level")+":Float:80",
-		_("Reorder Qty")+":Float:80",
-		_("Company")+":Link/Company:100"
+		{"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100},
+		{"label": _("Item Name"), "fieldname": "item_name", "width": 150},
+		{"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
+		{"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 90},
+		{"label": _("Description"), "fieldname": "description", "width": 140},
+		{"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100},
+		{"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90},
+		{"label": _("Opening Qty"), "fieldname": "opening_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
+		{"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Float", "width": 110},
+		{"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
+		{"label": _("In Value"), "fieldname": "in_val", "fieldtype": "Float", "width": 80},
+		{"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
+		{"label": _("Out Value"), "fieldname": "out_val", "fieldtype": "Float", "width": 80},
+		{"label": _("Balance Qty"), "fieldname": "bal_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
+		{"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100},
+		{"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate"},
+		{"label": _("Reorder Level"), "fieldname": "reorder_level", "fieldtype": "Float", "width": 80, "convertible": "qty"},
+		{"label": _("Reorder Qty"), "fieldname": "reorder_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
+		{"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 100}
 	]
 
 	return columns
@@ -210,11 +217,18 @@
 		items = list(set([d.item_code for d in sle]))
 
 	if items:
+		cf_field = cf_join = ""
+		if filters.get("include_uom"):
+			cf_field = ", ucd.`conversion_factor`"
+			cf_join = "LEFT JOIN `tabUOM Conversion Detail` ucd ON ucd.`parent`=item.`name` AND ucd.`uom`=%(include_uom)s"
+
 		for item in frappe.db.sql("""
-			SELECT `name`, `item_name`, `description`, `item_group`, `brand`, `stock_uom`
-			FROM `tabItem`
-			WHERE `name` IN ({0}) AND ifnull(`disabled`, 0) = 0
-			""".format(', '.join([frappe.db.escape(i, percent=False) for i in items])), as_dict=1):
+			SELECT item.`name`, item.`item_name`, item.`description`, item.`item_group`, item.`brand`, item.`stock_uom` {cf_field}
+			FROM `tabItem` item
+			{cf_join}
+			WHERE item.`name` IN ({names}) AND IFNULL(item.`disabled`, 0) = 0
+			""".format(cf_field=cf_field, cf_join=cf_join, names=', '.join([frappe.db.escape(i, percent=False) for i in items])),
+			{"include_uom": filters.get("include_uom")}, as_dict=1):
 				item_details.setdefault(item.name, item)
 
 	if filters.get('show_variant_attributes', 0) == 1:
@@ -254,4 +268,4 @@
 			attribute_map.setdefault(attr['parent'], {})
 			attribute_map[attr['parent']].update({attr['attribute']: attr['attribute_value']})
 
-	return attribute_map
\ No newline at end of file
+	return attribute_map
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index 660357c..3fab327 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -70,6 +70,12 @@
 			"label": __("Project"),
 			"fieldtype": "Link",
 			"options": "Project"
+		},
+		{
+			"fieldname":"include_uom",
+			"label": __("Include UOM"),
+			"fieldtype": "Link",
+			"options": "UOM"
 		}
 	]
 }
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index dc3cb28..805b314 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -4,15 +4,18 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
+from erpnext.stock.utils import update_included_uom_in_report
 
 def execute(filters=None):
+	include_uom = filters.get("include_uom")
 	columns = get_columns()
 	items = get_items(filters)
 	sl_entries = get_stock_ledger_entries(filters, items)
-	item_details = get_item_details(items, sl_entries)
+	item_details = get_item_details(items, sl_entries, include_uom)
 	opening_row = get_opening_balance(filters, columns)
 
 	data = []
+	conversion_factors = []
 	if opening_row:
 		data.append(opening_row)
 
@@ -26,28 +29,36 @@
 			sle.valuation_rate, sle.stock_value, sle.voucher_type, sle.voucher_no,
 			sle.batch_no, sle.serial_no, sle.project, sle.company])
 
+		if include_uom:
+			conversion_factors.append(item_detail.conversion_factor)
+
+	update_included_uom_in_report(columns, data, include_uom, conversion_factors)
 	return columns, data
 
 def get_columns():
 	columns = [
-		_("Date") + ":Datetime:95", _("Item") + ":Link/Item:130",
-		_("Item Name") + "::100", _("Item Group") + ":Link/Item Group:100",
-		_("Brand") + ":Link/Brand:100", _("Description") + "::200",
-		_("Warehouse") + ":Link/Warehouse:100", _("Stock UOM") + ":Link/UOM:100",
-		_("Qty") + ":Float:50", _("Balance Qty") + ":Float:100",
+		{"label": _("Date"), "fieldname": "date", "fieldtype": "Datetime", "width": 95},
+		{"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 130},
+		{"label": _("Item Name"), "fieldname": "item_name", "width": 100},
+		{"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
+		{"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 100},
+		{"label": _("Description"), "fieldname": "description", "width": 200},
+		{"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100},
+		{"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 100},
+		{"label": _("Qty"), "fieldname": "actual_qty", "fieldtype": "Float", "width": 50, "convertible": "qty"},
+		{"label": _("Balance Qty"), "fieldname": "qty_after_transaction", "fieldtype": "Float", "width": 100, "convertible": "qty"},
 		{"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Currency", "width": 110,
-			"options": "Company:company:default_currency"},
+			"options": "Company:company:default_currency", "convertible": "rate"},
 		{"label": _("Valuation Rate"), "fieldname": "valuation_rate", "fieldtype": "Currency", "width": 110,
-			"options": "Company:company:default_currency"},
+			"options": "Company:company:default_currency", "convertible": "rate"},
 		{"label": _("Balance Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": 110,
 			"options": "Company:company:default_currency"},
-		_("Voucher Type") + "::110",
-		_("Voucher #") + ":Dynamic Link/" + _("Voucher Type") + ":100",
-		_("Batch") + ":Link/Batch:100",
-		_("Serial #") + ":Link/Serial No:100",
-		_("Project") + ":Link/Project:100",
-		{"label": _("Company"), "fieldtype": "Link", "width": 110,
-			"options": "company", "fieldname": "company"}
+		{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
+		{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
+		{"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100},
+		{"label": _("Serial #"), "fieldname": "serial_no", "fieldtype": "Link", "options": "Serial No", "width": 100},
+		{"label": _("Project"), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100},
+		{"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 110}
 	]
 
 	return columns
@@ -88,7 +99,7 @@
 			.format(" and ".join(conditions)), filters)
 	return items
 
-def get_item_details(items, sl_entries):
+def get_item_details(items, sl_entries, include_uom):
 	item_details = {}
 	if not items:
 		items = list(set([d.item_code for d in sl_entries]))
@@ -96,11 +107,18 @@
 	if not items:
 		return item_details
 
+	cf_field = cf_join = ""
+	if include_uom:
+		cf_field = ", ucd.`conversion_factor`"
+		cf_join = "LEFT JOIN `tabUOM Conversion Detail` ucd ON ucd.`parent`=item.`name` and ucd.`uom`=%(include_uom)s"
+
 	for item in frappe.db.sql("""
-		select name, item_name, description, item_group, brand, stock_uom
-		from `tabItem`
-		where name in ({0})
-		""".format(', '.join([frappe.db.escape(i,percent=False) for i in items])), as_dict=1):
+		SELECT item.`name`, item.`item_name`, item.`description`, item.`item_group`, item.`brand`, item.`stock_uom` {cf_field}
+		FROM `tabItem` item
+		{cf_join}
+		where item.`name` in ({names})
+		""".format(cf_field=cf_field, cf_join=cf_join, names=', '.join([frappe.db.escape(i, percent=False) for i in items])),
+		{"include_uom": include_uom}, as_dict=1):
 			item_details.setdefault(item.name, item)
 
 	return item_details
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
index 51b9b0c..6589688 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
@@ -37,6 +37,12 @@
 			"label": __("Brand"),
 			"fieldtype": "Link",
 			"options": "Brand"
+		},
+		{
+			"fieldname":"include_uom",
+			"label": __("Include UOM"),
+			"fieldtype": "Link",
+			"options": "UOM"
 		}
 	]
 }
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index 885ab78..913d7d8 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -5,27 +5,18 @@
 import frappe
 from frappe import _
 from frappe.utils import flt, today
+from erpnext.stock.utils import update_included_uom_in_report
 
 def execute(filters=None):
 	filters = frappe._dict(filters or {})
-	return get_columns(), get_data(filters)
-
-def get_columns():
-	return [_("Item Code") + ":Link/Item:140", _("Item Name") + "::100", _("Description") + "::200",
-		_("Item Group") + ":Link/Item Group:100", _("Brand") + ":Link/Brand:100", _("Warehouse") + ":Link/Warehouse:120",
-		_("UOM") + ":Link/UOM:100", _("Actual Qty") + ":Float:100", _("Planned Qty") + ":Float:100",
-		_("Requested Qty") + ":Float:110", _("Ordered Qty") + ":Float:100",
-		_("Reserved Qty") + ":Float:100", _("Reserved Qty for Production") + ":Float:100",
-		_("Reserved for sub contracting") + ":Float:100",
-		_("Projected Qty") + ":Float:100", _("Reorder Level") + ":Float:100", _("Reorder Qty") + ":Float:100",
-		_("Shortage Qty") + ":Float:100"]
-
-def get_data(filters):
+	include_uom = filters.get("include_uom")
+	columns = get_columns()
 	bin_list = get_bin_list(filters)
-	item_map = get_item_map(filters.get("item_code"))
+	item_map = get_item_map(filters.get("item_code"), include_uom)
+
 	warehouse_company = {}
 	data = []
-
+	conversion_factors = []
 	for bin in bin_list:
 		item = item_map.get(bin.item_code)
 
@@ -60,7 +51,35 @@
 			bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract,
 			bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
 
-	return data
+		if include_uom:
+			conversion_factors.append(item.conversion_factor)
+
+	update_included_uom_in_report(columns, data, include_uom, conversion_factors)
+	return columns, data
+
+def get_columns():
+	return [
+		{"label": _("Item Code"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 140},
+		{"label": _("Item Name"), "fieldname": "item_name", "width": 100},
+		{"label": _("Description"), "fieldname": "description", "width": 200},
+		{"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
+		{"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 100},
+		{"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 120},
+		{"label": _("UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 100},
+		{"label": _("Actual Qty"), "fieldname": "actual_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
+		{"label": _("Planned Qty"), "fieldname": "planned_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
+		{"label": _("Requested Qty"), "fieldname": "indented_qty", "fieldtype": "Float", "width": 110, "convertible": "qty"},
+		{"label": _("Ordered Qty"), "fieldname": "ordered_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
+		{"label": _("Reserved Qty"), "fieldname": "reserved_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
+		{"label": _("Reserved Qty for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float",
+			"width": 100, "convertible": "qty"},
+		{"label": _("Reserved for sub contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float",
+			"width": 100, "convertible": "qty"},
+		{"label": _("Projected Qty"), "fieldname": "projected_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
+		{"label": _("Reorder Level"), "fieldname": "re_order_level", "fieldtype": "Float", "width": 100, "convertible": "qty"},
+		{"label": _("Reorder Qty"), "fieldname": "re_order_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
+		{"label": _("Shortage Qty"), "fieldname": "shortage_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}
+	]
 
 def get_bin_list(filters):
 	conditions = []
@@ -83,20 +102,29 @@
 
 	return bin_list
 
-def get_item_map(item_code):
+def get_item_map(item_code, include_uom):
 	"""Optimization: get only the item doc and re_order_levels table"""
 
 	condition = ""
 	if item_code:
 		condition = 'and item_code = {0}'.format(frappe.db.escape(item_code, percent=False))
 
-	items = frappe.db.sql("""select * from `tabItem` item
-		where is_stock_item = 1
-		and disabled=0
+	cf_field = cf_join = ""
+	if include_uom:
+		cf_field = ", ucd.conversion_factor"
+		cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%(include_uom)s"
+
+	items = frappe.db.sql("""
+		select item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom{cf_field}
+		from `tabItem` item
+		{cf_join}
+		where item.is_stock_item = 1
+		and item.disabled=0
 		{condition}
-		and (end_of_life > %(today)s or end_of_life is null or end_of_life='0000-00-00')
+		and (item.end_of_life > %(today)s or item.end_of_life is null or item.end_of_life='0000-00-00')
 		and exists (select name from `tabBin` bin where bin.item_code=item.name)"""\
-		.format(condition=condition), {"today": today()}, as_dict=True)
+		.format(cf_field=cf_field, cf_join=cf_join, condition=condition),
+		{"today": today(), "include_uom": include_uom}, as_dict=True)
 
 	condition = ""
 	if item_code:
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 55078a5..de31c54 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -246,3 +246,34 @@
 def is_group_warehouse(warehouse):
 	if frappe.db.get_value("Warehouse", warehouse, "is_group"):
 		frappe.throw(_("Group node warehouse is not allowed to select for transactions"))
+
+def update_included_uom_in_report(columns, result, include_uom, conversion_factors):
+	if not include_uom or not conversion_factors:
+		return
+
+	convertible_cols = {}
+	for col_idx in reversed(range(0, len(columns))):
+		col = columns[col_idx]
+		if isinstance(col, dict) and col.get("convertible") in ['rate', 'qty']:
+			convertible_cols[col_idx] = col['convertible']
+			columns.insert(col_idx+1, col.copy())
+			columns[col_idx+1]['fieldname'] += "_alt"
+			if convertible_cols[col_idx] == 'rate':
+				columns[col_idx+1]['label'] += " (per {})".format(include_uom)
+			else:
+				columns[col_idx+1]['label'] += " ({})".format(include_uom)
+
+	for row_idx, row in enumerate(result):
+		new_row = []
+		for col_idx, d in enumerate(row):
+			new_row.append(d)
+			if col_idx in convertible_cols:
+				if conversion_factors[row_idx]:
+					if convertible_cols[col_idx] == 'rate':
+						new_row.append(flt(d) * conversion_factors[row_idx])
+					else:
+						new_row.append(flt(d) / conversion_factors[row_idx])
+				else:
+					new_row.append(None)
+
+		result[row_idx] = new_row