fix: Added report 'Serial and Batch Summary' to view serial / batch nos
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 543d0e9..6410333 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -358,12 +358,14 @@
}
refresh() {
+
erpnext.toggle_naming_series();
erpnext.hide_company();
this.set_dynamic_labels();
this.setup_sms();
this.setup_quality_inspection();
this.validate_has_items();
+ erpnext.utils.view_serial_batch_nos(this.frm);
}
scan_barcode() {
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index a859a67..29c8aa0 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -113,6 +113,23 @@
}
},
+ view_serial_batch_nos: function(frm) {
+ let bundle_ids = frm.doc.items.filter(d => d.serial_and_batch_bundle);
+
+ if (bundle_ids?.length) {
+ frm.add_custom_button(__('Serial / Batch Nos'), () => {
+ frappe.route_options = {
+ "voucher_no": frm.doc.name,
+ "voucher_type": frm.doc.doctype,
+ "from_date": frm.doc.posting_date || frm.doc.transaction_date,
+ "to_date": frm.doc.posting_date || frm.doc.transaction_date,
+ "company": frm.doc.company,
+ };
+ frappe.set_route("query-report", "Serial and Batch Summary");
+ }, __('View'));
+ }
+ },
+
add_indicator_for_multicompany: function(frm, info) {
frm.dashboard.stats_area.show();
frm.dashboard.stats_area_row.addClass('flex');
@@ -1011,4 +1028,4 @@
$btn.on("click", function() {
context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true);
});
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
index 6955c76..c5b96ff 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
@@ -193,7 +193,7 @@
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Naming Series",
- "options": "SBB-.####"
+ "options": "SABB-.########"
},
{
"default": "0",
@@ -244,7 +244,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-04-10 20:02:42.964309",
+ "modified": "2023-07-16 10:53:04.045605",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial and Batch Bundle",
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 0c6d33b..43bd7ac 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -889,13 +889,16 @@
@frappe.whitelist()
-def get_serial_batch_ledgers(item_code, docstatus=None, voucher_no=None, name=None):
- filters = get_filters_for_bundle(item_code, docstatus=docstatus, voucher_no=voucher_no, name=name)
+def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, name=None):
+ filters = get_filters_for_bundle(
+ item_code=item_code, docstatus=docstatus, voucher_no=voucher_no, name=name
+ )
return frappe.get_all(
"Serial and Batch Bundle",
fields=[
"`tabSerial and Batch Bundle`.`name`",
+ "`tabSerial and Batch Bundle`.`item_code`",
"`tabSerial and Batch Entry`.`qty`",
"`tabSerial and Batch Entry`.`warehouse`",
"`tabSerial and Batch Entry`.`batch_no`",
@@ -906,12 +909,14 @@
)
-def get_filters_for_bundle(item_code, docstatus=None, voucher_no=None, name=None):
+def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name=None):
filters = [
- ["Serial and Batch Bundle", "item_code", "=", item_code],
["Serial and Batch Bundle", "is_cancelled", "=", 0],
]
+ if item_code:
+ filters.append(["Serial and Batch Bundle", "item_code", "=", item_code])
+
if not docstatus:
docstatus = [0, 1]
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 403e04a..3e83faf 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -925,6 +925,7 @@
this.toggle_related_fields(this.frm.doc);
this.toggle_enable_bom();
this.show_stock_ledger();
+ erpnext.utils.view_serial_batch_nos(this.frm);
if (this.frm.doc.docstatus===1 && erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
this.show_general_ledger();
}
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 0664c29..cb2adf1 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -337,6 +337,7 @@
refresh() {
if(this.frm.doc.docstatus > 0) {
this.show_stock_ledger();
+ erpnext.utils.view_serial_batch_nos(this.frm);
if (erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
this.show_general_ledger();
}
diff --git a/erpnext/stock/report/serial_and_batch_summary/__init__.py b/erpnext/stock/report/serial_and_batch_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/serial_and_batch_summary/__init__.py
diff --git a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js
new file mode 100644
index 0000000..10e5925
--- /dev/null
+++ b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js
@@ -0,0 +1,95 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Serial and Batch Summary"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ },
+ {
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.get_today()
+ },
+ {
+ "fieldname":"item_code",
+ "label": __("Item"),
+ "fieldtype": "Link",
+ "options": "Item",
+ },
+ {
+ "fieldname":"warehouse",
+ "label": __("Warehouse"),
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ },
+ {
+ "fieldname":"voucher_type",
+ "label": __("Voucher Type"),
+ "fieldtype": "Link",
+ "options": "DocType",
+ get_query: function() {
+ return {
+ query: "erpnext.stock.report.serial_and_batch_summary.serial_and_batch_summary.get_voucher_type",
+ }
+ }
+ },
+ {
+ "fieldname":"voucher_no",
+ "label": __("Voucher No"),
+ "fieldtype": "MultiSelectList",
+ get_data: function(txt) {
+ if (!frappe.query_report.filters) return;
+
+ let voucher_type = frappe.query_report.get_filter_value('voucher_type');
+ if (!voucher_type) return;
+
+ return frappe.db.get_link_options(voucher_type, txt);
+ },
+ },
+ {
+ "fieldname":"serial_no",
+ "label": __("Serial No"),
+ "fieldtype": "Link",
+ "options": "Serial No",
+ get_query: function() {
+ return {
+ query: "erpnext.stock.report.serial_and_batch_summary.serial_and_batch_summary.get_serial_nos",
+ filters: {
+ "item_code": frappe.query_report.get_filter_value('item_code'),
+ "voucher_type": frappe.query_report.get_filter_value('voucher_type'),
+ "voucher_no": frappe.query_report.get_filter_value('voucher_no'),
+ }
+ }
+ }
+ },
+ {
+ "fieldname":"batch_no",
+ "label": __("Batch No"),
+ "fieldtype": "Link",
+ "options": "Batch",
+ get_query: function() {
+ return {
+ query: "erpnext.stock.report.serial_and_batch_summary.serial_and_batch_summary.get_batch_nos",
+ filters: {
+ "item_code": frappe.query_report.get_filter_value('item_code'),
+ "voucher_type": frappe.query_report.get_filter_value('voucher_type'),
+ "voucher_no": frappe.query_report.get_filter_value('voucher_no'),
+ }
+ }
+ }
+ }
+ ]
+};
diff --git a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.json b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.json
new file mode 100644
index 0000000..7511e3a
--- /dev/null
+++ b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.json
@@ -0,0 +1,38 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2023-07-13 16:53:27.735091",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "modified": "2023-07-13 16:53:33.204591",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Serial and Batch Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Serial and Batch Bundle",
+ "report_name": "Serial and Batch Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "System Manager"
+ },
+ {
+ "role": "Sales User"
+ },
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Maintenance User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py
new file mode 100644
index 0000000..3ea5e82
--- /dev/null
+++ b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py
@@ -0,0 +1,245 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+
+
+def execute(filters=None):
+ data = get_data(filters)
+ columns = get_columns(filters, data)
+
+ return columns, data
+
+
+def get_data(filters):
+ filter_conditions = get_filter_conditions(filters)
+
+ return frappe.get_all(
+ "Serial and Batch Bundle",
+ fields=[
+ "`tabSerial and Batch Bundle`.`voucher_type`",
+ "`tabSerial and Batch Bundle`.`posting_date`",
+ "`tabSerial and Batch Bundle`.`name`",
+ "`tabSerial and Batch Bundle`.`company`",
+ "`tabSerial and Batch Bundle`.`voucher_no`",
+ "`tabSerial and Batch Bundle`.`item_code`",
+ "`tabSerial and Batch Bundle`.`item_name`",
+ "`tabSerial and Batch Entry`.`serial_no`",
+ "`tabSerial and Batch Entry`.`batch_no`",
+ "`tabSerial and Batch Entry`.`warehouse`",
+ "`tabSerial and Batch Entry`.`incoming_rate`",
+ "`tabSerial and Batch Entry`.`stock_value_difference`",
+ "`tabSerial and Batch Entry`.`qty`",
+ ],
+ filters=filter_conditions,
+ order_by="posting_date",
+ )
+
+
+def get_filter_conditions(filters):
+ filter_conditions = [
+ ["Serial and Batch Bundle", "docstatus", "=", 1],
+ ["Serial and Batch Bundle", "is_cancelled", "=", 0],
+ ]
+
+ for field in ["voucher_type", "voucher_no", "item_code", "warehouse", "company"]:
+ if filters.get(field):
+ if field == "voucher_no":
+ filter_conditions.append(["Serial and Batch Bundle", field, "in", filters.get(field)])
+ else:
+ filter_conditions.append(["Serial and Batch Bundle", field, "=", filters.get(field)])
+
+ if filters.get("from_date") and filters.get("to_date"):
+ filter_conditions.append(
+ [
+ "Serial and Batch Bundle",
+ "posting_date",
+ "between",
+ [filters.get("from_date"), filters.get("to_date")],
+ ]
+ )
+
+ for field in ["serial_no", "batch_no"]:
+ if filters.get(field):
+ filter_conditions.append(["Serial and Batch Entry", field, "=", filters.get(field)])
+
+ return filter_conditions
+
+
+def get_columns(filters, data):
+ columns = [
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 120,
+ },
+ {
+ "label": _("Serial and Batch Bundle"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Serial and Batch Bundle",
+ "width": 110,
+ },
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
+ ]
+
+ item_details = {}
+
+ item_codes = []
+ if filters.get("voucher_type"):
+ item_codes = [d.item_code for d in data]
+
+ if filters.get("item_code") or (item_codes and len(list(set(item_codes))) == 1):
+ item_details = frappe.get_cached_value(
+ "Item",
+ filters.get("item_code") or item_codes[0],
+ ["has_serial_no", "has_batch_no"],
+ as_dict=True,
+ )
+
+ if not filters.get("voucher_no"):
+ columns.extend(
+ [
+ {
+ "label": _("Voucher Type"),
+ "fieldname": "voucher_type",
+ "fieldtype": "Link",
+ "options": "DocType",
+ "width": 120,
+ },
+ {
+ "label": _("Voucher No"),
+ "fieldname": "voucher_no",
+ "fieldtype": "Dynamic Link",
+ "options": "voucher_type",
+ "width": 160,
+ },
+ ]
+ )
+
+ if not filters.get("item_code"):
+ columns.extend(
+ [
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120,
+ },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 120},
+ ]
+ )
+
+ if not filters.get("warehouse"):
+ columns.append(
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 120,
+ }
+ )
+
+ if not item_details or item_details.get("has_serial_no"):
+ columns.append(
+ {"label": _("Serial No"), "fieldname": "serial_no", "fieldtype": "Data", "width": 120}
+ )
+
+ if not item_details or item_details.get("has_batch_no"):
+ columns.extend(
+ [
+ {"label": _("Batch No"), "fieldname": "batch_no", "fieldtype": "Data", "width": 120},
+ {"label": _("Batch Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 120},
+ ]
+ )
+
+ columns.extend(
+ [
+ {"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Float", "width": 120},
+ {
+ "label": _("Change in Stock Value"),
+ "fieldname": "stock_value_difference",
+ "fieldtype": "Float",
+ "width": 120,
+ },
+ ]
+ )
+
+ return columns
+
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_voucher_type(doctype, txt, searchfield, start, page_len, filters):
+ child_doctypes = frappe.get_all(
+ "DocField",
+ filters={"fieldname": "serial_and_batch_bundle"},
+ fields=["distinct parent as parent"],
+ )
+
+ query_filters = {"options": ["in", [d.parent for d in child_doctypes]]}
+ if txt:
+ query_filters["parent"] = ["like", "%{}%".format(txt)]
+
+ return frappe.get_all("DocField", filters=query_filters, fields=["distinct parent"], as_list=True)
+
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_serial_nos(doctype, txt, searchfield, start, page_len, filters):
+ query_filters = {}
+
+ if txt:
+ query_filters["serial_no"] = ["like", f"%{txt}%"]
+
+ if filters.get("voucher_no"):
+ serial_batch_bundle = frappe.get_cached_value(
+ "Serial and Batch Bundle",
+ {"voucher_no": ("in", filters.get("voucher_no")), "docstatus": 1, "is_cancelled": 0},
+ "name",
+ )
+
+ query_filters["parent"] = serial_batch_bundle
+ if not txt:
+ query_filters["serial_no"] = ("is", "set")
+
+ return frappe.get_all(
+ "Serial and Batch Entry", filters=query_filters, fields=["serial_no"], as_list=True
+ )
+
+ else:
+ query_filters["item_code"] = filters.get("item_code")
+ return frappe.get_all("Serial No", filters=query_filters, as_list=True)
+
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_batch_nos(doctype, txt, searchfield, start, page_len, filters):
+ query_filters = {}
+
+ if txt:
+ query_filters["batch_no"] = ["like", f"%{txt}%"]
+
+ if filters.get("voucher_no"):
+ serial_batch_bundle = frappe.get_cached_value(
+ "Serial and Batch Bundle",
+ {"voucher_no": ("in", filters.get("voucher_no")), "docstatus": 1, "is_cancelled": 0},
+ "name",
+ )
+
+ query_filters["parent"] = serial_batch_bundle
+ if not txt:
+ query_filters["batch_no"] = ("is", "set")
+
+ return frappe.get_all(
+ "Serial and Batch Entry", filters=query_filters, fields=["batch_no"], as_list=True
+ )
+
+ else:
+ query_filters["item"] = filters.get("item_code")
+ return frappe.get_all("Batch", filters=query_filters, as_list=True)