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 8e2319f7bd6..202c77faf90 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -56,12 +56,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 @@ -526,12 +526,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__( @@ -599,15 +599,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 + } } """ @@ -1644,11 +1644,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 "" @@ -1670,8 +1670,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") @@ -1714,8 +1726,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(