77
88import frappe
99from frappe import _
10+ < << << << HEAD
1011from frappe .query_builder import Order
1112from frappe .query_builder .functions import Coalesce
13+ == == == =
14+ from frappe .query_builder .functions import Coalesce , Count
15+ > >> >> >> 0874 cbc268 (fix : old stock reco entries causing issue in the stock balance report )
1216from frappe .utils import add_days , cint , date_diff , flt , getdate
1317from frappe .utils .nestedset import get_descendants_of
1418
@@ -318,6 +322,7 @@ def prepare_stock_ledger_entries(self):
318322 sle .serial_no ,
319323 sle .serial_and_batch_bundle ,
320324 sle .has_serial_no ,
325+ sle .voucher_detail_no ,
321326 item_table .item_group ,
322327 item_table .stock_uom ,
323328 item_table .item_name ,
@@ -337,6 +342,202 @@ def prepare_stock_ledger_entries(self):
337342
338343 self .sle_query = query
339344
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 )
340541 def apply_inventory_dimensions_filters (self , query , sle ) -> str :
341542 inventory_dimension_fields = self .get_inventory_dimension_fields ()
342543 if inventory_dimension_fields :
0 commit comments