Update Serial No details through serial_no controller
diff --git a/accounts/doctype/sales_invoice/sales_invoice.py b/accounts/doctype/sales_invoice/sales_invoice.py
index 2eb9ae8..daf01ab 100644
--- a/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/accounts/doctype/sales_invoice/sales_invoice.py
@@ -83,7 +83,6 @@
 	def on_submit(self):
 		if cint(self.doc.update_stock) == 1:			
 			self.update_stock_ledger()
-			self.update_serial_nos()
 		else:
 			# Check for Approving Authority
 			if not self.doc.recurring_id:
@@ -111,7 +110,6 @@
 	def on_cancel(self):
 		if cint(self.doc.update_stock) == 1:
 			self.update_stock_ledger()
-			self.update_serial_nos(cancel = True)
 		
 		sales_com_obj = get_obj(dt = 'Sales Common')
 		sales_com_obj.check_stop_sales_order(self)
diff --git a/controllers/selling_controller.py b/controllers/selling_controller.py
index f1117ed..37674ee 100644
--- a/controllers/selling_controller.py
+++ b/controllers/selling_controller.py
@@ -233,34 +233,4 @@
 			self.doc.order_type = "Sales"
 		elif self.doc.order_type not in valid_types:
 			msgprint(_(self.meta.get_label("order_type")) + " " + 
-				_("must be one of") + ": " + comma_or(valid_types),
-				raise_exception=True)
-				
-	def update_serial_nos(self, cancel=False):
-		from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
-		update_serial_nos_after_submit(self, self.doc.doctype, self.fname)
-		update_serial_nos_after_submit(self, self.doc.doctype, "packing_details")
-
-		for table_fieldname in (self.fname, "packing_details"):
-			for d in self.doclist.get({"parentfield": table_fieldname}):
-				for serial_no in get_serial_nos(d.serial_no):
-					sr = webnotes.bean("Serial No", serial_no)
-					if cancel:
-						sr.doc.status = "Available"
-						for fieldname in ("warranty_expiry_date", "delivery_document_type", 
-							"delivery_document_no", "delivery_date", "delivery_time", "customer", 
-							"customer_name"):
-							sr.doc.fields[fieldname] = None
-					else:
-						sr.doc.delivery_document_type = self.doc.doctype
-						sr.doc.delivery_document_no = self.doc.name
-						sr.doc.delivery_date = self.doc.posting_date
-						sr.doc.delivery_time = self.doc.posting_time
-						sr.doc.customer = self.doc.customer
-						sr.doc.customer_name	= self.doc.customer_name
-						if sr.doc.warranty_period:
-							sr.doc.warranty_expiry_date = add_days(cstr(self.doc.posting_date), 
-								cint(sr.doc.warranty_period))
-						sr.doc.status =	'Delivered'
-
-					sr.save()
+				_("must be one of") + ": " + comma_or(valid_types), raise_exception=True)
\ No newline at end of file
diff --git a/patches/october_2013/p01_fix_serial_no_status.py b/patches/october_2013/p01_fix_serial_no_status.py
index 8700290..3842328 100644
--- a/patches/october_2013/p01_fix_serial_no_status.py
+++ b/patches/october_2013/p01_fix_serial_no_status.py
@@ -6,35 +6,12 @@
 from webnotes.utils import flt
 
 def execute():	
-	serial_nos = webnotes.conn.sql("""select name, item_code, status from `tabSerial No` 
-		where status!='Not in Use'""", as_dict=1)
+	serial_nos = webnotes.conn.sql("""select name from `tabSerial No` where status!='Not in Use' 
+		and docstatus=0""")
 	for sr in serial_nos:
-		last_sle = webnotes.conn.sql("""select voucher_type, voucher_no, actual_qty 
-			from `tabStock Ledger Entry` where serial_no like %s and item_code=%s
-			order by name desc limit 1""", 
-			("%%%s%%" % sr.name, sr.item_code), as_dict=1)
-
-		if flt(last_sle[0].actual_qty) > 0:
-			if last_sle[0].voucher_type == "Stock Entry" and webnotes.conn.get_value("Stock Entry", 
-				last_sle[0].voucher_no, "purpose") == "Sales Return":
-					status = "Sales Returned"
-			else:
-				status = "Available"
-		else:
-			if last_sle[0].voucher_type == "Stock Entry":
-				purpose = webnotes.conn.get_value("Stock Entry", last_sle[0].voucher_no, "purpose")
-				if purpose == "Purchase Return":
-					status = "Purchase Returned"
-				else:
-					status = "Not Available"
-			else:
-				status = "Delivered"
-		if sr.status != status:
-			webnotes.conn.sql("""update `tabSerial No` set status=%s where name=%s""", 
-				(status, sr.name))
+		sr_bean = webnotes.bean("Serial No", sr[0])
+		sr_bean.make_controller().via_stock_ledger = True
+		sr_bean.save()
 			
-	webnotes.conn.sql("""update `tabSerial No` set warehouse='' 
-		where status in ('Delivered', 'Purchase Returned')""")
-	
-		
-	
\ No newline at end of file
+	webnotes.conn.sql("""update `tabSerial No` set warehouse='' where status in 
+		('Delivered', 'Purchase Returned')""")
\ No newline at end of file
diff --git a/patches/october_2013/perpetual_inventory_stock_transfer_utility.py b/patches/october_2013/perpetual_inventory_stock_transfer_utility.py
index 8cee29a..d8cade7 100644
--- a/patches/october_2013/perpetual_inventory_stock_transfer_utility.py
+++ b/patches/october_2013/perpetual_inventory_stock_transfer_utility.py
@@ -29,9 +29,10 @@
 		cost_center = "Default CC Ledger - NISL"
 		
 		for bin in webnotes.conn.sql("""select * from tabBin bin where ifnull(item_code, '')!='' 
-				and ifnull(warehouse, '')!='' and ifnull(actual_qty, 0) != 0
-				and (select company from tabWarehouse where name=bin.warehouse)=%s""", 
-				company[0], as_dict=1):
+				and ifnull(warehouse, '') in (%s) and ifnull(actual_qty, 0) != 0
+				and (select company from tabWarehouse where name=bin.warehouse)=%s""" %
+				(', '.join(['%s']*len(warehouse_map)), '%s'), 
+				(warehouse_map.keys() + [company[0]]), as_dict=1):
 			item_details = item_map[bin.item_code]
 			new_warehouse = warehouse_map[bin.warehouse].get("fixed_asset_warehouse") \
 				if cstr(item_details.is_asset_item) == "Yes" \
@@ -40,7 +41,8 @@
 			if item_details.has_serial_no == "Yes":
 				serial_no = "\n".join([d[0] for d in webnotes.conn.sql("""select name 
 					from `tabSerial No` where item_code = %s and warehouse = %s 
-					and status='Available'""", (bin.item_code, bin.warehouse))])
+					and status in ('Available', 'Sales Returned')""", 
+					(bin.item_code, bin.warehouse))])
 			else:
 				serial_no = None
 			
diff --git a/stock/doctype/delivery_note/delivery_note.py b/stock/doctype/delivery_note/delivery_note.py
index e5e412e..2d1a3be 100644
--- a/stock/doctype/delivery_note/delivery_note.py
+++ b/stock/doctype/delivery_note/delivery_note.py
@@ -185,7 +185,6 @@
 		
 		# create stock ledger entry
 		self.update_stock_ledger()
-		self.update_serial_nos()
 
 		self.credit_limit()
 		
@@ -203,42 +202,12 @@
 		self.update_prevdoc_status()
 		
 		self.update_stock_ledger()
-		self.update_serial_nos(cancel=True)
 
 		webnotes.conn.set(self.doc, 'status', 'Cancelled')
 		self.cancel_packing_slips()
 		
 		self.make_cancel_gl_entries()
 
-	def update_serial_nos(self, cancel=False):
-		from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
-		update_serial_nos_after_submit(self, "Delivery Note", "delivery_note_details")
-		update_serial_nos_after_submit(self, "Delivery Note", "packing_details")
-
-		for table_fieldname in ("delivery_note_details", "packing_details"):
-			for d in self.doclist.get({"parentfield": table_fieldname}):
-				for serial_no in get_serial_nos(d.serial_no):
-					sr = webnotes.bean("Serial No", serial_no)
-					if cancel:
-						sr.doc.status = "Available"
-						for fieldname in ("warranty_expiry_date", "delivery_document_type", 
-							"delivery_document_no", "delivery_date", "delivery_time", "customer", 
-							"customer_name"):
-							sr.doc.fields[fieldname] = None
-					else:
-						sr.doc.delivery_document_type = "Delivery Note"
-						sr.doc.delivery_document_no = self.doc.name
-						sr.doc.delivery_date = self.doc.posting_date
-						sr.doc.delivery_time = self.doc.posting_time
-						sr.doc.customer = self.doc.customer
-						sr.doc.customer_name	= self.doc.customer_name
-						if sr.doc.warranty_period:
-							sr.doc.warranty_expiry_date	= add_days(cstr(self.doc.posting_date), 
-								cint(sr.doc.warranty_period))
-						sr.doc.status =	'Delivered'
-
-					sr.save()
-
 	def validate_packed_qty(self):
 		"""
 			Validate that if packed qty exists, it should be equal to qty
diff --git a/stock/doctype/landed_cost_wizard/landed_cost_wizard.py b/stock/doctype/landed_cost_wizard/landed_cost_wizard.py
index 0924957..89a3b81 100644
--- a/stock/doctype/landed_cost_wizard/landed_cost_wizard.py
+++ b/stock/doctype/landed_cost_wizard/landed_cost_wizard.py
@@ -85,7 +85,6 @@
 			pr_bean = webnotes.bean("Purchase Receipt", pr)
 			
 			pr_bean.run_method("update_ordered_qty", is_cancelled="Yes")
-			pr_bean.run_method("update_serial_nos", cancel=True)
 			
 			webnotes.conn.sql("""delete from `tabStock Ledger Entry` 
 				where voucher_type='Purchase Receipt' and voucher_no=%s""", pr)
@@ -97,5 +96,4 @@
 			pr_bean = webnotes.bean("Purchase Receipt", pr)
 			pr_bean.run_method("update_ordered_qty")
 			pr_bean.run_method("update_stock")
-			pr_bean.run_method("update_serial_nos")
 			pr_bean.run_method("make_gl_entries")
\ No newline at end of file
diff --git a/stock/doctype/purchase_receipt/purchase_receipt.py b/stock/doctype/purchase_receipt/purchase_receipt.py
index 7d663b8..6169b1d 100644
--- a/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -246,26 +246,12 @@
 		
 		self.update_stock()
 
-		self.update_serial_nos()
+		from stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
+		update_serial_nos_after_submit(self, "Purchase Receipt", "purchase_receipt_details")
 
 		purchase_controller.update_last_purchase_rate(self, 1)
 		
 		self.make_gl_entries()
-		
-	def update_serial_nos(self, cancel=False):
-		from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
-		update_serial_nos_after_submit(self, "Purchase Receipt", "purchase_receipt_details")
-
-		for d in self.doclist.get({"parentfield": "purchase_receipt_details"}):
-			for serial_no in get_serial_nos(d.serial_no):
-				sr = webnotes.bean("Serial No", serial_no)
-				if cancel:
-					sr.doc.supplier = None
-					sr.doc.supplier_name = None
-				else:
-					sr.doc.supplier = self.doc.supplier
-					sr.doc.supplier_name = self.doc.supplier_name
-				sr.save()
 
 	def check_next_docstatus(self):
 		submit_rv = webnotes.conn.sql("select t1.name from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 where t1.name = t2.parent and t2.purchase_receipt = '%s' and t1.docstatus = 1" % (self.doc.name))
@@ -292,7 +278,6 @@
 		self.update_ordered_qty()
 		
 		self.update_stock()
-		self.update_serial_nos(cancel=True)
 
 		self.update_prevdoc_status()
 		pc_obj.update_last_purchase_rate(self, 0)
@@ -308,7 +293,7 @@
 
 	def get_rate(self,arg):
 		return get_obj('Purchase Common').get_rate(arg,self)
-			
+		
 	def get_gl_entries_for_stock(self, warehouse_account=None):
 		against_stock_account = self.get_company_default("stock_received_but_not_billed")
 		
diff --git a/stock/doctype/serial_no/serial_no.py b/stock/doctype/serial_no/serial_no.py
index 1feab02..c8b7222 100644
--- a/stock/doctype/serial_no/serial_no.py
+++ b/stock/doctype/serial_no/serial_no.py
@@ -4,14 +4,22 @@
 from __future__ import unicode_literals
 import webnotes
 
-from webnotes.utils import cint, getdate, nowdate
+from webnotes.utils import cint, getdate, nowdate, cstr, flt, add_days
 import datetime
-from webnotes import msgprint, _
+from webnotes import msgprint, _, ValidationError
 
 from controllers.stock_controller import StockController
 
-class SerialNoCannotCreateDirectError(webnotes.ValidationError): pass
-class SerialNoCannotCannotChangeError(webnotes.ValidationError): pass
+class SerialNoCannotCreateDirectError(ValidationError): pass
+class SerialNoCannotCannotChangeError(ValidationError): pass
+class SerialNoNotRequiredError(ValidationError): pass
+class SerialNoRequiredError(ValidationError): pass
+class SerialNoQtyError(ValidationError): pass
+class SerialNoItemError(ValidationError): pass
+class SerialNoWarehouseError(ValidationError): pass
+class SerialNoStatusError(ValidationError): pass
+class SerialNoNotExistsError(ValidationError): pass
+class SerialNoDuplicateError(ValidationError): pass
 
 class DocType(StockController):
 	def __init__(self, doc, doclist=[]):
@@ -21,13 +29,18 @@
 
 	def validate(self):
 		if self.doc.fields.get("__islocal") and self.doc.warehouse:
-			webnotes.throw(_("New Serial No cannot have Warehouse. Warehouse must be set by Stock Entry or Purchase Receipt"), 
-				SerialNoCannotCreateDirectError)
+			webnotes.throw(_("New Serial No cannot have Warehouse. Warehouse must be \
+				set by Stock Entry or Purchase Receipt"), SerialNoCannotCreateDirectError)
 			
 		self.validate_warranty_status()
 		self.validate_amc_status()
 		self.validate_warehouse()
 		self.validate_item()
+		
+		if self.via_stock_ledger:
+			self.set_status()
+			self.set_purchase_details()
+			self.set_sales_details()
 
 	def validate_amc_status(self):
 		"""
@@ -41,7 +54,8 @@
 			validate warranty status	
 		"""
 		if (self.doc.maintenance_status == 'Out of Warranty' and self.doc.warranty_expiry_date and getdate(self.doc.warranty_expiry_date) >= datetime.date.today()) or (self.doc.maintenance_status == 'Under Warranty' and (not self.doc.warranty_expiry_date or getdate(self.doc.warranty_expiry_date) < datetime.date.today())):
-			msgprint("Warranty expiry date and maintenance status mismatch. Please verify", raise_exception=1)
+			msgprint("Warranty expiry date and maintenance status mismatch. Please verify", 
+				raise_exception=1)
 
 
 	def validate_warehouse(self):
@@ -49,10 +63,11 @@
 			item_code, warehouse = webnotes.conn.get_value("Serial No", 
 				self.doc.name, ["item_code", "warehouse"])
 			if item_code != self.doc.item_code:
-				webnotes.throw(_("Item Code cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
+				webnotes.throw(_("Item Code cannot be changed for Serial No."), 
+					SerialNoCannotCannotChangeError)
 			if not self.via_stock_ledger and warehouse != self.doc.warehouse:
-				webnotes.throw(_("Warehouse cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
-	
+				webnotes.throw(_("Warehouse cannot be changed for Serial No."), 
+					SerialNoCannotCannotChangeError)
 
 	def validate_item(self):
 		"""
@@ -67,16 +82,89 @@
 		self.doc.item_name = item.item_name
 		self.doc.brand = item.brand
 		self.doc.warranty_period = item.warranty_period
+				
+	def set_status(self):
+		last_sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry` 
+			where (serial_no like %s or serial_no like %s or serial_no=%s) 
+			and item_code=%s and ifnull(is_cancelled, 'No')='No' 
+			order by name desc limit 1""", 
+			("%%%s%%" % self.doc.name+"\n", "%%%s%%" % "\n"+self.doc.name, self.doc.name, 
+				self.doc.item_code), as_dict=1)
+		
+		if last_sle:
+			if last_sle[0].voucher_type == "Stock Entry":
+				document_type = webnotes.conn.get_value("Stock Entry", last_sle[0].voucher_no, 
+					"purpose")
+			else:
+				document_type = last_sle[0].voucher_type
+
+			if last_sle[0].actual_qty > 0:
+				if document_type == "Sales Return":
+					self.doc.status = "Sales Returned"
+				else:
+					self.doc.status = "Available"
+			else:
+				if document_type == "Purchase Return":
+					self.doc.status = "Purchase Returned"
+				elif last_sle[0].voucher_type in ("Delivery Note", "Sales Invoice"):
+					self.doc.status = "Delivered"
+				else:
+					self.doc.status = "Not Available"
+		
+	def set_purchase_details(self):
+		purchase_sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry` 
+			where (serial_no like %s or serial_no like %s or serial_no=%s) 
+			and item_code=%s and actual_qty > 0 
+			and ifnull(is_cancelled, 'No')='No' order by name asc limit 1""", 
+			("%%%s%%" % self.doc.name+"\n", "%%%s%%" % "\n"+self.doc.name, self.doc.name, 
+				 self.doc.item_code), as_dict=1)
 			
+		if purchase_sle:
+			self.doc.purchase_document_type = purchase_sle[0].voucher_type
+			self.doc.purchase_document_no = purchase_sle[0].voucher_no
+			self.doc.purchase_date = purchase_sle[0].posting_date
+			self.doc.purchase_time = purchase_sle[0].posting_time
+			self.doc.purchase_rate = purchase_sle[0].incoming_rate
+			if purchase_sle[0].voucher_type == "Purchase Receipt":
+				self.doc.supplier, self.doc.supplier_name = \
+					webnotes.conn.get_value("Purchase Receipt", purchase_sle[0].voucher_no, 
+						["supplier_name", "supplier_name"])
+		else:
+			for fieldname in ("purchase_document_type", "purchase_document_no", 
+				"purchase_date", "purchase_time", "purchase_rate", "supplier", "supplier_name"):
+					self.doc.fields[fieldname] = None
+				
+	def set_sales_details(self):
+		delivery_sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry` 
+			where (serial_no like %s or serial_no like %s or serial_no=%s)
+			and item_code=%s and actual_qty<0 
+			and voucher_type in ('Delivery Note', 'Sales Invoice')
+			and ifnull(is_cancelled, 'No')='No' order by name desc limit 1""", 
+			("%%%s%%" % self.doc.name+"\n", "%%%s%%" % "\n"+self.doc.name, self.doc.name, 
+				 self.doc.item_code), as_dict=1)
+		if delivery_sle:
+			self.doc.delivery_document_type = delivery_sle[0].voucher_type
+			self.doc.delivery_document_no = delivery_sle[0].voucher_no
+			self.doc.delivery_date = delivery_sle[0].posting_date
+			self.doc.delivery_time = delivery_sle[0].posting_time
+			self.doc.customer, self.doc.customer_name = \
+				webnotes.conn.get_value(delivery_sle[0].voucher_type, delivery_sle[0].voucher_no, 
+					["customer", "customer_name"])
+			if self.doc.warranty_period:
+				self.doc.warranty_expiry_date	= add_days(cstr(delivery_sle[0].posting_date), 
+					cint(self.doc.warranty_period))
+		else:
+			for fieldname in ("delivery_document_type", "delivery_document_no", 
+				"delivery_date", "delivery_time", "customer", "customer_name", 
+				"warranty_expiry_date"):
+					self.doc.fields[fieldname] = None		
+		
 	def on_trash(self):
 		if self.doc.status == 'Delivered':
-			msgprint("Cannot trash Serial No : %s as it is already Delivered" % (self.doc.name), raise_exception = 1)
+			webnotes.throw(_("Delivered Serial No ") + self.doc.name + _(" can not be deleted"))
 		if self.doc.warehouse:
-			webnotes.throw(_("Cannot delete Serial No in warehouse. First remove from warehouse, then delete.") + \
-				": " + self.doc.name)
-
-	def on_cancel(self):
-		self.on_trash()
+			webnotes.throw(_("Cannot delete Serial No in warehouse. \
+				First remove from warehouse, then delete.") + ": " + self.doc.name)
 	
 	def on_rename(self, new, old, merge=False):
 		"""rename serial_no text fields"""
@@ -93,3 +181,106 @@
 				webnotes.conn.sql("""update `tab%s` set serial_no = %s 
 					where name=%s""" % (dt[0], '%s', '%s'),
 					('\n'.join(serial_nos), item[0]))
+
+def process_serial_no(sle):
+	item_det = get_item_details(sle.item_code)
+	validate_serial_no(sle, item_det)
+	update_serial_nos(sle, item_det)
+					
+def validate_serial_no(sle, item_det):
+	if item_det.has_serial_no=="No":
+		if sle.serial_no:
+			webnotes.throw(_("Serial Number should be blank for Non Serialized Item" + ": " 
+				+ sle.item_code), SerialNoNotRequiredError)
+	else:
+		if sle.serial_no:
+			serial_nos = get_serial_nos(sle.serial_no)
+			if cint(sle.actual_qty) != flt(sle.actual_qty):
+				webnotes.throw(_("Serial No qty cannot be a fraction") + \
+					(": %s (%s)" % (sle.item_code, sle.actual_qty)))
+			if len(serial_nos) and len(serial_nos) != abs(cint(sle.actual_qty)):
+				webnotes.throw(_("Serial Nos do not match with qty") + \
+					(": %s (%s)" % (sle.item_code, sle.actual_qty)), SerialNoQtyError)
+			
+			for serial_no in serial_nos:
+				if webnotes.conn.exists("Serial No", serial_no):
+					sr = webnotes.bean("Serial No", serial_no)
+					
+					if sr.doc.item_code!=sle.item_code:
+						webnotes.throw(_("Serial No does not belong to Item") + 
+							(": %s (%s)" % (sle.item_code, serial_no)), SerialNoItemError)
+							
+					if sr.doc.warehouse and sle.actual_qty > 0:
+						webnotes.throw(_("Same Serial No") + ": " + sr.doc.name + 
+							_(" can not be received twice"), SerialNoDuplicateError)
+					
+					if sle.actual_qty < 0:
+						if sr.doc.warehouse!=sle.warehouse:
+							webnotes.throw(_("Serial No") + ": " + serial_no + 
+								_(" does not belong to Warehouse") + ": " + sle.warehouse, 
+								SerialNoWarehouseError)
+					
+						if sle.voucher_type in ("Delivery Note", "Sales Invoice") \
+							and sr.doc.status != "Available":
+							webnotes.throw(_("Serial No status must be 'Available' to Deliver") 
+								+ ": " + serial_no, SerialNoStatusError)
+				elif sle.actual_qty < 0:
+					# transfer out
+					webnotes.throw(_("Serial No must exist to transfer out.") + \
+						": " + serial_no, SerialNoNotExistsError)
+		elif not item_det.serial_no_series:
+			webnotes.throw(_("Serial Number Required for Serialized Item" + ": " 
+				+ sle.item_code), SerialNoRequiredError)
+				
+def update_serial_nos(sle, item_det):
+	if sle.serial_no:
+		serial_nos = get_serial_nos(sle.serial_no)
+		for serial_no in serial_nos:
+			if webnotes.conn.exists("Serial No", serial_no):
+				sr = webnotes.bean("Serial No", serial_no)
+				sr.make_controller().via_stock_ledger = True
+				sr.doc.warehouse = sle.warehouse if sle.actual_qty > 0 else None
+				sr.save()
+			elif sle.actual_qty > 0:
+				make_serial_no(serial_no, sle)
+	elif sle.actual_qty > 0 and item_det.serial_no_series:
+		from webnotes.model.doc import make_autoname
+		serial_nos = []
+		for i in xrange(cint(sle.actual_qty)):
+			serial_nos.append(make_serial_no(make_autoname(item_det.serial_no_series), sle))
+		sle.serial_no = "\n".join(serial_nos)
+
+def get_item_details(item_code):
+	return webnotes.conn.sql("""select name, has_batch_no, docstatus, 
+		is_stock_item, has_serial_no, serial_no_series 
+		from tabItem where name=%s""", item_code, as_dict=True)[0]
+		
+def get_serial_nos(serial_no):
+	return [s.strip() for s in cstr(serial_no).strip().replace(',', '\n').split('\n') if s.strip()]
+
+def make_serial_no(serial_no, sle):
+	sr = webnotes.new_bean("Serial No")
+	sr.doc.serial_no = serial_no
+	sr.doc.item_code = sle.item_code
+	sr.make_controller().via_stock_ledger = True
+	sr.insert()
+	sr.doc.warehouse = sle.warehouse
+	sr.doc.status = "Available"
+	sr.save()
+	webnotes.msgprint(_("Serial No created") + ": " + sr.doc.name)
+	return sr.doc.name
+	
+def update_serial_nos_after_submit(controller, parenttype, parentfield):
+	if not hasattr(webnotes, "new_stock_ledger_entries"):
+		return
+		
+	for d in controller.doclist.get({"parentfield": parentfield}):
+		serial_no = None
+		for sle in webnotes.new_stock_ledger_entries:
+			if sle.voucher_detail_no==d.name:
+				serial_no = sle.serial_no
+				break
+
+		if d.serial_no != serial_no:
+			d.serial_no = serial_no
+			webnotes.conn.set_value(d.doctype, d.name, "serial_no", serial_no)
\ No newline at end of file
diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py
index a54f9bf..8d2e764 100644
--- a/stock/doctype/stock_entry/stock_entry.py
+++ b/stock/doctype/stock_entry/stock_entry.py
@@ -52,13 +52,15 @@
 		
 	def on_submit(self):
 		self.update_stock_ledger()
-		self.update_serial_no(1)
+
+		from stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
+		update_serial_nos_after_submit(self, "Stock Entry", "mtn_details")
+		
 		self.update_production_order(1)
 		self.make_gl_entries()
 
 	def on_cancel(self):
 		self.update_stock_ledger()
-		self.update_serial_no(0)
 		self.update_production_order(0)
 		self.make_cancel_gl_entries()
 		
@@ -294,24 +296,6 @@
 			from `tabStock Entry Detail` where parent in (
 				select name from `tabStock Entry` where `%s`=%s and docstatus=1)
 			group by item_code""" % (ref_fieldname, "%s"), (self.doc.fields.get(ref_fieldname),)))
-		
-	def update_serial_no(self, is_submit):
-		"""Create / Update Serial No"""
-
-		from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
-		update_serial_nos_after_submit(self, "Stock Entry", "mtn_details")
-		
-		for d in getlist(self.doclist, 'mtn_details'):
-			for serial_no in get_serial_nos(d.serial_no):
-				if self.doc.purpose == 'Purchase Return':
-					sr = webnotes.bean("Serial No", serial_no)
-					sr.doc.status = "Purchase Returned" if is_submit else "Available"
-					sr.save()
-				
-				if self.doc.purpose == "Sales Return":
-					sr = webnotes.bean("Serial No", serial_no)
-					sr.doc.status = "Sales Returned" if is_submit else "Delivered"
-					sr.save()
 						
 	def update_stock_ledger(self):
 		sl_entries = []			
diff --git a/stock/doctype/stock_entry/test_stock_entry.py b/stock/doctype/stock_entry/test_stock_entry.py
index e2358eb..b41a626 100644
--- a/stock/doctype/stock_entry/test_stock_entry.py
+++ b/stock/doctype/stock_entry/test_stock_entry.py
@@ -7,7 +7,7 @@
 from __future__ import unicode_literals
 import webnotes, unittest
 from webnotes.utils import flt
-from stock.doctype.stock_ledger_entry.stock_ledger_entry import *
+from stock.doctype.serial_no.serial_no import *
 from stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
 
 
@@ -48,7 +48,7 @@
 		webnotes.bean("Profile", "test2@example.com").get_controller()\
 			.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
 
-		from stock.doctype.stock_ledger_entry.stock_ledger_entry import InvalidWarehouseCompany
+		from stock.utils import InvalidWarehouseCompany
 		st1 = webnotes.bean(copy=test_records[0])
 		st1.doclist[1].t_warehouse="_Test Warehouse 2 - _TC1"
 		st1.insert()
@@ -721,6 +721,18 @@
 		se.insert()
 		self.assertRaises(SerialNoNotExistsError, se.submit)
 		
+	def test_serial_duplicate(self):
+		self._clear_stock_account_balance()
+		self.test_serial_by_series()
+		
+		se = webnotes.bean(copy=test_records[0])
+		se.doclist[1].item_code = "_Test Serialized Item With Series"
+		se.doclist[1].qty = 1
+		se.doclist[1].serial_no = "ABCD00001"
+		se.doclist[1].transfer_qty = 1
+		se.insert()
+		self.assertRaises(SerialNoDuplicateError, se.submit)
+		
 	def test_serial_by_series(self):
 		self._clear_stock_account_balance()
 		se = make_serialized_item()
diff --git a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 6fea546..8fef889 100644
--- a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -3,21 +3,10 @@
 
 from __future__ import unicode_literals
 import webnotes
-from webnotes import _, msgprint, ValidationError
+from webnotes import _, msgprint
 from webnotes.utils import cint, flt, getdate, cstr
 from webnotes.model.controller import DocListController
 
-class SerialNoNotRequiredError(ValidationError): pass
-class SerialNoRequiredError(ValidationError): pass
-class SerialNoQtyError(ValidationError): pass
-class SerialNoItemError(ValidationError): pass
-class SerialNoWarehouseError(ValidationError): pass
-class SerialNoStatusError(ValidationError): pass
-class SerialNoNotExistsError(ValidationError): pass
-
-def get_serial_nos(serial_no):
-	return [s.strip() for s in cstr(serial_no).strip().replace(',', '\n').split('\n') if s.strip()]
-
 class DocType(DocListController):
 	def __init__(self, doc, doclist=[]):
 		self.doc = doc
@@ -41,7 +30,9 @@
 	def on_submit(self):
 		self.check_stock_frozen_date()
 		self.actual_amt_check()
-		self.validate_serial_no()
+		
+		from stock.doctype.serial_no.serial_no import process_serial_no
+		process_serial_no(self.doc)
 		
 	#check for item quantity available in stock
 	def actual_amt_check(self):
@@ -73,7 +64,10 @@
 					msgprint("Warehouse: '%s' does not exist in the system. Please check." % self.doc.fields.get(k), raise_exception = 1)
 
 	def validate_item(self):
-		item_det = self.get_item_details()
+		item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus, 
+			is_stock_item, has_serial_no, serial_no_series 
+			from tabItem where name=%s""", 
+			self.doc.item_code, as_dict=True)[0]
 
 		if item_det.is_stock_item != 'Yes':
 			webnotes.throw("""Item: "%s" is not a Stock Item.""" % self.doc.item_code)
@@ -91,95 +85,6 @@
 		if not self.doc.stock_uom:
 			self.doc.stock_uom = item_det.stock_uom
 					
-	def get_item_details(self):
-		return webnotes.conn.sql("""select name, has_batch_no, docstatus, 
-			is_stock_item, has_serial_no, serial_no_series 
-			from tabItem where name=%s""", 
-			self.doc.item_code, as_dict=True)[0]
-	
-	def validate_serial_no(self):
-		item_det = self.get_item_details()
-
-		if item_det.has_serial_no=="No":
-			if self.doc.serial_no:
-				webnotes.throw(_("Serial Number should be blank for Non Serialized Item" + ": " + self.doc.item), 
-					SerialNoNotRequiredError)
-		else:
-			if self.doc.serial_no:
-				serial_nos = get_serial_nos(self.doc.serial_no)
-				if cint(self.doc.actual_qty) != flt(self.doc.actual_qty):
-					webnotes.throw(_("Serial No qty cannot be a fraction") + \
-						(": %s (%s)" % (self.doc.item_code, self.doc.actual_qty)))
-				if len(serial_nos) and len(serial_nos) != abs(cint(self.doc.actual_qty)):
-					webnotes.throw(_("Serial Nos do not match with qty") + \
-						(": %s (%s)" % (self.doc.item_code, self.doc.actual_qty)), SerialNoQtyError)
-
-				# check serial no exists, if yes then source
-				for serial_no in serial_nos:
-					if webnotes.conn.exists("Serial No", serial_no):
-						sr = webnotes.bean("Serial No", serial_no)
-
-						if sr.doc.item_code!=self.doc.item_code:
-							webnotes.throw(_("Serial No does not belong to Item") + \
-								(": %s (%s)" % (self.doc.item_code, serial_no)), SerialNoItemError)
-
-						sr.make_controller().via_stock_ledger = True
-
-						if self.doc.actual_qty < 0:
-							if sr.doc.warehouse!=self.doc.warehouse:
-								webnotes.throw(_("Serial No") + ": " + serial_no + 
-									_(" does not belong to Warehouse") + ": " + self.doc.warehouse, 
-									SerialNoWarehouseError)
-								
-							if self.doc.voucher_type in ("Delivery Note", "Sales Invoice") \
-								and sr.doc.status != "Available":
-								webnotes.throw(_("Serial No status must be 'Available' to Deliver") 
-									+ ": " + serial_no, SerialNoStatusError)
-								
-									
-							sr.doc.warehouse = None
-							sr.save()
-						else:
-							sr.doc.warehouse = self.doc.warehouse
-							sr.save()
-					else:
-						if self.doc.actual_qty < 0:
-							# transfer out
-							webnotes.throw(_("Serial No must exist to transfer out.") + \
-								": " + serial_no, SerialNoNotExistsError)
-						else:
-							# transfer in
-							self.make_serial_no(serial_no)
-			else:
-				if item_det.serial_no_series:
-					from webnotes.model.doc import make_autoname
-					serial_nos = []
-					for i in xrange(cint(self.doc.actual_qty)):
-						serial_nos.append(self.make_serial_no(make_autoname(item_det.serial_no_series)))
-					self.doc.serial_no = "\n".join(serial_nos)
-				else:
-					webnotes.throw(_("Serial Number Required for Serialized Item" + ": " + self.doc.item_code),
-						SerialNoRequiredError)
-	
-	def make_serial_no(self, serial_no):
-		sr = webnotes.new_bean("Serial No")
-		sr.doc.serial_no = serial_no
-		sr.doc.item_code = self.doc.item_code
-		sr.doc.purchase_rate = self.doc.incoming_rate
-		sr.doc.purchase_document_type = self.doc.voucher_type
-		sr.doc.purchase_document_no = self.doc.voucher_no
-		sr.doc.purchase_date = self.doc.posting_date
-		sr.doc.purchase_time = self.doc.posting_time
-		sr.make_controller().via_stock_ledger = True
-		sr.insert()
-		
-		# set warehouse
-		sr.doc.warehouse = self.doc.warehouse
-		sr.doc.status = "Available"
-		sr.save()
-		webnotes.msgprint(_("Serial No created") + ": " + sr.doc.name)
-		return sr.doc.name
-		
 	def check_stock_frozen_date(self):
 		stock_frozen_upto = webnotes.conn.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
 		if stock_frozen_upto:
@@ -191,21 +96,6 @@
 		if not self.doc.posting_time or self.doc.posting_time == '00:0':
 			self.doc.posting_time = '00:00'
 
-def update_serial_nos_after_submit(controller, parenttype, parentfield):
-	if not hasattr(webnotes, "new_stock_ledger_entries"):
-		return
-		
-	for d in controller.doclist.get({"parentfield": parentfield}):
-		serial_no = None
-		for sle in webnotes.new_stock_ledger_entries:
-			if sle.voucher_detail_no==d.name:
-				serial_no = sle.serial_no
-				break
-
-		if d.serial_no != serial_no:
-			d.serial_no = serial_no
-			webnotes.conn.set_value(d.doctype, d.name, "serial_no", serial_no)
-
 def on_doctype_update():
 	if not webnotes.conn.sql("""show index from `tabStock Ledger Entry` 
 		where Key_name="posting_sort_index" """):