From 0d2a88bafc4a88f193e6eec1d196ee6c2b1446f8 Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Thu, 3 Jul 2025 12:59:00 +0530 Subject: [PATCH 01/12] feat: update stock balance report to support multi-select for items and warehouses --- .../report/stock_balance/stock_balance.js | 33 +++++++-------- .../report/stock_balance/stock_balance.py | 42 ++++++++++++++----- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js index 0d68caa7e09..a8b2bd1d782 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.js +++ b/erpnext/stock/report/stock_balance/stock_balance.js @@ -36,38 +36,37 @@ frappe.query_reports["Stock Balance"] = { }, { fieldname: "item_code", - label: __("Item"), - fieldtype: "Link", + label: __("Items"), + fieldtype: "MultiSelectList", width: "80", options: "Item", - get_query: function () { + get_data: function (txt) { let item_group = frappe.query_report.get_filter_value("item_group"); - return { - query: "erpnext.controllers.queries.item_query", - filters: { - ...(item_group && { item_group }), - is_stock_item: 1, - }, + let filters = { + ...(item_group && { item_group }), + is_stock_item: 1, }; + + return frappe.db.get_link_options("Item", txt, filters); }, }, { fieldname: "warehouse", - label: __("Warehouse"), - fieldtype: "Link", + label: __("Warehouses"), + fieldtype: "MultiSelectList", width: "80", options: "Warehouse", - get_query: () => { + get_data: (txt) => { 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 }), - }, + let filters = { + ...(warehouse_type && { warehouse_type }), + ...(company && { company }), }; + + return frappe.db.get_link_options("Warehouse", txt, filters); }, }, { diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 3177e41642f..0af90474d0c 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -12,6 +12,7 @@ from frappe.query_builder import Order from frappe.query_builder.functions import Coalesce from frappe.utils import add_days, cint, date_diff, flt, getdate from frappe.utils.nestedset import get_descendants_of +from pypika.terms import ExistsCriterion import erpnext from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions @@ -26,8 +27,8 @@ class StockBalanceFilter(TypedDict): from_date: str to_date: str item_group: str | None - item: str | None - warehouse: str | None + item: list[str] | None + warehouse: list[str] | None warehouse_type: str | None include_uom: str | None # include extra info in converted UOM show_stock_ageing_data: bool @@ -359,8 +360,29 @@ class StockBalanceReport: def apply_warehouse_filters(self, query, sle) -> str: warehouse_table = frappe.qb.DocType("Warehouse") - if self.filters.get("warehouse"): - query = apply_warehouse_filter(query, sle, self.filters) + if warehouses := self.filters.get("warehouse"): + warehouse_range = frappe.get_all( + "Warehouse", + filters={ + "name": ("in", warehouses), + }, + fields=["lft", "rgt"], + as_list=True, + ) + + child_query = frappe.qb.from_(warehouse_table).select(warehouse_table.name) + + range_conditions = [ + (warehouse_table.lft >= lft) & (warehouse_table.rgt <= rgt) for lft, rgt in warehouse_range + ] + + combined_condition = range_conditions[0] + for condition in range_conditions[1:]: + combined_condition = combined_condition | condition + + child_query = child_query.where(combined_condition & (warehouse_table.name == sle.warehouse)) + query = query.where(ExistsCriterion(child_query)) + elif warehouse_type := self.filters.get("warehouse_type"): query = ( query.join(warehouse_table) @@ -375,13 +397,11 @@ class StockBalanceReport: children = get_descendants_of("Item Group", item_group, ignore_permissions=True) query = query.where(item_table.item_group.isin([*children, item_group])) - for field in ["item_code", "brand"]: - if not self.filters.get(field): - continue - elif field == "item_code": - query = query.where(item_table.name == self.filters.get(field)) - else: - query = query.where(item_table[field] == self.filters.get(field)) + if item_codes := self.filters.get("item_code"): + query = query.where(item_table.name.isin(item_codes)) + + if brand := self.filters.get("brand"): + query = query.where(item_table.brand == brand) return query From 2ff1dcc3911f5387a065eb36d1bca3094c69dd40 Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Thu, 3 Jul 2025 16:24:27 +0530 Subject: [PATCH 02/12] feat: enhance apply_warehouse_filter to support multiple warehouses in filters --- erpnext/stock/doctype/warehouse/warehouse.py | 42 ++++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index 7a472b1b816..d534e63209c 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -253,19 +253,35 @@ def get_warehouses_based_on_account(account, company=None): # Will be use for frappe.qb def apply_warehouse_filter(query, sle, filters): - if warehouse := filters.get("warehouse"): - warehouse_table = frappe.qb.DocType("Warehouse") + if not (warehouses := filters.get("warehouse")): + return query - lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) - chilren_subquery = ( - frappe.qb.from_(warehouse_table) - .select(warehouse_table.name) - .where( - (warehouse_table.lft >= lft) - & (warehouse_table.rgt <= rgt) - & (warehouse_table.name == sle.warehouse) - ) - ) - query = query.where(ExistsCriterion(chilren_subquery)) + warehouse_table = frappe.qb.DocType("Warehouse") + + if isinstance(warehouses, str): + warehouses = [warehouses] + + warehouse_range = frappe.get_all( + "Warehouse", + filters={ + "name": ("in", warehouses), + }, + fields=["lft", "rgt"], + as_list=True, + ) + + child_query = frappe.qb.from_(warehouse_table).select(warehouse_table.name) + + range_conditions = [ + (warehouse_table.lft >= lft) & (warehouse_table.rgt <= rgt) for lft, rgt in warehouse_range + ] + + combined_condition = range_conditions[0] + for condition in range_conditions[1:]: + combined_condition = combined_condition | condition + + child_query = child_query.where(combined_condition & (warehouse_table.name == sle.warehouse)) + + query = query.where(ExistsCriterion(child_query)) return query From 2882576479a129a9445b5da13d46c986351f2919 Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Thu, 3 Jul 2025 16:26:58 +0530 Subject: [PATCH 03/12] refactor: use existing functionality --- .../report/stock_balance/stock_balance.py | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 0af90474d0c..0698683aeab 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -360,28 +360,8 @@ class StockBalanceReport: def apply_warehouse_filters(self, query, sle) -> str: warehouse_table = frappe.qb.DocType("Warehouse") - if warehouses := self.filters.get("warehouse"): - warehouse_range = frappe.get_all( - "Warehouse", - filters={ - "name": ("in", warehouses), - }, - fields=["lft", "rgt"], - as_list=True, - ) - - child_query = frappe.qb.from_(warehouse_table).select(warehouse_table.name) - - range_conditions = [ - (warehouse_table.lft >= lft) & (warehouse_table.rgt <= rgt) for lft, rgt in warehouse_range - ] - - combined_condition = range_conditions[0] - for condition in range_conditions[1:]: - combined_condition = combined_condition | condition - - child_query = child_query.where(combined_condition & (warehouse_table.name == sle.warehouse)) - query = query.where(ExistsCriterion(child_query)) + if self.filters.get("warehouse"): + apply_warehouse_filter(query, sle, self.filters) elif warehouse_type := self.filters.get("warehouse_type"): query = ( From f2afd98725e3c0f242309e7486dbba37a4db5c3f Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Thu, 3 Jul 2025 18:04:05 +0530 Subject: [PATCH 04/12] feat: update stock ledger report to support multi-select for warehouses and items --- .../stock/report/stock_ledger/stock_ledger.js | 23 +++--- .../stock/report/stock_ledger/stock_ledger.py | 61 ++++++++++++---- erpnext/stock/stock_ledger.py | 71 +++++++++++-------- 3 files changed, 98 insertions(+), 57 deletions(-) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js index d4c11de74e6..28bf54437a6 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.js +++ b/erpnext/stock/report/stock_ledger/stock_ledger.js @@ -27,25 +27,24 @@ frappe.query_reports["Stock Ledger"] = { }, { fieldname: "warehouse", - label: __("Warehouse"), - fieldtype: "Link", + label: __("Warehouses"), + fieldtype: "MultiSelectList", options: "Warehouse", - get_query: function () { + get_data: function (txt) { const company = frappe.query_report.get_filter_value("company"); - return { - filters: { company: company }, - }; + + return frappe.db.get_link_options("Warehouse", txt, { + company: company, + }); }, }, { fieldname: "item_code", - label: __("Item"), - fieldtype: "Link", + label: __("Items"), + fieldtype: "MultiSelectList", options: "Item", - get_query: function () { - return { - query: "erpnext.controllers.queries.item_query", - }; + get_data: function (txt) { + return frappe.db.get_link_options("Item", txt, {}); }, }, { diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 391395503b0..54335ff6507 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -456,19 +456,23 @@ def get_items(filters): query = frappe.qb.from_(item).select(item.name) conditions = [] - if item_code := filters.get("item_code"): - conditions.append(item.name == item_code) + if item_codes := filters.get("item_code"): + conditions.append(item.name.isin(item_codes)) + 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 filters.get("item_group") and ( + condition := get_item_group_condition(filters.get("item_group"), item) + ): + conditions.append(condition) items = [] if conditions: for condition in conditions: query = query.where(condition) + items = [r[0] for r in query.run()] return items @@ -505,6 +509,7 @@ def get_item_details(items, sl_entries, include_uom): return item_details +# TODO: THIS IS NOT USED def get_sle_conditions(filters): conditions = [] if filters.get("warehouse"): @@ -535,8 +540,8 @@ def get_opening_balance_from_batch(filters, columns, sl_entries): } for fields in ["item_code", "warehouse"]: - if filters.get(fields): - query_filters[fields] = filters.get(fields) + value = filters.get(fields) + query_filters[fields] = ("in", value) opening_data = frappe.get_all( "Stock Ledger Entry", @@ -567,8 +572,16 @@ def get_opening_balance_from_batch(filters, columns, sl_entries): ) for field in ["item_code", "warehouse", "company"]: - if filters.get(field): - query = query.where(table[field] == filters.get(field)) + value = filters.get(field) + + if not value: + continue + + if isinstance(value, list | tuple): + query = query.where(table[field].isin(value)) + + else: + query = query.where(table[field] == value) bundle_data = query.run(as_dict=True) @@ -623,13 +636,31 @@ def get_opening_balance(filters, columns, sl_entries): return row -def get_warehouse_condition(warehouse): - warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1) - if warehouse_details: - return f" exists (select name from `tabWarehouse` wh \ - where wh.lft >= {warehouse_details.lft} and wh.rgt <= {warehouse_details.rgt} and warehouse = wh.name)" +def get_warehouse_condition(warehouses): + if isinstance(warehouses, str): + warehouses = [warehouses] - return "" + warehouse_range = frappe.get_all( + "Warehouse", + filters={ + "name": ("in", warehouses), + }, + fields=["lft", "rgt"], + as_list=True, + ) + + if not warehouse_range: + return "" + + alias = "wh" + condtions = [] + for lft, rgt in warehouse_range: + condtions.append(f"({alias}.lft >= {lft} and {alias}.rgt <= {rgt})") + + condtions = " or ".join(condtions) + + return f" exists (select name from `tabWarehouse` {alias} \ + where ({condtions}) and warehouse = {alias}.name)" def get_item_group_condition(item_group, item_table=None): diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 42bcf3ee11a..b2deb939872 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -57,12 +57,12 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc """Create SL entries from SL entry dicts args: - - allow_negative_stock: disable negative stock valiations if true - - via_landed_cost_voucher: landed cost voucher cancels and reposts - entries of purchase document. This flag is used to identify if - cancellation and repost is happening via landed cost voucher, in - such cases certain validations need to be ignored (like negative - stock) + - allow_negative_stock: disable negative stock valiations if true + - via_landed_cost_voucher: landed cost voucher cancels and reposts + entries of purchase document. This flag is used to identify if + cancellation and repost is happening via landed cost voucher, in + such cases certain validations need to be ignored (like negative + stock) """ from erpnext.controllers.stock_controller import future_sle_exists @@ -527,12 +527,12 @@ class update_entries_after: :param args: args as dict - args = { - "item_code": "ABC", - "warehouse": "XYZ", - "posting_date": "2012-12-12", - "posting_time": "12:00" - } + args = { + "item_code": "ABC", + "warehouse": "XYZ", + "posting_date": "2012-12-12", + "posting_time": "12:00" + } """ def __init__( @@ -603,15 +603,15 @@ class update_entries_after: :Data Structure: self.data = { - warehouse1: { - 'previus_sle': {}, - 'qty_after_transaction': 10, - 'valuation_rate': 100, - 'stock_value': 1000, - 'prev_stock_value': 1000, - 'stock_queue': '[[10, 100]]', - 'stock_value_difference': 1000 - } + warehouse1: { + 'previus_sle': {}, + 'qty_after_transaction': 10, + 'valuation_rate': 100, + 'stock_value': 1000, + 'prev_stock_value': 1000, + 'stock_queue': '[[10, 100]]', + 'stock_value_difference': 1000 + } } """ @@ -1656,11 +1656,11 @@ def get_previous_sle(args, for_update=False, extra_cond=None): is called from various transaction like stock entry, reco etc args = { - "item_code": "ABC", - "warehouse": "XYZ", - "posting_date": "2012-12-12", - "posting_time": "12:00", - "sle": "name of reference Stock Ledger Entry" + "item_code": "ABC" or ["ABC", "XYZ"], + "warehouse": "XYZ" or ["XYZ", "ABC"], + "posting_date": "2012-12-12", + "posting_time": "12:00", + "sle": "name of reference Stock Ledger Entry" } """ args["name"] = args.get("sle", None) or "" @@ -1682,8 +1682,20 @@ def get_stock_ledger_entries( ): """get stock ledger entries filtered by specific posting datetime conditions""" conditions = f" and posting_datetime {operator} %(posting_datetime)s" - if previous_sle.get("warehouse"): - conditions += " and warehouse = %(warehouse)s" + + if item_code := previous_sle.get("item_code"): + if isinstance(item_code, list | tuple): + conditions += " and item_code in %(item_code)s" + else: + conditions += " and item_code = %(item_code)s" + + if warehouse := previous_sle.get("warehouse"): + if isinstance(warehouse, list | tuple): + conditions += " and warehouse in %(warehouse)s" + + else: + conditions += " and warehouse = %(warehouse)s" + elif previous_sle.get("warehouse_condition"): conditions += " and " + previous_sle.get("warehouse_condition") @@ -1726,8 +1738,7 @@ def get_stock_ledger_entries( """ select *, posting_datetime as "timestamp" from `tabStock Ledger Entry` - where item_code = %(item_code)s - and is_cancelled = 0 + where is_cancelled = 0 {conditions} order by posting_datetime {order}, creation {order} {limit} {for_update}""".format( From 0a71ca6739b011c88ad1388f99c808747a62b037 Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Sat, 5 Jul 2025 10:04:34 +0530 Subject: [PATCH 05/12] fix(test): update tests --- .../doctype/purchase_receipt/test_purchase_receipt.py | 2 +- .../stock/report/stock_balance/test_stock_balance.py | 4 ++-- .../report/stock_ledger/test_stock_ledger_report.py | 2 +- erpnext/stock/report/test_reports.py | 11 +++++++++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c96536d049a..cef2f7420e8 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3693,7 +3693,7 @@ class TestPurchaseReceipt(IntegrationTestCase): columns, data = execute( filters=frappe._dict( - {"item_code": item_code, "warehouse": pr.items[0].warehouse, "company": pr.company} + {"item_code": [item_code], "warehouse": [pr.items[0].warehouse], "company": pr.company} ) ) diff --git a/erpnext/stock/report/stock_balance/test_stock_balance.py b/erpnext/stock/report/stock_balance/test_stock_balance.py index 1b856f3ed0d..a0176026c48 100644 --- a/erpnext/stock/report/stock_balance/test_stock_balance.py +++ b/erpnext/stock/report/stock_balance/test_stock_balance.py @@ -23,7 +23,7 @@ class TestStockBalance(IntegrationTestCase): self.filters = _dict( { "company": "_Test Company", - "item_code": self.item.name, + "item_code": [self.item.name], "from_date": "2020-01-01", "to_date": str(today()), } @@ -165,6 +165,6 @@ class TestStockBalance(IntegrationTestCase): variant.save() self.generate_stock_ledger(variant.name, [_dict(qty=5, rate=10)]) - rows = stock_balance(self.filters.update({"show_variant_attributes": 1, "item_code": variant.name})) + rows = stock_balance(self.filters.update({"show_variant_attributes": 1, "item_code": [variant.name]})) self.assertPartialDictEq(attributes, rows[0]) self.assertInvariants(rows) diff --git a/erpnext/stock/report/stock_ledger/test_stock_ledger_report.py b/erpnext/stock/report/stock_ledger/test_stock_ledger_report.py index e7bd53c68ab..66e7560caa6 100644 --- a/erpnext/stock/report/stock_ledger/test_stock_ledger_report.py +++ b/erpnext/stock/report/stock_ledger/test_stock_ledger_report.py @@ -17,7 +17,7 @@ class TestStockLedgerReeport(IntegrationTestCase): company="_Test Company", from_date=today(), to_date=add_days(today(), 30), - item_code="_Test Stock Report Serial Item", + item_code=["_Test Stock Report Serial Item"], ) def tearDown(self) -> None: diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index 36e3e2d29bf..986c1404cbe 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -18,8 +18,15 @@ batch = get_random("Batch") REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [ ("Stock Ledger", {"_optional": True}), ("Stock Ledger", {"batch_no": batch}), - ("Stock Ledger", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}), - ("Stock Balance", {"_optional": True}), + ("Stock Ledger", {"item_code": ["_Test Item"], "warehouse": ["_Test Warehouse - _TC"]}), + ( + "Stock Balance", + { + "item_code": ["_Test Item"], + "warehouse": ["_Test Warehouse - _TC"], + "item_group": "_Test Item Group", + }, + ), ("Stock Projected Qty", {"_optional": True}), ("Batch-Wise Balance History", {}), ("Itemwise Recommended Reorder Level", {"item_group": "All Item Groups"}), From e60c711fdc13bd02a9235780ae926bd54b50a52b Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Mon, 7 Jul 2025 11:06:46 +0530 Subject: [PATCH 06/12] fix: correct query filter assignment in stock ledger and balance reports --- erpnext/stock/report/stock_balance/stock_balance.py | 2 +- erpnext/stock/report/stock_ledger/stock_ledger.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 0698683aeab..79c279903a6 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -361,7 +361,7 @@ class StockBalanceReport: warehouse_table = frappe.qb.DocType("Warehouse") if self.filters.get("warehouse"): - apply_warehouse_filter(query, sle, self.filters) + query = apply_warehouse_filter(query, sle, self.filters) elif warehouse_type := self.filters.get("warehouse_type"): query = ( diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 54335ff6507..addf938ec61 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -540,8 +540,8 @@ def get_opening_balance_from_batch(filters, columns, sl_entries): } for fields in ["item_code", "warehouse"]: - value = filters.get(fields) - query_filters[fields] = ("in", value) + if value := filters.get(fields): + query_filters[fields] = ("in", value) opening_data = frappe.get_all( "Stock Ledger Entry", From 169caaf66f122cd9350414b748ac3c53cf4c8548 Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Tue, 15 Jul 2025 13:23:09 +0530 Subject: [PATCH 07/12] fix: use the item_query for get_data --- .../stock/report/stock_ledger/stock_ledger.js | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js index 28bf54437a6..db6c0c06281 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.js +++ b/erpnext/stock/report/stock_ledger/stock_ledger.js @@ -43,8 +43,28 @@ frappe.query_reports["Stock Ledger"] = { label: __("Items"), fieldtype: "MultiSelectList", options: "Item", - get_data: function (txt) { - return frappe.db.get_link_options("Item", txt, {}); + get_data: async function (txt) { + let { message: data } = await frappe.call({ + method: "erpnext.controllers.queries.item_query", + args: { + doctype: "Item", + txt: txt, + searchfield: "name", + start: 0, + page_len: 10, + filters: {}, + as_dict: 1, + }, + }); + + data = data.map(({ name, description }) => { + return { + value: name, + description: description, + }; + }); + + return data || []; }, }, { From fca9843fc28f3901358c873c28c9129ae0230525 Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Tue, 15 Jul 2025 13:39:39 +0530 Subject: [PATCH 08/12] fix: handle empty warehouse condition in get_warehouse_condition function; typo; --- erpnext/stock/report/stock_ledger/stock_ledger.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index addf938ec61..bbc85c5a4ad 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -637,6 +637,9 @@ def get_opening_balance(filters, columns, sl_entries): def get_warehouse_condition(warehouses): + if not warehouses: + return "" + if isinstance(warehouses, str): warehouses = [warehouses] @@ -653,14 +656,14 @@ def get_warehouse_condition(warehouses): return "" alias = "wh" - condtions = [] + conditions = [] for lft, rgt in warehouse_range: - condtions.append(f"({alias}.lft >= {lft} and {alias}.rgt <= {rgt})") + conditions.append(f"({alias}.lft >= {lft} and {alias}.rgt <= {rgt})") - condtions = " or ".join(condtions) + conditions = " or ".join(conditions) return f" exists (select name from `tabWarehouse` {alias} \ - where ({condtions}) and warehouse = {alias}.name)" + where ({conditions}) and warehouse = {alias}.name)" def get_item_group_condition(item_group, item_table=None): From 7a266113edf7caee28cddc341e2243931c640340 Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Tue, 15 Jul 2025 15:15:31 +0530 Subject: [PATCH 09/12] fix: warehouse filter query by chaining conditions --- erpnext/stock/doctype/warehouse/warehouse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index d534e63209c..978f2d4d13c 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -280,7 +280,7 @@ def apply_warehouse_filter(query, sle, filters): for condition in range_conditions[1:]: combined_condition = combined_condition | condition - child_query = child_query.where(combined_condition & (warehouse_table.name == sle.warehouse)) + child_query = child_query.where(combined_condition).where(warehouse_table.name == sle.warehouse) query = query.where(ExistsCriterion(child_query)) From bc46045cc751d651f6fb4d57e74e2ba7efae362b Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Wed, 16 Jul 2025 16:12:43 +0530 Subject: [PATCH 10/12] refactor: remove unused imports in stock_balance.py --- erpnext/stock/report/stock_balance/stock_balance.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 79c279903a6..026a064c7dc 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -8,11 +8,9 @@ from typing import Any, TypedDict import frappe from frappe import _ -from frappe.query_builder import Order from frappe.query_builder.functions import Coalesce from frappe.utils import add_days, cint, date_diff, flt, getdate from frappe.utils.nestedset import get_descendants_of -from pypika.terms import ExistsCriterion import erpnext from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions From 063c4e97202fbb57e14cd1059f8cd332765ec8da Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Thu, 17 Jul 2025 15:39:12 +0530 Subject: [PATCH 11/12] refactor: revert indentation --- erpnext/stock/stock_ledger.py | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b2deb939872..14a5c13fe64 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -57,12 +57,12 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc """Create SL entries from SL entry dicts args: - - allow_negative_stock: disable negative stock valiations if true - - via_landed_cost_voucher: landed cost voucher cancels and reposts - entries of purchase document. This flag is used to identify if - cancellation and repost is happening via landed cost voucher, in - such cases certain validations need to be ignored (like negative - stock) + - allow_negative_stock: disable negative stock valiations if true + - via_landed_cost_voucher: landed cost voucher cancels and reposts + entries of purchase document. This flag is used to identify if + cancellation and repost is happening via landed cost voucher, in + such cases certain validations need to be ignored (like negative + stock) """ from erpnext.controllers.stock_controller import future_sle_exists @@ -527,12 +527,12 @@ class update_entries_after: :param args: args as dict - args = { - "item_code": "ABC", - "warehouse": "XYZ", - "posting_date": "2012-12-12", - "posting_time": "12:00" - } + args = { + "item_code": "ABC", + "warehouse": "XYZ", + "posting_date": "2012-12-12", + "posting_time": "12:00" + } """ def __init__( @@ -603,15 +603,15 @@ class update_entries_after: :Data Structure: self.data = { - warehouse1: { - 'previus_sle': {}, - 'qty_after_transaction': 10, - 'valuation_rate': 100, - 'stock_value': 1000, - 'prev_stock_value': 1000, - 'stock_queue': '[[10, 100]]', - 'stock_value_difference': 1000 - } + warehouse1: { + 'previus_sle': {}, + 'qty_after_transaction': 10, + 'valuation_rate': 100, + 'stock_value': 1000, + 'prev_stock_value': 1000, + 'stock_queue': '[[10, 100]]', + 'stock_value_difference': 1000 + } } """ @@ -1656,11 +1656,11 @@ def get_previous_sle(args, for_update=False, extra_cond=None): is called from various transaction like stock entry, reco etc args = { - "item_code": "ABC" or ["ABC", "XYZ"], - "warehouse": "XYZ" or ["XYZ", "ABC"], - "posting_date": "2012-12-12", - "posting_time": "12:00", - "sle": "name of reference Stock Ledger Entry" + "item_code": "ABC", + "warehouse": "XYZ", + "posting_date": "2012-12-12", + "posting_time": "12:00", + "sle": "name of reference Stock Ledger Entry" } """ args["name"] = args.get("sle", None) or "" From 8a97b39028dcb20502aa1b1584c1be1c55f99503 Mon Sep 17 00:00:00 2001 From: Karm Soni Date: Fri, 18 Jul 2025 16:10:32 +0530 Subject: [PATCH 12/12] fix: update get_data function to use item_query --- .../report/stock_balance/stock_balance.js | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js index a8b2bd1d782..c53e80096d3 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.js +++ b/erpnext/stock/report/stock_balance/stock_balance.js @@ -40,7 +40,7 @@ frappe.query_reports["Stock Balance"] = { fieldtype: "MultiSelectList", width: "80", options: "Item", - get_data: function (txt) { + get_data: async function (txt) { let item_group = frappe.query_report.get_filter_value("item_group"); let filters = { @@ -48,7 +48,27 @@ frappe.query_reports["Stock Balance"] = { is_stock_item: 1, }; - return frappe.db.get_link_options("Item", txt, filters); + let { message: data } = await frappe.call({ + method: "erpnext.controllers.queries.item_query", + args: { + doctype: "Item", + txt: txt, + searchfield: "name", + start: 0, + page_len: 10, + filters: filters, + as_dict: 1, + }, + }); + + data = data.map(({ name, description }) => { + return { + value: name, + description: description, + }; + }); + + return data || []; }, }, {