fix: Added report 'Serial and Batch Summary' to view serial / batch nos

This commit is contained in:
Rohit Waghchaure 2023-07-10 16:54:55 +05:30
parent b16f364866
commit 708eefb383
10 changed files with 411 additions and 7 deletions

View File

@ -358,12 +358,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
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() {

View File

@ -113,6 +113,23 @@ $.extend(erpnext.utils, {
}
},
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');

View File

@ -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",

View File

@ -889,13 +889,16 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
@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_serial_batch_ledgers(item_code, docstatus=None, voucher_no=None, name=No
)
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]

View File

@ -925,6 +925,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
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();
}

View File

@ -337,6 +337,7 @@ erpnext.stock.StockReconciliation = class StockReconciliation extends erpnext.st
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();
}

View File

@ -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'),
}
}
}
}
]
};

View File

@ -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"
}
]
}

View File

@ -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)