77
88import frappe
99from frappe import _
10- < << << << HEAD
1110from frappe .query_builder import Order
12- from frappe .query_builder .functions import Coalesce
13- == == == =
1411from frappe .query_builder .functions import Coalesce , Count
15- > >> >> >> 0874 cbc268 (fix : old stock reco entries causing issue in the stock balance report )
1612from frappe .utils import add_days , cint , date_diff , flt , getdate
1713from frappe .utils .nestedset import get_descendants_of
1814
@@ -159,6 +155,8 @@ def get_item_warehouse_map(self):
159155 if self .filters .get ("show_stock_ageing_data" ):
160156 self .sle_entries = self .sle_query .run (as_dict = True )
161157
158+ self .prepare_stock_reco_voucher_wise_count ()
159+
162160 # HACK: This is required to avoid causing db query in flt
163161 _system_settings = frappe .get_cached_doc ("System Settings" )
164162 with frappe .db .unbuffered_cursor ():
@@ -185,6 +183,30 @@ def get_item_warehouse_map(self):
185183
186184 return item_warehouse_map
187185
186+ def prepare_stock_reco_voucher_wise_count (self ):
187+ self .stock_reco_voucher_wise_count = frappe ._dict ()
188+
189+ doctype = frappe .qb .DocType ("Stock Ledger Entry" )
190+ item = frappe .qb .DocType ("Item" )
191+
192+ query = (
193+ frappe .qb .from_ (doctype )
194+ .inner_join (item )
195+ .on (doctype .item_code == item .name )
196+ .select (doctype .voucher_detail_no , Count (doctype .name ).as_ ("count" ))
197+ .where (
198+ (doctype .voucher_type == "Stock Reconciliation" )
199+ & (doctype .docstatus < 2 )
200+ & (doctype .is_cancelled == 0 )
201+ & (item .has_serial_no == 1 )
202+ )
203+ .groupby (doctype .voucher_detail_no )
204+ )
205+
206+ data = query .run (as_list = True )
207+ if data :
208+ self .stock_reco_voucher_wise_count = frappe ._dict (data )
209+
188210 def get_sre_reserved_qty_details (self ) -> dict :
189211 from erpnext .stock .doctype .stock_reservation_entry .stock_reservation_entry import (
190212 get_sre_reserved_qty_for_items_and_warehouses as get_reserved_qty_details ,
@@ -203,9 +225,13 @@ def prepare_item_warehouse_map(self, item_warehouse_map, entry, group_by_key):
203225 qty_dict [field ] = entry .get (field )
204226
205227 if entry .voucher_type == "Stock Reconciliation" and (
206- not entry .batch_no and not entry .serial_no and not entry .serial_and_batch_bundle
228+ not entry .batch_no or entry .serial_no or entry .serial_and_batch_bundle
207229 ):
208- qty_diff = flt (entry .qty_after_transaction ) - flt (qty_dict .bal_qty )
230+ if entry .serial_no and self .stock_reco_voucher_wise_count .get (entry .voucher_detail_no , 0 ) == 1 :
231+ qty_dict .bal_qty = 0.0
232+ qty_diff = flt (entry .actual_qty )
233+ else :
234+ qty_diff = flt (entry .qty_after_transaction ) - flt (qty_dict .bal_qty )
209235 else :
210236 qty_diff = flt (entry .actual_qty )
211237
@@ -342,202 +368,6 @@ def prepare_stock_ledger_entries(self):
342368
343369 self .sle_query = query
344370
345- < << << << HEAD
346- == == == =
347- def prepare_item_warehouse_map_for_current_period (self ):
348- self .opening_vouchers = self .get_opening_vouchers ()
349-
350- if self .filters .get ("show_stock_ageing_data" ):
351- self .sle_entries = self .sle_query .run (as_dict = True )
352-
353- self .prepare_stock_reco_voucher_wise_count ()
354-
355- # HACK: This is required to avoid causing db query in flt
356- _system_settings = frappe .get_cached_doc ("System Settings" )
357- with frappe .db .unbuffered_cursor ():
358- if not self .filters .get ("show_stock_ageing_data" ):
359- self .sle_entries = self .sle_query .run (as_dict = True , as_iterator = True )
360-
361- for entry in self .sle_entries :
362- group_by_key = self .get_group_by_key (entry )
363- if group_by_key not in self .item_warehouse_map :
364- self .initialize_data (group_by_key , entry )
365-
366- self .prepare_item_warehouse_map (entry , group_by_key )
367-
368- self .item_warehouse_map = filter_items_with_no_transactions (
369- self .item_warehouse_map , self .float_precision , self .inventory_dimensions
370- )
371-
372- def prepare_stock_reco_voucher_wise_count (self ):
373- self .stock_reco_voucher_wise_count = frappe ._dict ()
374-
375- doctype = frappe .qb .DocType ("Stock Ledger Entry" )
376- item = frappe .qb .DocType ("Item" )
377-
378- query = (
379- frappe .qb .from_ (doctype )
380- .inner_join (item )
381- .on (doctype .item_code == item .name )
382- .select (doctype .voucher_detail_no , Count (doctype .name ).as_ ("count" ))
383- .where (
384- (doctype .voucher_type == "Stock Reconciliation" )
385- & (doctype .docstatus < 2 )
386- & (doctype .is_cancelled == 0 )
387- & (item .has_serial_no == 1 )
388- )
389- .groupby (doctype .voucher_detail_no )
390- )
391-
392- data = query .run (as_list = True )
393- if data :
394- self .stock_reco_voucher_wise_count = frappe ._dict (data )
395-
396- def prepare_new_data (self ):
397- if self .filters .get ("show_stock_ageing_data" ):
398- self .filters ["show_warehouse_wise_stock" ] = True
399- item_wise_fifo_queue = FIFOSlots (self .filters ).generate ()
400-
401- _func = itemgetter (1 )
402-
403- del self .sle_entries
404-
405- sre_details = self .get_sre_reserved_qty_details ()
406-
407- variant_values = {}
408- if self .filters .get ("show_variant_attributes" ):
409- variant_values = self .get_variant_values_for ()
410-
411- for _key , report_data in self .item_warehouse_map .items ():
412- if variant_data := variant_values .get (report_data .item_code ):
413- report_data .update (variant_data )
414-
415- if self .filters .get ("show_stock_ageing_data" ):
416- opening_fifo_queue = self .get_opening_fifo_queue (report_data ) or []
417-
418- fifo_queue = []
419- if fifo_queue := item_wise_fifo_queue .get ((report_data .item_code , report_data .warehouse )):
420- fifo_queue = fifo_queue .get ("fifo_queue" )
421-
422- if fifo_queue :
423- opening_fifo_queue .extend (fifo_queue )
424-
425- stock_ageing_data = {"average_age" : 0 , "earliest_age" : 0 , "latest_age" : 0 }
426-
427- if opening_fifo_queue :
428- fifo_queue = sorted (filter (_func , opening_fifo_queue ), key = _func )
429- if not fifo_queue :
430- continue
431-
432- to_date = self .to_date
433- stock_ageing_data ["average_age" ] = get_average_age (fifo_queue , to_date )
434- stock_ageing_data ["earliest_age" ] = date_diff (to_date , fifo_queue [0 ][1 ])
435- stock_ageing_data ["latest_age" ] = date_diff (to_date , fifo_queue [- 1 ][1 ])
436- stock_ageing_data ["fifo_queue" ] = fifo_queue
437-
438- report_data .update (stock_ageing_data )
439-
440- report_data .update (
441- {"reserved_stock" : sre_details .get ((report_data .item_code , report_data .warehouse ), 0.0 )}
442- )
443-
444- if (
445- not self .filters .get ("include_zero_stock_items" )
446- and report_data
447- and report_data .bal_qty == 0
448- and report_data .bal_val == 0
449- ):
450- continue
451-
452- self .data .append (report_data )
453-
454- def get_sre_reserved_qty_details (self ) -> dict :
455- from erpnext .stock .doctype .stock_reservation_entry .stock_reservation_entry import (
456- get_sre_reserved_qty_for_items_and_warehouses as get_reserved_qty_details ,
457- )
458-
459- item_code_list , warehouse_list = [], []
460- for d in self .item_warehouse_map :
461- item_code_list .append (d [0 ])
462- warehouse_list .append (d [1 ])
463-
464- return get_reserved_qty_details (item_code_list , warehouse_list )
465-
466- def prepare_item_warehouse_map (self , entry , group_by_key ):
467- qty_dict = self .item_warehouse_map [group_by_key ]
468- for field in self .inventory_dimensions :
469- qty_dict [field ] = entry .get (field )
470-
471- if entry .voucher_type == "Stock Reconciliation" and (
472- not entry .batch_no or entry .serial_no or entry .serial_and_batch_bundle
473- ):
474- if entry .serial_no and self .stock_reco_voucher_wise_count .get (entry .voucher_detail_no , 0 ) == 1 :
475- qty_dict .bal_qty = 0.0
476- qty_diff = flt (entry .actual_qty )
477- else :
478- qty_diff = flt (entry .qty_after_transaction ) - flt (qty_dict .bal_qty )
479- else :
480- qty_diff = flt (entry .actual_qty )
481-
482- value_diff = flt (entry .stock_value_difference )
483-
484- if entry .posting_date < self .from_date or entry .voucher_no in self .opening_vouchers .get (
485- entry .voucher_type , []
486- ):
487- qty_dict .opening_qty += qty_diff
488- qty_dict .opening_val += value_diff
489-
490- elif entry .posting_date >= self .from_date and entry .posting_date <= self .to_date :
491- if flt (qty_diff , self .float_precision ) >= 0 :
492- qty_dict .in_qty += qty_diff
493- else :
494- qty_dict .out_qty += abs (qty_diff )
495-
496- if flt (value_diff , self .float_precision ) >= 0 :
497- qty_dict .in_val += value_diff
498- else :
499- qty_dict .out_val += abs (value_diff )
500-
501- qty_dict .val_rate = entry .valuation_rate
502- qty_dict .bal_qty += qty_diff
503- qty_dict .bal_val += value_diff
504-
505- def initialize_data (self , group_by_key , entry ):
506- self .item_warehouse_map [group_by_key ] = frappe ._dict (
507- {
508- "item_code" : entry .item_code ,
509- "warehouse" : entry .warehouse ,
510- "item_group" : entry .item_group ,
511- "company" : entry .company ,
512- "currency" : self .company_currency ,
513- "stock_uom" : entry .stock_uom ,
514- "item_name" : entry .item_name ,
515- "opening_qty" : 0.0 ,
516- "opening_val" : 0.0 ,
517- "opening_fifo_queue" : [],
518- "in_qty" : 0.0 ,
519- "in_val" : 0.0 ,
520- "out_qty" : 0.0 ,
521- "out_val" : 0.0 ,
522- "bal_qty" : 0.0 ,
523- "bal_val" : 0.0 ,
524- "val_rate" : 0.0 ,
525- }
526- )
527-
528- def get_group_by_key (self , row ) -> tuple :
529- group_by_key = [row .item_code , row .warehouse ]
530-
531- for fieldname in self .inventory_dimensions :
532- if not row .get (fieldname ):
533- continue
534-
535- if self .filters .get (fieldname ) or self .filters .get ("show_dimension_wise_stock" ):
536- group_by_key .append (row .get (fieldname ))
537-
538- return tuple (group_by_key )
539-
540- >> >> >> > 0874 cbc268 (fix : old stock reco entries causing issue in the stock balance report )
541371 def apply_inventory_dimensions_filters (self , query , sle ) -> str :
542372 inventory_dimension_fields = self .get_inventory_dimension_fields ()
543373 if inventory_dimension_fields :
0 commit comments