Merge pull request #9372 from pratu16x7/serial-batch-entry

Serial no/batch entry dialog
This commit is contained in:
Makarand Bauskar 2017-06-27 16:12:30 +05:30 committed by GitHub
commit 90a3e60de7
16 changed files with 4037 additions and 3563 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

View File

@ -28,6 +28,9 @@ In ERPNext, you can select different type of Warehouses to stock your different
These numbers help to track individual units or batches of Items which you sell. It also tracks warranty and returns. In case any individual Item is recalled by the supplier the number system helps to track individual Item. The numbering system also manages expiry dates. Please note that if you sell your items in thousands, and if the items are very small like pens or erasers, you need not serialize them. In ERPNext, you will have to mention the serial number in some accounting entries. To create serial numbers you will have to manually create all the numbers in your entries. If your product is not a big consumer durable Item, if it has no warranty and has no chances of being recalled, avoid giving serial numbers. These numbers help to track individual units or batches of Items which you sell. It also tracks warranty and returns. In case any individual Item is recalled by the supplier the number system helps to track individual Item. The numbering system also manages expiry dates. Please note that if you sell your items in thousands, and if the items are very small like pens or erasers, you need not serialize them. In ERPNext, you will have to mention the serial number in some accounting entries. To create serial numbers you will have to manually create all the numbers in your entries. If your product is not a big consumer durable Item, if it has no warranty and has no chances of being recalled, avoid giving serial numbers.
> Tip: While entering an item code in an items table, if the table requires inventory details, then depending on whether the entered item is batched or serialized, you can enter serial or batch numbers right away in a pop-up dialog.
<img alt="Serial No modal" class="screenshot" src="{{docs_base_url}}/assets/img/stock/serial_no_modal.gif"><img alt="Batch No modal" class="screenshot" src="{{docs_base_url}}/assets/img/stock/batch_no_modal.png">
> Important: Once you mark an item as serialized or batched or neither, you cannot change it after you have made any stock entry. > Important: Once you mark an item as serialized or batched or neither, you cannot change it after you have made any stock entry.
* [Discussion on Serialized Inventory]({{docs_base_url}}/user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item.html) * [Discussion on Serialized Inventory]({{docs_base_url}}/user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item.html)

View File

@ -265,6 +265,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
item_code: function(doc, cdt, cdn, from_barcode) { item_code: function(doc, cdt, cdn, from_barcode) {
var me = this; var me = this;
var item = frappe.get_doc(cdt, cdn); var item = frappe.get_doc(cdt, cdn);
var update_stock = 0, show_batch_dialog = 0;
if(['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
update_stock = cint(me.frm.doc.update_stock);
show_batch_dialog = update_stock;
} else if((this.frm.doc.doctype === 'Purchase Receipt' && me.frm.doc.is_return) ||
this.frm.doc.doctype === 'Delivery Note') {
show_batch_dialog = 1;
}
// clear barcode if setting item (else barcode will take priority) // clear barcode if setting item (else barcode will take priority)
if(!from_barcode) { if(!from_barcode) {
@ -272,7 +282,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
if(item.item_code || item.barcode || item.serial_no) { if(item.item_code || item.barcode || item.serial_no) {
if(!this.validate_company_and_party()) { if(!this.validate_company_and_party()) {
cur_frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove(); this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();
} else { } else {
return this.frm.call({ return this.frm.call({
method: "erpnext.stock.get_item_details.get_item_details", method: "erpnext.stock.get_item_details.get_item_details",
@ -286,7 +296,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
customer: me.frm.doc.customer, customer: me.frm.doc.customer,
supplier: me.frm.doc.supplier, supplier: me.frm.doc.supplier,
currency: me.frm.doc.currency, currency: me.frm.doc.currency,
update_stock: in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0, update_stock: update_stock,
conversion_rate: me.frm.doc.conversion_rate, conversion_rate: me.frm.doc.conversion_rate,
price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list, price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list,
price_list_currency: me.frm.doc.price_list_currency, price_list_currency: me.frm.doc.price_list_currency,
@ -310,6 +320,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
if(!r.exc) { if(!r.exc) {
me.frm.script_manager.trigger("price_list_rate", cdt, cdn); me.frm.script_manager.trigger("price_list_rate", cdt, cdn);
me.toggle_conversion_factor(item); me.toggle_conversion_factor(item);
if(show_batch_dialog) {
var d = locals[cdt][cdn];
$.each(r.message, function(k, v) {
if(!d[k]) d[k] = v;
});
erpnext.show_serial_batch_selector(me.frm, d);
}
} }
} }
}); });
@ -1166,3 +1183,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
}, },
}); });
erpnext.show_serial_batch_selector = function(frm, d) {
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
new erpnext.SerialNoBatchSelector({
frm: frm,
item: d,
warehouse_details: {
type: "Warehouse",
name: d.warehouse
},
});
});
}

View File

@ -0,0 +1,327 @@
erpnext.SerialNoBatchSelector = Class.extend({
init: function(opts) {
$.extend(this, opts);
// frm, item, warehouse_details, has_batch, oldest
let d = this.item;
// Don't show dialog if batch no or serial no already set
if(d && d.has_batch_no && !d.batch_no) {
this.has_batch = 1;
this.setup();
} else if(d && d.has_serial_no && !d.serial_no) {
this.has_batch = 0;
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',
read_only: 1,
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()) {
me.set_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() {
var me = this;
if(this.has_batch) {
this.values.batches.map((batch, i) => {
let item_code_field = {};
let row = (i !== 0) ? this.frm.add_child("items", this.item) : this.item;
this.map_row_values(row, batch, 'batch_no',
'selected_qty', this.values.warehouse);
});
} else {
this.map_row_values(this.item, this.values, 'serial_no', 'qty');
}
refresh_field("items");
},
map_row_values: function(row, values, number, qty_field, warehouse) {
row.qty = values[qty_field];
row[number] = values[number];
if(this.warehouse_details.type === 'Source Warehouse') {
row.s_warehouse = values.warehouse || warehouse;
} else if(this.warehouse_details.type === 'Target Warehouse') {
row.t_warehouse = values.warehouse || warehouse;
} else {
row.warehouse = values.warehouse || warehouse;
}
},
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 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) {
let val = this.get_value();
if(val.length === 0) {
this.grid_row.on_grid_fields_dict
.available_qty.set_value(0);
return;
}
let selected_batches = this.grid.grid_rows.map((row) => {
if(row === this.grid_row) {
return "";
}
return row.on_grid_fields_dict.batch_no.get_value();
});
if(selected_batches.includes(val)) {
this.set_value("");
frappe.throw(__(`Batch ${val} already selected.`));
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 {
this.set_value("");
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'}
];
}
});

View File

@ -14,6 +14,7 @@
"engine": "InnoDB", "engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -39,12 +40,13 @@
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -76,6 +78,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -105,6 +108,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -136,6 +140,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -164,6 +169,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -194,6 +200,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -223,6 +230,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -253,6 +261,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -281,6 +290,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -311,6 +321,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -341,6 +352,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -369,6 +381,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -412,7 +425,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 5, "max_attachments": 5,
"modified": "2017-04-20 03:22:19.888058", "modified": "2017-05-21 21:00:11.096038",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Batch", "name": "Batch",

View File

@ -65,6 +65,14 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
group by batch_no''', (item_code, warehouse), as_dict=1) group by batch_no''', (item_code, warehouse), as_dict=1)
return out return out
@frappe.whitelist()
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)
batches_dates = [[batch, frappe.get_value('Batch', batch.batch_no, 'expiry_date')] for batch in batches]
batches_dates.sort(key=lambda tup: tup[1])
return batches_dates
@frappe.whitelist() @frappe.whitelist()
def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None): def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None):
'''Split the batch into a new batch''' '''Split the batch into a new batch'''

View File

@ -9,6 +9,7 @@ import unittest
from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError
class TestBatch(unittest.TestCase): class TestBatch(unittest.TestCase):
def test_item_has_batch_enabled(self): def test_item_has_batch_enabled(self):
self.assertRaises(ValidationError, frappe.get_doc({ self.assertRaises(ValidationError, frappe.get_doc({
"doctype": "Batch", "doctype": "Batch",
@ -16,14 +17,15 @@ class TestBatch(unittest.TestCase):
"item": "_Test Item" "item": "_Test Item"
}).save) }).save)
def make_batch_item(self): @classmethod
def make_batch_item(cls, item_name):
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
if not frappe.db.exists('ITEM-BATCH-1'): if not frappe.db.exists(item_name):
make_item('ITEM-BATCH-1', dict(has_batch_no = 1, create_new_batch = 1)) make_item(item_name, dict(has_batch_no = 1, create_new_batch = 1))
def test_purchase_receipt(self, batch_qty = 100): def test_purchase_receipt(self, batch_qty = 100):
'''Test automated batch creation from Purchase Receipt''' '''Test automated batch creation from Purchase Receipt'''
self.make_batch_item() self.make_batch_item('ITEM-BATCH-1')
receipt = frappe.get_doc(dict( receipt = frappe.get_doc(dict(
doctype = 'Purchase Receipt', doctype = 'Purchase Receipt',
@ -47,7 +49,7 @@ class TestBatch(unittest.TestCase):
def test_stock_entry_incoming(self): def test_stock_entry_incoming(self):
'''Test batch creation via Stock Entry (Production Order)''' '''Test batch creation via Stock Entry (Production Order)'''
self.make_batch_item() self.make_batch_item('ITEM-BATCH-1')
stock_entry = frappe.get_doc(dict( stock_entry = frappe.get_doc(dict(
doctype = 'Stock Entry', doctype = 'Stock Entry',
@ -150,4 +152,43 @@ class TestBatch(unittest.TestCase):
self.assertEquals(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 78) self.assertEquals(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 78)
self.assertEquals(get_batch_qty(new_batch, receipt.items[0].warehouse), 22) self.assertEquals(get_batch_qty(new_batch, receipt.items[0].warehouse), 22)
def test_get_batch_qty(self):
'''Test getting batch quantities by batch_numbers, item_code or warehouse'''
self.make_batch_item('ITEM-BATCH-2')
self.make_new_batch_and_entry('ITEM-BATCH-2', 'batch a', '_Test Warehouse - _TC')
self.make_new_batch_and_entry('ITEM-BATCH-2', 'batch b', '_Test Warehouse - _TC')
self.assertEquals(get_batch_qty(item_code = 'ITEM-BATCH-2', warehouse = '_Test Warehouse - _TC'),
[{'batch_no': u'batch a', 'qty': 90.0}, {'batch_no': u'batch b', 'qty': 90.0}])
self.assertEquals(get_batch_qty('batch a', '_Test Warehouse - _TC'), 90)
@classmethod
def make_new_batch_and_entry(cls, item_name, batch_name, warehouse):
'''Make a new stock entry for given target warehouse and batch name of item'''
if not frappe.db.exists("Batch", batch_name):
batch = frappe.get_doc(dict(
doctype = 'Batch',
item = item_name,
batch_id = batch_name
)).insert(ignore_permissions=True)
batch.submit()
stock_entry = frappe.get_doc(dict(
doctype = 'Stock Entry',
purpose = 'Material Receipt',
company = '_Test Company',
items = [
dict(
item_code = item_name,
qty = 90,
t_warehouse = warehouse,
cost_center = 'Main - _TC',
rate = 10,
batch_no = batch_name,
allow_zero_valuation_rate = 1
)
]
)).insert()
stock_entry.submit()

View File

@ -1457,7 +1457,7 @@
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Batch No", "label": "Batch No",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 1,
"oldfieldname": "batch_no", "oldfieldname": "batch_no",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Batch", "options": "Batch",
@ -1597,7 +1597,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": ":Company", "default": ":Company",
"depends_on": "", "depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)",
"fieldname": "cost_center", "fieldname": "cost_center",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,

View File

@ -41,6 +41,8 @@ frappe.ui.form.on('Stock Entry', {
} }
} }
}); });
}, },
refresh: function(frm) { refresh: function(frm) {
if(!frm.doc.docstatus) { if(!frm.doc.docstatus) {
@ -174,6 +176,7 @@ frappe.ui.form.on('Stock Entry Detail', {
d[k] = v; d[k] = v;
}); });
refresh_field("items"); refresh_field("items");
erpnext.stock.select_batch_and_serial_no(frm, d);
} }
} }
}); });
@ -542,3 +545,36 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
erpnext.utils.get_party_details(this.frm, null, null, null); erpnext.utils.get_party_details(this.frm, null, null, null);
} }
}); });
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) || '';
return {
type: 'Source Warehouse',
name: value
};
} else {
value = cstr(item.t_warehouse) || '';
return {
type: 'Target Warehouse',
name: value
};
}
}
if(item && item.has_serial_no
&& frm.doc.purpose === 'Material Receipt') {
return;
}
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
new erpnext.SerialNoBatchSelector({
frm: frm,
item: item,
warehouse_details: get_warehouse_type_and_name(item),
});
});
}

View File

@ -52,7 +52,7 @@ class StockEntry(StockController):
if self._action == 'submit': if self._action == 'submit':
self.make_batches('t_warehouse') self.make_batches('t_warehouse')
else: else:
set_batch_nos(self, 's_warehouse', True) set_batch_nos(self, 's_warehouse')
self.set_actual_qty() self.set_actual_qty()
self.calculate_rate_and_amount(update_finished_item_rate=False) self.calculate_rate_and_amount(update_finished_item_rate=False)

View File

@ -6,11 +6,12 @@ frappe.listview_settings['Stock Entry'] = {
var html = ""; var html = "";
if(doc.from_warehouse) { if(doc.from_warehouse) {
html += '<span class="filterable h6"\ html += '<span class="filterable h6"\
data-filter="from_warehouse,=,'+doc.from_warehouse+'">'+doc.from_warehouse+' </span>'; data-filter="from_warehouse,=,'+doc.from_warehouse+'">'
} +doc.from_warehouse+' </span>';
if(doc.from_warehouse || doc.to_warehouse) {
html += '<i class="octicon octfa fa-arrow-right text-muted"></i> ';
} }
// if(doc.from_warehouse || doc.to_warehouse) {
// html += '<i class="fa fa-arrow-right text-muted"></i> ';
// }
if(doc.to_warehouse) { if(doc.to_warehouse) {
html += '<span class="filterable h6"\ html += '<span class="filterable h6"\
data-filter="to_warehouse,=,'+doc.to_warehouse+'">'+doc.to_warehouse+'</span>'; data-filter="to_warehouse,=,'+doc.to_warehouse+'">'+doc.to_warehouse+'</span>';

View File

@ -822,7 +822,7 @@
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Serial No / Batch", "label": "Serial No / Batch",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 1,
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@ -910,7 +910,7 @@
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Batch No", "label": "Batch No",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 1,
"oldfieldname": "batch_no", "oldfieldname": "batch_no",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Batch", "options": "Batch",
@ -960,7 +960,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "", "depends_on": "eval:cint(frappe.sys_defaults.auto_accounting_for_stock)",
"fieldname": "expense_account", "fieldname": "expense_account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -1020,7 +1020,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": ":Company", "default": ":Company",
"depends_on": "", "depends_on": "eval:cint(frappe.sys_defaults.auto_accounting_for_stock)",
"fieldname": "cost_center", "fieldname": "cost_center",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,

View File

@ -96,6 +96,16 @@ def get_item_details(args):
return out return out
# print(frappe._dict({
# 'has_serial_no' : out.has_serial_no,
# 'has_batch_no' : out.has_batch_no
# }))
# return frappe._dict({
# 'has_serial_no' : out.has_serial_no,
# 'has_batch_no' : out.has_batch_no
# })
def process_args(args): def process_args(args):
if isinstance(args, basestring): if isinstance(args, basestring):
args = json.loads(args) args = json.loads(args)

View File

@ -448,10 +448,15 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
# syste does not found any SLE, then take valuation rate from Item # syste does not found any SLE, then take valuation rate from Item
valuation_rate = frappe.db.get_value("Item", item_code, "valuation_rate") valuation_rate = frappe.db.get_value("Item", item_code, "valuation_rate")
if not valuation_rate: if not valuation_rate:
# try in price list # try Item Standard rate
valuation_rate = frappe.db.get_value('Item Price', valuation_rate = frappe.db.get_value("Item", item_code, "standard_rate")
dict(item_code=item_code, buying=1, currency=currency), 'price_list_rate')
if not valuation_rate:
# try in price list
valuation_rate = frappe.db.get_value('Item Price',
dict(item_code=item_code, buying=1, currency=currency),
'price_list_rate')
if not allow_zero_rate and not valuation_rate \ if not allow_zero_rate and not valuation_rate \
and cint(erpnext.is_perpetual_inventory_enabled(company)): and cint(erpnext.is_perpetual_inventory_enabled(company)):