From 791e4269d2f80759389c80d8f35198e155772027 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 22 Apr 2024 15:23:16 +0530 Subject: [PATCH] feat: Available batches report as on specific date (cherry picked from commit b8f7979794d21a4fb53ad17b7a84e68be9ea031c) --- .../report/available_batch_report/__init__.py | 0 .../available_batch_report.js | 91 +++++++++ .../available_batch_report.json | 31 +++ .../available_batch_report.py | 178 ++++++++++++++++++ 4 files changed, 300 insertions(+) create mode 100644 erpnext/stock/report/available_batch_report/__init__.py create mode 100644 erpnext/stock/report/available_batch_report/available_batch_report.js create mode 100644 erpnext/stock/report/available_batch_report/available_batch_report.json create mode 100644 erpnext/stock/report/available_batch_report/available_batch_report.py diff --git a/erpnext/stock/report/available_batch_report/__init__.py b/erpnext/stock/report/available_batch_report/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/available_batch_report/available_batch_report.js b/erpnext/stock/report/available_batch_report/available_batch_report.js new file mode 100644 index 00000000000..011f7e09ca2 --- /dev/null +++ b/erpnext/stock/report/available_batch_report/available_batch_report.js @@ -0,0 +1,91 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.query_reports["Available Batch Report"] = { + filters: [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + width: "80", + options: "Company", + default: frappe.defaults.get_default("company"), + }, + { + fieldname: "to_date", + label: __("On This Date"), + fieldtype: "Date", + width: "80", + reqd: 1, + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + fieldname: "item_code", + label: __("Item"), + fieldtype: "Link", + width: "80", + options: "Item", + get_query: () => { + return { + filters: { + has_batch_no: 1, + disabled: 0, + }, + }; + }, + }, + { + fieldname: "warehouse", + label: __("Warehouse"), + fieldtype: "Link", + width: "80", + options: "Warehouse", + get_query: () => { + let warehouse_type = frappe.query_report.get_filter_value("warehouse_type"); + let company = frappe.query_report.get_filter_value("company"); + + return { + filters: { + ...(warehouse_type && { warehouse_type }), + ...(company && { company }), + }, + }; + }, + }, + { + fieldname: "warehouse_type", + label: __("Warehouse Type"), + fieldtype: "Link", + width: "80", + options: "Warehouse Type", + }, + { + fieldname: "batch_no", + label: __("Batch No"), + fieldtype: "Link", + width: "80", + options: "Batch", + get_query: () => { + let item = frappe.query_report.get_filter_value("item_code"); + + return { + filters: { + ...(item && { item }), + }, + }; + }, + }, + { + fieldname: "include_expired_batches", + label: __("Include Expired Batches"), + fieldtype: "Check", + width: "80", + }, + { + fieldname: "show_item_name", + label: __("Show Item Name"), + fieldtype: "Check", + width: "80", + }, + ], +}; diff --git a/erpnext/stock/report/available_batch_report/available_batch_report.json b/erpnext/stock/report/available_batch_report/available_batch_report.json new file mode 100644 index 00000000000..ddc03120e92 --- /dev/null +++ b/erpnext/stock/report/available_batch_report/available_batch_report.json @@ -0,0 +1,31 @@ +{ + "add_total_row": 1, + "columns": [], + "creation": "2024-04-11 17:03:32.253275", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "", + "letterhead": null, + "modified": "2024-04-23 17:18:19.779036", + "modified_by": "Administrator", + "module": "Stock", + "name": "Available Batch Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Available Batch Report", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Accounts Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/available_batch_report/available_batch_report.py b/erpnext/stock/report/available_batch_report/available_batch_report.py new file mode 100644 index 00000000000..07fcf36c827 --- /dev/null +++ b/erpnext/stock/report/available_batch_report/available_batch_report.py @@ -0,0 +1,178 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from collections import defaultdict + +import frappe +from frappe import _ +from frappe.query_builder.functions import Sum +from frappe.utils import flt, today + + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns(filters) + return columns, data + + +def get_columns(filters): + columns = [ + { + "label": _("Item Code"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 200, + } + ] + + if filters.show_item_name: + columns.append( + { + "label": _("Item Name"), + "fieldname": "item_name", + "fieldtype": "Link", + "options": "Item", + "width": 200, + } + ) + + columns.extend( + [ + { + "label": _("Warehouse"), + "fieldname": "warehouse", + "fieldtype": "Link", + "options": "Warehouse", + "width": 200, + }, + { + "label": _("Batch No"), + "fieldname": "batch_no", + "fieldtype": "Link", + "width": 150, + "options": "Batch", + }, + {"label": _("Balance Qty"), "fieldname": "balance_qty", "fieldtype": "Float", "width": 150}, + ] + ) + + return columns + + +def get_data(filters): + data = [] + batchwise_data = get_batchwise_data_from_stock_ledger(filters) + batchwise_data = get_batchwise_data_from_serial_batch_bundle(batchwise_data, filters) + + data = parse_batchwise_data(batchwise_data) + + return data + + +def parse_batchwise_data(batchwise_data): + data = [] + for key in batchwise_data: + d = batchwise_data[key] + if d.balance_qty == 0: + continue + + data.append(d) + + return data + + +def get_batchwise_data_from_stock_ledger(filters): + batchwise_data = frappe._dict({}) + + table = frappe.qb.DocType("Stock Ledger Entry") + batch = frappe.qb.DocType("Batch") + + query = ( + frappe.qb.from_(table) + .inner_join(batch) + .on(table.batch_no == batch.name) + .select( + table.item_code, + table.batch_no, + table.warehouse, + Sum(table.actual_qty).as_("balance_qty"), + ) + .where(table.is_cancelled == 0) + .groupby(table.batch_no, table.item_code, table.warehouse) + ) + + query = get_query_based_on_filters(query, batch, table, filters) + + for d in query.run(as_dict=True): + key = (d.item_code, d.warehouse, d.batch_no) + batchwise_data.setdefault(key, d) + + return batchwise_data + + +def get_batchwise_data_from_serial_batch_bundle(batchwise_data, filters): + table = frappe.qb.DocType("Stock Ledger Entry") + ch_table = frappe.qb.DocType("Serial and Batch Entry") + batch = frappe.qb.DocType("Batch") + + query = ( + frappe.qb.from_(table) + .inner_join(ch_table) + .on(table.serial_and_batch_bundle == ch_table.parent) + .inner_join(batch) + .on(ch_table.batch_no == batch.name) + .select( + table.item_code, + ch_table.batch_no, + table.warehouse, + Sum(ch_table.qty).as_("balance_qty"), + ) + .where((table.is_cancelled == 0) & (table.docstatus == 1)) + .groupby(ch_table.batch_no, table.item_code, ch_table.warehouse) + ) + + query = get_query_based_on_filters(query, batch, table, filters) + + for d in query.run(as_dict=True): + key = (d.item_code, d.warehouse, d.batch_no) + if key in batchwise_data: + batchwise_data[key].balance_qty += flt(d.balance_qty) + else: + batchwise_data.setdefault(key, d) + + return batchwise_data + + +def get_query_based_on_filters(query, batch, table, filters): + if filters.item_code: + query = query.where(table.item_code == filters.item_code) + + if filters.batch_no: + query = query.where(batch.name == filters.batch_no) + + if not filters.include_expired_batches: + query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull())) + if filters.to_date == today(): + query = query.where(batch.batch_qty > 0) + + if filters.warehouse: + lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"]) + warehouses = frappe.get_all( + "Warehouse", filters={"lft": (">=", lft), "rgt": ("<=", rgt), "is_group": 0}, pluck="name" + ) + + query = query.where(table.warehouse.isin(warehouses)) + + elif filters.warehouse_type: + warehouses = frappe.get_all( + "Warehouse", filters={"warehouse_type": filters.warehouse_type, "is_group": 0}, pluck="name" + ) + + query = query.where(table.warehouse.isin(warehouses)) + + if filters.show_item_name: + query = query.select(batch.item_name) + + return query