Added a reference of Sales Invoice in Serial No (#8855)
* [enhance] added Sales Invoice reference in Serial Number
* [patch] added test cases for Sales Invoice and added patch to copy sales invoice
* [minor] minor fixes in validate_serial_against_delivery_note
* [minor] fixes in sales invoice serial number validation
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 007afe4..3fbde29 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -323,7 +323,8 @@
}
}
- item_fields_stock = ['serial_no', 'batch_no', 'actual_qty', 'expense_account', 'warehouse', 'expense_account', 'warehouse']
+ 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));
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 6e3990a..f32cfcd 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -88,6 +88,8 @@
self.validate_c_form()
self.validate_time_sheets_are_submitted()
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
+ if not self.is_return:
+ self.validate_serial_numbers()
self.update_packing_list()
self.set_billing_hours_and_amount()
self.update_timesheet_billing_for_project()
@@ -125,6 +127,7 @@
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.check_credit_limit()
+ self.update_serial_no()
if not cint(self.is_pos) == 1 and not self.is_return:
self.update_against_document_in_jv()
@@ -155,6 +158,7 @@
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
+ self.update_serial_no(in_cancel=True)
self.validate_c_form_on_cancel()
@@ -781,6 +785,61 @@
self.due_date = None
+ def update_serial_no(self, in_cancel=False):
+ """ update Sales Invoice refrence in Serial No """
+
+ for item in self.items:
+ if not item.serial_no:
+ continue
+
+ serial_nos = ["'%s'"%serial_no for serial_no in item.serial_no.split("\n")]
+
+ frappe.db.sql(""" update `tabSerial No` set sales_invoice='{invoice}'
+ where name in ({serial_nos})""".format(
+ invoice='' if in_cancel else self.name,
+ serial_nos=",".join(serial_nos)
+ )
+ )
+
+ def validate_serial_numbers(self):
+ """
+ validate serial number agains Delivery Note and Sales Invoice
+ """
+ self.validate_serial_against_delivery_note()
+ self.validate_serial_against_sales_invoice()
+
+ def validate_serial_against_delivery_note(self):
+ """
+ validate if the serial numbers in Sales Invoice Items are same as in
+ Delivery Note Item
+ """
+
+ for item in self.items:
+ if not item.delivery_note or not item.dn_detail:
+ continue
+
+ serial_nos = frappe.db.get_value("Delivery Note Item", item.dn_detail, "serial_no") or ""
+ dn_serial_nos = set(serial_nos.split("\n"))
+
+ serial_nos = item.serial_no or ""
+ si_serial_nos = set(serial_nos.split("\n"))
+
+ if si_serial_nos - dn_serial_nos:
+ frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note".format(item.idx)))
+
+ def validate_serial_against_sales_invoice(self):
+ """ check if serial number is already used in other sales invoice """
+ for item in self.items:
+ if not item.serial_no:
+ continue
+
+ for serial_no in item.serial_no.split("\n"):
+ sales_invoice = frappe.db.get_value("Serial No", serial_no, "sales_invoice")
+ if sales_invoice and self.name != sales_invoice:
+ frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}".format(
+ serial_no, sales_invoice
+ )))
+
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
list_context = get_list_context(context)
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 0ada847..8335879 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -737,6 +737,12 @@
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0],
"delivery_document_no"), si.name)
+ self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"),
+ si.name)
+
+ # check if the serial number is already linked with any other Sales Invoice
+ _si = frappe.copy_doc(si.as_dict())
+ self.assertRaises(frappe.ValidationError, _si.insert)
return si
@@ -750,6 +756,7 @@
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0],
"delivery_document_no"))
+ self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"))
def test_serialize_status(self):
serial_no = frappe.get_doc({
@@ -768,6 +775,27 @@
self.assertRaises(SerialNoWarehouseError, si.submit)
+ def test_serial_numbers_against_delivery_note(self):
+ """
+ check if the sales invoice item serial numbers and the delivery note items
+ serial numbers are same
+ """
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+ from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+ se = make_serialized_item()
+ serial_nos = get_serial_nos(se.get("items")[0].serial_no)
+
+ dn = create_delivery_note(item=se.get("items")[0].item_code, serial_no=serial_nos[0])
+ dn.submit()
+
+ si = make_sales_invoice(dn.name)
+ si.save()
+
+ self.assertEquals(si.get("items")[0].serial_no, dn.get("items")[0].serial_no)
+
def test_invoice_due_date_against_customers_credit_days(self):
# set customer's credit days
frappe.db.set_value("Customer", "_Test Customer", "credit_days_based_on", "Fixed Days")
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ac91f60..0faf98e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -396,3 +396,4 @@
erpnext.patches.v8_0.rename_total_margin_to_rate_with_margin # 11-05-2017
erpnext.patches.v8_0.fix_status_for_invoices_with_negative_outstanding
erpnext.patches.v8_0.make_payments_table_blank_for_non_pos_invoice
+erpnext.patches.v8_0.set_sales_invoice_serial_number_from_delivery_note
\ No newline at end of file
diff --git a/erpnext/patches/v8_0/set_sales_invoice_serial_number_from_delivery_note.py b/erpnext/patches/v8_0/set_sales_invoice_serial_number_from_delivery_note.py
new file mode 100644
index 0000000..2ae74cd
--- /dev/null
+++ b/erpnext/patches/v8_0/set_sales_invoice_serial_number_from_delivery_note.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
+
+def execute():
+ """ Set the Serial Numbers in Sales Invoice Item from Delivery Note Item """
+
+ frappe.reload_doc("stock", "doctype", "serial_no")
+
+ frappe.db.sql(""" update `tabSales Invoice Item` sii inner join
+ `tabDelivery Note Item` dni on sii.dn_detail=dni.name and sii.qty=dni.qty
+ set sii.serial_no=dni.serial_no where sii.parent IN (select si.name
+ from `tabSales Invoice` si where si.update_stock=0 and si.docstatus=1)""")
+
+ items = frappe.db.sql(""" select sii.parent, sii.serial_no from `tabSales Invoice Item` sii
+ left join `tabSales Invoice` si on sii.parent=si.name
+ where si.docstatus=1 and si.update_stock=0""", as_dict=True)
+
+ for item in items:
+ sales_invoice = item.get("parent", None)
+ serial_nos = item.get("serial_no", "")
+
+ if not sales_invoice or not serial_nos:
+ continue
+
+ serial_nos = ["'%s'"%no for no in serial_nos.split("\n")]
+
+ frappe.db.sql("""
+ UPDATE
+ `tabSerial No`
+ SET
+ sales_invoice='{sales_invoice}'
+ WHERE
+ name in ({serial_nos})
+ """.format(
+ sales_invoice=sales_invoice,
+ serial_nos=",".join(serial_nos)
+ )
+ )
\ No newline at end of file
diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json
index 3b65ff8..b37713b 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.json
+++ b/erpnext/stock/doctype/serial_no/serial_no.json
@@ -1,5 +1,6 @@
{
"allow_copy": 0,
+ "allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:serial_no",
@@ -13,6 +14,7 @@
"editable_grid": 0,
"fields": [
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -42,6 +44,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -69,6 +72,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -99,6 +103,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -130,6 +135,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -162,6 +168,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -189,6 +196,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -217,6 +225,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -248,6 +257,7 @@
"width": "300px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -280,6 +290,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -311,6 +322,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -339,6 +351,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -367,6 +380,7 @@
"width": "50%"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -396,6 +410,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -425,6 +440,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -455,6 +471,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -483,6 +500,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -514,6 +532,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -542,6 +561,7 @@
"width": "50%"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -571,6 +591,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
@@ -599,6 +620,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -628,6 +650,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -657,6 +680,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -686,6 +710,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -716,6 +741,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -744,6 +770,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -775,6 +802,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -803,6 +831,7 @@
"width": "50%"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -834,6 +863,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
@@ -864,6 +894,68 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "invoice_details",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Invoice Details",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "sales_invoice",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Sales Invoice",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Sales Invoice",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -892,6 +984,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -920,6 +1013,7 @@
"width": "50%"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -952,6 +1046,7 @@
"width": "150px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -983,6 +1078,7 @@
"width": "150px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1011,6 +1107,7 @@
"width": "50%"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1042,6 +1139,7 @@
"width": "150px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1073,6 +1171,7 @@
"width": "150px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1101,6 +1200,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1129,6 +1229,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1158,18 +1259,18 @@
"unique": 0
}
],
+ "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-barcode",
"idx": 1,
"image_view": 0,
"in_create": 0,
- "in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-02-20 13:26:55.864960",
+ "modified": "2017-05-15 18:22:23.685286",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial No",