mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-03 04:09:11 +00:00
Merge pull request #49951 from aerele/feat/update-batch-qty-report
feat(report): add batch qty update functionality in report
This commit is contained in:
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.query_reports["Stock Qty vs Batch Qty"] = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
fieldname: "item",
|
||||||
|
label: __("Item"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Item",
|
||||||
|
get_query: function () {
|
||||||
|
return {
|
||||||
|
filters: { has_batch_no: true },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "batch",
|
||||||
|
label: __("Batch"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Batch",
|
||||||
|
get_query: function () {
|
||||||
|
const item_code = frappe.query_report.get_filter_value("item");
|
||||||
|
return {
|
||||||
|
filters: { item: item_code, disabled: 0 },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onload: function (report) {
|
||||||
|
report.page.add_inner_button(__("Update Batch Qty"), function () {
|
||||||
|
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
|
||||||
|
let selected_rows = indexes
|
||||||
|
.map((i) => frappe.query_report.data[i])
|
||||||
|
.filter((row) => row.difference != 0);
|
||||||
|
|
||||||
|
if (selected_rows.length) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.stock.report.stock_qty_vs_batch_qty.stock_qty_vs_batch_qty.update_batch_qty",
|
||||||
|
args: {
|
||||||
|
selected_batches: selected_rows,
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
if (!r.exc) {
|
||||||
|
report.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
frappe.msgprint(__("Please select at least one row with difference value"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
formatter: function (value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
if (column.fieldname == "difference" && data) {
|
||||||
|
if (data.difference > 0) {
|
||||||
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
|
} else if (data.difference < 0) {
|
||||||
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
get_datatable_options(options) {
|
||||||
|
return Object.assign(options, {
|
||||||
|
checkboxColumn: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"add_translate_data": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2025-10-07 20:03:45.952352",
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"letterhead": null,
|
||||||
|
"modified": "2025-11-18 11:35:04.615085",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
|
"name": "Stock Qty vs Batch Qty",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Item",
|
||||||
|
"report_name": "Stock Qty vs Batch Qty",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Item Manager"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeout": 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
if not filters:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
columns = get_columns()
|
||||||
|
data = get_data(filters)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns() -> list[dict]:
|
||||||
|
columns = [
|
||||||
|
{
|
||||||
|
"label": _("Item Code"),
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Item",
|
||||||
|
"width": 200,
|
||||||
|
},
|
||||||
|
{"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 200},
|
||||||
|
{"label": _("Batch"), "fieldname": "batch", "fieldtype": "Link", "options": "Batch", "width": 200},
|
||||||
|
{"label": _("Batch Qty"), "fieldname": "batch_qty", "fieldtype": "Float", "width": 150},
|
||||||
|
{"label": _("Stock Qty"), "fieldname": "stock_qty", "fieldtype": "Float", "width": 150},
|
||||||
|
{"label": _("Difference"), "fieldname": "difference", "fieldtype": "Float", "width": 150},
|
||||||
|
]
|
||||||
|
|
||||||
|
return columns
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(filters=None):
|
||||||
|
filters = filters or {}
|
||||||
|
|
||||||
|
item = filters.get("item")
|
||||||
|
batch_no = filters.get("batch")
|
||||||
|
|
||||||
|
batch_sle_data = (
|
||||||
|
get_batch_qty(
|
||||||
|
item_code=item, batch_no=batch_no, for_stock_levels=True, consider_negative_batches=True
|
||||||
|
)
|
||||||
|
or []
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_qty_map = {}
|
||||||
|
for row in batch_sle_data:
|
||||||
|
batch = row.get("batch_no")
|
||||||
|
if not batch:
|
||||||
|
continue
|
||||||
|
stock_qty_map[batch] = stock_qty_map.get(batch, 0) + (row.get("qty") or 0)
|
||||||
|
|
||||||
|
batch = frappe.qb.DocType("Batch")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(batch)
|
||||||
|
.select(batch.name, batch.item, batch.item_name, batch.batch_qty)
|
||||||
|
.where(batch.disabled == 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
if item:
|
||||||
|
query = query.where(batch.item == item)
|
||||||
|
if batch_no:
|
||||||
|
query = query.where(batch.name == batch_no)
|
||||||
|
|
||||||
|
batch_records = query.run(as_dict=True) or []
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for row in batch_records:
|
||||||
|
name = row.get("name")
|
||||||
|
batch_qty = row.get("batch_qty") or 0
|
||||||
|
stock_qty = stock_qty_map.get(name, 0)
|
||||||
|
difference = stock_qty - batch_qty
|
||||||
|
|
||||||
|
if difference != 0:
|
||||||
|
result.append(
|
||||||
|
{
|
||||||
|
"item_code": row.get("item"),
|
||||||
|
"item_name": row.get("item_name"),
|
||||||
|
"batch": name,
|
||||||
|
"batch_qty": batch_qty,
|
||||||
|
"stock_qty": stock_qty,
|
||||||
|
"difference": difference,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def update_batch_qty(selected_batches=None):
|
||||||
|
if not selected_batches:
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_batches = json.loads(selected_batches)
|
||||||
|
for row in selected_batches:
|
||||||
|
batch_name = row.get("batch")
|
||||||
|
|
||||||
|
batches = get_batch_qty(
|
||||||
|
batch_no=batch_name,
|
||||||
|
item_code=row.get("item_code"),
|
||||||
|
for_stock_levels=True,
|
||||||
|
consider_negative_batches=True,
|
||||||
|
)
|
||||||
|
batch_qty = 0.0
|
||||||
|
if batches:
|
||||||
|
for batch in batches:
|
||||||
|
batch_qty += batch.get("qty")
|
||||||
|
|
||||||
|
frappe.db.set_value("Batch", batch_name, "batch_qty", batch_qty)
|
||||||
|
|
||||||
|
frappe.msgprint(_("Batch Qty updated successfully"), alert=True)
|
||||||
Reference in New Issue
Block a user