Merge pull request #22131 from deepeshgarg007/tds_monthly_cancelled

fix: Cancelled entries in tds payable monthly report
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
new file mode 100644
index 0000000..d36b115
--- /dev/null
+++ b/.github/workflows/docker-release.yml
@@ -0,0 +1,14 @@
+name: Trigger Docker build on release
+on:
+  release:
+    types: [created]
+jobs:
+  curl:
+    runs-on: ubuntu-latest
+    container:
+      image: alpine:latest
+    steps:
+    - name: curl
+      run: |
+        apk add curl bash
+        curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.org/repo/frappe%2Ffrappe_docker/requests
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index d40e58b..66aa180 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -169,9 +169,11 @@
 
 	def append_subtotal_row(self, party):
 		sub_total_row = self.total_row_map.get(party)
-		self.data.append(sub_total_row)
-		self.data.append({})
-		self.update_sub_total_row(sub_total_row, 'Total')
+
+		if sub_total_row:
+			self.data.append(sub_total_row)
+			self.data.append({})
+			self.update_sub_total_row(sub_total_row, 'Total')
 
 	def get_voucher_balance(self, gle):
 		if self.filters.get("sales_person"):
@@ -232,7 +234,8 @@
 
 		if self.filters.get('group_by_party'):
 			self.append_subtotal_row(self.previous_party)
-			self.data.append(self.total_row_map.get('Total'))
+			if self.data:
+				self.data.append(self.total_row_map.get('Total'))
 
 	def append_row(self, row):
 		self.allocate_future_payments(row)
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 29f8dd5..50b17ab 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -70,7 +70,7 @@
 
 		else:
 			attributes_list = attribute_values.get(attribute.lower(), [])
-			validate_item_attribute_value(attributes_list, attribute, value, item.name)
+			validate_item_attribute_value(attributes_list, attribute, value, item.name, from_variant=True)
 
 def validate_is_incremental(numeric_attribute, attribute, value, item):
 	from_range = numeric_attribute.from_range
@@ -93,13 +93,20 @@
 			.format(attribute, from_range, to_range, increment, item),
 			InvalidItemAttributeValueError, title=_('Invalid Attribute'))
 
-def validate_item_attribute_value(attributes_list, attribute, attribute_value, item):
+def validate_item_attribute_value(attributes_list, attribute, attribute_value, item, from_variant=True):
 	allow_rename_attribute_value = frappe.db.get_single_value('Item Variant Settings', 'allow_rename_attribute_value')
 	if allow_rename_attribute_value:
 		pass
 	elif attribute_value not in attributes_list:
-		frappe.throw(_("The value {0} is already assigned to an exisiting Item {2}.").format(
-			attribute_value, attribute, item), InvalidItemAttributeValueError, title=_('Rename Not Allowed'))
+		if from_variant:
+			frappe.throw(_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
+				frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)), InvalidItemAttributeValueError, title=_("Invalid Value"))
+		else:
+			msg = _("The value {0} is already assigned to an exisiting Item {1}.").format(
+				frappe.bold(attribute_value), frappe.bold(item))
+			msg += "<br>" + _("To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings.").format(frappe.bold("Allow Rename Attribute Value"))
+
+			frappe.throw(msg, InvalidItemAttributeValueError, title=_('Edit Not Allowed'))
 
 def get_attribute_values(item):
 	if not frappe.flags.attribute_values:
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
index d84c823..fd16d1e 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
@@ -1,7 +1,9 @@
 // Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
-frappe.ui.form.on('Tally Migration', {
+frappe.provide("erpnext.tally_migration");
+
+frappe.ui.form.on("Tally Migration", {
 	onload: function (frm) {
 		let reload_status = true;
 		frappe.realtime.on("tally_migration_progress_update", function (data) {
@@ -35,7 +37,17 @@
 			}
 		});
 	},
+
 	refresh: function (frm) {
+		frm.trigger("show_logs_preview");
+		erpnext.tally_migration.failed_import_log = JSON.parse(frm.doc.failed_import_log);
+		erpnext.tally_migration.fixed_errors_log = JSON.parse(frm.doc.fixed_errors_log);
+
+		["default_round_off_account", "default_warehouse", "default_cost_center"].forEach(account => {
+			frm.toggle_reqd(account, frm.doc.is_master_data_imported === 1)
+			frm.toggle_enable(account, frm.doc.is_day_book_data_processed != 1)
+		})
+
 		if (frm.doc.master_data && !frm.doc.is_master_data_imported) {
 			if (frm.doc.is_master_data_processed) {
 				if (frm.doc.status != "Importing Master Data") {
@@ -47,6 +59,7 @@
 				}
 			}
 		}
+
 		if (frm.doc.day_book_data && !frm.doc.is_day_book_data_imported) {
 			if (frm.doc.is_day_book_data_processed) {
 				if (frm.doc.status != "Importing Day Book Data") {
@@ -59,6 +72,17 @@
 			}
 		}
 	},
+
+	erpnext_company: function (frm) {
+		frappe.db.exists("Company", frm.doc.erpnext_company).then(exists => {
+			if (exists) {
+				frappe.msgprint(
+					__("Company {0} already exists. Continuing will overwrite the Company and Chart of Accounts", [frm.doc.erpnext_company]),
+				);
+			}
+		});
+	},
+
 	add_button: function (frm, label, method) {
 		frm.add_custom_button(
 			label,
@@ -71,5 +95,255 @@
 				frm.reload_doc();
 			}
 		);
+	},
+
+	render_html_table(frm, shown_logs, hidden_logs, field) {
+		if (shown_logs && shown_logs.length > 0) {
+			frm.toggle_display(field, true);
+		} else {
+			frm.toggle_display(field, false);
+			return
+		}
+		let rows = erpnext.tally_migration.get_html_rows(shown_logs, field);
+		let rows_head, table_caption;
+
+		let table_footer = (hidden_logs && (hidden_logs.length > 0)) ? `<tr class="text-muted">
+				<td colspan="4">And ${hidden_logs.length} more others</td>
+			</tr>`: "";
+
+		if (field === "fixed_error_log_preview") {
+			rows_head = `<th width="75%">${__("Meta Data")}</th>
+			<th width="10%">${__("Unresolve")}</th>`
+			table_caption = "Resolved Issues"
+		} else {
+			rows_head = `<th width="75%">${__("Error Message")}</th>
+			<th width="10%">${__("Create")}</th>`
+			table_caption = "Error Log"
+		}
+
+		frm.get_field(field).$wrapper.html(`
+			<table class="table table-bordered">
+				<caption>${table_caption}</caption>
+				<tr class="text-muted">
+					<th width="5%">${__("#")}</th>
+					<th width="10%">${__("DocType")}</th>
+					${rows_head}
+				</tr>
+				${rows}
+				${table_footer}
+			</table>
+		`);
+	},
+
+	show_error_summary(frm) {
+		let summary = erpnext.tally_migration.failed_import_log.reduce((summary, row) => {
+			if (row.doc) {
+				if (summary[row.doc.doctype]) {
+					summary[row.doc.doctype] += 1;
+				} else {
+					summary[row.doc.doctype] = 1;
+				}
+			}
+			return summary
+		}, {});
+		console.table(summary);
+	},
+
+	show_logs_preview(frm) {
+		let empty = "[]";
+		let import_log = frm.doc.failed_import_log || empty;
+		let completed_log = frm.doc.fixed_errors_log || empty;
+		let render_section = !(import_log === completed_log && import_log === empty);
+
+		frm.toggle_display("import_log_section", render_section);
+		if (render_section) {
+			frm.trigger("show_error_summary");
+			frm.trigger("show_errored_import_log");
+			frm.trigger("show_fixed_errors_log");
+		}
+	},
+
+	show_errored_import_log(frm) {
+		let import_log = erpnext.tally_migration.failed_import_log;
+		let logs = import_log.slice(0, 20);
+		let hidden_logs = import_log.slice(20);
+
+		frm.events.render_html_table(frm, logs, hidden_logs, "failed_import_preview");
+	},
+
+	show_fixed_errors_log(frm) {
+		let completed_log = erpnext.tally_migration.fixed_errors_log;
+		let logs = completed_log.slice(0, 20);
+		let hidden_logs = completed_log.slice(20);
+
+		frm.events.render_html_table(frm, logs, hidden_logs, "fixed_error_log_preview");
 	}
 });
+
+erpnext.tally_migration.getError = (traceback) => {
+	/* Extracts the Error Message from the Python Traceback or Solved error */
+	let is_multiline = traceback.trim().indexOf("\n") != -1;
+	let message;
+
+	if (is_multiline) {
+		let exc_error_idx = traceback.trim().lastIndexOf("\n") + 1
+		let error_line = traceback.substr(exc_error_idx)
+		let split_str_idx = (error_line.indexOf(':') > 0) ? error_line.indexOf(':') + 1 : 0;
+		message = error_line.slice(split_str_idx).trim();
+	} else {
+		message = traceback;
+	}
+
+	return message
+}
+
+erpnext.tally_migration.cleanDoc = (obj) => {
+	/* Strips all null and empty values of your JSON object */
+	let temp = obj;
+	$.each(temp, function(key, value){
+		if (value === "" || value === null){
+			delete obj[key];
+		} else if (Object.prototype.toString.call(value) === '[object Object]') {
+			erpnext.tally_migration.cleanDoc(value);
+		} else if ($.isArray(value)) {
+			$.each(value, function (k,v) { erpnext.tally_migration.cleanDoc(v); });
+		}
+	});
+	return temp;
+}
+
+erpnext.tally_migration.unresolve = (document) => {
+	/* Mark document migration as unresolved ie. move to failed error log */
+	let frm = cur_frm;
+	let failed_log = erpnext.tally_migration.failed_import_log;
+	let fixed_log = erpnext.tally_migration.fixed_errors_log;
+
+	let modified_fixed_log = fixed_log.filter(row => {
+		if (!frappe.utils.deep_equal(erpnext.tally_migration.cleanDoc(row.doc), document)) {
+			return row
+		}
+	});
+
+	failed_log.push({ doc: document, exc: `Marked unresolved on ${Date()}` });
+
+	frm.doc.failed_import_log = JSON.stringify(failed_log);
+	frm.doc.fixed_errors_log = JSON.stringify(modified_fixed_log);
+
+	frm.dirty();
+	frm.save();
+}
+
+erpnext.tally_migration.resolve = (document) => {
+	/* Mark document migration as resolved ie. move to fixed error log */
+	let frm = cur_frm;
+	let failed_log = erpnext.tally_migration.failed_import_log;
+	let fixed_log = erpnext.tally_migration.fixed_errors_log;
+
+	let modified_failed_log = failed_log.filter(row => {
+		if (!frappe.utils.deep_equal(erpnext.tally_migration.cleanDoc(row.doc), document)) {
+			return row
+		}
+	});
+	fixed_log.push({ doc: document, exc: `Solved on ${Date()}` });
+
+	frm.doc.failed_import_log = JSON.stringify(modified_failed_log);
+	frm.doc.fixed_errors_log = JSON.stringify(fixed_log);
+
+	frm.dirty();
+	frm.save();
+}
+
+erpnext.tally_migration.create_new_doc = (document) => {
+	/* Mark as resolved and create new document */
+	erpnext.tally_migration.resolve(document);
+	return frappe.call({
+		type: "POST",
+		method: 'erpnext.erpnext_integrations.doctype.tally_migration.tally_migration.new_doc',
+		args: {
+			document
+		},
+		freeze: true,
+		callback: function(r) {
+			if(!r.exc) {
+				frappe.model.sync(r.message);
+				frappe.get_doc(r.message.doctype, r.message.name).__run_link_triggers = true;
+				frappe.set_route("Form", r.message.doctype, r.message.name);
+			}
+		}
+	});
+}
+
+erpnext.tally_migration.get_html_rows = (logs, field) => {
+	let index = 0;
+	let rows = logs
+		.map(({ doc, exc }) => {
+			let id = frappe.dom.get_unique_id();
+			let traceback = exc;
+
+			let error_message = erpnext.tally_migration.getError(traceback);
+			index++;
+
+			let show_traceback = `
+				<button class="btn btn-default btn-xs m-3" type="button" data-toggle="collapse" data-target="#${id}-traceback" aria-expanded="false" aria-controls="${id}-traceback">
+					${__("Show Traceback")}
+				</button>
+				<div class="collapse margin-top" id="${id}-traceback">
+					<div class="well">
+						<pre style="font-size: smaller;">${traceback}</pre>
+					</div>
+				</div>`;
+
+			let show_doc = `
+				<button class='btn btn-default btn-xs m-3' type='button' data-toggle='collapse' data-target='#${id}-doc' aria-expanded='false' aria-controls='${id}-doc'>
+					${__("Show Document")}
+				</button>
+				<div class="collapse margin-top" id="${id}-doc">
+					<div class="well">
+						<pre style="font-size: smaller;">${JSON.stringify(erpnext.tally_migration.cleanDoc(doc), null, 1)}</pre>
+					</div>
+				</div>`;
+
+			let create_button = `
+				<button class='btn btn-default btn-xs m-3' type='button' onclick='erpnext.tally_migration.create_new_doc(${JSON.stringify(doc)})'>
+					${__("Create Document")}
+				</button>`
+
+			let mark_as_unresolved = `
+				<button class='btn btn-default btn-xs m-3' type='button' onclick='erpnext.tally_migration.unresolve(${JSON.stringify(doc)})'>
+					${__("Mark as unresolved")}
+				</button>`
+
+			if (field === "fixed_error_log_preview") {
+				return `<tr>
+							<td>${index}</td>
+							<td>
+								<div>${doc.doctype}</div>
+							</td>
+							<td>
+								<div>${error_message}</div>
+								<div>${show_doc}</div>
+							</td>
+							<td>
+								<div>${mark_as_unresolved}</div>
+							</td>
+						</tr>`;
+			} else {
+				return `<tr>
+							<td>${index}</td>
+							<td>
+								<div>${doc.doctype}</div>
+							</td>
+							<td>
+								<div>${error_message}</div>
+								<div>${show_traceback}</div>
+								<div>${show_doc}</div>
+							</td>
+							<td>
+								<div>${create_button}</div>
+							</td>
+						</tr>`;
+			}
+		}).join("");
+
+	return rows
+}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
index dc6f093..417d943 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
@@ -28,14 +28,19 @@
   "vouchers",
   "accounts_section",
   "default_warehouse",
-  "round_off_account",
+  "default_round_off_account",
   "column_break_21",
   "default_cost_center",
   "day_book_section",
   "day_book_data",
   "column_break_27",
   "is_day_book_data_processed",
-  "is_day_book_data_imported"
+  "is_day_book_data_imported",
+  "import_log_section",
+  "failed_import_log",
+  "fixed_errors_log",
+  "failed_import_preview",
+  "fixed_error_log_preview"
  ],
  "fields": [
   {
@@ -57,6 +62,7 @@
    "fieldname": "tally_creditors_account",
    "fieldtype": "Data",
    "label": "Tally Creditors Account",
+   "read_only_depends_on": "eval:doc.is_master_data_processed==1",
    "reqd": 1
   },
   {
@@ -69,6 +75,7 @@
    "fieldname": "tally_debtors_account",
    "fieldtype": "Data",
    "label": "Tally Debtors Account",
+   "read_only_depends_on": "eval:doc.is_master_data_processed==1",
    "reqd": 1
   },
   {
@@ -92,7 +99,7 @@
    "fieldname": "erpnext_company",
    "fieldtype": "Data",
    "label": "ERPNext Company",
-   "read_only_depends_on": "eval:doc.is_master_data_processed == 1"
+   "read_only_depends_on": "eval:doc.is_master_data_processed==1"
   },
   {
    "fieldname": "processed_files_section",
@@ -136,6 +143,7 @@
   },
   {
    "depends_on": "is_master_data_imported",
+   "description": "The accounts are set by the system automatically but do confirm these defaults",
    "fieldname": "accounts_section",
    "fieldtype": "Section Break",
    "label": "Accounts"
@@ -147,12 +155,6 @@
    "options": "Warehouse"
   },
   {
-   "fieldname": "round_off_account",
-   "fieldtype": "Link",
-   "label": "Round Off Account",
-   "options": "Account"
-  },
-  {
    "fieldname": "column_break_21",
    "fieldtype": "Column Break"
   },
@@ -212,11 +214,47 @@
    "fieldname": "default_uom",
    "fieldtype": "Link",
    "label": "Default UOM",
-   "options": "UOM"
+   "options": "UOM",
+   "read_only_depends_on": "eval:doc.is_master_data_imported==1"
+  },
+  {
+   "default": "[]",
+   "fieldname": "failed_import_log",
+   "fieldtype": "Code",
+   "hidden": 1,
+   "options": "JSON"
+  },
+  {
+   "fieldname": "failed_import_preview",
+   "fieldtype": "HTML",
+   "label": "Failed Import Log"
+  },
+  {
+   "fieldname": "import_log_section",
+   "fieldtype": "Section Break",
+   "label": "Import Log"
+  },
+  {
+   "fieldname": "default_round_off_account",
+   "fieldtype": "Link",
+   "label": "Default Round Off Account",
+   "options": "Account"
+  },
+  {
+   "default": "[]",
+   "fieldname": "fixed_errors_log",
+   "fieldtype": "Code",
+   "hidden": 1,
+   "options": "JSON"
+  },
+  {
+   "fieldname": "fixed_error_log_preview",
+   "fieldtype": "HTML",
+   "label": "Fixed Error Log"
   }
  ],
  "links": [],
- "modified": "2020-04-16 13:03:28.894919",
+ "modified": "2020-04-28 00:29:18.039826",
  "modified_by": "Administrator",
  "module": "ERPNext Integrations",
  "name": "Tally Migration",
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
index 13474e1..462685f 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
@@ -6,6 +6,7 @@
 
 import json
 import re
+import sys
 import traceback
 import zipfile
 from decimal import Decimal
@@ -15,18 +16,34 @@
 import frappe
 from erpnext import encode_company_abbr
 from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
+from erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer import unset_existing_data
+
 from frappe import _
 from frappe.custom.doctype.custom_field.custom_field import create_custom_field
 from frappe.model.document import Document
 from frappe.model.naming import getseries, revert_series_if_last
 from frappe.utils.data import format_datetime
 
-
 PRIMARY_ACCOUNT = "Primary"
 VOUCHER_CHUNK_SIZE = 500
 
 
+@frappe.whitelist()
+def new_doc(document):
+	document = json.loads(document)
+	doctype = document.pop("doctype")
+	document.pop("name", None)
+	doc = frappe.new_doc(doctype)
+	doc.update(document)
+
+	return doc
+
 class TallyMigration(Document):
+	def validate(self):
+		failed_import_log = json.loads(self.failed_import_log)
+		sorted_failed_import_log = sorted(failed_import_log, key=lambda row: row["doc"]["creation"])
+		self.failed_import_log = json.dumps(sorted_failed_import_log)
+
 	def autoname(self):
 		if not self.name:
 			self.name = "Tally Migration on " + format_datetime(self.creation)
@@ -65,9 +82,17 @@
 				"attached_to_name": self.name,
 				"content": json.dumps(value),
 				"is_private": True
-			}).insert()
+			})
+			try:
+				f.insert()
+			except frappe.DuplicateEntryError:
+				pass
 			setattr(self, key, f.file_url)
 
+	def set_account_defaults(self):
+		self.default_cost_center, self.default_round_off_account = frappe.db.get_value("Company", self.erpnext_company, ["cost_center", "round_off_account"])
+		self.default_warehouse = frappe.db.get_value("Stock Settings", "Stock Settings", "default_warehouse")
+
 	def _process_master_data(self):
 		def get_company_name(collection):
 			return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string.strip()
@@ -84,7 +109,11 @@
 			children, parents = get_children_and_parent_dict(accounts)
 			group_set =  [acc[1] for acc in accounts if acc[2]]
 			children, customers, suppliers = remove_parties(parents, children, group_set)
-			coa = traverse({}, children, roots, roots, group_set)
+
+			try:
+				coa = traverse({}, children, roots, roots, group_set)
+			except RecursionError:
+				self.log(_("Error occured while parsing Chart of Accounts: Please make sure that no two accounts have the same name"))
 
 			for account in coa:
 				coa[account]["root_type"] = root_type_map[account]
@@ -126,14 +155,18 @@
 		def remove_parties(parents, children, group_set):
 			customers, suppliers = set(), set()
 			for account in parents:
+				found = False
 				if self.tally_creditors_account in parents[account]:
-					children.pop(account, None)
+					found = True
 					if account not in group_set:
 						suppliers.add(account)
-				elif self.tally_debtors_account in parents[account]:
-					children.pop(account, None)
+				if self.tally_debtors_account in parents[account]:
+					found = True
 					if account not in group_set:
 						customers.add(account)
+				if found:
+					children.pop(account, None)
+
 			return children, customers, suppliers
 
 		def traverse(tree, children, accounts, roots, group_set):
@@ -151,6 +184,7 @@
 			parties, addresses = [], []
 			for account in collection.find_all("LEDGER"):
 				party_type = None
+				links = []
 				if account.NAME.string.strip() in customers:
 					party_type = "Customer"
 					parties.append({
@@ -161,7 +195,9 @@
 						"territory": "All Territories",
 						"customer_type": "Individual",
 					})
-				elif account.NAME.string.strip() in suppliers:
+					links.append({"link_doctype": party_type, "link_name": account["NAME"]})
+
+				if account.NAME.string.strip() in suppliers:
 					party_type = "Supplier"
 					parties.append({
 						"doctype": party_type,
@@ -170,6 +206,8 @@
 						"supplier_group": "All Supplier Groups",
 						"supplier_type": "Individual",
 					})
+					links.append({"link_doctype": party_type, "link_name": account["NAME"]})
+
 				if party_type:
 					address = "\n".join([a.string.strip() for a in account.find_all("ADDRESS")])
 					addresses.append({
@@ -183,7 +221,7 @@
 						"mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
 						"phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
 						"gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None,
-						"links": [{"link_doctype": party_type, "link_name": account["NAME"]}],
+						"links": links
 					})
 			return parties, addresses
 
@@ -242,12 +280,18 @@
 		def create_company_and_coa(coa_file_url):
 			coa_file = frappe.get_doc("File", {"file_url": coa_file_url})
 			frappe.local.flags.ignore_chart_of_accounts = True
-			company = frappe.get_doc({
-				"doctype": "Company",
-				"company_name": self.erpnext_company,
-				"default_currency": "INR",
-				"enable_perpetual_inventory": 0,
-			}).insert()
+
+			try:
+				company = frappe.get_doc({
+					"doctype": "Company",
+					"company_name": self.erpnext_company,
+					"default_currency": "INR",
+					"enable_perpetual_inventory": 0,
+				}).insert()
+			except frappe.DuplicateEntryError:
+				company = frappe.get_doc("Company", self.erpnext_company)
+				unset_existing_data(self.erpnext_company)
+
 			frappe.local.flags.ignore_chart_of_accounts = False
 			create_charts(company.name, custom_chart=json.loads(coa_file.get_content()))
 			company.create_default_warehouses()
@@ -256,36 +300,35 @@
 			parties_file = frappe.get_doc("File", {"file_url": parties_file_url})
 			for party in json.loads(parties_file.get_content()):
 				try:
-					frappe.get_doc(party).insert()
+					party_doc = frappe.get_doc(party)
+					party_doc.insert()
 				except:
-					self.log(party)
+					self.log(party_doc)
 			addresses_file = frappe.get_doc("File", {"file_url": addresses_file_url})
 			for address in json.loads(addresses_file.get_content()):
 				try:
-					frappe.get_doc(address).insert(ignore_mandatory=True)
+					address_doc = frappe.get_doc(address)
+					address_doc.insert(ignore_mandatory=True)
 				except:
-					try:
-						gstin = address.pop("gstin", None)
-						frappe.get_doc(address).insert(ignore_mandatory=True)
-						self.log({"address": address, "message": "Invalid GSTIN: {}. Address was created without GSTIN".format(gstin)})
-					except:
-						self.log(address)
+					self.log(address_doc)
 
 		def create_items_uoms(items_file_url, uoms_file_url):
 			uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url})
 			for uom in json.loads(uoms_file.get_content()):
 				if not frappe.db.exists(uom):
 					try:
-						frappe.get_doc(uom).insert()
+						uom_doc = frappe.get_doc(uom)
+						uom_doc.insert()
 					except:
-						self.log(uom)
+						self.log(uom_doc)
 
 			items_file = frappe.get_doc("File", {"file_url": items_file_url})
 			for item in json.loads(items_file.get_content()):
 				try:
-					frappe.get_doc(item).insert()
+					item_doc = frappe.get_doc(item)
+					item_doc.insert()
 				except:
-					self.log(item)
+					self.log(item_doc)
 
 		try:
 			self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
@@ -299,10 +342,13 @@
 
 			self.publish("Import Master Data", _("Done"), 4, 4)
 
+			self.set_account_defaults()
 			self.is_master_data_imported = 1
+			frappe.db.commit()
 
 		except:
 			self.publish("Import Master Data", _("Process Failed"), -1, 5)
+			frappe.db.rollback()
 			self.log()
 
 		finally:
@@ -323,7 +369,9 @@
 					processed_voucher = function(voucher)
 					if processed_voucher:
 						vouchers.append(processed_voucher)
+					frappe.db.commit()
 				except:
+					frappe.db.rollback()
 					self.log(voucher)
 			return vouchers
 
@@ -349,6 +397,7 @@
 			journal_entry = {
 				"doctype": "Journal Entry",
 				"tally_guid": voucher.GUID.string.strip(),
+				"tally_voucher_no": voucher.VOUCHERNUMBER.string.strip() if voucher.VOUCHERNUMBER else "",
 				"posting_date": voucher.DATE.string.strip(),
 				"company": self.erpnext_company,
 				"accounts": accounts,
@@ -377,6 +426,7 @@
 				"doctype": doctype,
 				party_field: voucher.PARTYNAME.string.strip(),
 				"tally_guid": voucher.GUID.string.strip(),
+				"tally_voucher_no": voucher.VOUCHERNUMBER.string.strip() if voucher.VOUCHERNUMBER else "",
 				"posting_date": voucher.DATE.string.strip(),
 				"due_date": voucher.DATE.string.strip(),
 				"items": get_voucher_items(voucher, doctype),
@@ -468,14 +518,21 @@
 				oldest_year = new_year
 
 		def create_custom_fields(doctypes):
-			for doctype in doctypes:
-				df = {
-					"fieldtype": "Data",
-					"fieldname": "tally_guid",
-					"read_only": 1,
-					"label": "Tally GUID"
-				}
-				create_custom_field(doctype, df)
+			tally_guid_df = {
+				"fieldtype": "Data",
+				"fieldname": "tally_guid",
+				"read_only": 1,
+				"label": "Tally GUID"
+			}
+			tally_voucher_no_df = {
+				"fieldtype": "Data",
+				"fieldname": "tally_voucher_no",
+				"read_only": 1,
+				"label": "Tally Voucher Number"
+			}
+			for df in [tally_guid_df, tally_voucher_no_df]:
+				for doctype in doctypes:
+					create_custom_field(doctype, df)
 
 		def create_price_list():
 			frappe.get_doc({
@@ -490,7 +547,7 @@
 		try:
 			frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
 			frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
-			frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
+			frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.default_round_off_account)
 
 			vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
 			vouchers = json.loads(vouchers_file.get_content())
@@ -521,11 +578,14 @@
 
 		for index, voucher in enumerate(chunk, start=start):
 			try:
-				doc = frappe.get_doc(voucher).insert()
-				doc.submit()
+				voucher_doc = frappe.get_doc(voucher)
+				voucher_doc.insert()
+				voucher_doc.submit()
 				self.publish("Importing Vouchers", _("{} of {}").format(index, total), index, total)
+				frappe.db.commit()
 			except:
-				self.log(voucher)
+				frappe.db.rollback()
+				self.log(voucher_doc)
 
 		if is_last:
 			self.status = ""
@@ -551,9 +611,22 @@
 		frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
 
 	def log(self, data=None):
-		data = data or self.status
-		message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
-		return frappe.log_error(title="Tally Migration Error", message=message)
+		if isinstance(data, frappe.model.document.Document):
+			if sys.exc_info()[1].__class__ != frappe.DuplicateEntryError:
+				failed_import_log = json.loads(self.failed_import_log)
+				doc = data.as_dict()
+				failed_import_log.append({
+					"doc": doc,
+					"exc": traceback.format_exc()
+				})
+				self.failed_import_log = json.dumps(failed_import_log, separators=(',', ':'))
+				self.save()
+				frappe.db.commit()
+
+		else:
+			data = data or self.status
+			message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
+			return frappe.log_error(title="Tally Migration Error", message=message)
 
 	def set_status(self, status=""):
 		self.status = status
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index 473aae6..15ce468 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -38,6 +38,9 @@
 	},
 
 	validate: function(frm) {
+		if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1){
+			frm.doc.half_day_date = frm.doc.from_date;
+		}
 		frm.toggle_reqd("half_day_date", frm.doc.half_day == 1);
 	},
 
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index f2968bc..5eb84d7 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -33,6 +33,7 @@
 		self.validate_block_days()
 		self.validate_salary_processed_days()
 		self.validate_attendance()
+		self.set_half_day_date()
 		if frappe.db.get_value("Leave Type", self.leave_type, 'is_optional_leave'):
 			self.validate_optional_leave()
 		self.validate_applicable_after()
@@ -292,6 +293,10 @@
 				frappe.throw(_("{0} is not in Optional Holiday List").format(formatdate(day)), NotAnOptionalHoliday)
 			day = add_days(day, 1)
 
+	def set_half_day_date(self):
+		if self.from_date == self.to_date and self.half_day == 1:
+			self.half_day_date = self.from_date
+
 	def notify_employee(self):
 		employee = frappe.get_doc("Employee", self.employee)
 		if not employee.user_id:
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 3253a49..2543eec 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -112,13 +112,14 @@
 		if self.routing:
 			self.set("operations", [])
 			for d in frappe.get_all("BOM Operation", fields = ["*"],
-				filters = {'parenttype': 'Routing', 'parent': self.routing}):
+				filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="idx"):
 				child = self.append('operations', {
 					"operation": d.operation,
 					"workstation": d.workstation,
 					"description": d.description,
 					"time_in_mins": d.time_in_mins,
-					"batch_size": d.batch_size
+					"batch_size": d.batch_size,
+					"idx": d.idx
 				})
 				child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
 
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index df19995..4b93242 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -64,9 +64,21 @@
 				}, order_by="creation desc")
 	return frappe.get_doc("Member", members[0]['name'])
 
+def verify_signature(data):
+	signature = frappe.request.headers.get('X-Razorpay-Signature')
+
+	settings = frappe.get_doc("Membership Settings")
+	key = settings.get_webhook_secret()
+
+	controller = frappe.get_doc("Razorpay Settings")
+
+	controller.verify_signature(data, signature, key)
+
+
 @frappe.whitelist(allow_guest=True)
 def trigger_razorpay_subscription(*args, **kwargs):
 	data = frappe.request.get_data()
+	verify_signature(data)
 
 	if isinstance(data, six.string_types):
 		data = json.loads(data)
@@ -113,7 +125,6 @@
 	return True
 
 
-
 def notify_failure(log):
 	try:
 		content = """Dear System Manager,
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
index c01a0b2..8c0e3a4 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.js
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
@@ -1,8 +1,30 @@
 // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
-frappe.ui.form.on('Membership Settings', {
+frappe.ui.form.on("Membership Settings", {
 	refresh: function(frm) {
+		if (frm.doc.webhook_secret) {
+			frm.add_custom_button(__("Revoke <Key></Key>"), () => {
+				frm.call("revoke_key").then(() => {
+					frm.refresh();
+				})
+			});
+		}
+		frm.trigger("add_generate_button");
+	},
 
-	}
+	add_generate_button: function(frm) {
+		let label;
+
+		if (frm.doc.webhook_secret) {
+			label = __("Regenerate Webhook Secret");
+		} else {
+			label = __("Generate Webhook Secret");
+		}
+		frm.add_custom_button(label, () => {
+			frm.call("generate_webhook_key").then(() => {
+				frm.refresh();
+			});
+		});
+	},
 });
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
index 56b8eac..52b9d01 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
@@ -8,7 +8,8 @@
   "enable_razorpay",
   "razorpay_settings_section",
   "billing_cycle",
-  "billing_frequency"
+  "billing_frequency",
+  "webhook_secret"
  ],
  "fields": [
   {
@@ -34,11 +35,17 @@
    "fieldname": "billing_frequency",
    "fieldtype": "Int",
    "label": "Billing Frequency"
+  },
+  {
+   "fieldname": "webhook_secret",
+   "fieldtype": "Password",
+   "label": "Webhook Secret",
+   "read_only": 1
   }
  ],
  "issingle": 1,
  "links": [],
- "modified": "2020-04-07 18:42:51.496807",
+ "modified": "2020-05-22 12:38:27.103759",
  "modified_by": "Administrator",
  "module": "Non Profit",
  "name": "Membership Settings",
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.py b/erpnext/non_profit/doctype/membership_settings/membership_settings.py
index 2b8e37f..f3b2eee 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.py
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.py
@@ -4,11 +4,27 @@
 
 from __future__ import unicode_literals
 import frappe
+from frappe import _
 from frappe.integrations.utils import get_payment_gateway_controller
 from frappe.model.document import Document
 
 class MembershipSettings(Document):
-	pass
+	def generate_webhook_key(self):
+		key = frappe.generate_hash(length=20)
+		self.webhook_secret = key
+		self.save()
+
+		frappe.msgprint(
+			_("Here is your webhook secret, this will be shown to you only once.") + "<br><br>" + key,
+			_("Webhook Secret")
+		);
+
+	def revoke_key(self):
+		self.webhook_secret = None;
+		self.save()
+
+	def get_webhook_secret(self):
+		return self.get_password(fieldname="webhook_secret", raise_exception=False)
 
 @frappe.whitelist()
 def get_plans_for_membership(*args, **kwargs):
diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
index 6b9567c..31a7545 100644
--- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
@@ -64,7 +64,8 @@
 				"buying_price_list": self.default_buying_price_list
 			}
 
-			if not invoices_args.get("invoice_no", ''): return
+			if not invoices_args.get("bill_no", ''):
+				frappe.throw(_("Numero has not set in the XML file"))
 
 			supp_dict = get_supplier_details(file_content)
 			invoices_args["destination_code"] = get_destination_code_from_file(file_content)
diff --git a/erpnext/selling/desk_page/selling/selling.json b/erpnext/selling/desk_page/selling/selling.json
index c32a7c8..9ec6343 100644
--- a/erpnext/selling/desk_page/selling/selling.json
+++ b/erpnext/selling/desk_page/selling/selling.json
@@ -29,7 +29,7 @@
  "category": "Modules",
  "charts": [
   {
-   "chart_name": "Income",
+   "chart_name": "Incoming Bills (Purchase Invoice)",
    "label": "Income"
   }
  ],
@@ -43,7 +43,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Selling",
- "modified": "2020-05-28 13:46:08.314240",
+ "modified": "2020-06-03 13:23:24.861706",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling",
diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
index 88bd9c1..e78d0ff 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
@@ -8,180 +8,180 @@
 from frappe.utils import cint, cstr
 
 def execute(filters=None):
-    common_columns = [
-        {
-            'label': _('New Customers'),
-            'fieldname': 'new_customers',
-            'fieldtype': 'Int',
-            'default': 0,
-            'width': 125
-        },
-        {
-            'label': _('Repeat Customers'),
-            'fieldname': 'repeat_customers',
-            'fieldtype': 'Int',
-            'default': 0,
-            'width': 125
-        },
-        {
-            'label': _('Total'),
-            'fieldname': 'total',
-            'fieldtype': 'Int',
-            'default': 0,
-            'width': 100
-        },
-        {
-            'label': _('New Customer Revenue'),
-            'fieldname': 'new_customer_revenue',
-            'fieldtype': 'Currency',
-            'default': 0.0,
-            'width': 175
-        },
-        {
-            'label': _('Repeat Customer Revenue'),
-            'fieldname': 'repeat_customer_revenue',
-            'fieldtype': 'Currency',
-            'default': 0.0,
-            'width': 175
-        },
-        {
-            'label': _('Total Revenue'),
-            'fieldname': 'total_revenue',
-            'fieldtype': 'Currency',
-            'default': 0.0,
-            'width': 175
-        }
-    ]
-    if filters.get('view_type') == 'Monthly':
-        return get_data_by_time(filters, common_columns)
-    else:
-        return get_data_by_territory(filters, common_columns)
+	common_columns = [
+		{
+			'label': _('New Customers'),
+			'fieldname': 'new_customers',
+			'fieldtype': 'Int',
+			'default': 0,
+			'width': 125
+		},
+		{
+			'label': _('Repeat Customers'),
+			'fieldname': 'repeat_customers',
+			'fieldtype': 'Int',
+			'default': 0,
+			'width': 125
+		},
+		{
+			'label': _('Total'),
+			'fieldname': 'total',
+			'fieldtype': 'Int',
+			'default': 0,
+			'width': 100
+		},
+		{
+			'label': _('New Customer Revenue'),
+			'fieldname': 'new_customer_revenue',
+			'fieldtype': 'Currency',
+			'default': 0.0,
+			'width': 175
+		},
+		{
+			'label': _('Repeat Customer Revenue'),
+			'fieldname': 'repeat_customer_revenue',
+			'fieldtype': 'Currency',
+			'default': 0.0,
+			'width': 175
+		},
+		{
+			'label': _('Total Revenue'),
+			'fieldname': 'total_revenue',
+			'fieldtype': 'Currency',
+			'default': 0.0,
+			'width': 175
+		}
+	]
+	if filters.get('view_type') == 'Monthly':
+		return get_data_by_time(filters, common_columns)
+	else:
+		return get_data_by_territory(filters, common_columns)
 
 def get_data_by_time(filters, common_columns):
-    # key yyyy-mm
-    columns = [
-        {
-            'label': _('Year'),
-            'fieldname': 'year',
-            'fieldtype': 'Data',
-            'width': 100
-        },
-        {
-            'label': _('Month'),
-            'fieldname': 'month',
-            'fieldtype': 'Data',
-            'width': 100
-        },
-    ]
-    columns += common_columns
+	# key yyyy-mm
+	columns = [
+		{
+			'label': _('Year'),
+			'fieldname': 'year',
+			'fieldtype': 'Data',
+			'width': 100
+		},
+		{
+			'label': _('Month'),
+			'fieldname': 'month',
+			'fieldtype': 'Data',
+			'width': 100
+		},
+	]
+	columns += common_columns
 
-    customers_in = get_customer_stats(filters)
+	customers_in = get_customer_stats(filters)
 
-    # time series
-    from_year, from_month, temp = filters.get('from_date').split('-')
-    to_year, to_month, temp = filters.get('to_date').split('-')
+	# time series
+	from_year, from_month, temp = filters.get('from_date').split('-')
+	to_year, to_month, temp = filters.get('to_date').split('-')
 
-    from_year, from_month, to_year, to_month = \
-        cint(from_year), cint(from_month), cint(to_year), cint(to_month)
+	from_year, from_month, to_year, to_month = \
+		cint(from_year), cint(from_month), cint(to_year), cint(to_month)
 
-    out = []
-    for year in range(from_year, to_year+1):
-        for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
-            key = '{year}-{month:02d}'.format(year=year, month=month)
-            data = customers_in.get(key)
-            new = data['new'] if data else [0, 0.0]
-            repeat = data['repeat'] if data else [0, 0.0]
-            out.append({
-                'year': cstr(year),
-                'month': calendar.month_name[month],
-                'new_customers': new[0],
-                'repeat_customers': repeat[0],
-                'total': new[0] + repeat[0],
-                'new_customer_revenue': new[1],
-                'repeat_customer_revenue': repeat[1],
-                'total_revenue': new[1] + repeat[1]
-            })
-    return columns, out
+	out = []
+	for year in range(from_year, to_year+1):
+		for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
+			key = '{year}-{month:02d}'.format(year=year, month=month)
+			data = customers_in.get(key)
+			new = data['new'] if data else [0, 0.0]
+			repeat = data['repeat'] if data else [0, 0.0]
+			out.append({
+				'year': cstr(year),
+				'month': calendar.month_name[month],
+				'new_customers': new[0],
+				'repeat_customers': repeat[0],
+				'total': new[0] + repeat[0],
+				'new_customer_revenue': new[1],
+				'repeat_customer_revenue': repeat[1],
+				'total_revenue': new[1] + repeat[1]
+			})
+	return columns, out
 
 def get_data_by_territory(filters, common_columns):
-    columns = [{
-        'label': 'Territory',
-        'fieldname': 'territory',
-        'fieldtype': 'Link',
-        'options': 'Territory',
-        'width': 150
-    }]
-    columns += common_columns
+	columns = [{
+		'label': 'Territory',
+		'fieldname': 'territory',
+		'fieldtype': 'Link',
+		'options': 'Territory',
+		'width': 150
+	}]
+	columns += common_columns
 
-    customers_in = get_customer_stats(filters, tree_view=True)
+	customers_in = get_customer_stats(filters, tree_view=True)
 
-    territory_dict = {}
-    for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1):
-        territory_dict.update({
-            t.name: {
-                'parent': t.parent_territory,
-                'is_group': t.is_group
-            }
-        })
+	territory_dict = {}
+	for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1):
+		territory_dict.update({
+			t.name: {
+				'parent': t.parent_territory,
+				'is_group': t.is_group
+			}
+		})
 
-    depth_map = frappe._dict()
-    for name, info in territory_dict.items():
-        default = depth_map.get(info['parent']) + 1 if info['parent'] else 0
-        depth_map.setdefault(name, default)
+	depth_map = frappe._dict()
+	for name, info in territory_dict.items():
+		default = depth_map.get(info['parent']) + 1 if info['parent'] else 0
+		depth_map.setdefault(name, default)
 
-    data = []
-    for name, indent in depth_map.items():
-        condition = customers_in.get(name)
-        new = customers_in[name]['new'] if condition else [0, 0.0]
-        repeat = customers_in[name]['repeat'] if condition else [0, 0.0]
-        temp = {
-            'territory': name,
-            'parent_territory': territory_dict[name]['parent'],
-            'indent': indent,
-            'new_customers': new[0],
-            'repeat_customers': repeat[0],
-            'total': new[0] + repeat[0],
-            'new_customer_revenue': new[1],
-            'repeat_customer_revenue': repeat[1],
-            'total_revenue': new[1] + repeat[1],
-            'bold': 0 if indent else 1
-        }
-        data.append(temp)
+	data = []
+	for name, indent in depth_map.items():
+		condition = customers_in.get(name)
+		new = customers_in[name]['new'] if condition else [0, 0.0]
+		repeat = customers_in[name]['repeat'] if condition else [0, 0.0]
+		temp = {
+			'territory': name,
+			'parent_territory': territory_dict[name]['parent'],
+			'indent': indent,
+			'new_customers': new[0],
+			'repeat_customers': repeat[0],
+			'total': new[0] + repeat[0],
+			'new_customer_revenue': new[1],
+			'repeat_customer_revenue': repeat[1],
+			'total_revenue': new[1] + repeat[1],
+			'bold': 0 if indent else 1
+		}
+		data.append(temp)
 
-    loop_data = sorted(data, key=lambda k: k['indent'], reverse=True)
+	loop_data = sorted(data, key=lambda k: k['indent'], reverse=True)
 
-    for ld in loop_data:
-        if ld['parent_territory']:
-            parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0]
-            for key in parent_data.keys():
-                if key not in  ['indent', 'territory', 'parent_territory', 'bold']:
-                    parent_data[key] += ld[key]
+	for ld in loop_data:
+		if ld['parent_territory']:
+			parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0]
+			for key in parent_data.keys():
+				if key not in  ['indent', 'territory', 'parent_territory', 'bold']:
+					parent_data[key] += ld[key]
 
-    return columns, data, None, None, None, 1
+	return columns, data, None, None, None, 1
 
 def get_customer_stats(filters, tree_view=False):
-    """ Calculates number of new and repeated customers. """
-    company_condition = ''
-    if filters.get('company'):
-        company_condition = ' and company=%(company)s'
+	""" Calculates number of new and repeated customers. """
+	company_condition = ''
+	if filters.get('company'):
+		company_condition = ' and company=%(company)s'
 
-    customers = []
-    customers_in = {}
+	customers = []
+	customers_in = {}
 
-    for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice`
-        where docstatus=1 and posting_date <= %(to_date)s and posting_date >= %(from_date)s
-        {company_condition} order by posting_date'''.format(company_condition=company_condition),
-        filters, as_dict=1):
+	for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice`
+		where docstatus=1 and posting_date <= %(to_date)s
+		{company_condition} order by posting_date'''.format(company_condition=company_condition),
+		filters, as_dict=1):
 
-        key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
-        customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
+		key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
+		customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
 
-        if not si.customer in customers:
-            customers_in[key]['new'][0] += 1
-            customers_in[key]['new'][1] += si.base_grand_total
-            customers.append(si.customer)
-        else:
-            customers_in[key]['repeat'][0] += 1
-            customers_in[key]['repeat'][1] += si.base_grand_total
+		if not si.customer in customers:
+			customers_in[key]['new'][0] += 1
+			customers_in[key]['new'][1] += si.base_grand_total
+			customers.append(si.customer)
+		else:
+			customers_in[key]['repeat'][0] += 1
+			customers_in[key]['repeat'][1] += si.base_grand_total
 
-    return customers_in
+	return customers_in
diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py
index 71b998f..2f75bbd 100644
--- a/erpnext/stock/doctype/item_attribute/item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/item_attribute.py
@@ -34,7 +34,7 @@
 			if self.numeric_values:
 				validate_is_incremental(self, self.name, item.value, item.name)
 			else:
-				validate_item_attribute_value(attributes_list, self.name, item.value, item.name)
+				validate_item_attribute_value(attributes_list, self.name, item.value, item.name, from_variant=False)
 
 	def validate_numeric(self):
 		if self.numeric_values:
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 3562181..3a8deb6 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -18,7 +18,7 @@
 
 		// formatter for material request item
 		frm.set_indicator_formatter('item_code',
-			function(doc) { return (doc.qty<=doc.ordered_qty) ? "green" : "orange"; });
+			function(doc) { return (doc.stock_qty<=doc.ordered_qty) ? "green" : "orange"; });
 
 		frm.set_query("item_code", "items", function() {
 			return {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index e9568ee..50c18f6 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -25,7 +25,7 @@
 
 		frm.custom_make_buttons = {
 			'Stock Entry': 'Return',
-			'Purchase Invoice': 'Invoice'
+			'Purchase Invoice': 'Purchase Invoice'
 		};
 
 		frm.set_query("expense_account", "items", function() {