From 57c356a1cd4cca079c69bd7561d75789332315e9 Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Tue, 7 Oct 2025 15:16:19 +0000 Subject: [PATCH] feat(report): add batch qty update functionality in report (cherry picked from commit f40c492a050e763153efb39eee254016e41f0b52) --- .../report/stock_qty_vs_batch_qty/__init__.py | 0 .../stock_qty_vs_batch_qty.js | 71 +++++++++++++ .../stock_qty_vs_batch_qty.json | 31 ++++++ .../stock_qty_vs_batch_qty.py | 99 +++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 erpnext/stock/report/stock_qty_vs_batch_qty/__init__.py create mode 100644 erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.js create mode 100644 erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.json create mode 100644 erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.py diff --git a/erpnext/stock/report/stock_qty_vs_batch_qty/__init__.py b/erpnext/stock/report/stock_qty_vs_batch_qty/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.js b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.js new file mode 100644 index 00000000000..785a18b0c8c --- /dev/null +++ b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.js @@ -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 }, + }; + }, + }, + ], + 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: { + 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 = "" + value + ""; + } else if (data.difference < 0) { + value = "" + value + ""; + } + } + return value; + }, + get_datatable_options(options) { + return Object.assign(options, { + checkboxColumn: true, + }); + }, +}; diff --git a/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.json b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.json new file mode 100644 index 00000000000..b1885bb07f4 --- /dev/null +++ b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.json @@ -0,0 +1,31 @@ +{ + "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-10-07 20:03:45.952352", + "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": "Stock Manager" + }, + { + "role": "Stock User" + } + ], + "timeout": 0 +} diff --git a/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.py b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.py new file mode 100644 index 00000000000..56038111b93 --- /dev/null +++ b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.py @@ -0,0 +1,99 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import json + +import frappe +from frappe import _ +from frappe.query_builder import DocType + +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): + item_filter = filters.get("item") + batch_filter = filters.get("batch") + + Batch = DocType("Batch") + + query = ( + frappe.qb.from_(Batch) + .select(Batch.item.as_("item_code"), Batch.item_name, Batch.batch_qty, Batch.name.as_("batch_no")) + .where(Batch.disabled == 0) + ) + + if item_filter: + query = query.where(Batch.item == item_filter) + + if batch_filter: + query = query.where(Batch.name == batch_filter) + + batch_list = query.run(as_dict=True) + data = [] + for batch in batch_list: + batches = get_batch_qty(batch_no=batch.batch_no) + + if not batches: + continue + + batch_qty = batch.get("batch_qty", 0) + actual_qty = sum(b.get("qty", 0) for b in batches) + + difference = batch_qty - actual_qty + + row = { + "item_code": batch.item_code, + "item_name": batch.item_name, + "batch": batch.batch_no, + "batch_qty": batch_qty, + "stock_qty": actual_qty, + "difference": difference, + } + + data.append(row) + + return data + + +@frappe.whitelist() +def update_batch_qty(batches=None): + if not batches: + return + + batches = json.loads(batches) + for batch in batches: + batch_name = batch.get("batch") + stock_qty = batch.get("stock_qty") + + frappe.db.set_value("Batch", batch_name, "batch_qty", stock_qty) + + frappe.msgprint(_("Batch Qty updated successfully"), alert=True)