@@ -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+
106170def 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
0 commit comments