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"""
@@ -300,24 +302,22 @@ def do_queries(
300302 date_to ,
301303 additional_move_line_filter = None ,
302304 aml_model = None ,
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 ]
312+ aml_model = self .env [aml_model or "account.move.line" ]
313313 aml_model = aml_model .with_context (active_test = False )
314314 company_rates = self ._get_company_rates (date_to )
315315 # {(domain, mode): {account_id: (debit, credit)}}
316316 self ._data = defaultdict (dict )
317317 domain_by_mode = {}
318318 ends = []
319319 for key in self ._map_account_ids :
320- domain , mode = key
320+ ( domain , mode ) = key
321321 if mode == self .MODE_END and self .smart_end :
322322 # postpone computation of ending balance
323323 ends .append ((domain , mode ))
@@ -330,13 +330,16 @@ def do_queries(
330330 domain .append (("account_id" , "in" , self ._map_account_ids [key ]))
331331 if additional_move_line_filter :
332332 domain .extend (additional_move_line_filter )
333+
334+ get_fields = ["debit" , "credit" , "account_id" , "company_id" ]
335+ group_by_fields = ["account_id" , "company_id" ]
336+ if auto_expand_col_name :
337+ get_fields = [auto_expand_col_name ] + get_fields
338+ group_by_fields = [auto_expand_col_name ] + group_by_fields
339+
333340 # 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- )
341+ accs = aml_model .read_group (domain , get_fields , group_by_fields , lazy = False )
342+
340343 for acc in accs :
341344 rate , dp = company_rates [acc ["company_id" ][0 ]]
342345 debit = acc ["debit" ] or 0.0
@@ -346,19 +349,45 @@ def do_queries(
346349 ):
347350 # in initial mode, ignore accounts with 0 balance
348351 continue
349- self ._data [key ][acc ["account_id" ][0 ]] = (debit * rate , credit * rate )
352+ if (
353+ auto_expand_col_name
354+ and auto_expand_col_name in acc
355+ and acc [auto_expand_col_name ]
356+ ):
357+ rdi_id = acc [auto_expand_col_name ][0 ]
358+ else :
359+ rdi_id = UNCLASSIFIED_ROW_DETAIL
360+ if not self ._data [key ].get (rdi_id , False ):
361+ self ._data [key ][rdi_id ] = defaultdict (dict )
362+ self ._data [key ][rdi_id ][acc ["account_id" ][0 ]] = (
363+ debit * rate ,
364+ credit * rate ,
365+ )
350366 # compute ending balances by summing initial and variation
351367 for key in ends :
352368 domain , mode = key
353369 initial_data = self ._data [(domain , self .MODE_INITIAL )]
354370 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 )
371+ rdis = set (initial_data .keys ()) | set (variation_data .keys ())
372+ for rdi in rdis :
373+ if not initial_data .get (rdi , False ):
374+ initial_data [rdi ] = defaultdict (dict )
375+ if not variation_data .get (rdi , False ):
376+ variation_data [rdi ] = defaultdict (dict )
377+ if not self ._data [key ].get (rdi , False ):
378+ self ._data [key ][rdi ] = defaultdict (dict )
379+
380+ account_ids = set (initial_data [rdi ].keys ()) | set (
381+ variation_data [rdi ].keys ()
360382 )
361- self ._data [key ][account_id ] = (di + dv , ci + cv )
383+ for account_id in account_ids :
384+ di , ci = initial_data [rdi ].get (
385+ account_id , (AccountingNone , AccountingNone )
386+ )
387+ dv , cv = variation_data [rdi ].get (
388+ account_id , (AccountingNone , AccountingNone )
389+ )
390+ self ._data [key ][rdi ][account_id ] = (di + dv , ci + cv )
362391
363392 def replace_expr (self , expr ):
364393 """Replace accounting variables in an expression by their amount.
@@ -371,23 +400,25 @@ def replace_expr(self, expr):
371400 def f (mo ):
372401 field , mode , acc_domain , ml_domain = self ._parse_match_object (mo )
373402 key = (ml_domain , mode )
374- account_ids_data = self ._data [key ]
403+ rdi_ids_data = self ._data [key ]
375404 v = AccountingNone
376405 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
406+ for rdi in rdi_ids_data :
407+ account_ids_data = self ._data [key ][rdi ]
408+ for account_id in account_ids :
409+ debit , credit = account_ids_data .get (
410+ account_id , (AccountingNone , AccountingNone )
411+ )
412+ if field == "bal" :
413+ v += debit - credit
414+ elif field == "pbal" and debit >= credit :
415+ v += debit - credit
416+ elif field == "nbal" and debit < credit :
417+ v += debit - credit
418+ elif field == "deb" :
419+ v += debit
420+ elif field == "crd" :
421+ v += credit
391422 # in initial balance mode, assume 0 is None
392423 # as it does not make sense to distinguish 0 from "no data"
393424 if (
@@ -401,11 +432,11 @@ def f(mo):
401432 return self ._ACC_RE .sub (f , expr )
402433
403434 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.
435+ """This method is depreciated and replaced by replace_exprs_by_row_detail.
406436
437+ Replace accounting variables in a list of expression
438+ by their amount, iterating by accounts involved in the expression.
407439 yields account_id, replaced_expr
408-
409440 This method must be executed after do_queries().
410441 """
411442
@@ -417,7 +448,7 @@ def f(mo):
417448 if account_id not in self ._account_ids_by_acc_domain [acc_domain ]:
418449 return "(AccountingNone)"
419450 # here we know account_id is involved in acc_domain
420- account_ids_data = self ._data [key ]
451+ account_ids_data = self ._data [key ][ UNCLASSIFIED_ROW_DETAIL ]
421452 debit , credit = account_ids_data .get (
422453 account_id , (AccountingNone , AccountingNone )
423454 )
@@ -452,14 +483,66 @@ def f(mo):
452483 for mo in self ._ACC_RE .finditer (expr ):
453484 field , mode , acc_domain , ml_domain = self ._parse_match_object (mo )
454485 key = (ml_domain , mode )
455- account_ids_data = self ._data [key ]
486+ account_ids_data = self ._data [key ][ UNCLASSIFIED_ROW_DETAIL ]
456487 for account_id in self ._account_ids_by_acc_domain [acc_domain ]:
457488 if account_id in account_ids_data :
458489 account_ids .add (account_id )
459490
460491 for account_id in account_ids :
461492 yield account_id , [self ._ACC_RE .sub (f , expr ) for expr in exprs ]
462493
494+ def replace_exprs_by_row_detail (self , exprs ):
495+ """Replace accounting variables in a list of expression
496+ by their amount, iterating by accounts involved in the expression.
497+
498+ yields account_id, replaced_expr
499+
500+ This method must be executed after do_queries().
501+ """
502+
503+ def f (mo ):
504+ field , mode , acc_domain , ml_domain = self ._parse_match_object (mo )
505+ key = (ml_domain , mode )
506+ v = AccountingNone
507+ account_ids_data = self ._data [key ][rdi_id ]
508+ account_ids = self ._account_ids_by_acc_domain [acc_domain ]
509+
510+ for account_id in account_ids :
511+ debit , credit = account_ids_data .get (
512+ account_id , (AccountingNone , AccountingNone )
513+ )
514+ if field == "bal" :
515+ v += debit - credit
516+ elif field == "pbal" and debit >= credit :
517+ v += debit - credit
518+ elif field == "nbal" and debit < credit :
519+ v += debit - credit
520+ elif field == "deb" :
521+ v += debit
522+ elif field == "crd" :
523+ v += credit
524+ # in initial balance mode, assume 0 is None
525+ # as it does not make sense to distinguish 0 from "no data"
526+ if (
527+ v is not AccountingNone
528+ and mode in (self .MODE_INITIAL , self .MODE_UNALLOCATED )
529+ and float_is_zero (v , precision_digits = self .dp )
530+ ):
531+ v = AccountingNone
532+ return "(" + repr (v ) + ")"
533+
534+ rdi_ids = set ()
535+ for expr in exprs :
536+ for mo in self ._ACC_RE .finditer (expr ):
537+ field , mode , acc_domain , ml_domain = self ._parse_match_object (mo )
538+ key = (ml_domain , mode )
539+ rdis_data = self ._data [key ]
540+ for rdi_id in rdis_data .keys ():
541+ rdi_ids .add (rdi_id )
542+
543+ for rdi_id in rdi_ids :
544+ yield rdi_id , [self ._ACC_RE .sub (f , expr ) for expr in exprs ]
545+
463546 @classmethod
464547 def _get_balances (cls , mode , companies , date_from , date_to ):
465548 expr = "deb{mode}[], crd{mode}[]" .format (mode = mode )
@@ -470,7 +553,10 @@ def _get_balances(cls, mode, companies, date_from, date_to):
470553 aep .parse_expr (expr )
471554 aep .done_parsing ()
472555 aep .do_queries (date_from , date_to )
473- return aep ._data [((), mode )]
556+
557+ return aep ._data [((), mode )].get (UNCLASSIFIED_ROW_DETAIL , {})
558+ # to keep compatibility, we give the UNCLASSIFIED_ROW_DETAIL
559+ # (expecting that auto_expand_col_names=None was given to do_queries )
474560
475561 @classmethod
476562 def get_balances_initial (cls , companies , date ):
0 commit comments