Skip to content

Commit 519657f

Browse files
Merge pull request #53188 from frappe/mergify/bp/version-16/pr-53181
fix: balance qty for inv dimension (backport #52745) (backport #53181)
2 parents 78aa4cf + 354723d commit 519657f

2 files changed

Lines changed: 148 additions & 8 deletions

File tree

erpnext/stock/report/stock_ledger/stock_ledger.py

Lines changed: 145 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,23 @@ def execute(filters=None):
2727
items = get_items(filters)
2828
sl_entries = get_stock_ledger_entries(filters, items)
2929
item_details = get_item_details(items, sl_entries, include_uom)
30+
31+
inv_dimension_key = []
32+
inv_dimension_wise_value = get_inv_dimension_wise_value(filters)
33+
if inv_dimension_wise_value:
34+
for key in inv_dimension_wise_value:
35+
value = inv_dimension_wise_value[key]
36+
if isinstance(value, list):
37+
inv_dimension_key.extend(value)
38+
else:
39+
inv_dimension_key.append(value)
40+
3041
if filters.get("batch_no"):
3142
opening_row = get_opening_balance_from_batch(filters, columns, sl_entries)
43+
elif inv_dimension_wise_value:
44+
opening_row = get_opening_balance_for_inv_dimension(filters, inv_dimension_wise_value)
3245
else:
33-
opening_row = get_opening_balance(filters, columns, sl_entries)
46+
opening_row = get_opening_balance(filters, columns, sl_entries, inv_dimension_wise_value)
3447

3548
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
3649
bundle_details = {}
@@ -50,12 +63,16 @@ def execute(filters=None):
5063
stock_value = opening_row.get("stock_value")
5164

5265
available_serial_nos = {}
53-
inventory_dimension_filters_applied = check_inventory_dimension_filters_applied(filters)
5466

5567
batch_balance_dict = frappe._dict({})
5668
if actual_qty and filters.get("batch_no"):
5769
batch_balance_dict[filters.batch_no] = [actual_qty, stock_value]
5870

71+
inv_dimension_wise_dict = frappe._dict({})
72+
set_opening_row_for_inv_dimension(
73+
inv_dimension_wise_dict, filters, inv_dimension_key=inv_dimension_key, opening_row=opening_row
74+
)
75+
5976
for sle in sl_entries:
6077
item_detail = item_details[sle.item_code]
6178

@@ -64,7 +81,10 @@ def execute(filters=None):
6481
data.extend(get_segregated_bundle_entries(sle, bundle_info, batch_balance_dict, filters))
6582
continue
6683

67-
if filters.get("batch_no") or inventory_dimension_filters_applied:
84+
if inv_dimension_key:
85+
set_balance_value_for_inv_dimesion(inv_dimension_key, inv_dimension_wise_dict, sle)
86+
87+
if filters.get("batch_no"):
6888
actual_qty += flt(sle.actual_qty, precision)
6989
stock_value += sle.stock_value_difference
7090
if sle.batch_no:
@@ -103,6 +123,50 @@ def execute(filters=None):
103123
return columns, data
104124

105125

126+
def set_opening_row_for_inv_dimension(
127+
inv_dimension_wise_dict, filters, inv_dimension_key=None, opening_row=None
128+
):
129+
if (
130+
not inv_dimension_key
131+
or not opening_row
132+
or not filters.get("item_code")
133+
or not filters.get("warehouse")
134+
):
135+
return
136+
137+
if len(filters.get("item_code")) > 1 or len(filters.get("warehouse")) > 1:
138+
return
139+
140+
if inv_dimension_key and opening_row and filters.get("item_code") and filters.get("warehouse"):
141+
new_key = copy.deepcopy(inv_dimension_key)
142+
new_key.extend([filters.item_code[0], filters.warehouse[0]])
143+
144+
opening_key = tuple(new_key)
145+
inv_dimension_wise_dict[opening_key] = {
146+
"qty_after_transaction": flt(opening_row.get("qty_after_transaction")),
147+
"dimension_stock_value": flt(opening_row.get("stock_value")),
148+
}
149+
150+
151+
def set_balance_value_for_inv_dimesion(inv_dimension_key, inv_dimension_wise_dict, sle):
152+
new_key = copy.deepcopy(inv_dimension_key)
153+
new_key.extend([sle.item_code, sle.warehouse])
154+
new_key = tuple(new_key)
155+
156+
if new_key not in inv_dimension_wise_dict:
157+
inv_dimension_wise_dict[new_key] = {"qty_after_transaction": 0, "dimension_stock_value": 0}
158+
159+
inv_dimesion_value = inv_dimension_wise_dict[new_key]
160+
inv_dimesion_value["qty_after_transaction"] += sle.actual_qty
161+
inv_dimesion_value["dimension_stock_value"] += sle.stock_value_difference
162+
sle.update(
163+
{
164+
"qty_after_transaction": inv_dimesion_value["qty_after_transaction"],
165+
"stock_value": inv_dimesion_value["dimension_stock_value"],
166+
}
167+
)
168+
169+
106170
def get_segregated_bundle_entries(sle, bundle_details, batch_balance_dict, filters):
107171
segregated_entries = []
108172
qty_before_transaction = sle.qty_after_transaction - sle.actual_qty
@@ -605,19 +669,26 @@ def get_opening_balance_from_batch(filters, columns, sl_entries):
605669
}
606670

607671

608-
def get_opening_balance(filters, columns, sl_entries):
672+
def get_opening_balance(filters, columns, sl_entries, inv_dimension_wise_value=None):
609673
if not (filters.item_code and filters.warehouse and filters.from_date):
610674
return
611675

612676
from erpnext.stock.stock_ledger import get_previous_sle
613677

678+
project = None
679+
if filters.get("project") and not frappe.get_all(
680+
"Inventory Dimension", filters={"reference_document": "Project"}
681+
):
682+
project = filters.get("project")
683+
614684
last_entry = get_previous_sle(
615685
{
616686
"item_code": filters.item_code,
617687
"warehouse_condition": get_warehouse_condition(filters.warehouse),
618688
"posting_date": filters.from_date,
619689
"posting_time": "00:00:00",
620-
}
690+
"project": project,
691+
},
621692
)
622693

623694
# check if any SLEs are actually Opening Stock Reconciliation
@@ -689,9 +760,75 @@ def get_item_group_condition(item_group, item_table=None):
689760
where ig.lft >= {item_group_details.lft} and ig.rgt <= {item_group_details.rgt} and item.item_group = ig.name)"
690761

691762

692-
def check_inventory_dimension_filters_applied(filters) -> bool:
763+
def get_opening_balance_for_inv_dimension(filters, inv_dimension_wise_value):
764+
if not filters.item_code or not filters.warehouse or not filters.from_date:
765+
return
766+
767+
if len(filters.get("item_code")) > 1 or len(filters.get("warehouse")) > 1:
768+
return
769+
770+
sl_doctype = frappe.qb.DocType("Stock Ledger Entry")
771+
772+
query = (
773+
frappe.qb.from_(sl_doctype)
774+
.select(
775+
sl_doctype.item_code,
776+
sl_doctype.warehouse,
777+
Sum(sl_doctype.actual_qty).as_("qty_after_transaction"),
778+
Sum(sl_doctype.stock_value_difference).as_("stock_value"),
779+
)
780+
.where(
781+
(sl_doctype.posting_date < filters.from_date)
782+
& (sl_doctype.docstatus < 2)
783+
& (sl_doctype.is_cancelled == 0)
784+
)
785+
)
786+
787+
if filters.get("item_code"):
788+
if isinstance(filters.item_code, list | tuple):
789+
query = query.where(sl_doctype.item_code.isin(filters.item_code))
790+
else:
791+
query = query.where(sl_doctype.item_code == filters.item_code)
792+
793+
if filters.get("warehouse"):
794+
if isinstance(filters.warehouse, list | tuple):
795+
query = query.where(sl_doctype.warehouse.isin(filters.warehouse))
796+
else:
797+
query = query.where(sl_doctype.warehouse == filters.warehouse)
798+
799+
for key, value in inv_dimension_wise_value.items():
800+
if isinstance(value, list | tuple):
801+
query = query.where(sl_doctype[key].isin(value))
802+
else:
803+
query = query.where(sl_doctype[key] == value)
804+
805+
opening_data = query.run(as_dict=True)
806+
807+
if opening_data:
808+
return frappe._dict(
809+
{
810+
"item_code": _("'Opening'"),
811+
"qty_after_transaction": opening_data[0].qty_after_transaction,
812+
"stock_value": opening_data[0].stock_value,
813+
"valuation_rate": flt(opening_data[0].stock_value)
814+
/ flt(opening_data[0].qty_after_transaction)
815+
if opening_data[0].qty_after_transaction
816+
else 0,
817+
}
818+
)
819+
820+
return frappe._dict({})
821+
822+
823+
def get_inv_dimension_wise_value(filters) -> list:
824+
inv_dimension_key = frappe._dict({})
693825
for dimension in get_inventory_dimensions():
694826
if dimension.fieldname in filters and filters.get(dimension.fieldname):
695-
return True
827+
inv_dimension_key[dimension.fieldname] = filters.get(dimension.fieldname)
828+
829+
if filters.get("project") and not frappe.get_all(
830+
"Inventory Dimension", filters={"reference_document": "Project"}
831+
):
832+
inv_dimension_key["project"] = filters.get("project")
696833

697-
return False
834+
return inv_dimension_key

erpnext/stock/stock_ledger.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,9 @@ def get_stock_ledger_entries(
18621862
if extra_cond:
18631863
conditions += f"{extra_cond}"
18641864

1865+
if previous_sle.get("project"):
1866+
conditions += " and project = %(project)s"
1867+
18651868
# nosemgrep
18661869
return frappe.db.sql(
18671870
"""

0 commit comments

Comments
 (0)