fix: use old serial / batch fields to make serial batch bundle

(cherry picked from commit 9fafc83632c4e5c73186bda038b892b7a43d16b8)
This commit is contained in:
Rohit Waghchaure 2024-02-04 10:42:31 +05:30 committed by Mergify
parent 53992deb10
commit 282c19e7e1
43 changed files with 652 additions and 370 deletions

View File

@ -80,13 +80,16 @@
"target_warehouse",
"quality_inspection",
"serial_and_batch_bundle",
"batch_no",
"use_serial_batch_fields",
"col_break5",
"allow_zero_valuation_rate",
"serial_no",
"item_tax_rate",
"actual_batch_qty",
"actual_qty",
"section_break_tlhi",
"serial_no",
"column_break_ciit",
"batch_no",
"edit_references",
"sales_order",
"so_detail",
@ -628,13 +631,13 @@
"options": "Quality Inspection"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"hidden": 1,
"label": "Batch No",
"options": "Batch",
"print_hide": 1,
"read_only": 1
"print_hide": 1
},
{
"fieldname": "col_break5",
@ -649,14 +652,14 @@
"print_hide": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Small Text",
"hidden": 1,
"in_list_view": 1,
"label": "Serial No",
"oldfieldname": "serial_no",
"oldfieldtype": "Small Text",
"read_only": 1
"oldfieldtype": "Small Text"
},
{
"fieldname": "item_tax_rate",
@ -824,17 +827,33 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
"no_copy": 1,
"options": "Serial and Batch Bundle",
"print_hide": 1
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_tlhi",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_ciit",
"fieldtype": "Column Break"
}
],
"istable": 1,
"links": [],
"modified": "2023-11-14 18:33:22.585715",
"modified": "2024-02-04 16:36:25.665743",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",

View File

@ -82,6 +82,7 @@ class POSInvoiceItem(Document):
target_warehouse: DF.Link | None
total_weight: DF.Float
uom: DF.Link
use_serial_batch_fields: DF.Check
warehouse: DF.Link | None
weight_per_unit: DF.Float
weight_uom: DF.Link | None

View File

@ -696,6 +696,7 @@ class PurchaseInvoice(BuyingController):
# Updating stock ledger should always be called after updating prevdoc status,
# because updating ordered qty in bin depends upon updated ordered qty in PO
if self.update_stock == 1:
self.make_bundle_using_old_serial_batch_fields()
self.update_stock_ledger()
if self.is_old_subcontracting_flow:

View File

@ -62,16 +62,19 @@
"rm_supp_cost",
"warehouse_section",
"warehouse",
"from_warehouse",
"quality_inspection",
"add_serial_batch_bundle",
"serial_and_batch_bundle",
"serial_no",
"use_serial_batch_fields",
"col_br_wh",
"from_warehouse",
"quality_inspection",
"rejected_warehouse",
"rejected_serial_and_batch_bundle",
"batch_no",
"section_break_rqbe",
"serial_no",
"rejected_serial_no",
"column_break_vbbb",
"batch_no",
"manufacture_details",
"manufacturer",
"column_break_13",
@ -440,13 +443,11 @@
"print_hide": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset",
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"hidden": 1,
"label": "Batch No",
"options": "Batch",
"read_only": 1,
"search_index": 1
},
{
@ -454,21 +455,18 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.is_fixed_asset",
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"hidden": 1,
"label": "Serial No",
"read_only": 1
"label": "Serial No"
},
{
"depends_on": "eval:!doc.is_fixed_asset",
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "rejected_serial_no",
"fieldtype": "Text",
"label": "Rejected Serial No",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
"print_hide": 1
},
{
"fieldname": "accounting",
@ -891,7 +889,7 @@
"label": "Apply TDS"
},
{
"depends_on": "eval:parent.update_stock == 1",
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@ -901,7 +899,7 @@
"search_index": 1
},
{
"depends_on": "eval:parent.update_stock == 1",
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"fieldname": "rejected_serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Rejected Serial and Batch Bundle",
@ -916,16 +914,31 @@
"options": "Asset"
},
{
"depends_on": "eval:parent.update_stock === 1",
"depends_on": "eval:parent.update_stock === 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"fieldname": "add_serial_batch_bundle",
"fieldtype": "Button",
"label": "Add Serial / Batch No"
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "section_break_rqbe",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_vbbb",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-01-21 19:46:25.537861",
"modified": "2024-02-04 14:11:52.742228",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@ -88,6 +88,7 @@ class PurchaseInvoiceItem(Document):
stock_uom_rate: DF.Currency
total_weight: DF.Float
uom: DF.Link
use_serial_batch_fields: DF.Check
valuation_rate: DF.Currency
warehouse: DF.Link | None
weight_per_unit: DF.Float

View File

@ -446,6 +446,7 @@ class SalesInvoice(SellingController):
# Updating stock ledger should always be called after updating prevdoc status,
# because updating reserved qty in bin depends upon updated delivered qty in SO
if self.update_stock == 1:
self.make_bundle_using_old_serial_batch_fields()
self.update_stock_ledger()
# this sequence because outstanding may get -ve

View File

@ -83,14 +83,17 @@
"quality_inspection",
"pick_serial_and_batch",
"serial_and_batch_bundle",
"batch_no",
"incoming_rate",
"use_serial_batch_fields",
"col_break5",
"allow_zero_valuation_rate",
"serial_no",
"incoming_rate",
"item_tax_rate",
"actual_batch_qty",
"actual_qty",
"section_break_eoec",
"serial_no",
"column_break_ytgd",
"batch_no",
"edit_references",
"sales_order",
"so_detail",
@ -600,12 +603,11 @@
"options": "Quality Inspection"
},
{
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"hidden": 1,
"label": "Batch No",
"options": "Batch",
"read_only": 1,
"search_index": 1
},
{
@ -621,13 +623,12 @@
"print_hide": 1
},
{
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "serial_no",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Serial No",
"oldfieldname": "serial_no",
"oldfieldtype": "Small Text",
"read_only": 1
"oldfieldtype": "Small Text"
},
{
"fieldname": "item_group",
@ -891,6 +892,7 @@
"read_only": 1
},
{
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@ -904,12 +906,27 @@
"fieldname": "pick_serial_and_batch",
"fieldtype": "Button",
"label": "Pick Serial / Batch No"
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "section_break_eoec",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_ytgd",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-12-29 13:03:14.121298",
"modified": "2024-02-04 11:52:16.106541",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@ -86,6 +86,7 @@ class SalesInvoiceItem(Document):
target_warehouse: DF.Link | None
total_weight: DF.Float
uom: DF.Link
use_serial_batch_fields: DF.Check
warehouse: DF.Link | None
weight_per_unit: DF.Float
weight_uom: DF.Link | None

View File

@ -126,6 +126,7 @@ class AssetCapitalization(StockController):
self.create_target_asset()
def on_submit(self):
self.make_bundle_using_old_serial_batch_fields()
self.update_stock_ledger()
self.make_gl_entries()
self.update_target_asset()

View File

@ -18,9 +18,12 @@
"amount",
"batch_and_serial_no_section",
"serial_and_batch_bundle",
"use_serial_batch_fields",
"column_break_13",
"batch_no",
"section_break_bfqc",
"serial_no",
"column_break_mbuv",
"batch_no",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
@ -39,13 +42,13 @@
"reqd": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"no_copy": 1,
"options": "Batch",
"print_hide": 1,
"read_only": 1
"print_hide": 1
},
{
"fieldname": "section_break_6",
@ -102,12 +105,12 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Serial No",
"print_hide": 1,
"read_only": 1
"print_hide": 1
},
{
"fieldname": "item_code",
@ -148,18 +151,34 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
"no_copy": 1,
"options": "Serial and Batch Bundle",
"print_hide": 1
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_bfqc",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_mbuv",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-04-06 01:10:17.947952",
"modified": "2024-02-04 16:41:09.239762",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Stock Item",

View File

@ -27,6 +27,7 @@ class AssetCapitalizationStockItem(Document):
serial_no: DF.SmallText | None
stock_qty: DF.Float
stock_uom: DF.Link
use_serial_batch_fields: DF.Check
valuation_rate: DF.Currency
warehouse: DF.Link
# end: auto-generated types

View File

@ -126,6 +126,54 @@ class StockController(AccountsController):
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)
def make_bundle_using_old_serial_batch_fields(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
for row in self.items:
if not row.serial_no and not row.batch_no and not row.get("rejected_serial_no"):
continue
if not row.use_serial_batch_fields and (
row.serial_no or row.batch_no or row.get("rejected_serial_no")
):
frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle"))
if row.use_serial_batch_fields and (
not row.serial_and_batch_bundle or not row.get("rejected_serial_and_batch_bundle")
):
sn_doc = SerialBatchCreation(
{
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"voucher_type": self.doctype,
"voucher_no": self.name,
"voucher_detail_no": row.name,
"qty": row.stock_qty,
"type_of_transaction": "Inward" if row.stock_qty > 0 else "Outward",
"company": self.company,
"is_rejected": 1 if row.get("rejected_warehouse") else 0,
"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
"batches": frappe._dict({row.batch_no: row.stock_qty}) if row.batch_no else None,
"batch_no": row.batch_no,
"use_serial_batch_fields": row.use_serial_batch_fields,
}
).make_serial_and_batch_bundle()
if sn_doc.is_rejected:
row.rejected_serial_and_batch_bundle = sn_doc.name
row.db_set("rejected_serial_and_batch_bundle", sn_doc.name)
else:
row.serial_and_batch_bundle = sn_doc.name
row.db_set("serial_and_batch_bundle", sn_doc.name)
def set_use_serial_batch_fields(self):
if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
for row in self.items:
row.use_serial_batch_fields = 1
def get_gl_entries(
self, warehouse_account=None, default_expense_account=None, default_cost_center=None
):

View File

@ -7,6 +7,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
super.setup();
let me = this;
this.set_fields_onload_for_line_item();
this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle'];
frappe.flags.hide_serial_batch_dialog = true;
@ -105,6 +106,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
frappe.ui.form.on(this.frm.doctype + " Item", {
items_add: function(frm, cdt, cdn) {
debugger
var item = frappe.get_doc(cdt, cdn);
if (!item.warehouse && frm.doc.set_warehouse) {
item.warehouse = frm.doc.set_warehouse;
@ -118,6 +120,13 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
item.from_warehouse = frm.doc.set_from_warehouse;
}
if (item.docstatus === 0
&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1
) {
frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1);
}
erpnext.accounts.dimensions.copy_dimension_from_first_row(frm, cdt, cdn, 'items');
}
});
@ -222,7 +231,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
};
});
}
}
set_fields_onload_for_line_item() {
if (this.frm.is_new && this.frm.doc?.items) {
this.frm.doc.items.forEach(item => {
if (item.docstatus === 0
&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1
) {
frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1);
}
})
}
}
toggle_enable_for_stock_uom(field) {
@ -462,6 +483,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
this.frm.doc.doctype === 'Delivery Note') {
show_batch_dialog = 1;
}
if (show_batch_dialog && item.use_serial_batch_fields === 1) {
show_batch_dialog = 0;
}
item.barcode = null;
@ -1242,20 +1268,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
}
sync_bundle_data() {
let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
barcode_scanner.sync_bundle_data();
barcode_scanner.remove_item_from_localstorage();
}
}
before_save(doc) {
this.sync_bundle_data();
}
service_start_date(frm, cdt, cdn) {
var child = locals[cdt][cdn];

View File

@ -1,12 +1,15 @@
erpnext.utils.BarcodeScanner = class BarcodeScanner {
constructor(opts) {
this.frm = opts.frm;
// frappe.flags.trigger_from_barcode_scanner is used for custom scripts
// field from which to capture input of scanned data
this.scan_field_name = opts.scan_field_name || "scan_barcode";
this.scan_barcode_field = this.frm.fields_dict[this.scan_field_name];
this.barcode_field = opts.barcode_field || "barcode";
this.serial_no_field = opts.serial_no_field || "serial_no";
this.batch_no_field = opts.batch_no_field || "batch_no";
this.uom_field = opts.uom_field || "uom";
this.qty_field = opts.qty_field || "qty";
// field name on row which defines max quantity to be scanned e.g. picklist
@ -105,53 +108,52 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
this.frm.has_items = false;
}
if (serial_no) {
this.is_duplicate_serial_no(row, item_code, serial_no)
.then((is_duplicate) => {
if (!is_duplicate) {
this.run_serially_tasks(row, data, resolve);
} else {
this.clean_up();
reject();
return;
}
});
} else {
this.run_serially_tasks(row, data, resolve);
if (this.is_duplicate_serial_no(row, serial_no)) {
this.clean_up();
reject();
return;
}
frappe.run_serially([
() => this.set_selector_trigger_flag(data),
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
this.show_scan_message(row.idx, row.item_code, qty);
}),
() => this.set_barcode_uom(row, uom),
() => this.set_serial_no(row, serial_no),
() => this.set_batch_no(row, batch_no),
() => this.set_barcode(row, barcode),
() => this.clean_up(),
() => this.revert_selector_flag(),
() => resolve(row)
]);
});
}
run_serially_tasks(row, data, resolve) {
const {item_code, barcode, batch_no, serial_no, uom} = data;
// batch and serial selector is reduandant when all info can be added by scan
// this flag on item row is used by transaction.js to avoid triggering selector
set_selector_trigger_flag(data) {
const {batch_no, serial_no, has_batch_no, has_serial_no} = data;
frappe.run_serially([
() => this.set_serial_and_batch(row, item_code, serial_no, batch_no),
() => this.set_barcode(row, barcode),
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
this.show_scan_message(row.idx, row.item_code, qty);
}),
() => this.set_barcode_uom(row, uom),
() => this.clean_up(),
() => {
if (row.serial_and_batch_bundle && !this.frm.is_new()) {
this.frm.save();
}
const require_selecting_batch = has_batch_no && !batch_no;
const require_selecting_serial = has_serial_no && !serial_no;
frappe.flags.trigger_from_barcode_scanner = false;
},
() => resolve(row),
]);
if (!(require_selecting_batch || require_selecting_serial)) {
frappe.flags.hide_serial_batch_dialog = true;
}
}
revert_selector_flag() {
frappe.flags.hide_serial_batch_dialog = false;
frappe.flags.trigger_from_barcode_scanner = false;
}
set_item(row, item_code, barcode, batch_no, serial_no) {
return new Promise(resolve => {
const increment = async (value = 1) => {
const item_data = {item_code: item_code};
item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
const item_data = {item_code: item_code, use_serial_batch_fields: 1.0};
frappe.flags.trigger_from_barcode_scanner = true;
item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
await frappe.model.set_value(row.doctype, row.name, item_data);
return value;
};
@ -160,6 +162,8 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
increment(value).then((value) => resolve(value));
});
} else if (this.frm.has_items) {
this.prepare_item_for_scan(row, item_code, barcode, batch_no, serial_no);
} else {
increment().then((value) => resolve(value));
}
@ -182,8 +186,9 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
frappe.model.set_value(row.doctype, row.name, item_data);
frappe.run_serially([
() => this.set_batch_no(row, this.dialog.get_value("batch_no")),
() => this.set_barcode(row, this.dialog.get_value("barcode")),
() => this.set_serial_and_batch(row, item_code, this.dialog.get_value("serial_no"), this.dialog.get_value("batch_no")),
() => this.set_serial_no(row, this.dialog.get_value("serial_no")),
() => this.add_child_for_remaining_qty(row),
() => this.clean_up()
]);
@ -337,144 +342,32 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
}
}
async set_serial_and_batch(row, item_code, serial_no, batch_no) {
if (this.frm.is_new() || !row.serial_and_batch_bundle) {
this.set_bundle_in_localstorage(row, item_code, serial_no, batch_no);
} else if(row.serial_and_batch_bundle) {
frappe.call({
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.update_serial_or_batch",
args: {
bundle_id: row.serial_and_batch_bundle,
serial_no: serial_no,
batch_no: batch_no,
},
})
}
}
async set_serial_no(row, serial_no) {
if (serial_no && frappe.meta.has_field(row.doctype, this.serial_no_field)) {
const existing_serial_nos = row[this.serial_no_field];
let new_serial_nos = "";
get_key_for_localstorage() {
let parts = this.frm.doc.name.split("-");
return parts[parts.length - 1] + this.frm.doc.doctype;
}
update_localstorage_scanned_data() {
let docname = this.frm.doc.name
if (localStorage[docname]) {
let items = JSON.parse(localStorage[docname]);
let existing_items = this.frm.doc.items.map(d => d.item_code);
if (!existing_items.length) {
localStorage.removeItem(docname);
return;
if (!!existing_serial_nos) {
new_serial_nos = existing_serial_nos + "\n" + serial_no;
} else {
new_serial_nos = serial_no;
}
for (let item_code in items) {
if (!existing_items.includes(item_code)) {
delete items[item_code];
}
}
localStorage[docname] = JSON.stringify(items);
await frappe.model.set_value(row.doctype, row.name, this.serial_no_field, new_serial_nos);
}
}
async set_bundle_in_localstorage(row, item_code, serial_no, batch_no) {
let docname = this.frm.doc.name
let entries = JSON.parse(localStorage.getItem(docname));
if (!entries) {
entries = {};
}
let key = item_code;
if (!entries[key]) {
entries[key] = [];
}
let existing_row = [];
if (!serial_no && batch_no) {
existing_row = entries[key].filter((e) => e.batch_no === batch_no);
if (existing_row.length) {
existing_row[0].qty += 1;
}
} else if (serial_no) {
existing_row = entries[key].filter((e) => e.serial_no === serial_no);
if (existing_row.length) {
frappe.throw(__("Serial No {0} has already scanned.", [serial_no]));
}
}
if (!existing_row.length) {
entries[key].push({
"serial_no": serial_no,
"batch_no": batch_no,
"qty": 1
});
}
localStorage.setItem(docname, JSON.stringify(entries));
// Auto remove from localstorage after 1 hour
setTimeout(() => {
localStorage.removeItem(docname);
}, 3600000)
}
remove_item_from_localstorage() {
let docname = this.frm.doc.name;
if (localStorage[docname]) {
localStorage.removeItem(docname);
}
}
async sync_bundle_data() {
let docname = this.frm.doc.name;
if (localStorage[docname]) {
let entries = JSON.parse(localStorage[docname]);
if (entries) {
for (let entry in entries) {
let row = this.frm.doc.items.filter((item) => {
if (item.item_code === entry) {
return true;
}
})[0];
if (row) {
this.create_serial_and_batch_bundle(row, entries, entry)
.then(() => {
if (!entries) {
localStorage.removeItem(docname);
}
});
}
}
}
}
}
async create_serial_and_batch_bundle(row, entries, key) {
frappe.call({
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers",
args: {
entries: entries[key],
child_row: row,
doc: this.frm.doc,
warehouse: row.warehouse,
do_not_save: 1
},
callback: function(r) {
row.serial_and_batch_bundle = r.message.name;
delete entries[key];
}
})
}
async set_barcode_uom(row, uom) {
if (uom && frappe.meta.has_field(row.doctype, this.uom_field)) {
await frappe.model.set_value(row.doctype, row.name, this.uom_field, uom);
}
}
async set_batch_no(row, batch_no) {
if (batch_no && frappe.meta.has_field(row.doctype, this.batch_no_field)) {
await frappe.model.set_value(row.doctype, row.name, this.batch_no_field, batch_no);
}
}
async set_barcode(row, barcode) {
if (barcode && frappe.meta.has_field(row.doctype, this.barcode_field)) {
await frappe.model.set_value(row.doctype, row.name, this.barcode_field, barcode);
@ -490,58 +383,13 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
}
}
async is_duplicate_serial_no(row, item_code, serial_no) {
let is_duplicate = false;
const promise = new Promise((resolve, reject) => {
if (this.frm.is_new() || !row.serial_and_batch_bundle) {
is_duplicate = this.check_duplicate_serial_no_in_localstorage(item_code, serial_no);
if (is_duplicate) {
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
}
is_duplicate_serial_no(row, serial_no) {
const is_duplicate = row[this.serial_no_field]?.includes(serial_no);
resolve(is_duplicate);
} else if (row.serial_and_batch_bundle) {
this.check_duplicate_serial_no_in_db(row, serial_no, (r) => {
if (r.message) {
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
}
is_duplicate = r.message;
resolve(is_duplicate);
})
}
});
return await promise;
}
check_duplicate_serial_no_in_db(row, serial_no, response) {
frappe.call({
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_duplicate_serial_no",
args: {
serial_no: serial_no,
bundle_id: row.serial_and_batch_bundle
},
callback(r) {
response(r);
}
});
}
check_duplicate_serial_no_in_localstorage(item_code, serial_no) {
let docname = this.frm.doc.name
let entries = JSON.parse(localStorage.getItem(docname));
if (!entries) {
return false;
if (is_duplicate) {
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
}
let existing_row = [];
if (entries[item_code]) {
existing_row = entries[item_code].filter((e) => e.serial_no === serial_no);
}
return existing_row.length;
return is_duplicate;
}
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
@ -587,4 +435,4 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
show_alert(msg, indicator, duration=3) {
frappe.show_alert({message: msg, indicator: indicator}, duration);
}
};
};

View File

@ -904,6 +904,7 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
target.run_method("set_missing_values")
target.run_method("set_po_nos")
target.run_method("calculate_taxes_and_totals")
target.run_method("set_use_serial_batch_fields")
if source.company_address:
target.update({"company_address": source.company_address})
@ -1024,6 +1025,7 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
target.run_method("set_missing_values")
target.run_method("set_po_nos")
target.run_method("calculate_taxes_and_totals")
target.run_method("set_use_serial_batch_fields")
if source.company_address:
target.update({"company_address": source.company_address})
@ -1606,7 +1608,11 @@ def create_pick_list(source_name, target_doc=None):
"Sales Order",
source_name,
{
"Sales Order": {"doctype": "Pick List", "validation": {"docstatus": ["=", 1]}},
"Sales Order": {
"doctype": "Pick List",
"field_map": {"set_warehouse": "parent_warehouse"},
"validation": {"docstatus": ["=", 1]},
},
"Sales Order Item": {
"doctype": "Pick List Item",
"field_map": {"parent": "sales_order", "name": "sales_order_item"},

View File

@ -398,6 +398,8 @@ class DeliveryNote(SellingController):
self.check_credit_limit()
elif self.issue_credit_note:
self.make_return_invoice()
self.make_bundle_using_old_serial_batch_fields()
# Updating stock ledger should always be called after updating prevdoc status,
# because updating reserved qty in bin depends upon updated delivered qty in SO
self.update_stock_ledger()

View File

@ -80,8 +80,11 @@
"section_break_40",
"pick_serial_and_batch",
"serial_and_batch_bundle",
"use_serial_batch_fields",
"column_break_eaoe",
"section_break_qyjv",
"serial_no",
"column_break_rxvc",
"batch_no",
"available_qty_section",
"actual_batch_qty",
@ -850,6 +853,7 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@ -859,6 +863,7 @@
"search_index": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"fieldname": "pick_serial_and_batch",
"fieldtype": "Button",
"label": "Pick Serial / Batch No"
@ -874,27 +879,40 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"hidden": 1,
"label": "Serial No",
"read_only": 1
"label": "Serial No"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"hidden": 1,
"label": "Batch No",
"options": "Batch",
"read_only": 1,
"search_index": 1
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_qyjv",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_rxvc",
"fieldtype": "Column Break"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-11-14 18:37:38.638144",
"modified": "2024-02-04 14:10:31.750340",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",

View File

@ -82,6 +82,7 @@ class DeliveryNoteItem(Document):
target_warehouse: DF.Link | None
total_weight: DF.Float
uom: DF.Link
use_serial_batch_fields: DF.Check
warehouse: DF.Link | None
weight_per_unit: DF.Float
weight_uom: DF.Link | None

View File

@ -20,9 +20,12 @@
"uom",
"section_break_9",
"pick_serial_and_batch",
"serial_and_batch_bundle",
"serial_no",
"use_serial_batch_fields",
"column_break_11",
"serial_and_batch_bundle",
"section_break_bgys",
"serial_no",
"column_break_qlha",
"batch_no",
"actual_batch_qty",
"section_break_13",
@ -118,10 +121,10 @@
"fieldtype": "Section Break"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"read_only": 1
"label": "Serial No"
},
{
"fieldname": "column_break_11",
@ -131,8 +134,7 @@
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch",
"read_only": 1
"options": "Batch"
},
{
"fieldname": "section_break_13",
@ -259,6 +261,7 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@ -267,16 +270,32 @@
"print_hide": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "pick_serial_and_batch",
"fieldtype": "Button",
"label": "Pick Serial / Batch No"
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_bgys",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_qlha",
"fieldtype": "Column Break"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-04-28 13:16:38.460806",
"modified": "2024-02-04 16:30:44.263964",
"modified_by": "Administrator",
"module": "Stock",
"name": "Packed Item",

View File

@ -47,6 +47,7 @@ class PackedItem(Document):
serial_no: DF.Text | None
target_warehouse: DF.Link | None
uom: DF.Link | None
use_serial_batch_fields: DF.Check
warehouse: DF.Link | None
# end: auto-generated types

View File

@ -16,7 +16,6 @@ frappe.ui.form.on('Pick List', {
frm.set_query('parent_warehouse', () => {
return {
filters: {
'is_group': 1,
'company': frm.doc.company
}
};

View File

@ -51,7 +51,7 @@
"description": "Items under this warehouse will be suggested",
"fieldname": "parent_warehouse",
"fieldtype": "Link",
"label": "Parent Warehouse",
"label": "Warehouse",
"options": "Warehouse"
},
{
@ -188,7 +188,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2023-01-24 10:33:43.244476",
"modified": "2024-02-01 16:17:44.877426",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",

View File

@ -13,7 +13,7 @@ from frappe.model.mapper import map_child_doc
from frappe.query_builder import Case
from frappe.query_builder.custom import GROUP_CONCAT
from frappe.query_builder.functions import Coalesce, Locate, Replace, Sum
from frappe.utils import cint, floor, flt
from frappe.utils import ceil, cint, floor, flt
from frappe.utils.nestedset import get_descendants_of
from erpnext.selling.doctype.sales_order.sales_order import (
@ -122,11 +122,43 @@ class PickList(Document):
def on_submit(self):
self.validate_serial_and_batch_bundle()
self.make_bundle_using_old_serial_batch_fields()
self.update_status()
self.update_bundle_picked_qty()
self.update_reference_qty()
self.update_sales_order_picking_status()
def make_bundle_using_old_serial_batch_fields(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
for row in self.locations:
if not row.serial_no and not row.batch_no:
continue
if not row.use_serial_batch_fields and (row.serial_no or row.batch_no):
frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle"))
if row.use_serial_batch_fields and (not row.serial_and_batch_bundle):
sn_doc = SerialBatchCreation(
{
"item_code": row.item_code,
"warehouse": row.warehouse,
"voucher_type": self.doctype,
"voucher_no": self.name,
"voucher_detail_no": row.name,
"qty": row.stock_qty,
"type_of_transaction": "Inward" if row.stock_qty > 0 else "Outward",
"company": self.company,
"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
"batches": frappe._dict({row.batch_no: row.stock_qty}) if row.batch_no else None,
"batch_no": row.batch_no,
"use_serial_batch_fields": row.use_serial_batch_fields,
}
).make_serial_and_batch_bundle()
row.serial_and_batch_bundle = sn_doc.name
row.db_set("serial_and_batch_bundle", sn_doc.name)
def on_update_after_submit(self) -> None:
if self.has_reserved_stock():
msg = _(
@ -317,8 +349,12 @@ class PickList(Document):
self.item_location_map = frappe._dict()
from_warehouses = None
if self.parent_warehouse:
if self.parent_warehouse and frappe.get_cached_value(
"Warehouse", self.parent_warehouse, "is_group"
):
from_warehouses = get_descendants_of("Warehouse", self.parent_warehouse)
elif self.parent_warehouse:
from_warehouses = [self.parent_warehouse]
# Create replica before resetting, to handle empty table on update after submit.
locations_replica = self.get("locations")
@ -351,13 +387,13 @@ class PickList(Document):
for row in locations:
location = item_doc.as_dict()
location.update(row)
bundle = location.serial_and_batch_bundle or location.serial_no or location.batch_no
key = (
location.item_code,
location.warehouse,
location.uom,
location.batch_no,
location.serial_no,
location.sales_order_item or location.material_request_item,
bundle,
)
if key not in updated_locations:
@ -645,7 +681,9 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
"qty": qty,
"stock_qty": stock_qty,
"warehouse": item_location.warehouse,
"serial_and_batch_bundle": item_location.serial_and_batch_bundle,
"serial_no": item_location.serial_no,
"batch_no": item_location.batch_no,
"use_serial_batch_fields": 1,
}
)
)
@ -673,6 +711,7 @@ def get_available_item_locations(
company,
ignore_validation=False,
picked_item_details=None,
consider_rejected_warehouses=False,
):
locations = []
total_picked_qty = (
@ -681,7 +720,16 @@ def get_available_item_locations(
has_serial_no = frappe.get_cached_value("Item", item_code, "has_serial_no")
has_batch_no = frappe.get_cached_value("Item", item_code, "has_batch_no")
if has_serial_no:
if has_batch_no and has_serial_no:
locations = get_available_item_locations_for_serial_and_batched_item(
item_code,
from_warehouses,
required_qty,
company,
total_picked_qty,
consider_rejected_warehouses=consider_rejected_warehouses,
)
elif has_serial_no:
locations = get_available_item_locations_for_serialized_item(
item_code, from_warehouses, required_qty, company, total_picked_qty
)
@ -724,6 +772,49 @@ def get_available_item_locations(
return locations
def get_available_item_locations_for_serial_and_batched_item(
item_code,
from_warehouses,
required_qty,
company,
total_picked_qty=0,
consider_rejected_warehouses=False,
):
# Get batch nos by FIFO
locations = get_available_item_locations_for_batched_item(
item_code,
from_warehouses,
required_qty,
company,
consider_rejected_warehouses=consider_rejected_warehouses,
)
if locations:
sn = frappe.qb.DocType("Serial No")
conditions = (sn.item_code == item_code) & (sn.company == company)
for location in locations:
location.qty = (
required_qty if location.qty > required_qty else location.qty
) # if extra qty in batch
serial_nos = (
frappe.qb.from_(sn)
.select(sn.name)
.where(
(conditions) & (sn.batch_no == location.batch_no) & (sn.warehouse == location.warehouse)
)
.orderby(sn.purchase_date)
.limit(ceil(location.qty + total_picked_qty))
).run(as_dict=True)
serial_nos = [sn.name for sn in serial_nos]
location.serial_no = serial_nos
location.qty = len(serial_nos)
return locations
def get_available_item_locations_for_serialized_item(
item_code, from_warehouses, required_qty, company, total_picked_qty=0
):
@ -760,25 +851,12 @@ def get_available_item_locations_for_serialized_item(
for warehouse, serial_nos in warehouse_serial_nos_map.items():
qty = len(serial_nos)
bundle_doc = SerialBatchCreation(
{
"item_code": item_code,
"warehouse": warehouse,
"voucher_type": "Pick List",
"total_qty": qty * -1,
"serial_nos": serial_nos,
"type_of_transaction": "Outward",
"company": company,
"do_not_submit": True,
}
).make_serial_and_batch_bundle()
locations.append(
{
"qty": qty,
"warehouse": warehouse,
"item_code": item_code,
"serial_and_batch_bundle": bundle_doc.name,
"serial_nos": serial_nos,
}
)
@ -808,29 +886,15 @@ def get_available_item_locations_for_batched_item(
warehouse_wise_batches[d.warehouse][d.batch_no] += d.qty
for warehouse, batches in warehouse_wise_batches.items():
qty = sum(batches.values())
bundle_doc = SerialBatchCreation(
{
"item_code": item_code,
"warehouse": warehouse,
"voucher_type": "Pick List",
"total_qty": qty * -1,
"batches": batches,
"type_of_transaction": "Outward",
"company": company,
"do_not_submit": True,
}
).make_serial_and_batch_bundle()
locations.append(
{
"qty": qty,
"warehouse": warehouse,
"item_code": item_code,
"serial_and_batch_bundle": bundle_doc.name,
}
)
for batch_no, qty in batches.items():
locations.append(
{
"qty": qty,
"warehouse": warehouse,
"item_code": item_code,
"batch_no": batch_no,
}
)
return locations

View File

@ -24,8 +24,11 @@
"serial_no_and_batch_section",
"pick_serial_and_batch",
"serial_and_batch_bundle",
"serial_no",
"use_serial_batch_fields",
"column_break_20",
"section_break_ecxc",
"serial_no",
"column_break_belw",
"batch_no",
"column_break_15",
"sales_order",
@ -72,19 +75,17 @@
"read_only": 1
},
{
"depends_on": "serial_no",
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Small Text",
"label": "Serial No",
"read_only": 1
"label": "Serial No"
},
{
"depends_on": "batch_no",
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch",
"read_only": 1,
"search_index": 1
},
{
@ -195,6 +196,7 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@ -204,6 +206,7 @@
"search_index": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "pick_serial_and_batch",
"fieldtype": "Button",
"label": "Pick Serial / Batch No"
@ -218,11 +221,26 @@
"print_hide": 1,
"read_only": 1,
"report_hide": 1
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_ecxc",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_belw",
"fieldtype": "Column Break"
}
],
"istable": 1,
"links": [],
"modified": "2023-07-26 12:54:15.785962",
"modified": "2024-02-04 16:12:16.257951",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List Item",

View File

@ -37,6 +37,7 @@ class PickListItem(Document):
stock_reserved_qty: DF.Float
stock_uom: DF.Link | None
uom: DF.Link | None
use_serial_batch_fields: DF.Check
warehouse: DF.Link | None
# end: auto-generated types

View File

@ -368,6 +368,7 @@ class PurchaseReceipt(BuyingController):
else:
self.db_set("status", "Completed")
self.make_bundle_using_old_serial_batch_fields()
# Updating stock ledger should always be called after updating prevdoc status,
# because updating ordered qty, reserved_qty_for_subcontract in bin
# depends upon updated ordered qty in PO

View File

@ -94,6 +94,7 @@
"section_break_45",
"add_serial_batch_bundle",
"serial_and_batch_bundle",
"use_serial_batch_fields",
"col_break5",
"add_serial_batch_for_rejected_qty",
"rejected_serial_and_batch_bundle",
@ -1003,6 +1004,7 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@ -1020,24 +1022,22 @@
{
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"read_only": 1
"label": "Serial No"
},
{
"fieldname": "rejected_serial_no",
"fieldtype": "Text",
"label": "Rejected Serial No",
"read_only": 1
"label": "Rejected Serial No"
},
{
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch",
"read_only": 1,
"search_index": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"fieldname": "rejected_serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Rejected Serial and Batch Bundle",
@ -1045,11 +1045,13 @@
"options": "Serial and Batch Bundle"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "add_serial_batch_for_rejected_qty",
"fieldtype": "Button",
"label": "Add Serial / Batch No (Rejected Qty)"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_3vxt",
"fieldtype": "Section Break"
},
@ -1058,6 +1060,7 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "add_serial_batch_bundle",
"fieldtype": "Button",
"label": "Add Serial / Batch No"
@ -1098,12 +1101,18 @@
"read_only": 1,
"report_hide": 1,
"search_index": 1
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-12-25 22:32:09.801965",
"modified": "2024-02-04 11:48:06.653771",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",

View File

@ -99,6 +99,7 @@ class PurchaseReceiptItem(Document):
supplier_part_no: DF.Data | None
total_weight: DF.Float
uom: DF.Link
use_serial_batch_fields: DF.Check
valuation_rate: DF.Currency
warehouse: DF.Link | None
weight_per_unit: DF.Float

View File

@ -1677,7 +1677,10 @@ def get_reserved_batches_for_sre(kwargs) -> dict:
query = query.where(sb_entry.batch_no == kwargs.batch_no)
if kwargs.warehouse:
query = query.where(sre.warehouse == kwargs.warehouse)
if isinstance(kwargs.warehouse, list):
query = query.where(sre.warehouse.isin(kwargs.warehouse))
else:
query = query.where(sre.warehouse == kwargs.warehouse)
if kwargs.ignore_voucher_nos:
query = query.where(sre.name.notin(kwargs.ignore_voucher_nos))

View File

@ -274,6 +274,7 @@ class StockEntry(StockController):
def on_submit(self):
self.validate_closed_subcontracting_order()
self.make_bundle_using_old_serial_batch_fields()
self.update_stock_ledger()
self.update_work_order()
self.validate_subcontract_order()

View File

@ -47,9 +47,12 @@
"amount",
"serial_no_batch",
"add_serial_batch_bundle",
"serial_and_batch_bundle",
"use_serial_batch_fields",
"col_break4",
"serial_and_batch_bundle",
"section_break_rdtg",
"serial_no",
"column_break_prps",
"batch_no",
"accounting",
"expense_account",
@ -289,27 +292,27 @@
"no_copy": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Small Text",
"label": "Serial No",
"no_copy": 1,
"oldfieldname": "serial_no",
"oldfieldtype": "Text",
"read_only": 1
"oldfieldtype": "Text"
},
{
"fieldname": "col_break4",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"no_copy": 1,
"oldfieldname": "batch_no",
"oldfieldtype": "Link",
"options": "Batch",
"read_only": 1
"options": "Batch"
},
{
"depends_on": "eval:parent.inspection_required && doc.t_warehouse",
@ -573,24 +576,41 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "add_serial_batch_bundle",
"fieldtype": "Button",
"label": "Add Serial / Batch No"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
"no_copy": 1,
"options": "Serial and Batch Bundle",
"print_hide": 1
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_rdtg",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_prps",
"fieldtype": "Column Break"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-01-12 11:56:04.626103",
"modified": "2024-02-04 16:16:47.606270",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",

View File

@ -63,6 +63,7 @@ class StockEntryDetail(Document):
transfer_qty: DF.Float
transferred_qty: DF.Float
uom: DF.Link
use_serial_batch_fields: DF.Check
valuation_rate: DF.Currency
# end: auto-generated types

View File

@ -99,6 +99,7 @@ class StockReconciliation(StockController):
)
def on_submit(self):
self.make_bundle_using_old_serial_batch_fields()
self.update_stock_ledger()
self.make_gl_entries()
self.repost_future_sle_and_gle()

View File

@ -19,11 +19,14 @@
"allow_zero_valuation_rate",
"serial_no_and_batch_section",
"add_serial_batch_bundle",
"serial_and_batch_bundle",
"batch_no",
"use_serial_batch_fields",
"column_break_11",
"serial_and_batch_bundle",
"current_serial_and_batch_bundle",
"section_break_lypk",
"serial_no",
"column_break_eefq",
"batch_no",
"section_break_3",
"current_qty",
"current_amount",
@ -103,10 +106,10 @@
"label": "Serial No and Batch"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Long Text",
"label": "Serial No",
"read_only": 1
"label": "Serial No"
},
{
"fieldname": "column_break_11",
@ -171,11 +174,11 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch",
"read_only": 1,
"search_index": 1
},
{
@ -195,6 +198,7 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial / Batch Bundle",
@ -204,6 +208,7 @@
"search_index": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "current_serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Current Serial / Batch Bundle",
@ -212,6 +217,7 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "add_serial_batch_bundle",
"fieldtype": "Button",
"label": "Add Serial / Batch No"
@ -222,11 +228,26 @@
"fieldtype": "Link",
"label": "Item Group",
"options": "Item Group"
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_lypk",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_eefq",
"fieldtype": "Column Break"
}
],
"istable": 1,
"links": [],
"modified": "2024-01-14 10:04:23.599951",
"modified": "2024-02-04 16:19:44.576022",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",

View File

@ -26,6 +26,7 @@ class StockReconciliationItem(Document):
current_valuation_rate: DF.Currency
has_item_scanned: DF.Data | None
item_code: DF.Link
item_group: DF.Link | None
item_name: DF.Data | None
parent: DF.Data
parentfield: DF.Data
@ -34,6 +35,7 @@ class StockReconciliationItem(Document):
quantity_difference: DF.ReadOnly | None
serial_and_batch_bundle: DF.Link | None
serial_no: DF.LongText | None
use_serial_batch_fields: DF.Check
valuation_rate: DF.Currency
warehouse: DF.Link
# end: auto-generated types

View File

@ -50,6 +50,7 @@
"disable_serial_no_and_batch_selector",
"use_naming_series",
"naming_series_prefix",
"use_serial_batch_fields",
"stock_planning_tab",
"auto_material_request",
"auto_indent",
@ -420,6 +421,12 @@
"fieldname": "auto_reserve_stock_for_sales_order_on_purchase",
"fieldtype": "Check",
"label": "Auto Reserve Stock for Sales Order on Purchase"
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial / Batch Fields"
}
],
"icon": "icon-cog",
@ -427,7 +434,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-01-30 14:03:52.143457",
"modified": "2024-02-04 12:01:31.931864",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",

View File

@ -57,6 +57,7 @@ class StockSettings(Document):
stock_uom: DF.Link | None
update_existing_price_list_rate: DF.Check
use_naming_series: DF.Check
use_serial_batch_fields: DF.Check
valuation_method: DF.Literal["FIFO", "Moving Average", "LIFO"]
# end: auto-generated types
@ -68,6 +69,7 @@ class StockSettings(Document):
"allow_negative_stock",
"default_warehouse",
"set_qty_in_transactions_based_on_serial_no_input",
"use_serial_batch_fields",
]:
frappe.db.set_default(key, self.get(key, ""))

View File

@ -794,6 +794,9 @@ class SerialBatchCreation:
setattr(self, "actual_qty", qty)
self.__dict__["actual_qty"] = self.actual_qty
if not hasattr(self, "use_serial_batch_fields"):
setattr(self, "use_serial_batch_fields", 0)
def duplicate_package(self):
if not self.serial_and_batch_bundle:
return
@ -902,9 +905,14 @@ class SerialBatchCreation:
self.batches = get_available_batches(kwargs)
def set_auto_serial_batch_entries_for_inward(self):
print(self.get("serial_nos"))
if (self.get("batches") and self.has_batch_no) or (
self.get("serial_nos") and self.has_serial_no
):
if self.use_serial_batch_fields and self.get("serial_nos"):
self.make_serial_no_if_not_exists()
return
self.batch_no = None
@ -916,6 +924,59 @@ class SerialBatchCreation:
else:
self.batches = frappe._dict({self.batch_no: abs(self.actual_qty)})
def make_serial_no_if_not_exists(self):
non_exists_serial_nos = []
for row in self.serial_nos:
if not frappe.db.exists("Serial No", row):
non_exists_serial_nos.append(row)
if non_exists_serial_nos:
self.make_serial_nos(non_exists_serial_nos)
def make_serial_nos(self, serial_nos):
serial_nos_details = []
batch_no = None
if self.batches:
batch_no = list(self.batches.keys())[0]
for serial_no in serial_nos:
serial_nos_details.append(
(
serial_no,
serial_no,
now(),
now(),
frappe.session.user,
frappe.session.user,
self.warehouse,
self.company,
self.item_code,
self.item_name,
self.description,
"Active",
batch_no,
)
)
if serial_nos_details:
fields = [
"name",
"serial_no",
"creation",
"modified",
"owner",
"modified_by",
"warehouse",
"company",
"item_code",
"item_name",
"description",
"status",
"batch_no",
]
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
def set_serial_batch_entries(self, doc):
if self.get("serial_nos"):
serial_no_wise_batch = frappe._dict({})

View File

@ -149,6 +149,7 @@ class SubcontractingReceipt(SubcontractingController):
self.update_prevdoc_status()
self.set_subcontracting_order_status()
self.set_consumed_qty_in_subcontract_order()
self.make_bundle_using_old_serial_batch_fields()
self.update_stock_ledger()
self.make_gl_entries()
self.repost_future_sle_and_gle()

View File

@ -48,11 +48,14 @@
"reference_name",
"section_break_45",
"serial_and_batch_bundle",
"serial_no",
"use_serial_batch_fields",
"col_break5",
"rejected_serial_and_batch_bundle",
"batch_no",
"section_break_jshh",
"serial_no",
"rejected_serial_no",
"column_break_henr",
"batch_no",
"manufacture_details",
"manufacturer",
"column_break_16",
@ -311,22 +314,20 @@
"label": "Serial and Batch Details"
},
{
"depends_on": "eval:!doc.is_fixed_asset",
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Small Text",
"label": "Serial No",
"no_copy": 1,
"read_only": 1
"no_copy": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset",
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"no_copy": 1,
"options": "Batch",
"print_hide": 1,
"read_only": 1
"print_hide": 1
},
{
"depends_on": "eval: !parent.is_return",
@ -478,6 +479,7 @@
"label": "Accounting Details"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@ -486,6 +488,7 @@
"print_hide": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "rejected_serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Rejected Serial and Batch Bundle",
@ -546,12 +549,27 @@
"fieldtype": "Check",
"label": "Include Exploded Items",
"print_hide": 1
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_jshh",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_henr",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-11-30 12:05:51.920705",
"modified": "2024-02-04 16:23:30.374865",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Item",

View File

@ -58,6 +58,7 @@ class SubcontractingReceiptItem(Document):
subcontracting_order: DF.Link | None
subcontracting_order_item: DF.Data | None
subcontracting_receipt_item: DF.Data | None
use_serial_batch_fields: DF.Check
warehouse: DF.Link | None
# end: auto-generated types

View File

@ -26,10 +26,13 @@
"current_stock",
"secbreak_3",
"serial_and_batch_bundle",
"batch_no",
"use_serial_batch_fields",
"col_break4",
"subcontracting_order",
"section_break_zwnh",
"serial_no",
"subcontracting_order"
"column_break_qibi",
"batch_no"
],
"fields": [
{
@ -60,19 +63,19 @@
"width": "300px"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"no_copy": 1,
"options": "Batch",
"read_only": 1
"options": "Batch"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"no_copy": 1,
"read_only": 1
"no_copy": 1
},
{
"fieldname": "col_break1",
@ -198,6 +201,7 @@
},
{
"columns": 2,
"depends_on": "eval:doc.use_serial_batch_fields === 0",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"in_list_view": 1,
@ -205,12 +209,27 @@
"no_copy": 1,
"options": "Serial and Batch Bundle",
"print_hide": 1
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_zwnh",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_qibi",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-03-15 13:55:08.132626",
"modified": "2024-02-04 16:32:17.534162",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Supplied Item",

View File

@ -35,6 +35,7 @@ class SubcontractingReceiptSuppliedItem(Document):
serial_no: DF.Text | None
stock_uom: DF.Link | None
subcontracting_order: DF.Link | None
use_serial_batch_fields: DF.Check
# end: auto-generated types
pass