From b6bf13ff02e71b81c0d686b0a6d460e30fa6e4dd Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 23:29:51 +0530 Subject: [PATCH] refactor: available serial no report (backport #47333) (#47500) * refactor: available serial no report (cherry picked from commit 74eb611563ac7ad01f3ce6fa44367163fd7ac2ea) * chore: further optimizations (cherry picked from commit 653e0a2e3a17514335f0c6249221284c93c2f555) --------- Co-authored-by: Mihir Kandoi --- .../available_serial_no.js | 38 ---- .../available_serial_no.py | 162 ++++-------------- 2 files changed, 30 insertions(+), 170 deletions(-) diff --git a/erpnext/stock/report/available_serial_no/available_serial_no.js b/erpnext/stock/report/available_serial_no/available_serial_no.js index 17f8c666e04..c69c6503de8 100644 --- a/erpnext/stock/report/available_serial_no/available_serial_no.js +++ b/erpnext/stock/report/available_serial_no/available_serial_no.js @@ -51,49 +51,11 @@ frappe.query_reports["Available Serial No"] = { }; }, }, - { - fieldname: "item_group", - label: __("Item Group"), - fieldtype: "Link", - options: "Item Group", - }, - { - fieldname: "batch_no", - label: __("Batch No"), - fieldtype: "Link", - options: "Batch", - on_change() { - const batch_no = frappe.query_report.get_filter_value("batch_no"); - if (batch_no) { - frappe.query_report.set_filter_value("segregate_serial_batch_bundle", 1); - } else { - frappe.query_report.set_filter_value("segregate_serial_batch_bundle", 0); - } - }, - }, - { - fieldname: "brand", - label: __("Brand"), - fieldtype: "Link", - options: "Brand", - }, { fieldname: "voucher_no", label: __("Voucher #"), fieldtype: "Data", }, - { - fieldname: "project", - label: __("Project"), - fieldtype: "Link", - options: "Project", - }, - { - fieldname: "include_uom", - label: __("Include UOM"), - fieldtype: "Link", - options: "UOM", - }, { fieldname: "valuation_field_type", label: __("Valuation Field Type"), diff --git a/erpnext/stock/report/available_serial_no/available_serial_no.py b/erpnext/stock/report/available_serial_no/available_serial_no.py index bdde9c7f3b6..6911b979ae4 100644 --- a/erpnext/stock/report/available_serial_no/available_serial_no.py +++ b/erpnext/stock/report/available_serial_no/available_serial_no.py @@ -3,108 +3,62 @@ import frappe from frappe import _ -from frappe.query_builder.functions import Sum from frappe.utils import cint, flt -from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_serial_nos_from_sle_list -from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import get_stock_balance_for from erpnext.stock.report.stock_ledger.stock_ledger import ( - check_inventory_dimension_filters_applied, get_item_details, - get_item_group_condition, get_opening_balance, - get_opening_balance_from_batch, get_stock_ledger_entries, ) -from erpnext.stock.utils import ( - is_reposting_item_valuation_in_progress, - update_included_uom_in_report, -) +from erpnext.stock.utils import is_reposting_item_valuation_in_progress def execute(filters=None): is_reposting_item_valuation_in_progress() - include_uom = filters.get("include_uom") columns = get_columns(filters) items = get_items(filters) sl_entries = get_stock_ledger_entries(filters, items) - item_details = get_item_details(items, sl_entries, include_uom) + item_details = get_item_details(items, sl_entries, False) - opening_row, actual_qty, stock_value = get_opening_balance_data(filters, columns, sl_entries) + opening_row = get_opening_balance_data(filters, columns, sl_entries) precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) - data, conversion_factors = process_stock_ledger_entries( - filters, sl_entries, item_details, opening_row, actual_qty, stock_value, precision - ) + data = process_stock_ledger_entries(sl_entries, item_details, opening_row, precision) - update_included_uom_in_report(columns, data, include_uom, conversion_factors) return columns, data def get_opening_balance_data(filters, columns, sl_entries): - if filters.get("batch_no"): - opening_row = get_opening_balance_from_batch(filters, columns, sl_entries) - else: - opening_row = get_opening_balance(filters, columns, sl_entries) - - actual_qty = opening_row.get("qty_after_transaction") if opening_row else 0 - stock_value = opening_row.get("stock_value") if opening_row else 0 - return opening_row, actual_qty, stock_value + opening_row = get_opening_balance(filters, columns, sl_entries) + return opening_row -def process_stock_ledger_entries( - filters, sl_entries, item_details, opening_row, actual_qty, stock_value, precision -): +def process_stock_ledger_entries(sl_entries, item_details, opening_row, precision): data = [] - conversion_factors = [] if opening_row: data.append(opening_row) - conversion_factors.append(0) - batch_balance_dict = frappe._dict({}) + available_serial_nos = {} + if sabb_list := [sle.serial_and_batch_bundle for sle in sl_entries if sle.serial_and_batch_bundle]: + available_serial_nos = get_serial_nos_from_sle_list(sabb_list) - if actual_qty and filters.get("batch_no"): - batch_balance_dict[filters.batch_no] = [actual_qty, stock_value] - - available_serial_nos = get_serial_nos_from_sle_list( - [sle.serial_and_batch_bundle for sle in sl_entries if sle.serial_and_batch_bundle] - ) + if not available_serial_nos: + return [], [] for sle in sl_entries: - update_stock_ledger_entry( - sle, item_details, filters, actual_qty, stock_value, batch_balance_dict, precision - ) + update_stock_ledger_entry(sle, item_details, precision) update_available_serial_nos(available_serial_nos, sle) data.append(sle) - if filters.get("include_uom"): - conversion_factors.append(item_details[sle.item_code].conversion_factor) - - return data, conversion_factors + return data -def update_stock_ledger_entry( - sle, item_details, filters, actual_qty, stock_value, batch_balance_dict, precision -): +def update_stock_ledger_entry(sle, item_details, precision): item_detail = item_details[sle.item_code] sle.update(item_detail) - if filters.get("batch_no") or check_inventory_dimension_filters_applied(filters): - actual_qty += flt(sle.actual_qty, precision) - stock_value += sle.stock_value_difference - - if sle.batch_no: - batch_balance_dict.setdefault(sle.batch_no, [0, 0]) - batch_balance_dict[sle.batch_no][0] += sle.actual_qty - - if sle.voucher_type == "Stock Reconciliation" and not sle.actual_qty: - actual_qty = sle.qty_after_transaction - stock_value = sle.stock_value - - sle.update({"qty_after_transaction": actual_qty, "stock_value": stock_value}) - sle.update({"in_qty": max(sle.actual_qty, 0), "out_qty": min(sle.actual_qty, 0)}) if sle.actual_qty: @@ -120,13 +74,10 @@ def update_available_serial_nos(available_serial_nos, sle): else available_serial_nos.get(sle.serial_and_batch_bundle) ) key = (sle.item_code, sle.warehouse) + sle.serial_no = "\n".join(serial_nos) if serial_nos else "" if key not in available_serial_nos: - stock_balance = get_stock_balance_for( - sle.item_code, sle.warehouse, sle.posting_date, sle.posting_time - ) - serials = get_serial_nos(stock_balance["serial_nos"]) if stock_balance["serial_nos"] else [] - available_serial_nos.setdefault(key, serials) - sle.balance_serial_no = "\n".join(serials) + available_serial_nos.setdefault(key, serial_nos) + sle.balance_serial_no = "\n".join(serial_nos) return existing_serial_no = available_serial_nos[key] @@ -151,25 +102,14 @@ def get_columns(filters): }, {"label": _("Item Name"), "fieldname": "item_name", "width": 100}, { - "label": _("Stock UOM"), + "label": _("UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", - "width": 90, + "width": 60, }, ] - for dimension in get_inventory_dimensions(): - columns.append( - { - "label": _(dimension.doctype), - "fieldname": dimension.fieldname, - "fieldtype": "Link", - "options": dimension.doctype, - "width": 110, - } - ) - columns.extend( [ { @@ -201,20 +141,11 @@ def get_columns(filters): "width": 150, }, { - "label": _("Item Group"), - "fieldname": "item_group", - "fieldtype": "Link", - "options": "Item Group", - "width": 100, + "label": _("Serial No (In/Out)"), + "fieldname": "serial_no", + "width": 150, }, - { - "label": _("Brand"), - "fieldname": "brand", - "fieldtype": "Link", - "options": "Brand", - "width": 100, - }, - {"label": _("Description"), "fieldname": "description", "width": 200}, + {"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 150}, { "label": _("Incoming Rate"), "fieldname": "incoming_rate", @@ -257,28 +188,6 @@ def get_columns(filters): "width": 110, "options": "Company:company:default_currency", }, - {"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110}, - { - "label": _("Voucher #"), - "fieldname": "voucher_no", - "fieldtype": "Dynamic Link", - "options": "voucher_type", - "width": 100, - }, - { - "label": _("Batch"), - "fieldname": "batch_no", - "fieldtype": "Link", - "options": "Batch", - "width": 100, - }, - { - "label": _("Serial No"), - "fieldname": "serial_no", - "fieldtype": "Link", - "options": "Serial No", - "width": 100, - }, { "label": _("Serial and Batch Bundle"), "fieldname": "serial_and_batch_bundle", @@ -286,12 +195,12 @@ def get_columns(filters): "options": "Serial and Batch Bundle", "width": 100, }, - {"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100}, + {"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110}, { - "label": _("Project"), - "fieldname": "project", - "fieldtype": "Link", - "options": "Project", + "label": _("Voucher #"), + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "options": "voucher_type", "width": 100, }, { @@ -310,19 +219,8 @@ def get_columns(filters): def get_items(filters): item = frappe.qb.DocType("Item") query = frappe.qb.from_(item).select(item.name).where(item.has_serial_no == 1) - conditions = [] if item_code := filters.get("item_code"): - conditions.append(item.name == item_code) - else: - if brand := filters.get("brand"): - conditions.append(item.brand == brand) - if item_group := filters.get("item_group"): - if condition := get_item_group_condition(item_group, item): - conditions.append(condition) - - if conditions: - for condition in conditions: - query = query.where(condition) + query = query.where(item.name == item_code) return query.run(pluck=True)