From 70eca9462bf36364a7368d6a9d27ec9091e59e88 Mon Sep 17 00:00:00 2001 From: pratu16x7 Date: Thu, 22 Jun 2017 15:49:42 +0530 Subject: [PATCH] make erpnext.SerialNoBatchSelector --- erpnext/public/build.json | 1 + .../js/utils/serial_no_batch_selector.js | 294 ++++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.js | 289 +---------------- 3 files changed, 312 insertions(+), 272 deletions(-) create mode 100644 erpnext/public/js/utils/serial_no_batch_selector.js diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 393e90c052..94057193e6 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -30,6 +30,7 @@ "public/js/payment/payment_details.html", "public/js/templates/item_selector.html", "public/js/utils/item_selector.js", + "public/js/utils/serial_no_batch_selector.js", "public/js/help_links.js", "public/js/schools/student_button.html", "public/js/schools/assessment_result_tool.html" diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js new file mode 100644 index 0000000000..f54fc0f377 --- /dev/null +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -0,0 +1,294 @@ + +erpnext.SerialNoBatchSelector = Class.extend({ + init: function(opts) { + $.extend(this, opts); + // frm, item, warehouse_details, has_batch, oldest + this.setup(); + }, + + setup: function() { + this.item_code = this.item.item_code; + this.qty = this.item.qty; + this.make_dialog(); + }, + + make_dialog: function() { + var me = this; + + this.data = this.oldest ? this.oldest : []; + let title = ""; + let fields = [ + {fieldname: 'item_code', read_only: 1, fieldtype:'Link', options: 'Item', + label: __('Item Code'), 'default': me.item_code}, + {fieldtype:'Column Break'}, + { + fieldname: 'warehouse', + fieldtype:'Link', + options: 'Warehouse', + label: __(me.warehouse_details.type), + default: me.warehouse_details.name, + onchange: function(e) { + me.warehouse_details.name = this.get_value(); + var batches = this.layout.fields_dict.batches; + if(batches) { + batches.grid.df.data = []; + batches.grid.refresh(); + batches.grid.add_new_row(null, null, null); + } + } + }, + {fieldtype:'Column Break'}, + {fieldname: 'qty', fieldtype:'Float', label: __(me.has_batch ? 'Total Qty' : 'Qty'), 'default': me.qty}, + ]; + + if(this.has_batch) { + title = __("Select Batch Numbers"); + fields = fields.concat(this.get_batch_fields()); + } else { + title = __("Select Serial Numbers"); + fields = fields.concat(this.get_serial_no_fields()); + } + + this.dialog = new frappe.ui.Dialog({ + title: title, + fields: fields + }); + + this.bind_qty(); + + this.dialog.set_primary_action(__('Insert'), function() { + me.values = me.dialog.get_values(); + if(!me.validate()) return; + me.set_items(); + refresh_field("items"); + me.dialog.hide(); + }); + + this.dialog.show(); + }, + + validate: function() { + let values = this.values; + if(!values.warehouse) { + frappe.throw(__("Please select a warehouse")); + return false; + } + if(this.has_batch) { + if(values.batches.length === 0 || !values.batches) { + frappe.throw(__("Please select batches for batched item " + + values.item_code)); + return false; + } + values.batches.map((batch, i) => { + if(!batch.selected_qty || batch.selected_qty === 0 ) { + frappe.throw(__("Please select quantity on row " + (i+1))); + return false; + } + }); + return true; + + } else { + let serial_nos = values.serial_no || ''; + if (!serial_nos || !serial_nos.replace(/\s/g, '').length) { + frappe.throw(__("Please enter serial numbers for serialized item " + + values.item_code)); + return false; + } + return true; + } + }, + + set_items: function() { + if(this.has_batch) { + this.values.batches.map((batch, i) => { + if(i === 0) { + this.map_item_values(this.item, batch, 'batch_no', + 'selected_qty', this.values.warehouse); + } else { + let row = this.frm.add_child("items"); + row.item_code = this.item_code; + this.map_item_values(row, batch, 'batch_no', + 'selected_qty', this.values.warehouse); + } + }); + } else { + this.map_item_values(this.item, this.values, 'serial_no', 'qty'); + } + }, + + map_item_values: function(item, values, attribute, qty_field, warehouse) { + item[attribute] = values[attribute]; + if(this.warehouse_details.type === 'Source Warehouse') { + item.s_warehouse = values.warehouse || warehouse; + } else { + item.t_warehouse = values.warehouse || warehouse; + } + item.qty = values[qty_field]; + }, + + bind_qty: function() { + let serial_no_link = this.dialog.fields_dict.serial_no_select; + let serial_no_list_field = this.dialog.fields_dict.serial_no; + let batches_field = this.dialog.fields_dict.batches; + + let qty_field = this.dialog.fields_dict.qty; + let item_code = this.item_code; + + let update_quantity = (batch) => { + if(batch) { + let total_qty = 0; + batches_field.grid.wrapper.find( + 'input[data-fieldname="selected_qty"]').each(function() { + + total_qty += Number($(this).val()); + }); + qty_field.set_input(total_qty); + } else { + let serial_numbers = serial_no_list_field.get_value() + .replace(/\n/g, ' ').match(/\S+/g) || []; + qty_field.set_input(serial_numbers.length); + } + } + + if(serial_no_link) { + let serial_list = []; + serial_no_link.$input.on('awesomplete-selectcomplete', function() { + if(serial_no_link.get_value().length > 0) { + let new_no = serial_no_link.get_value(); + let list_value = serial_no_list_field.get_value(); + let new_line = '\n'; + if(!serial_no_list_field.get_value()) { + new_line = ''; + } else { + serial_list = list_value.replace(/\s+/g, ' ').split(' '); + } + if(!serial_list.includes(new_no)) { + serial_no_link.set_new_description(''); + serial_no_list_field.set_value(list_value + new_line + new_no); + update_quantity(0); + } else { + serial_no_link.set_new_description(new_no + ' is already selected.'); + } + } + + // Should, but doesn't work + serial_no_link.set_input(''); + serial_no_link.$input.blur(); + }); + + serial_no_list_field.$input.on('input', function() { + serial_list = serial_no_list_field.get_value().replace(/\s+/g, ' ').split(' '); + update_quantity(0); + }); + } + + if(batches_field) { + batches_field.grid.wrapper.on('change', function() { + update_quantity(1); + }); + } + }, + + get_batch_fields: function() { + var me = this; + return [ + {fieldtype:'Section Break', label: __('Batches')}, + {fieldname: 'batches', fieldtype: 'Table', + fields: [ + { + fieldtype:'Link', + fieldname:'batch_no', + options: 'Batch', + label: __('Select Batch'), + in_list_view:1, + get_query: function() { + return {filters: {item: me.item_code }}; + }, + onchange: function(e) { + if(this.get_value().length === 0) { + this.grid_row.on_grid_fields_dict + .available_qty.set_value(0); + return; + } + if(me.warehouse_details.name) { + frappe.call({ + method: 'erpnext.stock.doctype.batch.batch.get_batch_qty', + args: { + batch_no: this.doc.batch_no, + warehouse: me.warehouse_details.name, + item_code: me.item_code + }, + callback: (r) => { + this.grid_row.on_grid_fields_dict + .available_qty.set_value(r.message || 0); + } + }); + + } else { + frappe.throw(__(`Please select a warehouse to get available + quantities`)); + } + // e.stopImmediatePropagation(); + } + }, + { + fieldtype:'Float', + read_only:1, + fieldname:'available_qty', + label: __('Available'), + in_list_view:1, + default: 0, + onchange: function() { + this.grid_row.on_grid_fields_dict.selected_qty.set_value('0'); + } + }, + { + fieldtype:'Float', + fieldname:'selected_qty', + label: __('Qty'), + in_list_view:1, + 'default': 0, + onchange: function(e) { + var batch_no = this.grid_row.on_grid_fields_dict.batch_no.get_value(); + var available_qty = this.grid_row.on_grid_fields_dict.available_qty.get_value(); + var selected_qty = this.grid_row.on_grid_fields_dict.selected_qty.get_value(); + + if(batch_no.length === 0 && parseInt(selected_qty)!==0) { + frappe.throw(__("Please select a batch")); + } + if(me.warehouse_details.type === 'Source Warehouse' && + parseFloat(available_qty) < parseFloat(selected_qty)) { + this.set_value('0'); + frappe.throw(__(`For transfer from source, selected quantity cannot be + greater than available quantity`)); + } else { + this.grid.refresh(); + } + } + }, + ], + in_place_edit: true, + data: this.data, + get_data: function() { + return this.data; + }, + } + ]; + }, + + get_serial_no_fields: function() { + var me = this; + return [ + {fieldtype: 'Section Break', label: __('Serial No')}, + { + fieldtype: 'Link', fieldname: 'serial_no_select', options: 'Serial No', + label: __('Select'), + get_query: function() { + return { filters: {item_code: me.item_code}}; + } + }, + {fieldtype: 'Column Break'}, + {fieldname: 'serial_no', fieldtype: 'Small Text'} + ]; + } +}); \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index ee555f4635..1504f7100a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -546,8 +546,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ } }); -erpnext.stock.select_batch_and_serial_no = (frm, d = undefined) => { - let get_warehouse = (item) => { +erpnext.stock.select_batch_and_serial_no = (frm, item) => { + let get_warehouse_type_and_name = (item) => { let value = ''; if(frm.fields_dict.from_warehouse.disp_status === "Write") { value = cstr(item.s_warehouse) || ''; @@ -564,275 +564,20 @@ erpnext.stock.select_batch_and_serial_no = (frm, d = undefined) => { } } - if(d && d.has_batch_no && !d.batch_no) { - erpnext.stock.show_batch_serial_modal(frm, d, d.item_code, d.qty, get_warehouse(d), 1); - } else if(d && d.has_serial_no && !d.serial_no && frm.doc.purpose !== 'Material Receipt') { - erpnext.stock.show_batch_serial_modal(frm, d, d.item_code, d.qty, get_warehouse(d), 0); + let opts = { + frm: frm, + item: item, + warehouse_details: get_warehouse_type_and_name(item), } + + if(item && item.has_batch_no && !item.batch_no) { + opts.has_batch = 1; + } else if(item && item.has_serial_no && !item.serial_no + && frm.doc.purpose !== 'Material Receipt') { + + opts.has_batch = 0; + } + + let serial_no_batch_selector = new erpnext.SerialNoBatchSelector(opts); + } - -erpnext.stock.show_batch_serial_modal = (frm, item, item_code, qty, warehouse_details, - 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: 'warehouse', - fieldtype:'Link', - options: 'Warehouse', - label: __(warehouse_details.type), - default: warehouse_details.name, - onchange: function(e) { - warehouse_details.name = this.get_value(); - var batches = this.layout.fields_dict.batches; - if(batches) { - batches.grid.df.data = []; - batches.grid.refresh(); - batches.grid.add_new_row(null, null, null); - } - } - }, - {fieldtype:'Column Break'}, - {fieldname: 'qty', fieldtype:'Float', label: __(has_batch ? 'Total Qty' : 'Qty'), 'default': qty}, - ]; - - if(has_batch) { - title = __("Select Batch Numbers"); - fields = fields.concat([ - {fieldtype:'Section Break', label: __('Batches')}, - {fieldname: 'batches', fieldtype: 'Table', - fields: [ - { - fieldtype:'Link', - fieldname:'batch_no', - options: 'Batch', - label: __('Select Batch'), - in_list_view:1, - get_query: function() { - return {filters: {item: item_code }}; - }, - onchange: function(e) { - if(this.get_value().length === 0) { - this.grid_row.on_grid_fields_dict - .available_qty.set_value(0); - return; - } - if(warehouse_details.name) { - frappe.call({ - method: 'erpnext.stock.doctype.batch.batch.get_batch_qty', - args: { - batch_no: this.doc.batch_no, - warehouse: warehouse_details.name, - item_code: item_code - }, - callback: (r) => { - this.grid_row.on_grid_fields_dict - .available_qty.set_value(r.message || 0); - } - }); - - } else { - frappe.throw(__("Please select a warehouse to get available quantities")); - } - // e.stopImmediatePropagation(); - } - }, - { - fieldtype:'Float', - read_only:1, - fieldname:'available_qty', - label: __('Available'), - in_list_view:1, - default: 0, - onchange: function() { - this.grid_row.on_grid_fields_dict.selected_qty.set_value('0'); - } - }, - { - fieldtype:'Float', - fieldname:'selected_qty', - label: __('Qty'), - in_list_view:1, - 'default': 0, - onchange: function(e) { - var batch_no = this.grid_row.on_grid_fields_dict.batch_no.get_value(); - var available_qty = this.grid_row.on_grid_fields_dict.available_qty.get_value(); - var selected_qty = this.grid_row.on_grid_fields_dict.selected_qty.get_value(); - - if(batch_no.length === 0 && parseInt(selected_qty)!==0) { - frappe.throw(__("Please select a batch")); - } - if(warehouse_details.type === 'Source Warehouse' && - parseFloat(available_qty) < parseFloat(selected_qty)) { - this.set_value('0'); - frappe.throw(__(`For transfer from source, selected quantity cannot be - greater than available quantity`)); - } else { - this.grid.refresh(); - } - } - }, - ], - in_place_edit: true, - data: data, - get_data: function() { - return this.data; - }, - } - ]); - - } 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() { - return { filters: {item_code: item_code}}; - } - }, - {fieldtype: 'Column Break'}, - {fieldname: 'serial_no', fieldtype: 'Small Text'} - ]); - } - - let dialog = new frappe.ui.Dialog({ - title: title, - fields: fields - }); - - erpnext.stock.bind_batch_serial_dialog_qty(dialog, warehouse_details); - - let map_item_values = (item, values, attribute, qty_field, warehouse) => { - item[attribute] = values[attribute]; - if(warehouse_details.type === 'Source Warehouse') { - item.s_warehouse = values.warehouse || warehouse; - } else { - item.t_warehouse = values.warehouse || warehouse; - } - item.qty = values[qty_field]; - } - - let validate_batch_dialog = (values) => { - if(values.batches.length === 0 || !values.batches) { - frappe.throw(__("Please select batches for batched item " + values.item_code)); - return false; - } - values.batches.map((batch, i) => { - if(!batch.selected_qty || batch.selected_qty === 0 ) { - frappe.throw(__("Please select quantity on row " + (i+1))); - return false; - } - }); - return true; - } - - let set_batched_items = () => { - let values = dialog.get_values(); - if(!validate_batch_dialog(values)) { - return; - } - - values.batches.map((batch, i) => { - if(i === 0) { - map_item_values(item, batch, 'batch_no', - 'selected_qty', values.warehouse); - } else { - let row = frm.add_child("items"); - row.item_code = item.item_code; - map_item_values(row, batch, 'batch_no', - 'selected_qty', values.warehouse); - } - }); - } - - let validate_serial_no_dialog = (values) => { - let serial_nos = values.serial_no || ''; - if (!serial_nos || !serial_nos.replace(/\s/g, '').length) { - frappe.throw(__("Please enter serial numbers for serialized item " + values.item_code)); - return false; - } - return true; - } - - let set_serialized_items = () => { - let values = dialog.get_values(); - if (!validate_serial_no_dialog(values)) { - return; - } - map_item_values(item, values, 'serial_no', 'qty'); - } - - dialog.set_primary_action(__('Get Items'), function() { - if(!dialog.get_values().warehouse) { - frappe.throw(__("Please select a warehouse")); - } - has_batch ? set_batched_items() : set_serialized_items(); - refresh_field("items"); - dialog.hide(); - }) - dialog.show(); -} - -erpnext.stock.bind_batch_serial_dialog_qty = (dialog, warehouse_details) => { - let serial_no_link = dialog.fields_dict.serial_no_select; - let serial_no_list_field = dialog.fields_dict.serial_no; - let batches_field = dialog.fields_dict.batches; - - let qty_field = dialog.fields_dict.qty; - let item_code = dialog.get_value('item_code'); - - let update_quantity = (batch) => { - if(batch) { - let total_qty = 0; - batches_field.grid.wrapper.find('input[data-fieldname="selected_qty"]').each(function() { - total_qty += Number($(this).val()); - }); - qty_field.set_input(total_qty); - } else { - let serial_numbers = serial_no_list_field.get_value().replace(/\n/g, ' ').match(/\S+/g) || []; - qty_field.set_input(serial_numbers.length); - } - } - - if(serial_no_link) { - let serial_list = []; - serial_no_link.$input.on('awesomplete-selectcomplete', function() { - if(serial_no_link.get_value().length > 0) { - let new_no = serial_no_link.get_value(); - let list_value = serial_no_list_field.get_value(); - let new_line = '\n'; - if(!serial_no_list_field.get_value()) { - new_line = ''; - } else { - serial_list = list_value.replace(/\s+/g, ' ').split(' '); - } - if(!serial_list.includes(new_no)) { - serial_no_link.set_new_description(''); - serial_no_list_field.set_value(list_value + new_line + new_no); - update_quantity(0); - } else { - serial_no_link.set_new_description(new_no + ' is already selected.'); - } - } - serial_no_link.set_input(''); - serial_no_link.$input.blur(); - }); - - serial_no_list_field.$input.on('input', function() { - serial_list = serial_no_list_field.get_value().replace(/\s+/g, ' ').split(' '); - update_quantity(0); - }); - } - - if(batches_field) { - batches_field.grid.wrapper.on('change', function() { - update_quantity(1); - }); - } -} \ No newline at end of file