From 7e149969958f32c0b12cce11c5da543a725a1b7f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Jul 2017 19:00:18 +0530 Subject: [PATCH] [Fix] Sales invoice serial no validation issue --- .../doctype/sales_invoice/sales_invoice.js | 7 ------- .../doctype/sales_invoice/sales_invoice.py | 16 +++++++++++++-- .../sales_invoice_item.json | 4 ++-- .../doctype/delivery_note/delivery_note.py | 4 ++++ .../delivery_note/test_delivery_note.py | 20 +++++++++++++++++++ erpnext/stock/doctype/serial_no/serial_no.py | 14 +++++++++++++ 6 files changed, 54 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 68b5d4683f..230933fd7f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -343,13 +343,6 @@ cur_frm.cscript.hide_fields = function(doc) { } } - - 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 2dd4e7a733..e940b15db3 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.projects.doctype.timesheet.timesheet import get_projectwise_timeshe 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" @@ -812,9 +813,16 @@ class SalesInvoice(SellingController): """ 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 @@ -826,14 +834,18 @@ class SalesInvoice(SellingController): 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 5810c69cd1..41b794c176 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 5c5f0f8b6a..82beff8782 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -14,6 +14,7 @@ from erpnext.controllers.selling_controller import SellingController 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 make_sales_invoice(source_name, target_doc=None): 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 751d5274b4..24e520c1e0 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 @@ class TestDeliveryNote(unittest.TestCase): "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 @@ class TestDeliveryNote(unittest.TestCase): "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 fe158c9bbb..76a0406f09 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -337,3 +337,17 @@ def update_maintenance_status(): 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