Merge pull request #9902 from rohitwaghchaure/sales_invoice_serial_no_issue_from_dn

[Fix] Sales invoice serial no validation issue
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 270adf9..c386450 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -363,13 +363,6 @@
 		}
 	}
 
-
-	var item_fields_stock = ['batch_no', 'actual_batch_qty', 'actual_qty', 'expense_account',
-		'warehouse', 'expense_account', 'quality_inspection']
-	cur_frm.fields_dict['items'].grid.set_column_disp(item_fields_stock,
-		(cint(doc.update_stock)==1 || cint(doc.is_return)==1 ? true : false));
-
-
 	// India related fields
 	if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
 	else hide_field(['c_form_applicable', 'c_form_no']);
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 28a7817..9dfacbd 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -18,6 +18,7 @@
 from erpnext.accounts.doctype.asset.depreciation \
 	import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal
 from erpnext.stock.doctype.batch.batch import set_batch_nos
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
 
 form_grid_templates = {
 	"items": "templates/form_grid/item_grid.html"
@@ -814,9 +815,16 @@
 		"""
 			validate serial number agains Delivery Note and Sales Invoice
 		"""
+		self.set_serial_no_against_delivery_note()
 		self.validate_serial_against_delivery_note()
 		self.validate_serial_against_sales_invoice()
 
+	def set_serial_no_against_delivery_note(self):
+		for item in self.items:
+			if item.serial_no and item.delivery_note and \
+				item.qty != len(get_serial_nos(item.serial_no)):
+				item.serial_no = get_delivery_note_serial_no(item.item_code, item.qty, item.delivery_note)
+
 	def validate_serial_against_delivery_note(self):
 		"""
 			validate if the serial numbers in Sales Invoice Items are same as in
@@ -828,14 +836,18 @@
 				continue
 
 			serial_nos = frappe.db.get_value("Delivery Note Item", item.dn_detail, "serial_no") or ""
-			dn_serial_nos = set(serial_nos.split("\n"))
+			dn_serial_nos = set(get_serial_nos(serial_nos))
 
 			serial_nos = item.serial_no or ""
-			si_serial_nos = set(serial_nos.split("\n"))
+			si_serial_nos = set(get_serial_nos(serial_nos))
 
 			if si_serial_nos - dn_serial_nos:
 				frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note".format(item.idx)))
 
+			if item.serial_no and cint(item.qty) != len(si_serial_nos):
+				frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.".format(
+					item.idx, item.qty, item.item_code, len(si_serial_nos))))
+
 	def validate_serial_against_sales_invoice(self):
 		""" check if serial number is already used in other sales invoice """
 		for item in self.items:
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index 5810c69..41b794c 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -1424,7 +1424,7 @@
    "collapsible": 1, 
    "collapsible_depends_on": "eval:doc.serial_no || doc.batch_no", 
    "columns": 0, 
-   "depends_on": "eval: parent.update_stock", 
+   "depends_on": "", 
    "fieldname": "warehouse_and_reference", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -2166,7 +2166,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2017-07-06 17:54:03.347700", 
+ "modified": "2017-07-17 17:54:48.246507", 
  "modified_by": "Administrator", 
  "module": "Accounts", 
  "name": "Sales Invoice Item", 
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 5c5f0f8..82beff8 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -14,6 +14,7 @@
 from frappe.desk.notifications import clear_doctype_notifications
 from erpnext.stock.doctype.batch.batch import set_batch_nos
 from frappe.contacts.doctype.address.address import get_company_address
+from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no
 
 form_grid_templates = {
 	"items": "templates/form_grid/item_grid.html"
@@ -390,6 +391,9 @@
 
 	def update_item(source_doc, target_doc, source_parent):
 		target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0)
+		if source_doc.serial_no and source_parent.per_billed > 0:
+			target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code,
+				target_doc.qty, source_parent.name)
 
 	doc = get_mapped_doc("Delivery Note", source_name, 	{
 		"Delivery Note": {
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 751d527..24e520c 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -170,6 +170,10 @@
 			"delivery_document_no": dn.name
 		})
 
+		si = make_sales_invoice(dn.name)
+		si.insert(ignore_permissions=True)
+		self.assertEquals(dn.items[0].serial_no, si.items[0].serial_no)
+
 		dn.cancel()
 
 		self.check_serial_no_values(serial_no, {
@@ -177,6 +181,22 @@
 			"delivery_document_no": ""
 		})
 
+	def test_serialized_partial_sales_invoice(self):
+		se = make_serialized_item()
+		serial_no = get_serial_nos(se.get("items")[0].serial_no)
+		serial_no = '\n'.join(serial_no)
+
+		dn = create_delivery_note(item_code="_Test Serialized Item With Series", qty=2, serial_no=serial_no)
+
+		si = make_sales_invoice(dn.name)
+		si.items[0].qty = 1
+		si.submit()
+		self.assertEquals(si.items[0].qty, 1)
+
+		si = make_sales_invoice(dn.name)
+		si.submit()
+		self.assertEquals(si.items[0].qty, len(get_serial_nos(si.items[0].serial_no)))
+
 	def test_serialize_status(self):
 		from frappe.model.naming import make_autoname
 		serial_no = frappe.get_doc({
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index fe158c9..76a0406 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -337,3 +337,17 @@
 		doc = frappe.get_doc("Serial No", serial_no[0])
 		doc.set_maintenance_status()
 		frappe.db.set_value('Serial No', doc.name, 'maintenance_status', doc.maintenance_status)
+
+def get_delivery_note_serial_no(item_code, qty, delivery_note):
+	serial_nos = ''
+	dn_serial_nos = frappe.db.sql_list(""" select name from `tabSerial No`
+		where item_code = %(item_code)s and delivery_document_no = %(delivery_note)s
+		and sales_invoice is null limit {0}""".format(cint(qty)), {
+		'item_code': item_code,
+		'delivery_note': delivery_note
+	})
+
+	if dn_serial_nos and len(dn_serial_nos)>0:
+		serial_nos = '\n'.join(dn_serial_nos)
+
+	return serial_nos
\ No newline at end of file