batch item distribution by oldest, serial no check
diff --git a/erpnext/stock/doctype/batch/batch.json b/erpnext/stock/doctype/batch/batch.json
index b4a1405..5050541 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 992d8fc..2ee8c62 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -66,13 +66,14 @@
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 @@
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 53fac4d..8f82b6a 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -76,7 +76,7 @@
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.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 @@
});
}
- 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();
+}