1414
1515_DOMAIN_START_RE = re .compile (r"\(|(['\"])[!&|]\1" )
1616
17+ UNCLASSIFIED_ROW_DETAIL = "other"
18+
1719
1820def _is_domain (s ):
1921 """Test if a string looks like an Odoo domain"""
@@ -299,25 +301,22 @@ def do_queries(
299301 date_from ,
300302 date_to ,
301303 additional_move_line_filter = None ,
302- aml_model = None ,
304+ aml_model = "account.move.line" ,
305+ auto_expand_col_name = None ,
303306 ):
304307 """Query sums of debit and credit for all accounts and domains
305308 used in expressions.
306309
307310 This method must be executed after done_parsing().
308311 """
309- if not aml_model :
310- aml_model = self .env ["account.move.line" ]
311- else :
312- aml_model = self .env [aml_model ]
313- aml_model = aml_model .with_context (active_test = False )
312+ aml_model = self .env [aml_model ].with_context (active_test = False )
314313 company_rates = self ._get_company_rates (date_to )
315314 # {(domain, mode): {account_id: (debit, credit)}}
316315 self ._data = defaultdict (dict )
317316 domain_by_mode = {}
318317 ends = []
319318 for key in self ._map_account_ids :
320- domain , mode = key
319+ ( domain , mode ) = key
321320 if mode == self .MODE_END and self .smart_end :
322321 # postpone computation of ending balance
323322 ends .append ((domain , mode ))
@@ -330,13 +329,16 @@ def do_queries(
330329 domain .append (("account_id" , "in" , self ._map_account_ids [key ]))
331330 if additional_move_line_filter :
332331 domain .extend (additional_move_line_filter )
332+
333+ get_fields = ["debit" , "credit" , "account_id" , "company_id" ]
334+ group_by_fields = ["account_id" , "company_id" ]
335+ if auto_expand_col_name :
336+ get_fields = [auto_expand_col_name ] + get_fields
337+ group_by_fields = [auto_expand_col_name ] + group_by_fields
338+
333339 # fetch sum of debit/credit, grouped by account_id
334- accs = aml_model .read_group (
335- domain ,
336- ["debit" , "credit" , "account_id" , "company_id" ],
337- ["account_id" , "company_id" ],
338- lazy = False ,
339- )
340+ accs = aml_model .read_group (domain , get_fields , group_by_fields , lazy = False )
341+
340342 for acc in accs :
341343 rate , dp = company_rates [acc ["company_id" ][0 ]]
342344 debit = acc ["debit" ] or 0.0
@@ -346,19 +348,45 @@ def do_queries(
346348 ):
347349 # in initial mode, ignore accounts with 0 balance
348350 continue
349- self ._data [key ][acc ["account_id" ][0 ]] = (debit * rate , credit * rate )
351+ if (
352+ auto_expand_col_name
353+ and auto_expand_col_name in acc
354+ and acc [auto_expand_col_name ]
355+ ):
356+ rdi_id = acc [auto_expand_col_name ][0 ]
357+ else :
358+ rdi_id = UNCLASSIFIED_ROW_DETAIL
359+ if not self ._data [key ].get (rdi_id , False ):
360+ self ._data [key ][rdi_id ] = defaultdict (dict )
361+ self ._data [key ][rdi_id ][acc ["account_id" ][0 ]] = (
362+ debit * rate ,
363+ credit * rate ,
364+ )
350365 # compute ending balances by summing initial and variation
351366 for key in ends :
352367 domain , mode = key
353368 initial_data = self ._data [(domain , self .MODE_INITIAL )]
354369 variation_data = self ._data [(domain , self .MODE_VARIATION )]
355- account_ids = set (initial_data .keys ()) | set (variation_data .keys ())
356- for account_id in account_ids :
357- di , ci = initial_data .get (account_id , (AccountingNone , AccountingNone ))
358- dv , cv = variation_data .get (
359- account_id , (AccountingNone , AccountingNone )
370+ rdis = set (initial_data .keys ()) | set (variation_data .keys ())
371+ for rdi in rdis :
372+ if not initial_data .get (rdi , False ):
373+ initial_data [rdi ] = defaultdict (dict )
374+ if not variation_data .get (rdi , False ):
375+ variation_data [rdi ] = defaultdict (dict )
376+ if not self ._data [key ].get (rdi , False ):
377+ self ._data [key ][rdi ] = defaultdict (dict )
378+
379+ account_ids = set (initial_data [rdi ].keys ()) | set (
380+ variation_data [rdi ].keys ()
360381 )
361- self ._data [key ][account_id ] = (di + dv , ci + cv )
382+ for account_id in account_ids :
383+ di , ci = initial_data [rdi ].get (
384+ account_id , (AccountingNone , AccountingNone )
385+ )
386+ dv , cv = variation_data [rdi ].get (
387+ account_id , (AccountingNone , AccountingNone )
388+ )
389+ self ._data [key ][rdi ][account_id ] = (di + dv , ci + cv )
362390
363391 def replace_expr (self , expr ):
364392 """Replace accounting variables in an expression by their amount.
@@ -371,23 +399,25 @@ def replace_expr(self, expr):
371399 def f (mo ):
372400 field , mode , acc_domain , ml_domain = self ._parse_match_object (mo )
373401 key = (ml_domain , mode )
374- account_ids_data = self ._data [key ]
402+ rdi_ids_data = self ._data [key ]
375403 v = AccountingNone
376404 account_ids = self ._account_ids_by_acc_domain [acc_domain ]
377- for account_id in account_ids :
378- debit , credit = account_ids_data .get (
379- account_id , (AccountingNone , AccountingNone )
380- )
381- if field == "bal" :
382- v += debit - credit
383- elif field == "pbal" and debit >= credit :
384- v += debit - credit
385- elif field == "nbal" and debit < credit :
386- v += debit - credit
387- elif field == "deb" :
388- v += debit
389- elif field == "crd" :
390- v += credit
405+ for rdi in rdi_ids_data :
406+ account_ids_data = self ._data [key ][rdi ]
407+ for account_id in account_ids :
408+ debit , credit = account_ids_data .get (
409+ account_id , (AccountingNone , AccountingNone )
410+ )
411+ if field == "bal" :
412+ v += debit - credit
413+ elif field == "pbal" and debit >= credit :
414+ v += debit - credit
415+ elif field == "nbal" and debit < credit :
416+ v += debit - credit
417+ elif field == "deb" :
418+ v += debit
419+ elif field == "crd" :
420+ v += credit
391421 # in initial balance mode, assume 0 is None
392422 # as it does not make sense to distinguish 0 from "no data"
393423 if (
@@ -401,11 +431,11 @@ def f(mo):
401431 return self ._ACC_RE .sub (f , expr )
402432
403433 def replace_exprs_by_account_id (self , exprs ):
404- """Replace accounting variables in a list of expression
405- by their amount, iterating by accounts involved in the expression.
434+ """This method is depreciated and replaced by replace_exprs_by_row_detail.
406435
436+ Replace accounting variables in a list of expression
437+ by their amount, iterating by accounts involved in the expression.
407438 yields account_id, replaced_expr
408-
409439 This method must be executed after do_queries().
410440 """
411441
@@ -417,7 +447,7 @@ def f(mo):
417447 if account_id not in self ._account_ids_by_acc_domain [acc_domain ]:
418448 return "(AccountingNone)"
419449 # here we know account_id is involved in acc_domain
420- account_ids_data = self ._data [key ]
450+ account_ids_data = self ._data [key ][ UNCLASSIFIED_ROW_DETAIL ]
421451 debit , credit = account_ids_data .get (
422452 account_id , (AccountingNone , AccountingNone )
423453 )
@@ -452,14 +482,66 @@ def f(mo):
452482 for mo in self ._ACC_RE .finditer (expr ):
453483 field , mode , acc_domain , ml_domain = self ._parse_match_object (mo )
454484 key = (ml_domain , mode )
455- account_ids_data = self ._data [key ]
485+ account_ids_data = self ._data [key ][ UNCLASSIFIED_ROW_DETAIL ]
456486 for account_id in self ._account_ids_by_acc_domain [acc_domain ]:
457487 if account_id in account_ids_data :
458488 account_ids .add (account_id )
459489
460490 for account_id in account_ids :
461491 yield account_id , [self ._ACC_RE .sub (f , expr ) for expr in exprs ]
462492
493+ def replace_exprs_by_row_detail (self , exprs ):
494+ """Replace accounting variables in a list of expression
495+ by their amount, iterating by accounts involved in the expression.
496+
497+ yields account_id, replaced_expr
498+
499+ This method must be executed after do_queries().
500+ """
501+
502+ def f (mo ):
503+ field , mode , acc_domain , ml_domain = self ._parse_match_object (mo )
504+ key = (ml_domain , mode )
505+ v = AccountingNone
506+ account_ids_data = self ._data [key ][rdi_id ]
507+ account_ids = self ._account_ids_by_acc_domain [acc_domain ]
508+
509+ for account_id in account_ids :
510+ debit , credit = account_ids_data .get (
511+ account_id , (AccountingNone , AccountingNone )
512+ )
513+ if field == "bal" :
514+ v += debit - credit
515+ elif field == "pbal" and debit >= credit :
516+ v += debit - credit
517+ elif field == "nbal" and debit < credit :
518+ v += debit - credit
519+ elif field == "deb" :
520+ v += debit
521+ elif field == "crd" :
522+ v += credit
523+ # in initial balance mode, assume 0 is None
524+ # as it does not make sense to distinguish 0 from "no data"
525+ if (
526+ v is not AccountingNone
527+ and mode in (self .MODE_INITIAL , self .MODE_UNALLOCATED )
528+ and float_is_zero (v , precision_digits = self .dp )
529+ ):
530+ v = AccountingNone
531+ return "(" + repr (v ) + ")"
532+
533+ rdi_ids = set ()
534+ for expr in exprs :
535+ for mo in self ._ACC_RE .finditer (expr ):
536+ field , mode , acc_domain , ml_domain = self ._parse_match_object (mo )
537+ key = (ml_domain , mode )
538+ rdis_data = self ._data [key ]
539+ for rdi_id in rdis_data .keys ():
540+ rdi_ids .add (rdi_id )
541+
542+ for rdi_id in rdi_ids :
543+ yield rdi_id , [self ._ACC_RE .sub (f , expr ) for expr in exprs ]
544+
463545 @classmethod
464546 def _get_balances (cls , mode , companies , date_from , date_to ):
465547 expr = "deb{mode}[], crd{mode}[]" .format (mode = mode )
@@ -470,7 +552,10 @@ def _get_balances(cls, mode, companies, date_from, date_to):
470552 aep .parse_expr (expr )
471553 aep .done_parsing ()
472554 aep .do_queries (date_from , date_to )
473- return aep ._data [((), mode )]
555+
556+ return aep ._data [((), mode )].get (UNCLASSIFIED_ROW_DETAIL , {})
557+ # to keep compatibility, we give the UNCLASSIFIED_ROW_DETAIL
558+ # (expecting that auto_expand_col_names=None was given to do_queries )
474559
475560 @classmethod
476561 def get_balances_initial (cls , companies , date ):
0 commit comments