Merge pull request #3822 from neilLasrado/item-uom

Changed UOM validation for Item Master
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 089c067..eaf904d 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -48,7 +48,7 @@
 			self.website_image = self.image
 
 		self.check_warehouse_is_set_for_stock_item()
-		self.check_stock_uom_with_bin()
+		self.validate_uom()
 		self.add_default_uom_in_conversion_factor_table()
 		self.validate_conversion_factor()
 		self.validate_item_type()
@@ -105,35 +105,6 @@
 
 		[self.remove(d) for d in to_remove]
 
-
-	def check_stock_uom_with_bin(self):
-		if not self.get("__islocal"):
-			if self.stock_uom == frappe.db.get_value("Item", self.name, "stock_uom"):
-				return
-
-			matched=True
-			ref_uom = frappe.db.get_value("Stock Ledger Entry",
-				{"item_code": self.name}, "stock_uom")
-
-			if ref_uom:
-				if cstr(ref_uom) != cstr(self.stock_uom):
-					matched = False
-			else:
-				bin_list = frappe.db.sql("select * from tabBin where item_code=%s",
-					self.item_code, as_dict=1)
-				for bin in bin_list:
-					if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \
-						or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(self.stock_uom):
-							matched = False
-							break
-
-				if matched and bin_list:
-					frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""",
-						(self.stock_uom, self.name))
-
-			if not matched:
-				frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module.").format(self.name))
-
 	def update_template_tables(self):
 		template = frappe.get_doc("Item", self.variant_of)
 
@@ -344,6 +315,17 @@
 				or ifnull(reserved_qty, 0) > 0 or ifnull(indented_qty, 0) > 0 or ifnull(planned_qty, 0) > 0)""", self.name)
 			if stock_in:
 				frappe.throw(_("Item Template cannot have stock or Open Sales/Purchase/Production Orders."), ItemTemplateCannotHaveStock)
+				
+	def validate_uom(self):
+		if not self.get("__islocal"):
+			check_stock_uom_with_bin(self.name, self.stock_uom)
+		if self.has_variants:
+			for d in frappe.db.get_all("Item", filters= {"variant_of": self.name}):
+				check_stock_uom_with_bin(d.name, self.stock_uom)
+		if self.variant_of:
+			template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
+			if template_uom != self.stock_uom:
+				frappe.throw(_("Default Unit of Measure for Variant must be same as Template"))
 
 def validate_end_of_life(item_code, end_of_life=None, verbose=1):
 	if not end_of_life:
@@ -449,3 +431,30 @@
 
 	if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group:
 		invalidate_cache_for(doc, doc.old_item_group)
+
+def check_stock_uom_with_bin(item, stock_uom):
+	if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
+		return
+
+	matched=True
+	ref_uom = frappe.db.get_value("Stock Ledger Entry",
+		{"item_code": item}, "stock_uom")
+
+	if ref_uom:
+		if cstr(ref_uom) != cstr(stock_uom):
+			matched = False
+	else:
+		bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
+		for bin in bin_list:
+			if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \
+				or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
+					matched = False
+					break
+
+		if matched and bin_list:
+			frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item))
+
+	if not matched:
+		frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because \
+			you have already made some transaction(s) with another UOM. To change default UOM, \
+			use 'UOM Replace Utility' tool under Stock module.").format(item))
diff --git a/erpnext/stock/doctype/stock_uom_replace_utility/stock_uom_replace_utility.py b/erpnext/stock/doctype/stock_uom_replace_utility/stock_uom_replace_utility.py
index c3f530a..5b5419d 100644
--- a/erpnext/stock/doctype/stock_uom_replace_utility/stock_uom_replace_utility.py
+++ b/erpnext/stock/doctype/stock_uom_replace_utility/stock_uom_replace_utility.py
@@ -10,6 +10,28 @@
 from frappe.model.document import Document
 
 class StockUOMReplaceUtility(Document):
+	
+	# Update Stock UOM
+	def update_stock_uom(self):
+		self.validate_item()
+		self.validate_mandatory()
+		self.validate_uom_integer_type()
+		
+		update_stock_ledger_entry(self.item_code, self.new_stock_uom, self.conversion_factor)
+		update_bin(self.item_code, self.new_stock_uom, self.conversion_factor)
+		update_item_master(self.item_code, self.new_stock_uom, self.conversion_factor)
+		
+		#if item is template change UOM for all associated variants
+		if frappe.db.get_value("Item", self.item_code, "has_variants"):
+			for d in frappe.db.get_all("Item", filters= {"variant_of": self.item_code}):
+				update_stock_ledger_entry(d.name, self.new_stock_uom, self.conversion_factor)
+				update_bin(d.name, self.new_stock_uom, self.conversion_factor)
+				update_item_master(d.name, self.new_stock_uom, self.conversion_factor)
+		
+	def validate_item(self):
+		if frappe.db.get_value("Item", self.item_code, "variant_of"):
+			frappe.throw(_("You cannot change default UOM of Variant. To change default UOM for Variant change default UOM of the Template"))
+		
 	def validate_mandatory(self):
 		if not cstr(self.item_code):
 			frappe.throw(_("Item is required"))
@@ -27,72 +49,7 @@
 		stock_uom = frappe.db.get_value("Item", self.item_code, "stock_uom")
 		if cstr(self.new_stock_uom) == cstr(stock_uom):
 			frappe.throw(_("Item is updated"))
-
-	def update_item_master(self):
-		item_doc = frappe.get_doc("Item", self.item_code)
-		item_doc.stock_uom = self.new_stock_uom
-		item_doc.save()
-
-		frappe.msgprint(_("Stock UOM updated for Item {0}").format(self.item_code))
-
-	def update_bin(self):
-		# update bin
-		if flt(self.conversion_factor) != flt(1):
-			frappe.db.sql("""update `tabBin`
-				set stock_uom = %s,
-					indented_qty = ifnull(indented_qty,0) * %s,
-					ordered_qty = ifnull(ordered_qty,0) * %s,
-					reserved_qty = ifnull(reserved_qty,0) * %s,
-					planned_qty = ifnull(planned_qty,0) * %s,
-					projected_qty = actual_qty + ordered_qty + indented_qty +
-						planned_qty - reserved_qty
-				where item_code = %s""", (self.new_stock_uom, self.conversion_factor,
-					self.conversion_factor, self.conversion_factor,
-					self.conversion_factor, self.item_code))
-		else:
-			frappe.db.sql("update `tabBin` set stock_uom = %s where item_code = %s",
-				 (self.new_stock_uom, self.item_code) )
-
-		# acknowledge user
-		frappe.msgprint(_("Stock balances updated"))
-
-	def update_stock_ledger_entry(self):
-		# update stock ledger entry
-		from erpnext.stock.stock_ledger import update_entries_after
-
-		if flt(self.conversion_factor) != flt(1):
-			frappe.db.sql("""update `tabStock Ledger Entry`
-				set stock_uom = %s, actual_qty = ifnull(actual_qty,0) * %s
-				where item_code = %s""",
-				(self.new_stock_uom, self.conversion_factor, self.item_code))
-		else:
-			frappe.db.sql("""update `tabStock Ledger Entry` set stock_uom=%s
-				where item_code=%s""", (self.new_stock_uom, self.item_code))
-
-		# acknowledge user
-		frappe.msgprint(_("Stock Ledger entries balances updated"))
-
-		# update item valuation
-		if flt(self.conversion_factor) != flt(1):
-			wh = frappe.db.sql("select name from `tabWarehouse`")
-			for w in wh:
-				update_entries_after({"item_code": self.item_code, "warehouse": w[0]})
-
-		# acknowledge user
-		frappe.msgprint(_("Item valuation updated"))
-
-	# Update Stock UOM
-	def update_stock_uom(self):
-		self.validate_mandatory()
-		self.validate_uom_integer_type()
-
-		self.update_stock_ledger_entry()
-
-		self.update_bin()
-
-		self.update_item_master()
-
-
+		
 	def validate_uom_integer_type(self):
 		current_is_integer = frappe.db.get_value("UOM", self.current_stock_uom, "must_be_whole_number")
 		new_is_integer = frappe.db.get_value("UOM", self.new_stock_uom, "must_be_whole_number")
@@ -103,6 +60,53 @@
 		if current_is_integer and new_is_integer and cint(self.conversion_factor)!=self.conversion_factor:
 			frappe.throw(_("Conversion factor cannot be in fractions"))
 
+def update_item_master(item_code, new_stock_uom, conversion_factor):
+	frappe.db.set_value("Item", item_code, "stock_uom", new_stock_uom)
+	frappe.msgprint(_("Stock UOM updated for Item {0}").format(item_code))
+
+def update_bin(item_code, new_stock_uom, conversion_factor):
+	# update bin
+	if flt(conversion_factor) != flt(1):
+		frappe.db.sql("""update `tabBin`
+			set stock_uom = %s,
+				indented_qty = ifnull(indented_qty,0) * %s,
+				ordered_qty = ifnull(ordered_qty,0) * %s,
+				reserved_qty = ifnull(reserved_qty,0) * %s,
+				planned_qty = ifnull(planned_qty,0) * %s,
+				projected_qty = actual_qty + ordered_qty + indented_qty +
+					planned_qty - reserved_qty
+			where item_code = %s""", (new_stock_uom, conversion_factor,
+				conversion_factor, conversion_factor,
+				conversion_factor, item_code))
+	else:
+		frappe.db.sql("update `tabBin` set stock_uom = %s where item_code = %s",
+			 (new_stock_uom, item_code) )
+
+def update_stock_ledger_entry(item_code, new_stock_uom, conversion_factor):
+	# update stock ledger entry
+	from erpnext.stock.stock_ledger import update_entries_after
+
+	if flt(conversion_factor) != flt(1):
+		frappe.db.sql("""update `tabStock Ledger Entry`
+			set stock_uom = %s, actual_qty = ifnull(actual_qty,0) * %s
+			where item_code = %s""",
+			(new_stock_uom, conversion_factor, item_code))
+	else:
+		frappe.db.sql("""update `tabStock Ledger Entry` set stock_uom=%s
+			where item_code=%s""", (new_stock_uom, item_code))
+
+	# acknowledge user
+	frappe.msgprint(_("Stock Ledger entries balances updated"))
+
+	# update item valuation
+	if flt(conversion_factor) != flt(1):
+		wh = frappe.db.sql("select name from `tabWarehouse`")
+		for w in wh:
+			update_entries_after({"item_code": item_code, "warehouse": w[0]})
+
+	# acknowledge user
+	frappe.msgprint(_("Item valuation updated"))
+
 @frappe.whitelist()
 def get_stock_uom(item_code):
 	return { 'current_stock_uom': cstr(frappe.db.get_value('Item', item_code, 'stock_uom')) }