feat: serial and batch bundle for Stock Reconciliation
This commit is contained in:
parent
5ddd55a8ae
commit
9c097e85f8
@ -1,623 +1,3 @@
|
|||||||
|
|
||||||
erpnext.SerialNoBatchSelector = class SerialNoBatchSelector {
|
|
||||||
constructor(opts, show_dialog) {
|
|
||||||
$.extend(this, opts);
|
|
||||||
this.show_dialog = show_dialog;
|
|
||||||
// frm, item, warehouse_details, has_batch, oldest
|
|
||||||
let d = this.item;
|
|
||||||
this.has_batch = 0; this.has_serial_no = 0;
|
|
||||||
|
|
||||||
if (d && d.has_batch_no && (!d.batch_no || this.show_dialog)) this.has_batch = 1;
|
|
||||||
// !(this.show_dialog == false) ensures that show_dialog is implictly true, even when undefined
|
|
||||||
if(d && d.has_serial_no && !(this.show_dialog == false)) this.has_serial_no = 1;
|
|
||||||
|
|
||||||
this.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
this.item_code = this.item.item_code;
|
|
||||||
this.qty = this.item.qty;
|
|
||||||
this.make_dialog();
|
|
||||||
this.on_close_dialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
make_dialog() {
|
|
||||||
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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'warehouse',
|
|
||||||
fieldtype:'Link',
|
|
||||||
options: 'Warehouse',
|
|
||||||
reqd: me.has_batch && !me.has_serial_no ? 0 : 1,
|
|
||||||
label: __(me.warehouse_details.type),
|
|
||||||
default: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
|
|
||||||
onchange: function(e) {
|
|
||||||
me.warehouse_details.name = this.get_value();
|
|
||||||
|
|
||||||
if(me.has_batch && !me.has_serial_no) {
|
|
||||||
fields = fields.concat(me.get_batch_fields());
|
|
||||||
} else {
|
|
||||||
fields = fields.concat(me.get_serial_no_fields());
|
|
||||||
}
|
|
||||||
|
|
||||||
var batches = this.layout.fields_dict.batches;
|
|
||||||
if(batches) {
|
|
||||||
batches.grid.df.data = [];
|
|
||||||
batches.grid.refresh();
|
|
||||||
batches.grid.add_new_row(null, null, null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get_query: function() {
|
|
||||||
return {
|
|
||||||
query: "erpnext.controllers.queries.warehouse_query",
|
|
||||||
filters: [
|
|
||||||
["Bin", "item_code", "=", me.item_code],
|
|
||||||
["Warehouse", "is_group", "=", 0],
|
|
||||||
["Warehouse", "company", "=", me.frm.doc.company]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{fieldtype:'Column Break'},
|
|
||||||
{
|
|
||||||
fieldname: 'qty',
|
|
||||||
fieldtype:'Float',
|
|
||||||
read_only: me.has_batch && !me.has_serial_no,
|
|
||||||
label: __(me.has_batch && !me.has_serial_no ? 'Selected Qty' : 'Qty'),
|
|
||||||
default: flt(me.item.stock_qty) || flt(me.item.transfer_qty),
|
|
||||||
},
|
|
||||||
...get_pending_qty_fields(me),
|
|
||||||
{
|
|
||||||
fieldname: 'uom',
|
|
||||||
read_only: 1,
|
|
||||||
fieldtype: 'Link',
|
|
||||||
options: 'UOM',
|
|
||||||
label: __('UOM'),
|
|
||||||
default: me.item.uom
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'auto_fetch_button',
|
|
||||||
fieldtype:'Button',
|
|
||||||
hidden: me.has_batch && !me.has_serial_no,
|
|
||||||
label: __('Auto Fetch'),
|
|
||||||
description: __('Fetch Serial Numbers based on FIFO'),
|
|
||||||
click: () => {
|
|
||||||
let qty = this.dialog.fields_dict.qty.get_value();
|
|
||||||
let already_selected_serial_nos = get_selected_serial_nos(me);
|
|
||||||
let numbers = frappe.call({
|
|
||||||
method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number",
|
|
||||||
args: {
|
|
||||||
qty: qty,
|
|
||||||
item_code: me.item_code,
|
|
||||||
warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
|
|
||||||
batch_nos: me.item.batch_no || null,
|
|
||||||
posting_date: me.frm.doc.posting_date || me.frm.doc.transaction_date,
|
|
||||||
exclude_sr_nos: already_selected_serial_nos
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
numbers.then((data) => {
|
|
||||||
let auto_fetched_serial_numbers = data.message;
|
|
||||||
let records_length = auto_fetched_serial_numbers.length;
|
|
||||||
if (!records_length) {
|
|
||||||
const warehouse = me.dialog.fields_dict.warehouse.get_value().bold();
|
|
||||||
frappe.msgprint(
|
|
||||||
__('Serial numbers unavailable for Item {0} under warehouse {1}. Please try changing warehouse.', [me.item.item_code.bold(), warehouse])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (records_length < qty) {
|
|
||||||
frappe.msgprint(__('Fetched only {0} available serial numbers.', [records_length]));
|
|
||||||
}
|
|
||||||
let serial_no_list_field = this.dialog.fields_dict.serial_no;
|
|
||||||
numbers = auto_fetched_serial_numbers.join('\n');
|
|
||||||
serial_no_list_field.set_value(numbers);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
if (this.has_batch && !this.has_serial_no) {
|
|
||||||
title = __("Select Batch Numbers");
|
|
||||||
fields = fields.concat(this.get_batch_fields());
|
|
||||||
} else {
|
|
||||||
// if only serial no OR
|
|
||||||
// if both batch_no & serial_no then only select serial_no and auto set batches nos
|
|
||||||
title = __("Select Serial Numbers");
|
|
||||||
fields = fields.concat(this.get_serial_no_fields());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dialog = new frappe.ui.Dialog({
|
|
||||||
title: title,
|
|
||||||
fields: fields
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dialog.set_primary_action(__('Insert'), function() {
|
|
||||||
me.values = me.dialog.get_values();
|
|
||||||
if(me.validate()) {
|
|
||||||
frappe.run_serially([
|
|
||||||
() => me.update_batch_items(),
|
|
||||||
() => me.update_serial_no_item(),
|
|
||||||
() => me.update_batch_serial_no_items(),
|
|
||||||
() => {
|
|
||||||
refresh_field("items");
|
|
||||||
refresh_field("packed_items");
|
|
||||||
if (me.callback) {
|
|
||||||
return me.callback(me.item);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => me.dialog.hide()
|
|
||||||
])
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(this.show_dialog) {
|
|
||||||
let d = this.item;
|
|
||||||
if (this.item.serial_no) {
|
|
||||||
this.dialog.fields_dict.serial_no.set_value(this.item.serial_no);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.has_batch && !this.has_serial_no && d.batch_no) {
|
|
||||||
this.frm.doc.items.forEach(data => {
|
|
||||||
if(data.item_code == d.item_code) {
|
|
||||||
this.dialog.fields_dict.batches.df.data.push({
|
|
||||||
'batch_no': data.batch_no,
|
|
||||||
'actual_qty': data.actual_qty,
|
|
||||||
'selected_qty': data.qty,
|
|
||||||
'available_qty': data.actual_batch_qty
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.dialog.fields_dict.batches.grid.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.has_batch && !this.has_serial_no) {
|
|
||||||
this.update_total_qty();
|
|
||||||
this.update_pending_qtys();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
on_close_dialog() {
|
|
||||||
this.dialog.get_close_btn().on('click', () => {
|
|
||||||
this.on_close && this.on_close(this.item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
validate() {
|
|
||||||
let values = this.values;
|
|
||||||
if(!values.warehouse) {
|
|
||||||
frappe.throw(__("Please select a warehouse"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(this.has_batch && !this.has_serial_no) {
|
|
||||||
if(values.batches.length === 0 || !values.batches) {
|
|
||||||
frappe.throw(__("Please select batches for batched item {0}", [values.item_code]));
|
|
||||||
}
|
|
||||||
values.batches.map((batch, i) => {
|
|
||||||
if(!batch.selected_qty || batch.selected_qty === 0 ) {
|
|
||||||
if (!this.show_dialog) {
|
|
||||||
frappe.throw(__("Please select quantity on row {0}", [i+1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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 {0}", [values.item_code]));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update_batch_items() {
|
|
||||||
// clones an items if muliple batches are selected.
|
|
||||||
if(this.has_batch && !this.has_serial_no) {
|
|
||||||
this.values.batches.map((batch, i) => {
|
|
||||||
let batch_no = batch.batch_no;
|
|
||||||
let row = '';
|
|
||||||
|
|
||||||
if (i !== 0 && !this.batch_exists(batch_no)) {
|
|
||||||
row = this.frm.add_child("items", { ...this.item });
|
|
||||||
} else {
|
|
||||||
row = this.frm.doc.items.find(i => i.batch_no === batch_no);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!row) {
|
|
||||||
row = this.item;
|
|
||||||
}
|
|
||||||
// this ensures that qty & batch no is set
|
|
||||||
this.map_row_values(row, batch, 'batch_no',
|
|
||||||
'selected_qty', this.values.warehouse);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update_serial_no_item() {
|
|
||||||
// just updates serial no for the item
|
|
||||||
if(this.has_serial_no && !this.has_batch) {
|
|
||||||
this.map_row_values(this.item, this.values, 'serial_no', 'qty');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update_batch_serial_no_items() {
|
|
||||||
// if serial no selected is from different batches, adds new rows for each batch.
|
|
||||||
if(this.has_batch && this.has_serial_no) {
|
|
||||||
const selected_serial_nos = this.values.serial_no.split(/\n/g).filter(s => s);
|
|
||||||
|
|
||||||
return frappe.db.get_list("Serial No", {
|
|
||||||
filters: { 'name': ["in", selected_serial_nos]},
|
|
||||||
fields: ["batch_no", "name"]
|
|
||||||
}).then((data) => {
|
|
||||||
// data = [{batch_no: 'batch-1', name: "SR-001"},
|
|
||||||
// {batch_no: 'batch-2', name: "SR-003"}, {batch_no: 'batch-2', name: "SR-004"}]
|
|
||||||
const batch_serial_map = data.reduce((acc, d) => {
|
|
||||||
if (!acc[d['batch_no']]) acc[d['batch_no']] = [];
|
|
||||||
acc[d['batch_no']].push(d['name'])
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
// batch_serial_map = { "batch-1": ['SR-001'], "batch-2": ["SR-003", "SR-004"]}
|
|
||||||
Object.keys(batch_serial_map).map((batch_no, i) => {
|
|
||||||
let row = '';
|
|
||||||
const serial_no = batch_serial_map[batch_no];
|
|
||||||
if (i == 0) {
|
|
||||||
row = this.item;
|
|
||||||
this.map_row_values(row, {qty: serial_no.length, batch_no: batch_no}, 'batch_no',
|
|
||||||
'qty', this.values.warehouse);
|
|
||||||
} else if (!this.batch_exists(batch_no)) {
|
|
||||||
row = this.frm.add_child("items", { ...this.item });
|
|
||||||
row.batch_no = batch_no;
|
|
||||||
} else {
|
|
||||||
row = this.frm.doc.items.find(i => i.batch_no === batch_no);
|
|
||||||
}
|
|
||||||
const values = {
|
|
||||||
'qty': serial_no.length,
|
|
||||||
'serial_no': serial_no.join('\n')
|
|
||||||
}
|
|
||||||
this.map_row_values(row, values, 'serial_no',
|
|
||||||
'qty', this.values.warehouse);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
batch_exists(batch) {
|
|
||||||
const batches = this.frm.doc.items.map(data => data.batch_no);
|
|
||||||
return (batches && in_list(batches, batch)) ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
map_row_values(row, values, number, qty_field, warehouse) {
|
|
||||||
row.qty = values[qty_field];
|
|
||||||
row.transfer_qty = flt(values[qty_field]) * flt(row.conversion_factor);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.frm.dirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
update_total_qty() {
|
|
||||||
let qty_field = this.dialog.fields_dict.qty;
|
|
||||||
let total_qty = 0;
|
|
||||||
|
|
||||||
this.dialog.fields_dict.batches.df.data.forEach(data => {
|
|
||||||
total_qty += flt(data.selected_qty);
|
|
||||||
});
|
|
||||||
|
|
||||||
qty_field.set_input(total_qty);
|
|
||||||
}
|
|
||||||
|
|
||||||
update_pending_qtys() {
|
|
||||||
const pending_qty_field = this.dialog.fields_dict.pending_qty;
|
|
||||||
const total_selected_qty_field = this.dialog.fields_dict.total_selected_qty;
|
|
||||||
|
|
||||||
if (!pending_qty_field || !total_selected_qty_field) return;
|
|
||||||
|
|
||||||
const me = this;
|
|
||||||
const required_qty = this.dialog.fields_dict.required_qty.value;
|
|
||||||
const selected_qty = this.dialog.fields_dict.qty.value;
|
|
||||||
const total_selected_qty = selected_qty + calc_total_selected_qty(me);
|
|
||||||
const pending_qty = required_qty - total_selected_qty;
|
|
||||||
|
|
||||||
pending_qty_field.set_input(pending_qty);
|
|
||||||
total_selected_qty_field.set_input(total_selected_qty);
|
|
||||||
}
|
|
||||||
|
|
||||||
get_batch_fields() {
|
|
||||||
var me = this;
|
|
||||||
|
|
||||||
return [
|
|
||||||
{fieldtype:'Section Break', label: __('Batches')},
|
|
||||||
{fieldname: 'batches', fieldtype: 'Table', label: __('Batch Entries'),
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
'fieldtype': 'Link',
|
|
||||||
'read_only': 0,
|
|
||||||
'fieldname': 'batch_no',
|
|
||||||
'options': 'Batch',
|
|
||||||
'label': __('Select Batch'),
|
|
||||||
'in_list_view': 1,
|
|
||||||
get_query: function () {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
item_code: me.item_code,
|
|
||||||
warehouse: me.warehouse || typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : ''
|
|
||||||
},
|
|
||||||
query: 'erpnext.controllers.queries.get_batch_no'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
change: function () {
|
|
||||||
const batch_no = this.get_value();
|
|
||||||
if (!batch_no) {
|
|
||||||
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 "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (row.on_grid_fields_dict.batch_no) {
|
|
||||||
return row.on_grid_fields_dict.batch_no.get_value();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (selected_batches.includes(batch_no)) {
|
|
||||||
this.set_value("");
|
|
||||||
frappe.throw(__('Batch {0} already selected.', [batch_no]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me.warehouse_details.name) {
|
|
||||||
frappe.call({
|
|
||||||
method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
|
|
||||||
args: {
|
|
||||||
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,
|
|
||||||
change: function () {
|
|
||||||
this.grid_row.on_grid_fields_dict.selected_qty.set_value('0');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'fieldtype': 'Float',
|
|
||||||
'read_only': 0,
|
|
||||||
'fieldname': 'selected_qty',
|
|
||||||
'label': __('Qty'),
|
|
||||||
'in_list_view': 1,
|
|
||||||
'default': 0,
|
|
||||||
change: function () {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
me.update_total_qty();
|
|
||||||
me.update_pending_qtys();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
in_place_edit: true,
|
|
||||||
data: this.data,
|
|
||||||
get_data: function () {
|
|
||||||
return this.data;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
get_serial_no_fields() {
|
|
||||||
var me = this;
|
|
||||||
this.serial_list = [];
|
|
||||||
|
|
||||||
let serial_no_filters = {
|
|
||||||
item_code: me.item_code,
|
|
||||||
delivery_document_no: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.item.batch_no) {
|
|
||||||
serial_no_filters["batch_no"] = this.item.batch_no;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me.warehouse_details.name) {
|
|
||||||
serial_no_filters['warehouse'] = me.warehouse_details.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me.frm.doc.doctype === 'POS Invoice' && !this.showing_reserved_serial_nos_error) {
|
|
||||||
frappe.call({
|
|
||||||
method: "erpnext.stock.doctype.serial_no.serial_no.get_pos_reserved_serial_nos",
|
|
||||||
args: {
|
|
||||||
filters: {
|
|
||||||
item_code: me.item_code,
|
|
||||||
warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).then((data) => {
|
|
||||||
serial_no_filters['name'] = ["not in", data.message[0]]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{fieldtype: 'Section Break', label: __('Serial Numbers')},
|
|
||||||
{
|
|
||||||
fieldtype: 'Link', fieldname: 'serial_no_select', options: 'Serial No',
|
|
||||||
label: __('Select to add Serial Number.'),
|
|
||||||
get_query: function() {
|
|
||||||
return {
|
|
||||||
filters: serial_no_filters
|
|
||||||
};
|
|
||||||
},
|
|
||||||
onchange: function(e) {
|
|
||||||
if(this.in_local_change) return;
|
|
||||||
this.in_local_change = 1;
|
|
||||||
|
|
||||||
let serial_no_list_field = this.layout.fields_dict.serial_no;
|
|
||||||
let qty_field = this.layout.fields_dict.qty;
|
|
||||||
|
|
||||||
let new_number = this.get_value();
|
|
||||||
let list_value = serial_no_list_field.get_value();
|
|
||||||
let new_line = '\n';
|
|
||||||
if(!list_value) {
|
|
||||||
new_line = '';
|
|
||||||
} else {
|
|
||||||
me.serial_list = list_value.replace(/\n/g, ' ').match(/\S+/g) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!me.serial_list.includes(new_number)) {
|
|
||||||
this.set_new_description('');
|
|
||||||
serial_no_list_field.set_value(me.serial_list.join('\n') + new_line + new_number);
|
|
||||||
me.serial_list = serial_no_list_field.get_value().replace(/\n/g, ' ').match(/\S+/g) || [];
|
|
||||||
} else {
|
|
||||||
this.set_new_description(new_number + ' is already selected.');
|
|
||||||
}
|
|
||||||
|
|
||||||
qty_field.set_input(me.serial_list.length);
|
|
||||||
this.$input.val("");
|
|
||||||
this.in_local_change = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{fieldtype: 'Column Break'},
|
|
||||||
{
|
|
||||||
fieldname: 'serial_no',
|
|
||||||
fieldtype: 'Small Text',
|
|
||||||
label: __(me.has_batch && !me.has_serial_no ? 'Selected Batch Numbers' : 'Selected Serial Numbers'),
|
|
||||||
onchange: function() {
|
|
||||||
me.serial_list = this.get_value()
|
|
||||||
.replace(/\n/g, ' ').match(/\S+/g) || [];
|
|
||||||
this.layout.fields_dict.qty.set_input(me.serial_list.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function get_pending_qty_fields(me) {
|
|
||||||
if (!check_can_calculate_pending_qty(me)) return [];
|
|
||||||
const { frm: { doc: { fg_completed_qty }}, item: { item_code, stock_qty }} = me;
|
|
||||||
const { qty_consumed_per_unit } = erpnext.stock.bom.items[item_code];
|
|
||||||
|
|
||||||
const total_selected_qty = calc_total_selected_qty(me);
|
|
||||||
const required_qty = flt(fg_completed_qty) * flt(qty_consumed_per_unit);
|
|
||||||
const pending_qty = required_qty - (flt(stock_qty) + total_selected_qty);
|
|
||||||
|
|
||||||
const pending_qty_fields = [
|
|
||||||
{ fieldtype: 'Section Break', label: __('Pending Quantity') },
|
|
||||||
{
|
|
||||||
fieldname: 'required_qty',
|
|
||||||
read_only: 1,
|
|
||||||
fieldtype: 'Float',
|
|
||||||
label: __('Required Qty'),
|
|
||||||
default: required_qty
|
|
||||||
},
|
|
||||||
{ fieldtype: 'Column Break' },
|
|
||||||
{
|
|
||||||
fieldname: 'total_selected_qty',
|
|
||||||
read_only: 1,
|
|
||||||
fieldtype: 'Float',
|
|
||||||
label: __('Total Selected Qty'),
|
|
||||||
default: total_selected_qty
|
|
||||||
},
|
|
||||||
{ fieldtype: 'Column Break' },
|
|
||||||
{
|
|
||||||
fieldname: 'pending_qty',
|
|
||||||
read_only: 1,
|
|
||||||
fieldtype: 'Float',
|
|
||||||
label: __('Pending Qty'),
|
|
||||||
default: pending_qty
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return pending_qty_fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all items with same item code except row for which selector is open.
|
|
||||||
function get_rows_with_same_item_code(me) {
|
|
||||||
const { frm: { doc: { items }}, item: { name, item_code }} = me;
|
|
||||||
return items.filter(item => (item.name !== name) && (item.item_code === item_code))
|
|
||||||
}
|
|
||||||
|
|
||||||
function calc_total_selected_qty(me) {
|
|
||||||
const totalSelectedQty = get_rows_with_same_item_code(me)
|
|
||||||
.map(item => flt(item.qty))
|
|
||||||
.reduce((i, j) => i + j, 0);
|
|
||||||
return totalSelectedQty;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_selected_serial_nos(me) {
|
|
||||||
const selected_serial_nos = get_rows_with_same_item_code(me)
|
|
||||||
.map(item => item.serial_no)
|
|
||||||
.filter(serial => serial)
|
|
||||||
.map(sr_no_string => sr_no_string.split('\n'))
|
|
||||||
.reduce((acc, arr) => acc.concat(arr), [])
|
|
||||||
.filter(serial => serial);
|
|
||||||
return selected_serial_nos;
|
|
||||||
};
|
|
||||||
|
|
||||||
function check_can_calculate_pending_qty(me) {
|
|
||||||
const { frm: { doc }, item } = me;
|
|
||||||
const docChecks = doc.bom_no
|
|
||||||
&& doc.fg_completed_qty
|
|
||||||
&& erpnext.stock.bom
|
|
||||||
&& erpnext.stock.bom.name === doc.bom_no;
|
|
||||||
const itemChecks = !!item
|
|
||||||
&& !item.original_item
|
|
||||||
&& erpnext.stock.bom && erpnext.stock.bom.items
|
|
||||||
&& (item.item_code in erpnext.stock.bom.items);
|
|
||||||
return docChecks && itemChecks;
|
|
||||||
}
|
|
||||||
|
|
||||||
//# sourceURL=serial_no_batch_selector.js
|
|
||||||
|
|
||||||
|
|
||||||
erpnext.SerialNoBatchBundleUpdate = class SerialNoBatchBundleUpdate {
|
erpnext.SerialNoBatchBundleUpdate = class SerialNoBatchBundleUpdate {
|
||||||
constructor(frm, item, callback) {
|
constructor(frm, item, callback) {
|
||||||
this.frm = frm;
|
this.frm = frm;
|
||||||
|
@ -18,7 +18,7 @@ class SerialandBatchBundle(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_serial_and_batch_no()
|
self.validate_serial_and_batch_no()
|
||||||
self.validate_duplicate_serial_and_batch_no()
|
self.validate_duplicate_serial_and_batch_no()
|
||||||
self.validate_voucher_no()
|
# self.validate_voucher_no()
|
||||||
self.validate_serial_nos()
|
self.validate_serial_nos()
|
||||||
|
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
@ -101,6 +101,9 @@ class SerialandBatchBundle(Document):
|
|||||||
rate = frappe.db.get_value(self.child_table, self.voucher_detail_no, "valuation_rate")
|
rate = frappe.db.get_value(self.child_table, self.voucher_detail_no, "valuation_rate")
|
||||||
|
|
||||||
for d in self.ledgers:
|
for d in self.ledgers:
|
||||||
|
if self.voucher_type in ["Stock Reconciliation", "Stock Entry"] and d.incoming_rate:
|
||||||
|
continue
|
||||||
|
|
||||||
if not rate or flt(rate, precision) == flt(d.incoming_rate, precision):
|
if not rate or flt(rate, precision) == flt(d.incoming_rate, precision):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -134,7 +137,7 @@ class SerialandBatchBundle(Document):
|
|||||||
if values_to_set:
|
if values_to_set:
|
||||||
self.db_set(values_to_set)
|
self.db_set(values_to_set)
|
||||||
|
|
||||||
self.validate_voucher_no()
|
# self.validate_voucher_no()
|
||||||
self.validate_quantity(row)
|
self.validate_quantity(row)
|
||||||
self.set_incoming_rate(save=True, row=row)
|
self.set_incoming_rate(save=True, row=row)
|
||||||
|
|
||||||
@ -196,6 +199,9 @@ class SerialandBatchBundle(Document):
|
|||||||
row.warehouse = self.warehouse
|
row.warehouse = self.warehouse
|
||||||
|
|
||||||
def set_total_qty(self, save=False):
|
def set_total_qty(self, save=False):
|
||||||
|
if not self.ledgers:
|
||||||
|
return
|
||||||
|
|
||||||
self.total_qty = sum([row.qty for row in self.ledgers])
|
self.total_qty = sum([row.qty for row in self.ledgers])
|
||||||
if save:
|
if save:
|
||||||
self.db_set("total_qty", self.total_qty)
|
self.db_set("total_qty", self.total_qty)
|
||||||
@ -638,7 +644,7 @@ def get_available_serial_nos(item_code, warehouse):
|
|||||||
"warehouse": ("is", "set"),
|
"warehouse": ("is", "set"),
|
||||||
}
|
}
|
||||||
|
|
||||||
fields = ["name", "warehouse", "batch_no"]
|
fields = ["name as serial_no", "warehouse", "batch_no"]
|
||||||
|
|
||||||
if warehouse:
|
if warehouse:
|
||||||
filters["warehouse"] = warehouse
|
filters["warehouse"] = warehouse
|
||||||
@ -654,6 +660,8 @@ def get_available_batch_nos(item_code, warehouse):
|
|||||||
for entry in sl_entries:
|
for entry in sl_entries:
|
||||||
batchwise_qty[entry.batch_no] += flt(entry.qty, precision)
|
batchwise_qty[entry.batch_no] += flt(entry.qty, precision)
|
||||||
|
|
||||||
|
return batchwise_qty
|
||||||
|
|
||||||
|
|
||||||
def get_stock_ledger_entries(item_code, warehouse):
|
def get_stock_ledger_entries(item_code, warehouse):
|
||||||
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
@ -68,7 +68,8 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Incoming Rate",
|
"label": "Incoming Rate",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"read_only_depends_on": "eval:parent.type_of_transaction == \"Outward\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "outgoing_rate",
|
"fieldname": "outgoing_rate",
|
||||||
@ -106,7 +107,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-10 12:02:49.560343",
|
"modified": "2023-03-17 09:11:31.548862",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Serial and Batch Ledger",
|
"name": "Serial and Batch Ledger",
|
||||||
|
@ -12,6 +12,10 @@ import erpnext
|
|||||||
from erpnext.accounts.utils import get_company_default
|
from erpnext.accounts.utils import get_company_default
|
||||||
from erpnext.controllers.stock_controller import StockController
|
from erpnext.controllers.stock_controller import StockController
|
||||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||||
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
|
get_available_batch_nos,
|
||||||
|
get_available_serial_nos,
|
||||||
|
)
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
from erpnext.stock.utils import get_stock_balance
|
from erpnext.stock.utils import get_stock_balance
|
||||||
|
|
||||||
@ -37,6 +41,8 @@ class StockReconciliation(StockController):
|
|||||||
if not self.cost_center:
|
if not self.cost_center:
|
||||||
self.cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
|
self.cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
|
||||||
self.validate_posting_time()
|
self.validate_posting_time()
|
||||||
|
self.set_current_serial_and_batch_bundle()
|
||||||
|
self.set_new_serial_and_batch_bundle()
|
||||||
self.remove_items_with_no_change()
|
self.remove_items_with_no_change()
|
||||||
self.validate_data()
|
self.validate_data()
|
||||||
self.validate_expense_account()
|
self.validate_expense_account()
|
||||||
@ -49,6 +55,9 @@ class StockReconciliation(StockController):
|
|||||||
if self._action == "submit":
|
if self._action == "submit":
|
||||||
self.validate_reserved_stock()
|
self.validate_reserved_stock()
|
||||||
|
|
||||||
|
def on_update(self):
|
||||||
|
self.set_serial_and_batch_bundle()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
@ -71,6 +80,87 @@ class StockReconciliation(StockController):
|
|||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
self.delete_auto_created_batches()
|
self.delete_auto_created_batches()
|
||||||
|
|
||||||
|
def set_current_serial_and_batch_bundle(self):
|
||||||
|
"""Set Serial and Batch Bundle for each item"""
|
||||||
|
for item in self.items:
|
||||||
|
item_details = frappe.get_cached_value(
|
||||||
|
"Item", item.item_code, ["has_serial_no", "has_batch_no"], as_dict=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
item_details.has_serial_no or item_details.has_batch_no
|
||||||
|
) and not item.current_serial_and_batch_bundle:
|
||||||
|
serial_and_batch_bundle = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Serial and Batch Bundle",
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"warehouse": item.warehouse,
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"posting_time": self.posting_time,
|
||||||
|
"voucher_type": self.doctype,
|
||||||
|
"voucher_no": self.name,
|
||||||
|
"type_of_transaction": "Outward",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if item_details.has_serial_no:
|
||||||
|
serial_nos_details = get_available_serial_nos(item.item_code, item.warehouse)
|
||||||
|
|
||||||
|
for serial_no_row in serial_nos_details:
|
||||||
|
serial_and_batch_bundle.append(
|
||||||
|
"ledgers",
|
||||||
|
{
|
||||||
|
"serial_no": serial_no_row.serial_no,
|
||||||
|
"qty": -1,
|
||||||
|
"warehouse": serial_no_row.warehouse,
|
||||||
|
"batch_no": serial_no_row.batch_no,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if item_details.has_batch_no:
|
||||||
|
batch_nos_details = get_available_batch_nos(item.item_code, item.warehouse)
|
||||||
|
|
||||||
|
for batch_no, qty in batch_nos_details.items():
|
||||||
|
serial_and_batch_bundle.append(
|
||||||
|
"ledgers",
|
||||||
|
{
|
||||||
|
"batch_no": batch_no,
|
||||||
|
"qty": qty * -1,
|
||||||
|
"warehouse": item.warehouse,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
item.current_serial_and_batch_bundle = serial_and_batch_bundle.save().name
|
||||||
|
|
||||||
|
def set_new_serial_and_batch_bundle(self):
|
||||||
|
for item in self.items:
|
||||||
|
if item.current_serial_and_batch_bundle and not item.serial_and_batch_bundle:
|
||||||
|
current_doc = frappe.get_doc("Serial and Batch Bundle", item.current_serial_and_batch_bundle)
|
||||||
|
|
||||||
|
item.qty = abs(current_doc.total_qty)
|
||||||
|
item.valuation_rate = abs(current_doc.avg_rate)
|
||||||
|
|
||||||
|
bundle_doc = frappe.copy_doc(current_doc)
|
||||||
|
bundle_doc.warehouse = item.warehouse
|
||||||
|
bundle_doc.type_of_transaction = "Inward"
|
||||||
|
|
||||||
|
for row in bundle_doc.ledgers:
|
||||||
|
if row.qty < 0:
|
||||||
|
row.qty = abs(row.qty)
|
||||||
|
|
||||||
|
if row.stock_value_difference < 0:
|
||||||
|
row.stock_value_difference = abs(row.stock_value_difference)
|
||||||
|
|
||||||
|
row.is_outward = 0
|
||||||
|
|
||||||
|
bundle_doc.set_total_qty()
|
||||||
|
bundle_doc.set_avg_rate()
|
||||||
|
bundle_doc.flags.ignore_permissions = True
|
||||||
|
bundle_doc.save()
|
||||||
|
item.serial_and_batch_bundle = bundle_doc.name
|
||||||
|
elif item.serial_and_batch_bundle:
|
||||||
|
pass
|
||||||
|
|
||||||
def remove_items_with_no_change(self):
|
def remove_items_with_no_change(self):
|
||||||
"""Remove items if qty or rate is not changed"""
|
"""Remove items if qty or rate is not changed"""
|
||||||
self.difference_amount = 0.0
|
self.difference_amount = 0.0
|
||||||
@ -80,10 +170,11 @@ class StockReconciliation(StockController):
|
|||||||
item.item_code, item.warehouse, self.posting_date, self.posting_time, batch_no=item.batch_no
|
item.item_code, item.warehouse, self.posting_date, self.posting_time, batch_no=item.batch_no
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if item.current_serial_and_batch_bundle:
|
||||||
(item.qty is None or item.qty == item_dict.get("qty"))
|
return True
|
||||||
and (item.valuation_rate is None or item.valuation_rate == item_dict.get("rate"))
|
|
||||||
and (not item.serial_no or (item.serial_no == item_dict.get("serial_nos")))
|
if (item.qty is None or item.qty == item_dict.get("qty")) and (
|
||||||
|
item.valuation_rate is None or item.valuation_rate == item_dict.get("rate")
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@ -94,11 +185,6 @@ class StockReconciliation(StockController):
|
|||||||
if item.valuation_rate is None:
|
if item.valuation_rate is None:
|
||||||
item.valuation_rate = item_dict.get("rate")
|
item.valuation_rate = item_dict.get("rate")
|
||||||
|
|
||||||
if item_dict.get("serial_nos"):
|
|
||||||
item.current_serial_no = item_dict.get("serial_nos")
|
|
||||||
if self.purpose == "Stock Reconciliation" and not item.serial_no and item.qty:
|
|
||||||
item.serial_no = item.current_serial_no
|
|
||||||
|
|
||||||
item.current_qty = item_dict.get("qty")
|
item.current_qty = item_dict.get("qty")
|
||||||
item.current_valuation_rate = item_dict.get("rate")
|
item.current_valuation_rate = item_dict.get("rate")
|
||||||
self.difference_amount += flt(item.qty, item.precision("qty")) * flt(
|
self.difference_amount += flt(item.qty, item.precision("qty")) * flt(
|
||||||
@ -279,15 +365,14 @@ class StockReconciliation(StockController):
|
|||||||
has_serial_no = False
|
has_serial_no = False
|
||||||
has_batch_no = False
|
has_batch_no = False
|
||||||
for row in self.items:
|
for row in self.items:
|
||||||
item = frappe.get_doc("Item", row.item_code)
|
item = frappe.get_cached_value(
|
||||||
if item.has_batch_no:
|
"Item", row.item_code, ["has_serial_no", "has_batch_no"], as_dict=1
|
||||||
has_batch_no = True
|
)
|
||||||
|
|
||||||
if item.has_serial_no or item.has_batch_no:
|
if item.has_serial_no or item.has_batch_no:
|
||||||
has_serial_no = True
|
self.get_sle_for_serialized_items(row, sl_entries)
|
||||||
self.get_sle_for_serialized_items(row, sl_entries, item)
|
|
||||||
else:
|
else:
|
||||||
if row.serial_no or row.batch_no:
|
if row.serial_and_batch_bundle:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it."
|
"Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it."
|
||||||
@ -337,89 +422,32 @@ class StockReconciliation(StockController):
|
|||||||
if has_serial_no and sl_entries:
|
if has_serial_no and sl_entries:
|
||||||
self.update_valuation_rate_for_serial_no()
|
self.update_valuation_rate_for_serial_no()
|
||||||
|
|
||||||
def get_sle_for_serialized_items(self, row, sl_entries, item):
|
def get_sle_for_serialized_items(self, row, sl_entries):
|
||||||
from erpnext.stock.stock_ledger import get_previous_sle
|
if row.current_serial_and_batch_bundle:
|
||||||
|
|
||||||
serial_nos = get_serial_nos(row.serial_no)
|
|
||||||
|
|
||||||
# To issue existing serial nos
|
|
||||||
if row.current_qty and (row.current_serial_no or row.batch_no):
|
|
||||||
args = self.get_sle_for_items(row)
|
args = self.get_sle_for_items(row)
|
||||||
args.update(
|
args.update(
|
||||||
{
|
{
|
||||||
"actual_qty": -1 * row.current_qty,
|
"actual_qty": -1 * row.current_qty,
|
||||||
"serial_no": row.current_serial_no,
|
"serial_and_batch_bundle": row.current_serial_and_batch_bundle,
|
||||||
"batch_no": row.batch_no,
|
|
||||||
"valuation_rate": row.current_valuation_rate,
|
"valuation_rate": row.current_valuation_rate,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if row.current_serial_no:
|
|
||||||
args.update(
|
|
||||||
{
|
|
||||||
"qty_after_transaction": 0,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
sl_entries.append(args)
|
sl_entries.append(args)
|
||||||
|
|
||||||
qty_after_transaction = 0
|
if row.current_serial_and_batch_bundle:
|
||||||
for serial_no in serial_nos:
|
|
||||||
args = self.get_sle_for_items(row, [serial_no])
|
|
||||||
|
|
||||||
previous_sle = get_previous_sle(
|
|
||||||
{
|
|
||||||
"item_code": row.item_code,
|
|
||||||
"posting_date": self.posting_date,
|
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"serial_no": serial_no,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if previous_sle and row.warehouse != previous_sle.get("warehouse"):
|
|
||||||
# If serial no exists in different warehouse
|
|
||||||
|
|
||||||
warehouse = previous_sle.get("warehouse", "") or row.warehouse
|
|
||||||
|
|
||||||
if not qty_after_transaction:
|
|
||||||
qty_after_transaction = get_stock_balance(
|
|
||||||
row.item_code, warehouse, self.posting_date, self.posting_time
|
|
||||||
)
|
|
||||||
|
|
||||||
qty_after_transaction -= 1
|
|
||||||
|
|
||||||
new_args = args.copy()
|
|
||||||
new_args.update(
|
|
||||||
{
|
|
||||||
"actual_qty": -1,
|
|
||||||
"qty_after_transaction": qty_after_transaction,
|
|
||||||
"warehouse": warehouse,
|
|
||||||
"valuation_rate": previous_sle.get("valuation_rate"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
sl_entries.append(new_args)
|
|
||||||
|
|
||||||
if row.qty:
|
|
||||||
args = self.get_sle_for_items(row)
|
args = self.get_sle_for_items(row)
|
||||||
|
|
||||||
if item.has_serial_no and item.has_batch_no:
|
|
||||||
args["qty_after_transaction"] = row.qty
|
|
||||||
|
|
||||||
args.update(
|
args.update(
|
||||||
{
|
{
|
||||||
"actual_qty": row.qty,
|
"actual_qty": frappe.get_cached_value(
|
||||||
"incoming_rate": row.valuation_rate,
|
"Serial and Batch Bundle", row.serial_and_batch_bundle, "total_qty"
|
||||||
"valuation_rate": row.valuation_rate,
|
),
|
||||||
|
"serial_and_batch_bundle": row.current_serial_and_batch_bundle,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
sl_entries.append(args)
|
sl_entries.append(args)
|
||||||
|
|
||||||
if serial_nos == get_serial_nos(row.current_serial_no):
|
|
||||||
# update valuation rate
|
|
||||||
self.update_valuation_rate_for_serial_nos(row, serial_nos)
|
|
||||||
|
|
||||||
def update_valuation_rate_for_serial_no(self):
|
def update_valuation_rate_for_serial_no(self):
|
||||||
for d in self.items:
|
for d in self.items:
|
||||||
if not d.serial_no:
|
if not d.serial_no:
|
||||||
@ -456,8 +484,6 @@ class StockReconciliation(StockController):
|
|||||||
"company": self.company,
|
"company": self.company,
|
||||||
"stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
|
"stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
|
||||||
"is_cancelled": 1 if self.docstatus == 2 else 0,
|
"is_cancelled": 1 if self.docstatus == 2 else 0,
|
||||||
"serial_no": "\n".join(serial_nos) if serial_nos else "",
|
|
||||||
"batch_no": row.batch_no,
|
|
||||||
"valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate")),
|
"valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate")),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user