Merge branch 'master' into staging-fixes
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 1af7e91..217d091 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.53'
+__version__ = '10.1.54'
 
 def get_default_company(user=None):
 	'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py
index 4ec97c4..f2a5c16 100755
--- a/erpnext/accounts/doctype/sales_invoice/pos.py
+++ b/erpnext/accounts/doctype/sales_invoice/pos.py
@@ -572,7 +572,7 @@
 			frappe.db.commit()
 			name_list.append(name)
 	except Exception:
-		frappe.log_error(frappe.get_traceback())
 		frappe.db.rollback()
+		frappe.log_error(frappe.get_traceback())
 
 	return name_list
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index c91bb8f..3f6cb44 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -16,16 +16,23 @@
 		self.update_new_bom()
 		bom_list = self.get_parent_boms(self.new_bom)
 		updated_bom = []
+
 		for bom in bom_list:
-			bom_obj = frappe.get_doc("BOM", bom)
-			bom_obj.get_doc_before_save()
-			updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom)
-			bom_obj.calculate_cost()
-			bom_obj.update_parent_cost()
-			bom_obj.db_update()
-			if (getattr(bom_obj.meta, 'track_changes', False)
-				and bom_obj._doc_before_save and not bom_obj.flags.ignore_version):
-				bom_obj.save_version()
+			try:
+				bom_obj = frappe.get_doc("BOM", bom)
+				bom_obj.get_doc_before_save()
+				updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom)
+				bom_obj.calculate_cost()
+				bom_obj.update_parent_cost()
+				bom_obj.db_update()
+				if (getattr(bom_obj.meta, 'track_changes', False)
+					and bom_obj._doc_before_save and not bom_obj.flags.ignore_version):
+					bom_obj.save_version()
+
+				frappe.db.commit()
+			except Exception:
+				frappe.db.rollback()
+				frappe.log_error(frappe.get_traceback())
 
 	def validate_bom(self):
 		if cstr(self.current_bom) == cstr(self.new_bom):
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 872daba..598504a 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -191,14 +191,14 @@
 	update_serial_nos(sle, item_det)
 
 def validate_serial_no(sle, item_det):
+	serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else []
 
 	if item_det.has_serial_no==0:
-		if sle.serial_no:
+		if serial_nos:
 			frappe.throw(_("Item {0} is not setup for Serial Nos. Column must be blank").format(sle.item_code),
 				SerialNoNotRequiredError)
 	elif sle.is_cancelled == "No":
-		if sle.serial_no:
-			serial_nos = get_serial_nos(sle.serial_no)
+		if serial_nos:
 			if cint(sle.actual_qty) != flt(sle.actual_qty):
 				frappe.throw(_("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty))
 
@@ -285,6 +285,12 @@
 		elif sle.actual_qty < 0 or not item_det.serial_no_series:
 			frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code),
 				SerialNoRequiredError)
+	elif serial_nos:
+		for serial_no in serial_nos:
+			sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1)
+			if sr and sle.actual_qty < 0 and sr.warehouse != sle.warehouse:
+				frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
+					.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse))
 
 def validate_so_serial_no(sr, sales_order,):
 	if not sr.sales_order or sr.sales_order!= sales_order: