mirror of
https://github.com/frappe/erpnext.git
synced 2026-03-29 19:55:41 +00:00
Merge pull request #53204 from frappe/mergify/bp/version-16-hotfix/pr-52549
feat: option to enable serial / batch feature (backport #52549)
This commit is contained in:
@@ -16,6 +16,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
|
||||
refresh() {
|
||||
this.show_general_ledger();
|
||||
erpnext.toggle_serial_batch_fields(this.frm);
|
||||
|
||||
if (
|
||||
(this.frm.doc.stock_items && this.frm.doc.stock_items.length) ||
|
||||
|
||||
@@ -469,3 +469,4 @@ erpnext.patches.v15_0.delete_quotation_lost_record_detail
|
||||
erpnext.patches.v16_0.add_portal_redirects
|
||||
erpnext.patches.v16_0.complete_onboarding_steps_for_older_sites #2
|
||||
erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po
|
||||
erpnext.patches.v16_0.enable_serial_batch_setting
|
||||
|
||||
9
erpnext/patches/v16_0/enable_serial_batch_setting.py
Normal file
9
erpnext/patches/v16_0/enable_serial_batch_setting.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.get_all("Serial No", limit=1) and not frappe.get_all("Batch", limit=1):
|
||||
return
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_serial_and_batch_no_for_item", 1)
|
||||
frappe.db.set_default("enable_serial_and_batch_no_for_item", 1)
|
||||
@@ -580,6 +580,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
this.validate_has_items();
|
||||
erpnext.utils.view_serial_batch_nos(this.frm);
|
||||
this.set_route_options_for_new_doc();
|
||||
erpnext.toggle_serial_batch_fields(this.frm);
|
||||
}
|
||||
|
||||
set_route_options_for_new_doc() {
|
||||
|
||||
@@ -19,6 +19,71 @@ $.extend(erpnext, {
|
||||
return currency_list;
|
||||
},
|
||||
|
||||
toggle_serial_batch_fields(frm) {
|
||||
let hide_fields = cint(frappe.user_defaults?.enable_serial_and_batch_no_for_item) === 0 ? 1 : 0;
|
||||
let fields = ["serial_and_batch_bundle", "use_serial_batch_fields", "serial_no", "batch_no"];
|
||||
|
||||
if (
|
||||
[
|
||||
"Stock Entry",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
"Stock Reconciliation",
|
||||
"Subcontracting Receipt",
|
||||
].includes(frm.doc.doctype)
|
||||
) {
|
||||
fields.push("add_serial_batch_bundle");
|
||||
}
|
||||
|
||||
if (["Stock Reconciliation"].includes(frm.doc.doctype)) {
|
||||
fields.push("reconcile_all_serial_batch");
|
||||
}
|
||||
|
||||
if (["Sales Invoice", "Delivery Note", "Pick List"].includes(frm.doc.doctype)) {
|
||||
fields.push("pick_serial_and_batch");
|
||||
}
|
||||
|
||||
if (["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(frm.doc.doctype)) {
|
||||
fields.push("add_serial_batch_for_rejected_qty", "rejected_serial_and_batch_bundle");
|
||||
}
|
||||
|
||||
let child_name = "items";
|
||||
if (frm.doc.doctype === "Pick List") {
|
||||
child_name = "locations";
|
||||
}
|
||||
|
||||
if (frm.doc.doctype === "Asset Capitalization") {
|
||||
child_name = "stock_items";
|
||||
}
|
||||
|
||||
fields.forEach((field) => {
|
||||
frm.fields_dict[child_name].grid.update_docfield_property(field, "hidden", hide_fields);
|
||||
|
||||
frm.fields_dict[child_name].grid.update_docfield_property(
|
||||
field,
|
||||
"in_list_view",
|
||||
hide_fields ? 0 : 1
|
||||
);
|
||||
|
||||
if (
|
||||
frm.doc.doctype === "Subcontracting Receipt" &&
|
||||
!["add_serial_batch_for_rejected_qty", "rejected_serial_and_batch_bundle"].includes(field)
|
||||
) {
|
||||
frm.fields_dict["supplied_items"].grid.update_docfield_property(field, "hidden", hide_fields);
|
||||
|
||||
frm.fields_dict["supplied_items"].grid.update_docfield_property(
|
||||
field,
|
||||
"in_list_view",
|
||||
hide_fields ? 0 : 1
|
||||
);
|
||||
|
||||
frm.fields_dict["supplied_items"].grid.reset_grid();
|
||||
}
|
||||
});
|
||||
|
||||
frm.fields_dict[child_name].grid.reset_grid();
|
||||
},
|
||||
|
||||
toggle_naming_series: function () {
|
||||
if (
|
||||
cur_frm &&
|
||||
|
||||
@@ -221,6 +221,8 @@ def set_defaults_for_tests():
|
||||
frappe.db.set_default(key, value)
|
||||
frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_serial_and_batch_no_for_item", 1)
|
||||
|
||||
|
||||
def insert_record(records):
|
||||
from frappe.desk.page.setup_wizard.setup_wizard import make_records
|
||||
|
||||
@@ -84,7 +84,25 @@ frappe.ui.form.on("Item", {
|
||||
}
|
||||
},
|
||||
|
||||
toggle_has_serial_batch_fields(frm) {
|
||||
let hide_fields = cint(frappe.user_defaults?.enable_serial_and_batch_no_for_item) === 0 ? 1 : 0;
|
||||
|
||||
frm.toggle_display(["serial_no_series", "batch_number_series", "create_new_batch"], !hide_fields);
|
||||
frm.toggle_enable(["has_serial_no", "has_batch_no"], !hide_fields);
|
||||
|
||||
if (hide_fields) {
|
||||
let description = __(
|
||||
"To enable the Serial No and Batch No feature, please check the 'Enable Serial / Batch No for Item' checkbox in Stock Settings."
|
||||
);
|
||||
|
||||
frm.set_df_property("has_serial_no", "description", description);
|
||||
frm.set_df_property("has_batch_no", "description", description);
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
frm.trigger("toggle_has_serial_batch_fields");
|
||||
|
||||
if (frm.doc.is_stock_item) {
|
||||
frm.add_custom_button(
|
||||
__("Stock Balance"),
|
||||
|
||||
@@ -452,6 +452,7 @@
|
||||
"fieldname": "batch_number_series",
|
||||
"fieldtype": "Data",
|
||||
"label": "Batch Number Series",
|
||||
"show_description_on_click": 1,
|
||||
"translatable": 1
|
||||
},
|
||||
{
|
||||
@@ -493,7 +494,8 @@
|
||||
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
|
||||
"fieldname": "serial_no_series",
|
||||
"fieldtype": "Data",
|
||||
"label": "Serial Number Series"
|
||||
"label": "Serial Number Series",
|
||||
"show_description_on_click": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
@@ -985,7 +987,7 @@
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2026-02-05 17:20:35.605734",
|
||||
"modified": "2026-03-05 16:29:31.653447",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -218,6 +218,7 @@ class Item(Document):
|
||||
self.validate_auto_reorder_enabled_in_stock_settings()
|
||||
self.cant_change()
|
||||
self.validate_item_tax_net_rate_range()
|
||||
self.validate_allow_to_set_serial_batch()
|
||||
|
||||
if not self.is_new():
|
||||
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
|
||||
@@ -226,6 +227,18 @@ class Item(Document):
|
||||
self.update_variants()
|
||||
self.update_item_price()
|
||||
|
||||
def validate_allow_to_set_serial_batch(self):
|
||||
if not self.has_serial_no and not self.has_batch_no:
|
||||
return
|
||||
|
||||
if not frappe.db.get_single_value("Stock Settings", "enable_serial_and_batch_no_for_item"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please check the 'Enable Serial and Batch No for Item' checkbox in the {0} to set Serial No or Batch No for the item."
|
||||
).format(get_link_to_form("Stock Settings", "Stock Settings")),
|
||||
title=_("Serial and Batch No for Item Disabled"),
|
||||
)
|
||||
|
||||
def validate_description(self):
|
||||
"""Clean HTML description if set"""
|
||||
if (
|
||||
|
||||
@@ -119,6 +119,8 @@ frappe.ui.form.on("Pick List", {
|
||||
refresh: (frm) => {
|
||||
frm.trigger("add_get_items_button");
|
||||
frm.trigger("update_warehouse_property");
|
||||
erpnext.toggle_serial_batch_fields(frm);
|
||||
|
||||
if (frm.doc.docstatus === 1) {
|
||||
const status_completed = frm.doc.status === "Completed";
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ class SerialandBatchBundle(Document):
|
||||
self.autoname()
|
||||
|
||||
def validate(self):
|
||||
self.validate_allow_to_set_serial_batch()
|
||||
if self.docstatus == 1 and self.voucher_detail_no:
|
||||
self.validate_voucher_detail_no()
|
||||
|
||||
@@ -143,6 +144,15 @@ class SerialandBatchBundle(Document):
|
||||
self.calculate_qty_and_amount()
|
||||
self.set_child_details()
|
||||
|
||||
def validate_allow_to_set_serial_batch(self):
|
||||
if not frappe.db.get_single_value("Stock Settings", "enable_serial_and_batch_no_for_item"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please check the 'Enable Serial and Batch No for Item' checkbox in the {0} to make Serial and Batch Bundle for the item."
|
||||
).format(get_link_to_form("Stock Settings", "Stock Settings")),
|
||||
title=_("Serial and Batch No for Item Disabled"),
|
||||
)
|
||||
|
||||
def validate_serial_no_status(self):
|
||||
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||
invalid_serial_nos = frappe.get_all(
|
||||
|
||||
@@ -245,6 +245,7 @@ frappe.ui.form.on("Stock Entry", {
|
||||
refresh: function (frm) {
|
||||
frm.trigger("get_items_from_transit_entry");
|
||||
frm.trigger("toggle_warehouse_fields");
|
||||
erpnext.toggle_serial_batch_fields(frm);
|
||||
|
||||
if (!frm.doc.docstatus && !frm.doc.subcontracting_inward_order) {
|
||||
frm.trigger("validate_purpose_consumption");
|
||||
|
||||
@@ -76,6 +76,8 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
erpnext.toggle_serial_batch_fields(frm);
|
||||
|
||||
if (frm.doc.docstatus < 1) {
|
||||
frm.add_custom_button(__("Fetch Items from Warehouse"), function () {
|
||||
frm.events.get_items(frm);
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"allow_internal_transfer_at_arms_length_price",
|
||||
"validate_material_transfer_warehouses",
|
||||
"serial_and_batch_item_settings_tab",
|
||||
"enable_serial_and_batch_no_for_item",
|
||||
"section_break_7",
|
||||
"allow_existing_serial_no",
|
||||
"do_not_use_batchwise_valuation",
|
||||
@@ -48,9 +49,8 @@
|
||||
"use_serial_batch_fields",
|
||||
"do_not_update_serial_batch_on_creation_of_auto_bundle",
|
||||
"allow_negative_stock_for_batch",
|
||||
"serial_and_batch_bundle_section",
|
||||
"set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||
"section_break_gnhq",
|
||||
"set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||
"use_naming_series",
|
||||
"column_break_wslv",
|
||||
"naming_series_prefix",
|
||||
@@ -158,6 +158,7 @@
|
||||
"label": "Convert Item Description to Clean HTML in Transactions"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_serial_and_batch_no_for_item",
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Serial & Batch Item Settings"
|
||||
@@ -487,11 +488,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Reserve Stock"
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_and_batch_bundle_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Serial and Batch Bundle"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||
@@ -499,6 +495,7 @@
|
||||
"label": "Set Serial and Batch Bundle Naming Based on Naming Series"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_serial_and_batch_no_for_item",
|
||||
"fieldname": "section_break_gnhq",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
@@ -554,6 +551,11 @@
|
||||
"fieldname": "allow_negative_stock_for_batch",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Negative Stock for Batch"
|
||||
},
|
||||
{
|
||||
"fieldname": "enable_serial_and_batch_no_for_item",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Serial / Batch No for Item"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
@@ -562,7 +564,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-25 09:56:34.105949",
|
||||
"modified": "2026-02-25 10:56:34.105949",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
|
||||
@@ -47,6 +47,7 @@ class StockSettings(Document):
|
||||
disable_serial_no_and_batch_selector: DF.Check
|
||||
do_not_update_serial_batch_on_creation_of_auto_bundle: DF.Check
|
||||
do_not_use_batchwise_valuation: DF.Check
|
||||
enable_serial_and_batch_no_for_item: DF.Check
|
||||
enable_stock_reservation: DF.Check
|
||||
item_group: DF.Link | None
|
||||
item_naming_by: DF.Literal["Item Code", "Naming Series"]
|
||||
@@ -82,6 +83,7 @@ class StockSettings(Document):
|
||||
"default_warehouse",
|
||||
"set_qty_in_transactions_based_on_serial_no_input",
|
||||
"use_serial_batch_fields",
|
||||
"enable_serial_and_batch_no_for_item",
|
||||
"set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||
]:
|
||||
frappe.db.set_default(key, self.get(key, ""))
|
||||
@@ -104,6 +106,7 @@ class StockSettings(Document):
|
||||
)
|
||||
|
||||
self.validate_warehouses()
|
||||
self.validate_serial_and_batch_no_settings()
|
||||
self.cant_change_valuation_method()
|
||||
self.validate_clean_description_html()
|
||||
self.validate_pending_reposts()
|
||||
@@ -112,6 +115,25 @@ class StockSettings(Document):
|
||||
self.change_precision_for_for_sales()
|
||||
self.change_precision_for_purchase()
|
||||
|
||||
def validate_serial_and_batch_no_settings(self):
|
||||
doc_before_save = self.get_doc_before_save()
|
||||
if not doc_before_save:
|
||||
return
|
||||
|
||||
if doc_before_save.enable_serial_and_batch_no_for_item == self.enable_serial_and_batch_no_for_item:
|
||||
return
|
||||
|
||||
if (
|
||||
doc_before_save.enable_serial_and_batch_no_for_item
|
||||
and not self.enable_serial_and_batch_no_for_item
|
||||
):
|
||||
if frappe.get_all("Serial and Batch Bundle", filters={"docstatus": 1}, limit=1, pluck="name"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot disable Serial and Batch No for Item, as there are existing records for serial / batch."
|
||||
)
|
||||
)
|
||||
|
||||
def validate_warehouses(self):
|
||||
warehouse_fields = ["default_warehouse", "sample_retention_warehouse"]
|
||||
for field in warehouse_fields:
|
||||
|
||||
@@ -30,6 +30,7 @@ frappe.ui.form.on("Subcontracting Receipt", {
|
||||
refresh: (frm) => {
|
||||
frappe.dynamic_link = { doc: frm.doc, fieldname: "supplier", doctype: "Supplier" };
|
||||
|
||||
erpnext.toggle_serial_batch_fields(frm);
|
||||
if (frm.doc.docstatus === 1) {
|
||||
frm.add_custom_button(
|
||||
__("Stock Ledger"),
|
||||
|
||||
Reference in New Issue
Block a user