From e4e00d2215ac3dc5e36b04bfd8621c0041309653 Mon Sep 17 00:00:00 2001 From: pratu16x7 Date: Thu, 18 May 2017 11:46:09 +0530 Subject: [PATCH] batch item distribution by oldest, serial no check --- erpnext/stock/doctype/batch/batch.json | 19 +- erpnext/stock/doctype/batch/batch.py | 17 +- .../stock/doctype/stock_entry/stock_entry.js | 346 +++++++++++------- 3 files changed, 227 insertions(+), 155 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.json b/erpnext/stock/doctype/batch/batch.json index b4a1405650..5050541a20 100644 --- a/erpnext/stock/doctype/batch/batch.json +++ b/erpnext/stock/doctype/batch/batch.json @@ -14,6 +14,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -39,12 +40,13 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -76,6 +78,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -105,6 +108,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -136,6 +140,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -164,6 +169,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -194,6 +200,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -223,6 +230,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -253,6 +261,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -281,6 +290,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -311,6 +321,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -341,6 +352,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -369,6 +381,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -412,8 +425,8 @@ "issingle": 0, "istable": 0, "max_attachments": 5, - "modified": "2017-04-20 03:22:19.888058", - "modified_by": "Administrator", + "modified": "2017-05-21 21:00:11.096038", + "modified_by": "prateeksha@erpnext.com", "module": "Stock", "name": "Batch", "owner": "harshada@webnotestech.com", diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 992d8fc35a..2ee8c62861 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -66,13 +66,14 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None): return out @frappe.whitelist() -def get_oldest_batch_qty(item_code, warehouse): +def get_batches_by_oldest(item_code, warehouse): '''Returns the oldest batch and qty for the given item_code and warehouse''' batches = get_batch_qty(item_code = item_code, warehouse = warehouse) - oldest_date = min([frappe.get_value('Batch', batch.batch_no, 'expiry_date') for batch in batches]) - for batch in batches: - if (frappe.get_value('Batch', batch.batch_no, 'expiry_date') == oldest_date): - return batch + batches_dates = [[batch, frappe.get_value('Batch', batch.batch_no, 'expiry_date')] for batch in batches] + batches_dates.sort(key=lambda tup: tup[1]) + + sorted_batches = [tup[0] for tup in batches_dates] + return sorted_batches @frappe.whitelist() def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None): @@ -132,10 +133,4 @@ def get_batch_no(item_code, warehouse, qty): frappe.msgprint(_('Please select a Batch for Item {0}. Unable to find a single batch that fulfills this requirement').format(frappe.bold(item_code))) if throw: raise UnableToSelectBatchError - # oldest_expiry_date = min([batch.expiry_date for batch in batches]) - - # for batch in batches: - # if batch.expiry_date == oldest_expiry_date: - # batch_no = batch.batch_no - return batch_no diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 53fac4d967..8f82b6a4af 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -76,7 +76,7 @@ frappe.ui.form.on('Stock Entry', { frm.fields_dict.items.grid.refresh(); frm.cscript.toggle_related_fields(frm.doc); }, - validate: function(frm) { + after_save: function(frm) { erpnext.select_batch_and_serial_no(frm); }, company: function(frm) { @@ -547,6 +547,63 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }); erpnext.select_batch_and_serial_no = (frm) => { + let get_warehouse = (item) => { + return cstr(item.s_warehouse) ? cstr(item.s_warehouse) : cstr(item.t_warehouse); + } + + let show_modal_with_oldest_batch = (item, item_code, total_qty, warehouse, has_batch) => { + frappe.call({ + method: 'erpnext.stock.doctype.batch.batch.get_batches_by_oldest', + args: { + warehouse: warehouse, + item_code: item_code + }, + callback: (r) => { + if (r.message) { + batch_rows_by_oldest = []; + if(cstr(item.s_warehouse)) { + qty = total_qty; + for(var i = 0; i < r.message.length; i++) { + batch_row = {name: 'batch 1'}; + batch_row.batch_no = r.message[i].batch_no; + batch_row.available_qty = r.message[i].qty; + if (parseInt(qty) <= parseInt(r.message[i].qty)) { + batch_row.selected_qty = qty; + batch_rows_by_oldest.push(batch_row); + break; + } else { + batch_row.selected_qty = r.message[i].qty; + qty -= r.message[i].qty; + batch_rows_by_oldest.push(batch_row); + } + } + } + erpnext.show_batch_serial_modal(frm, item, item_code, total_qty, warehouse, has_batch, batch_rows_by_oldest); + } + } + }); + } + + frm.doc.items.forEach(function(d) { + if(d.has_batch_no && !d.batch_no) { + show_modal_with_oldest_batch(d, d.item_code, d.qty, get_warehouse(d), 1); + } else if(d.has_serial_no && !d.serial_no) { + erpnext.show_batch_serial_modal(frm, d, d.item_code, d.qty, get_warehouse(d), 0); + } + }); + + +} + +erpnext.show_batch_serial_modal = (frm, item, item_code, qty, warehouse, has_batch, oldest = undefined) => { + let data = oldest ? oldest : [] + let title = ""; + let fields = [ + {fieldname: 'item_code', read_only: 1, fieldtype:'Link', options: 'Item', + label: __('Item Code'), 'default': item_code}, + {fieldtype:'Column Break'}, + {fieldname: 'qty', read_only: 1, fieldtype:'Float', label: __('Qty'), 'default': qty}, + ]; let set_available_qty = (item_code, batch_no, warehouse, field) => { frappe.call({ @@ -565,163 +622,170 @@ erpnext.select_batch_and_serial_no = (frm) => { }); } - let get_warehouse = (i) => { - // console.log("i", i); - // console.log("cstr(i.s_warehouse) || cstr(i.t_warehouse)", cstr(i.s_warehouse) || cstr(i.t_warehouse)); - return cstr(i.s_warehouse) ? cstr(i.s_warehouse) : cstr(i.t_warehouse); + if(has_batch) { + title = __("Select Batch Numbers"); + fields = fields.concat([ + {fieldtype:'Section Break', label: __('Batch No')}, + {fieldname: 'batches', fieldtype: 'Table', + fields: [ + {fieldtype:'Link', fieldname:'batch_no', options: 'Batch', + label: __('Select Batch'), in_list_view:1, get_query: function(doc) { + return {filters: {item: item_code }}; + }}, + {fieldtype:'Float', read_only: 1, fieldname:'available_qty', + label: __('Available'), in_list_view:1}, + {fieldtype:'Float', fieldname:'selected_qty', + label: __('Qty'), in_list_view:1}, + ], + data: data, + get_data: function() { + return this.data; + }, + on_setup: function(grid) { + var me = this; + grid.wrapper.on('change', 'input[data-fieldname="batch_no"]', function(e) { + let $row = $(this).closest('.grid-row'); + let name = $row.attr('data-name'); + let row = grid.grid_rows_by_docname[name]; + row.on_grid_fields[2].set_value('0'); + row.on_grid_fields[2].$input.trigger('change'); + set_available_qty(item_code, row.doc.batch_no, warehouse, row.on_grid_fields[1]); + }); + } + } + ]); + } else { + title = __("Select Serial Numbers"); + fields = fields.concat([ + {fieldtype: 'Section Break', label: __('Serial No')}, + { + fieldtype: 'Link', fieldname: 'serial_no_select', options: 'Serial No', + label: __('Select'), + get_query: function(doc) { + return { filters: {item_code: item_code}}; + } + }, + {fieldtype: 'Column Break'}, + {fieldname: 'serial_no', fieldtype: 'Small Text'} + ]) } - // console.log("available_qty", get_available_qty("_Test FG Item", 'tfi', 'Stores - A')); - // get_warehouse(); + let dialog = new frappe.ui.Dialog({ + title: title, + fields: fields + }); - let show_modal = (item, item_code, qty, warehouse, has_batch, has_serial, oldest = undefined) => { - let data = oldest ? [oldest] : [] - let fields = [ - {fieldname: 'item_code', read_only: 1, fieldtype:'Link', options: 'Item', - label: __('Item Code'), 'default': item_code}, - {fieldtype:'Column Break'}, - {fieldname: 'qty', fieldtype:'Float', label: __('Qty'), 'default': qty}, - {fieldtype:'Section Break'} - ]; - if(has_batch) { - fields.push( - {fieldname: 'batches', fieldtype: 'Table', - fields: [ - {fieldtype:'Link', fieldname:'batch_no', options: 'Batch', - label: __('Select Batch'), in_list_view:1, get_query: function(doc) { - return {filters: {item: item_code }}; - }}, - {fieldtype:'Float', read_only: 1, fieldname:'available_qty', - label: __('Available'), in_list_view:1}, - {fieldtype:'Float', fieldname:'selected_qty', - label: __('Qty'), in_list_view:1}, - ], - data: data, - get_data: function() { - return this.data; - }, - bind_events: function(grid) { - var me = this; - grid.wrapper.on('change', 'input[data-fieldname="batch_no"]', function(e) { - let $row = $(this).closest('.grid-row'); - let name = $row.attr('data-name'); - let row = grid.grid_rows_by_docname[name]; - // console.log("bind changed: ", $(this), name, row.doc.batch_no, row.doc.idx); - // console.log(grid.grid_rows_by_docname); - set_available_qty(item_code, row.doc.batch_no, warehouse, row.on_grid_fields[1]); - }); - } - }); - } - if(has_serial) { - fields.push( - {fieldname: 'serial_no', fieldtype: 'Small Text', label: __('Serial No')} - ) - } - let dialog = new frappe.ui.Dialog({ - title: __("Select Batches"), - fields: fields + let serial_no_link = dialog.fields_dict.serial_no_select; + if(serial_no_link) { + serial_no_link.$input.on('change', function(e) { + let serial_no_list = dialog.fields_dict.serial_no; + if(serial_no_link.get_value()) { + let new_line = '\n'; + if(!serial_no_list.get_value()) { + new_line = ''; + } + serial_no_list.set_value(serial_no_list.get_value() + new_line + serial_no_link.get_value()); + serial_no_link.$input.val(''); + serial_no_link.set_value(''); + } + }); + } + + item_temp = {}; + Object.assign(item_temp, item); + delete item_temp['batch_no']; + delete item_temp['qty']; + delete item_temp['idx']; + + let validate_batch_dialog = (values) => { + var sum = 0; + + values.batches.map((batch) => { + sum += parseInt(batch.selected_qty); + if(batch.batch_no && batch.selected_qty) { + if(parseInt(batch.selected_qty) > parseInt(batch.available_qty) + && cstr(item.s_warehouse)) { + frappe.throw(__("Cannot select more than the available qty")); + return false; + } + } else { + if(!batch.batch_no) { + frappe.throw(__("Please select batch_no")); + return false; + } else { + frappe.throw(__("Please select qty")); + return false; + } + } }); - item_temp = {}; - Object.assign(item_temp, item); - - let validate_dialog_values = () => { - var values = dialog.get_values(); - - values.batches.map((batch) => { - if (batch.batch_no && batch.selected_qty) { - if (parseInt(batch.selected_qty) > parseInt(batch.available_qty)) { - frappe.throw(__("Cannot select more than the available qty")); - return false; - } - } else { - if (!batch.batch_no) { - frappe.throw(__("Please select batch_no")); - return false; - } else { - frappe.throw(__("Please select qty")); - return false; - } - } + // validate total_qty + if(sum > values.qty) { + frappe.confirm(__(`Total of selected quantities is greater than the previously + set item quantity. Update?`), function(){ + return true; }); - - // validate total_qty + } else if (sum < values.qty){ + frappe.confirm(__(`Total of selected quantities is less than the previously + set item quantity. Update?`), function(){ + return true; + }); + } else { return true; } - - dialog.set_primary_action(__('Get Items'), function() { - - if (!validate_dialog_values()) { - return; - } - - var values = dialog.get_values(); - - values.batches.map((batch, i) => { - if (i === 0) { - item.batch_no = batch.batch_no; - item.qty = batch.selected_qty; - console.log("item", item, frm.doc.items); - } else { - var row = frappe.model.add_child(frm.doc, "Stock Entry Detail", "items"); - Object.assign(row, item_temp); - row.idx = i+1; - row.batch_no = batch.batch_no; - row.qty = batch.selected_qty; - console.log("row", row, frm.doc.items); - } - refresh_field("items"); - }); - - - refresh_field("items"); - dialog.hide(); - }) - - dialog.show(); } - // show_modal("_Test FG Item", 10, 'Stores - A', 1, 0); + let set_batched_items = () => { + var values = dialog.get_values(); + if(!validate_batch_dialog(values)) { + return; + } - let show_modal_with_oldest_batch = (item, item_code, qty, warehouse, has_batch, has_serial_no) => { - frappe.call({ - method: 'erpnext.stock.doctype.batch.batch.get_oldest_batch_qty', - args: { - warehouse: warehouse, - item_code: item_code - }, - callback: (r) => { - if (r.message) { - oldest = {name: 'batch 1'}; - oldest.batch_no = r.message.batch_no; - oldest.available_qty = r.message.qty; - // if required overall qty is less than available qty in oldest batch, preselect - if (parseInt(qty) < parseInt(r.message.qty)) { - oldest.selected_qty = qty; - } - show_modal(item, item_code, qty, warehouse, has_batch, has_serial_no, oldest); - } + values.batches.map((batch, i) => { + if(i === 0) { + item.batch_no = batch.batch_no; + item.qty = batch.selected_qty; + console.log("item", item, frm.doc.items); + } else { + var row = frm.add_child("items"); + Object.assign(row, item_temp); + console.log("item", item_temp); + + row.item_code = item_temp.item_code; + row.batch_no = batch.batch_no; + row.qty = batch.selected_qty; + console.log("row", row, frm.doc.items); } + refresh_field("items"); }); } - frm.doc.items.forEach(function(d){ - if(d.has_batch_no && !d.batch_no) { - console.log("item code, "); + let validate_serial_no_dialog = (values) => { + var serial_nos = values.serial_no || ''; + if (!serial_nos || !serial_nos.replace(/\s/g, '').length) { + frappe.throw(__("Please enter serial numbers")); + return false; } - }); + return true; + } - frm.doc.items.forEach(function(d) { - if(d.has_batch_no && !d.batch_no) { - // console.log(d.item_code, get_warehouse(d)); - show_modal_with_oldest_batch(d, d.item_code, d.qty, get_warehouse(d), 1, 0); - // show_modal(d, d.item_code, d.qty, get_warehouse(d), 1, 0 , oldest); + let set_serialized_items = () => { + var values = dialog.get_values(); + if (!validate_serial_no_dialog(values)) { + return; } - if(d.has_serial_no && !d.serial_no) { - show_modal(d, d.item_code, d.qty, get_warehouse(d), 0, 1); + item.serial_no = values.serial_no; + } + + dialog.set_primary_action(__('Get Items'), function() { + if(has_batch) { + set_batched_items(); + } else { + set_serialized_items(); } - }); - -} \ No newline at end of file + refresh_field("items"); + dialog.hide(); + }) + dialog.show(); +}