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.
> 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.
* [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) {
var me = this;
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)
if(!from_barcode) {
@ -272,7 +282,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}
if(item.item_code || item.barcode || item.serial_no) {
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 {
return this.frm.call({
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,
supplier: me.frm.doc.supplier,
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,
price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list,
price_list_currency: me.frm.doc.price_list_currency,
@ -310,6 +320,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
if(!r.exc) {
me.frm.script_manager.trigger("price_list_rate", cdt, cdn);
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

@ -1,450 +1,463 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2013-03-05 14:50:38",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB",
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2013-03-05 14:50:38",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.__islocal",
"fieldname": "batch_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Batch ID",
"length": 0,
"no_copy": 1,
"oldfieldname": "batch_id",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.__islocal",
"fieldname": "batch_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Batch ID",
"length": 0,
"no_copy": 1,
"oldfieldname": "batch_id",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Item",
"length": 0,
"no_copy": 0,
"oldfieldname": "item",
"oldfieldtype": "Link",
"options": "Item",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Item",
"length": 0,
"no_copy": 0,
"oldfieldname": "item",
"oldfieldtype": "Link",
"options": "Item",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "image",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "image",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.parent_batch",
"fieldname": "parent_batch",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Parent Batch",
"length": 0,
"no_copy": 0,
"options": "Batch",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.parent_batch",
"fieldname": "parent_batch",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Parent Batch",
"length": 0,
"no_copy": 0,
"options": "Batch",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "expiry_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Expiry Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "expiry_date",
"oldfieldtype": "Date",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "expiry_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Expiry Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "expiry_date",
"oldfieldtype": "Date",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "source",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Source",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "source",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Source",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "supplier",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supplier",
"length": 0,
"no_copy": 0,
"options": "Supplier",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "supplier",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supplier",
"length": 0,
"no_copy": 0,
"options": "Supplier",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Source Document Type",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Source Document Type",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Source Document Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Source Document Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Batch Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "description",
"oldfieldtype": "Small Text",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Batch Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "description",
"oldfieldtype": "Small Text",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "300px"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-archive",
"idx": 1,
"image_field": "image",
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 5,
"modified": "2017-04-20 03:22:19.888058",
"modified_by": "Administrator",
"module": "Stock",
"name": "Batch",
"owner": "harshada@webnotestech.com",
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-archive",
"idx": 1,
"image_field": "image",
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 5,
"modified": "2017-05-21 21:00:11.096038",
"modified_by": "Administrator",
"module": "Stock",
"name": "Batch",
"owner": "harshada@webnotestech.com",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Item Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Item Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "DESC",
"title_field": "item",
"track_changes": 0,
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "DESC",
"title_field": "item",
"track_changes": 0,
"track_seen": 0
}

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)
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()
def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None):
'''Split the batch into a new batch'''
@ -107,7 +115,7 @@ def set_batch_nos(doc, warehouse_field, throw = False):
def get_batch_no(item_code, warehouse, qty, throw=False):
'''get the smallest batch with for the given item_code, warehouse and qty'''
batch_no = None
batches = get_batch_qty(item_code = item_code, warehouse = warehouse)
if batches:
@ -117,9 +125,9 @@ def get_batch_no(item_code, warehouse, qty, throw=False):
batch_no = b.batch_no
# found!
break
if not batch_no:
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
return batch_no
return batch_no

View File

@ -9,6 +9,7 @@ import unittest
from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError
class TestBatch(unittest.TestCase):
def test_item_has_batch_enabled(self):
self.assertRaises(ValidationError, frappe.get_doc({
"doctype": "Batch",
@ -16,14 +17,15 @@ class TestBatch(unittest.TestCase):
"item": "_Test Item"
}).save)
def make_batch_item(self):
@classmethod
def make_batch_item(cls, item_name):
from erpnext.stock.doctype.item.test_item import make_item
if not frappe.db.exists('ITEM-BATCH-1'):
make_item('ITEM-BATCH-1', dict(has_batch_no = 1, create_new_batch = 1))
if not frappe.db.exists(item_name):
make_item(item_name, dict(has_batch_no = 1, create_new_batch = 1))
def test_purchase_receipt(self, batch_qty = 100):
'''Test automated batch creation from Purchase Receipt'''
self.make_batch_item()
self.make_batch_item('ITEM-BATCH-1')
receipt = frappe.get_doc(dict(
doctype = 'Purchase Receipt',
@ -47,7 +49,7 @@ class TestBatch(unittest.TestCase):
def test_stock_entry_incoming(self):
'''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(
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(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

@ -38,7 +38,7 @@ frappe.ui.form.on("Delivery Note", {
}
});
frm.set_query('expense_account', 'items', function(doc, cdt, cdn) {
if (erpnext.is_perpetual_inventory_enabled(doc.company)) {
return {
@ -61,7 +61,7 @@ frappe.ui.form.on("Delivery Note", {
}
}
});
$.extend(frm.cscript, new erpnext.stock.DeliveryNoteController({frm: frm}));
},
@ -235,7 +235,7 @@ frappe.ui.form.on('Delivery Note', {
company: function(frm) {
frm.trigger("unhide_account_head");
},
unhide_account_head: function(frm) {
// unhide expense_account and cost_center if perpetual inventory is enabled in the company
var aii_enabled = erpnext.is_perpetual_inventory_enabled(frm.doc.company)

View File

@ -41,6 +41,8 @@ frappe.ui.form.on('Stock Entry', {
}
}
});
},
refresh: function(frm) {
if(!frm.doc.docstatus) {
@ -174,6 +176,7 @@ frappe.ui.form.on('Stock Entry Detail', {
d[k] = v;
});
refresh_field("items");
erpnext.stock.select_batch_and_serial_no(frm, d);
}
}
});
@ -524,7 +527,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
} else {
doc.customer = doc.customer_name = doc.customer_address =
doc.delivery_note_no = doc.sales_invoice_no = doc.supplier =
doc.supplier_name = doc.supplier_address = doc.purchase_receipt_no =
doc.supplier_name = doc.supplier_address = doc.purchase_receipt_no =
doc.address_display = null;
}
if(doc.purpose == "Material Receipt") {
@ -541,4 +544,37 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
supplier: function(doc) {
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':
self.make_batches('t_warehouse')
else:
set_batch_nos(self, 's_warehouse', True)
set_batch_nos(self, 's_warehouse')
self.set_actual_qty()
self.calculate_rate_and_amount(update_finished_item_rate=False)

View File

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

View File

@ -74,10 +74,10 @@ def get_item_details(args):
out.update(get_pricing_rule_for_item(args))
if (args.get("doctype") == "Delivery Note" or
if (args.get("doctype") == "Delivery Note" or
(args.get("doctype") == "Sales Invoice" and args.get('update_stock'))) \
and out.warehouse and out.stock_qty > 0:
if out.has_serial_no:
out.serial_no = get_serial_no(out)
@ -96,6 +96,16 @@ def get_item_details(args):
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):
if isinstance(args, basestring):
args = json.loads(args)

View File

@ -259,7 +259,7 @@ class update_entries_after(object):
if not self.valuation_rate and actual_qty > 0:
self.valuation_rate = sle.incoming_rate
# Get valuation rate from previous SLE or Item master, if item does not have the
# Get valuation rate from previous SLE or Item master, if item does not have the
# allow zero valuration rate flag set
if not self.valuation_rate and sle.voucher_detail_no:
allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
@ -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
valuation_rate = frappe.db.get_value("Item", item_code, "valuation_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 valuation_rate:
# try Item Standard rate
valuation_rate = frappe.db.get_value("Item", item_code, "standard_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 \
and cint(erpnext.is_perpetual_inventory_enabled(company)):