Merge pull request #45706 from frappe/mergify/bp/version-15-hotfix/pr-45698

feat: report to find incorrect SABB (backport #45698)
This commit is contained in:
rohitwaghchaure
2025-02-04 15:45:40 +05:30
committed by GitHub
5 changed files with 211 additions and 1 deletions

View File

@@ -169,7 +169,7 @@ class DeprecatedBatchNoValuation:
if not self.non_batchwise_balance_qty:
continue
if self.non_batchwise_balance_qty.get(batch_no) == 0:
if not self.non_batchwise_balance_qty.get(batch_no):
self.batch_avg_rate[batch_no] = 0.0
self.stock_value_differece[batch_no] = 0.0
else:

View File

@@ -0,0 +1,47 @@
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.query_reports["Incorrect Serial and Batch Bundle"] = {
filters: [
{
fieldname: "item_code",
label: __("Item Code"),
fieldtype: "Link",
options: "Item",
},
{
fieldname: "warehouse",
label: __("Warehouse"),
fieldtype: "Link",
options: "Warehouse",
},
],
get_datatable_options(options) {
return Object.assign(options, {
checkboxColumn: true,
});
},
onload(report) {
report.page.add_inner_button(__("Remove SABB Entry"), () => {
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
let selected_rows = indexes.map((i) => frappe.query_report.data[i]);
if (!selected_rows.length) {
frappe.throw(__("Please select a row to create a Reposting Entry"));
} else {
frappe.call({
method: "erpnext.stock.report.incorrect_serial_and_batch_bundle.incorrect_serial_and_batch_bundle.remove_sabb_entry",
freeze: true,
args: {
selected_rows: selected_rows,
},
callback: function (r) {
frappe.query_report.refresh();
},
});
}
});
},
};

View File

@@ -0,0 +1,56 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2025-02-03 15:39:44.521366",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"json": "{}",
"letter_head": "Test",
"letterhead": null,
"modified": "2025-02-03 15:39:47.613040",
"modified_by": "Administrator",
"module": "Stock",
"name": "Incorrect Serial and Batch Bundle",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Serial and Batch Bundle",
"report_name": "Incorrect Serial and Batch Bundle",
"report_type": "Script Report",
"roles": [
{
"role": "Stock User"
},
{
"role": "Purchase Manager"
},
{
"role": "Delivery Manager"
},
{
"role": "System Manager"
},
{
"role": "Delivery User"
},
{
"role": "Manufacturing User"
},
{
"role": "Purchase User"
},
{
"role": "Stock Manager"
},
{
"role": "Manufacturing Manager"
},
{
"role": "Maintenance User"
}
],
"timeout": 0
}

View File

@@ -0,0 +1,107 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
def execute(filters: dict | None = None):
"""Return columns and data for the report.
This is the main entry point for the report. It accepts the filters as a
dictionary and should return columns and data. It is called by the framework
every time the report is refreshed or a filter is updated.
"""
columns = get_columns()
data = get_data(filters)
return columns, data
def get_columns() -> list[dict]:
"""Return columns for the report.
One field definition per column, just like a DocType field definition.
"""
return [
{
"label": _("Serial and Batch Bundle"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Serial and Batch Bundle",
"width": 200,
},
{
"label": _("Voucher Type"),
"fieldname": "voucher_type",
"fieldtype": "Data",
"width": 200,
},
{
"label": _("Voucher No"),
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"options": "voucher_type",
"width": 200,
},
{
"label": _("Voucher Detail No"),
"fieldname": "voucher_detail_no",
"fieldtype": "Data",
"width": 200,
},
]
def get_data(filters) -> list[list]:
"""Return data for the report.
The report data is a list of rows, with each row being a list of cell values.
"""
SABB = frappe.qb.DocType("Serial And Batch Bundle")
SLE = frappe.qb.DocType("Stock Ledger Entry")
ignore_voycher_types = [
"Installation Note",
"Job Card",
"Maintenance Schedule",
"Pick List",
]
query = (
frappe.qb.from_(SABB)
.left_join(SLE)
.on(SABB.name == SLE.serial_and_batch_bundle)
.select(
SABB.name,
SABB.voucher_type,
SABB.voucher_no,
SABB.voucher_detail_no,
)
.where(
(SLE.serial_and_batch_bundle.isnull())
& (SABB.docstatus == 1)
& (SABB.is_cancelled == 0)
& (SABB.voucher_type.notin(ignore_voycher_types))
)
)
for field in filters:
query = query.where(SABB[field] == filters[field])
data = query.run(as_dict=1)
return data
@frappe.whitelist()
def remove_sabb_entry(selected_rows):
if isinstance(selected_rows, str):
selected_rows = frappe.parse_json(selected_rows)
for row in selected_rows:
doc = frappe.get_doc("Serial and Batch Bundle", row.get("name"))
doc.cancel()
doc.delete()
frappe.msgprint(_("Selected Serial and Batch Bundle entries have been removed."))