mirror of
https://github.com/frappe/erpnext.git
synced 2026-03-08 04:53:26 +00:00
feat: Negative Batch report
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Negative Batch Report"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_default("company"),
|
||||
},
|
||||
{
|
||||
fieldname: "item_code",
|
||||
label: __("Item Code"),
|
||||
fieldtype: "Link",
|
||||
options: "Item",
|
||||
get_query: function () {
|
||||
return {
|
||||
filters: {
|
||||
has_batch_no: 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "warehouse",
|
||||
label: __("Warehouse"),
|
||||
fieldtype: "Link",
|
||||
options: "Warehouse",
|
||||
get_query: function () {
|
||||
return {
|
||||
filters: {
|
||||
is_group: 0,
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"add_translate_data": 0,
|
||||
"columns": [],
|
||||
"creation": "2026-02-17 11:34:21.549485",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"json": "",
|
||||
"letter_head": null,
|
||||
"modified": "2026-02-17 11:34:59.106045",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Negative Batch Report",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Serial and Batch Bundle",
|
||||
"report_name": "Negative Batch Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"role": "Purchase Manager"
|
||||
},
|
||||
{
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"role": "Stock Manager"
|
||||
},
|
||||
{
|
||||
"role": "Delivery User"
|
||||
},
|
||||
{
|
||||
"role": "Delivery Manager"
|
||||
},
|
||||
{
|
||||
"role": "Manufacturing User"
|
||||
},
|
||||
{
|
||||
"role": "Manufacturing Manager"
|
||||
}
|
||||
],
|
||||
"timeout": 0
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import add_to_date, flt, today
|
||||
|
||||
from erpnext.stock.report.stock_ledger.stock_ledger import execute as stock_ledger_execute
|
||||
|
||||
|
||||
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 [
|
||||
{
|
||||
"label": _("Posting Datetime"),
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Datetime",
|
||||
"width": 160,
|
||||
},
|
||||
{
|
||||
"label": _("Batch No"),
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"options": "Batch",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Item Code"),
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 150,
|
||||
},
|
||||
{
|
||||
"label": _("Warehouse"),
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"width": 160,
|
||||
},
|
||||
{
|
||||
"label": _("Previous Qty"),
|
||||
"fieldname": "previous_qty",
|
||||
"fieldtype": "Float",
|
||||
"width": 130,
|
||||
},
|
||||
{
|
||||
"label": _("Transaction Qty"),
|
||||
"fieldname": "actual_qty",
|
||||
"fieldtype": "Float",
|
||||
"width": 130,
|
||||
},
|
||||
{
|
||||
"label": _("Qty After Transaction"),
|
||||
"fieldname": "qty_after_transaction",
|
||||
"fieldtype": "Float",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("Document Type"),
|
||||
"fieldname": "voucher_type",
|
||||
"fieldtype": "Data",
|
||||
"width": 130,
|
||||
},
|
||||
{
|
||||
"label": _("Document No"),
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "voucher_type",
|
||||
"width": 130,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_data(filters) -> list[dict]:
|
||||
batches = get_batches(filters)
|
||||
companies = get_companies(filters)
|
||||
batch_negative_data = []
|
||||
|
||||
flt_precision = frappe.db.get_default("float_precision") or 2
|
||||
for company in companies:
|
||||
for batch in batches:
|
||||
_c, data = stock_ledger_execute(
|
||||
frappe._dict(
|
||||
{
|
||||
"company": company,
|
||||
"batch_no": batch,
|
||||
"from_date": add_to_date(today(), years=-12),
|
||||
"to_date": today(),
|
||||
"segregate_serial_batch_bundle": 1,
|
||||
"warehouse": filters.get("warehouse"),
|
||||
"valuation_field_type": "Currency",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
previous_qty = 0
|
||||
for row in data:
|
||||
if flt(row.get("qty_after_transaction"), flt_precision) < 0:
|
||||
batch_negative_data.append(
|
||||
{
|
||||
"posting_date": row.get("date"),
|
||||
"batch_no": row.get("batch_no"),
|
||||
"item_code": row.get("item_code"),
|
||||
"item_name": row.get("item_name"),
|
||||
"warehouse": row.get("warehouse"),
|
||||
"actual_qty": row.get("actual_qty"),
|
||||
"qty_after_transaction": row.get("qty_after_transaction"),
|
||||
"previous_qty": previous_qty,
|
||||
"voucher_type": row.get("voucher_type"),
|
||||
"voucher_no": row.get("voucher_no"),
|
||||
}
|
||||
)
|
||||
|
||||
previous_qty = row.get("qty_after_transaction")
|
||||
|
||||
return batch_negative_data
|
||||
|
||||
|
||||
def get_batches(filters):
|
||||
batch_filters = {}
|
||||
if filters.get("item_code"):
|
||||
batch_filters["item"] = filters["item_code"]
|
||||
|
||||
return frappe.get_all("Batch", pluck="name", filters=batch_filters)
|
||||
|
||||
|
||||
def get_companies(filters):
|
||||
company_filters = {}
|
||||
if filters.get("company"):
|
||||
company_filters["name"] = filters["company"]
|
||||
|
||||
return frappe.get_all("Company", pluck="name", filters=company_filters)
|
||||
Reference in New Issue
Block a user