# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import json from odoo import models, fields, api, _ from odoo.tools.misc import format_date from odoo.tools import get_lang from odoo.exceptions import UserError from datetime import timedelta from collections import defaultdict class GeneralLedgerCustomHandler(models.AbstractModel): _name = 'account.general.ledger.report.handler' _inherit = 'account.report.custom.handler' _description = 'General Ledger Custom Handler' def _get_custom_display_config(self): return { 'templates': { 'AccountReportLineName': 'account_reports.GeneralLedgerLineName', }, } def _custom_options_initializer(self, report, options, previous_options=None): # Remove multi-currency columns if needed super()._custom_options_initializer(report, options, previous_options=previous_options) if self.user_has_groups('base.group_multi_currency'): options['multi_currency'] = True else: options['columns'] = [ column for column in options['columns'] if column['expression_label'] != 'amount_currency' ] # Automatically unfold the report when printing it, unless some specific lines have been unfolded options['unfold_all'] = (options['export_mode'] == 'print' and not options.get('unfolded_lines')) or options['unfold_all'] def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None): lines = [] date_from = fields.Date.from_string(options['date']['date_from']) company_currency = self.env.company.currency_id totals_by_column_group = defaultdict(lambda: {'debit': 0, 'credit': 0, 'balance': 0}) for account, column_group_results in self._query_values(report, options): eval_dict = {} has_lines = False for column_group_key, results in column_group_results.items(): account_sum = results.get('sum', {}) account_un_earn = results.get('unaffected_earnings', {}) account_debit = account_sum.get('debit', 0.0) + account_un_earn.get('debit', 0.0) account_credit = account_sum.get('credit', 0.0) + account_un_earn.get('credit', 0.0) account_balance = account_sum.get('balance', 0.0) + account_un_earn.get('balance', 0.0) eval_dict[column_group_key] = { 'amount_currency': account_sum.get('amount_currency', 0.0) + account_un_earn.get('amount_currency', 0.0), 'debit': account_debit, 'credit': account_credit, 'balance': account_balance, } max_date = account_sum.get('max_date') has_lines = has_lines or (max_date and max_date >= date_from) totals_by_column_group[column_group_key]['debit'] += account_debit totals_by_column_group[column_group_key]['credit'] += account_credit totals_by_column_group[column_group_key]['balance'] += account_balance lines.append(self._get_account_title_line(report, options, account, has_lines, eval_dict)) # Report total line. for totals in totals_by_column_group.values(): totals['balance'] = company_currency.round(totals['balance']) # Tax Declaration lines. journal_options = report._get_options_journals(options) if len(options['column_groups']) == 1 and len(journal_options) == 1 and journal_options[0]['type'] in ('sale', 'purchase'): lines += self._tax_declaration_lines(report, options, journal_options[0]['type']) # Total line lines.append(self._get_total_line(report, options, totals_by_column_group)) return [(0, line) for line in lines] def _custom_unfold_all_batch_data_generator(self, report, options, lines_to_expand_by_function): account_ids_to_expand = [] for line_dict in lines_to_expand_by_function.get('_report_expand_unfoldable_line_general_ledger', []): model, model_id = report._get_model_info_from_id(line_dict['id']) if model == 'account.account': account_ids_to_expand.append(model_id) limit_to_load = report.load_more_limit if report.load_more_limit and not options.get('export_mode') else None has_more_per_account_id = {} unlimited_aml_results_per_account_id = self._get_aml_values(report, options, account_ids_to_expand)[0] if limit_to_load: # Apply the load_more_limit. # load_more_limit cannot be passed to the call to _get_aml_values, otherwise it won't be applied per account but on the whole result. # We gain perf from batching, but load every result ; then we need to filter them. aml_results_per_account_id = {} for account_id, account_aml_results in unlimited_aml_results_per_account_id.items(): account_values = {} for key, value in account_aml_results.items(): if len(account_values) == limit_to_load: has_more_per_account_id[account_id] = True break account_values[key] = value aml_results_per_account_id[account_id] = account_values else: aml_results_per_account_id = unlimited_aml_results_per_account_id return { 'initial_balances': self._get_initial_balance_values(report, account_ids_to_expand, options), 'aml_results': aml_results_per_account_id, 'has_more': has_more_per_account_id, } def _tax_declaration_lines(self, report, options, tax_type): labels_replacement = { 'debit': _("Base Amount"), 'credit': _("Tax Amount"), } rslt = [{ 'id': report._get_generic_line_id(None, None, markup='tax_decl_header_1'), 'name': _('Tax Declaration'), 'columns': [{} for column in options['columns']], 'level': 1, 'unfoldable': False, 'unfolded': False, }, { 'id': report._get_generic_line_id(None, None, markup='tax_decl_header_2'), 'name': _('Name'), 'columns': [{'name': labels_replacement.get(col['expression_label'], '')} for col in options['columns']], 'level': 3, 'unfoldable': False, 'unfolded': False, }] # Call the generic tax report generic_tax_report = self.env.ref('account.generic_tax_report') tax_report_options = generic_tax_report.get_options({**options, 'selected_variant_id': generic_tax_report.id, 'forced_domain': [('tax_line_id.type_tax_use', '=', tax_type)]}) tax_report_lines = generic_tax_report._get_lines(tax_report_options) tax_type_parent_line_id = generic_tax_report._get_generic_line_id(None, None, markup=tax_type) for tax_report_line in tax_report_lines: if tax_report_line.get('parent_id') == tax_type_parent_line_id: original_columns = tax_report_line['columns'] row_column_map = { 'debit': original_columns[0], 'credit': original_columns[1], } tax_report_line['columns'] = [row_column_map.get(col['expression_label'], {}) for col in options['columns']] rslt.append(tax_report_line) return rslt def _query_values(self, report, options): """ Executes the queries, and performs all the computations. :return: [(record, values_by_column_group), ...], where - record is an account.account record. - values_by_column_group is a dict in the form {column_group_key: values, ...} - column_group_key is a string identifying a column group, as in options['column_groups'] - values is a list of dictionaries, one per period containing: - sum: {'debit': float, 'credit': float, 'balance': float} - (optional) initial_balance: {'debit': float, 'credit': float, 'balance': float} - (optional) unaffected_earnings: {'debit': float, 'credit': float, 'balance': float} """ # Execute the queries and dispatch the results. query, params = self._get_query_sums(report, options) if not query: return [] groupby_accounts = {} groupby_companies = {} self._cr.execute(query, params) for res in self._cr.dictfetchall(): # No result to aggregate. if res['groupby'] is None: continue column_group_key = res['column_group_key'] key = res['key'] if key == 'sum': groupby_accounts.setdefault(res['groupby'], {col_group_key: {} for col_group_key in options['column_groups']}) groupby_accounts[res['groupby']][column_group_key][key] = res elif key == 'initial_balance': groupby_accounts.setdefault(res['groupby'], {col_group_key: {} for col_group_key in options['column_groups']}) groupby_accounts[res['groupby']][column_group_key][key] = res elif key == 'unaffected_earnings': groupby_companies.setdefault(res['groupby'], {col_group_key: {} for col_group_key in options['column_groups']}) groupby_companies[res['groupby']][column_group_key] = res # Affect the unaffected earnings to the first fetched account of type 'account.data_unaffected_earnings'. # There is an unaffected earnings for each company but it's less costly to fetch all candidate accounts in # a single search and then iterate it. if groupby_companies: equity_unaffected_account_ids_by_company = self.env['account.account'].browse( self.env['account.account']._name_search(options.get('filter_search_bar'), [ *self.env['account.account']._check_company_domain(list(groupby_companies.keys())), ('account_type', '=', 'equity_unaffected'), ]) ).grouped('company_id') for company_id, groupby_company in groupby_companies.items(): if equity_unaffected_account := equity_unaffected_account_ids_by_company.get(self.env['res.company'].browse(company_id).root_id): for column_group_key in options['column_groups']: groupby_accounts.setdefault(equity_unaffected_account.id, {col_group_key: {'unaffected_earnings': {}} for col_group_key in options['column_groups']}) if unaffected_earnings := groupby_company.get(column_group_key): if groupby_accounts[equity_unaffected_account.id][column_group_key].get('unaffected_earnings'): for key in ['amount_currency', 'debit', 'credit', 'balance']: groupby_accounts[equity_unaffected_account.id][column_group_key]['unaffected_earnings'][key] += unaffected_earnings[key] else: groupby_accounts[equity_unaffected_account.id][column_group_key]['unaffected_earnings'] = unaffected_earnings # Retrieve the accounts to browse. # groupby_accounts.keys() contains all account ids affected by: # - the amls in the current period. # - the amls affecting the initial balance. # - the unaffected earnings allocation. # Note a search is done instead of a browse to preserve the table ordering. if groupby_accounts: accounts = self.env['account.account'].search([('id', 'in', list(groupby_accounts.keys()))]) else: accounts = [] return [(account, groupby_accounts[account.id]) for account in accounts] def _get_query_sums(self, report, options): """ Construct a query retrieving all the aggregated sums to build the report. It includes: - sums for all accounts. - sums for the initial balances. - sums for the unaffected earnings. - sums for the tax declaration. :return: (query, params) """ options_by_column_group = report._split_options_per_column_group(options) params = [] queries = [] # Create the currency table. # As the currency table is the same whatever the comparisons, create it only once. ct_query = report._get_query_currency_table(options) # ============================================ # 1) Get sums for all accounts. # ============================================ for column_group_key, options_group in options_by_column_group.items(): if not options.get('general_ledger_strict_range'): options_group = self._get_options_sum_balance(options_group) # Sum is computed including the initial balance of the accounts configured to do so, unless a special option key is used # (this is required for trial balance, which is based on general ledger) sum_date_scope = 'strict_range' if options_group.get('general_ledger_strict_range') else 'normal' query_domain = [] if options.get('export_mode') == 'print' and options.get('filter_search_bar'): query_domain.append(('account_id', 'ilike', options['filter_search_bar'])) if options_group.get('include_current_year_in_unaff_earnings'): query_domain += [('account_id.include_initial_balance', '=', True)] tables, where_clause, where_params = report._query_get(options_group, sum_date_scope, domain=query_domain) params.append(column_group_key) params += where_params queries.append(f""" SELECT account_move_line.account_id AS groupby, 'sum' AS key, MAX(account_move_line.date) AS max_date, %s AS column_group_key, COALESCE(SUM(account_move_line.amount_currency), 0.0) AS amount_currency, SUM(ROUND(account_move_line.debit * currency_table.rate, currency_table.precision)) AS debit, SUM(ROUND(account_move_line.credit * currency_table.rate, currency_table.precision)) AS credit, SUM(ROUND(account_move_line.balance * currency_table.rate, currency_table.precision)) AS balance FROM {tables} LEFT JOIN {ct_query} ON currency_table.company_id = account_move_line.company_id WHERE {where_clause} GROUP BY account_move_line.account_id """) # ============================================ # 2) Get sums for the unaffected earnings. # ============================================ if not options_group.get('general_ledger_strict_range'): unaff_earnings_domain = [('account_id.include_initial_balance', '=', False)] # The period domain is expressed as: # [ # ('date' <= fiscalyear['date_from'] - 1), # ('account_id.include_initial_balance', '=', False), # ] new_options = self._get_options_unaffected_earnings(options_group) tables, where_clause, where_params = report._query_get(new_options, 'strict_range', domain=unaff_earnings_domain) params.append(column_group_key) params += where_params queries.append(f""" SELECT account_move_line.company_id AS groupby, 'unaffected_earnings' AS key, NULL AS max_date, %s AS column_group_key, COALESCE(SUM(account_move_line.amount_currency), 0.0) AS amount_currency, SUM(ROUND(account_move_line.debit * currency_table.rate, currency_table.precision)) AS debit, SUM(ROUND(account_move_line.credit * currency_table.rate, currency_table.precision)) AS credit, SUM(ROUND(account_move_line.balance * currency_table.rate, currency_table.precision)) AS balance FROM {tables} LEFT JOIN {ct_query} ON currency_table.company_id = account_move_line.company_id WHERE {where_clause} GROUP BY account_move_line.company_id """) return ' UNION ALL '.join(queries), params def _get_options_unaffected_earnings(self, options): ''' Create options used to compute the unaffected earnings. The unaffected earnings are the amount of benefits/loss that have not been allocated to another account in the previous fiscal years. The resulting dates domain will be: [ ('date' <= fiscalyear['date_from'] - 1), ('account_id.include_initial_balance', '=', False), ] :param options: The report options. :return: A copy of the options. ''' new_options = options.copy() new_options.pop('filter_search_bar', None) fiscalyear_dates = self.env.company.compute_fiscalyear_dates(fields.Date.from_string(options['date']['date_from'])) # Trial balance uses the options key, general ledger does not new_date_to = fields.Date.from_string(new_options['date']['date_to']) if options.get('include_current_year_in_unaff_earnings') else fiscalyear_dates['date_from'] - timedelta(days=1) new_options['date'] = { 'mode': 'single', 'date_to': fields.Date.to_string(new_date_to), } return new_options def _get_aml_values(self, report, options, expanded_account_ids, offset=0, limit=None): rslt = {account_id: {} for account_id in expanded_account_ids} aml_query, aml_params = self._get_query_amls(report, options, expanded_account_ids, offset=offset, limit=limit) self._cr.execute(aml_query, aml_params) aml_results_number = 0 has_more = False for aml_result in self._cr.dictfetchall(): aml_results_number += 1 if aml_results_number == limit: has_more = True break if aml_result['ref']: aml_result['communication'] = f"{aml_result['ref']} - {aml_result['name']}" else: aml_result['communication'] = aml_result['name'] # The same aml can return multiple results when using account_report_cash_basis module, if the receivable/payable # is reconciled with multiple payments. In this case, the date shown for the move lines actually corresponds to the # reconciliation date. In order to keep distinct lines in this case, we include date in the grouping key. aml_key = (aml_result['id'], aml_result['date']) account_result = rslt[aml_result['account_id']] if not aml_key in account_result: account_result[aml_key] = {col_group_key: {} for col_group_key in options['column_groups']} already_present_result = account_result[aml_key][aml_result['column_group_key']] if already_present_result: # In case the same move line gives multiple results at the same date, add them. # This does not happen in standard GL report, but could because of custom shadowing of account.move.line, # such as the one done in account_report_cash_basis (if the payable/receivable line is reconciled twice at the same date). already_present_result['debit'] += aml_result['debit'] already_present_result['credit'] += aml_result['credit'] already_present_result['balance'] += aml_result['balance'] already_present_result['amount_currency'] += aml_result['amount_currency'] else: account_result[aml_key][aml_result['column_group_key']] = aml_result return rslt, has_more def _get_query_amls(self, report, options, expanded_account_ids, offset=0, limit=None): """ Construct a query retrieving the account.move.lines when expanding a report line with or without the load more. :param options: The report options. :param expanded_account_ids: The account.account ids corresponding to consider. If None, match every account. :param offset: The offset of the query (used by the load more). :param limit: The limit of the query (used by the load more). :return: (query, params) """ additional_domain = [('account_id', 'in', expanded_account_ids)] if expanded_account_ids is not None else None queries = [] all_params = [] lang = self.env.user.lang or get_lang(self.env).code journal_name = f"COALESCE(journal.name->>'{lang}', journal.name->>'en_US')" if \ self.pool['account.journal'].name.translate else 'journal.name' account_name = f"COALESCE(account.name->>'{lang}', account.name->>'en_US')" if \ self.pool['account.account'].name.translate else 'account.name' for column_group_key, group_options in report._split_options_per_column_group(options).items(): # Get sums for the account move lines. # period: [('date' <= options['date_to']), ('date', '>=', options['date_from'])] tables, where_clause, where_params = report._query_get(group_options, domain=additional_domain, date_scope='strict_range') ct_query = report._get_query_currency_table(group_options) query = f''' (SELECT account_move_line.id, account_move_line.date, account_move_line.date_maturity, account_move_line.name, account_move_line.ref, account_move_line.company_id, account_move_line.account_id, account_move_line.payment_id, account_move_line.partner_id, account_move_line.currency_id, account_move_line.amount_currency, COALESCE(account_move_line.invoice_date, account_move_line.date) AS invoice_date, account_move_line.date AS date, ROUND(account_move_line.debit * currency_table.rate, currency_table.precision) AS debit, ROUND(account_move_line.credit * currency_table.rate, currency_table.precision) AS credit, ROUND(account_move_line.balance * currency_table.rate, currency_table.precision) AS balance, move.name AS move_name, company.currency_id AS company_currency_id, partner.name AS partner_name, move.move_type AS move_type, account.code AS account_code, {account_name} AS account_name, journal.code AS journal_code, {journal_name} AS journal_name, full_rec.id AS full_rec_name, %s AS column_group_key FROM {tables} JOIN account_move move ON move.id = account_move_line.move_id LEFT JOIN {ct_query} ON currency_table.company_id = account_move_line.company_id LEFT JOIN res_company company ON company.id = account_move_line.company_id LEFT JOIN res_partner partner ON partner.id = account_move_line.partner_id LEFT JOIN account_account account ON account.id = account_move_line.account_id LEFT JOIN account_journal journal ON journal.id = account_move_line.journal_id LEFT JOIN account_full_reconcile full_rec ON full_rec.id = account_move_line.full_reconcile_id WHERE {where_clause} ORDER BY account_move_line.date, account_move_line.move_name, account_move_line.id) ''' queries.append(query) all_params.append(column_group_key) all_params += where_params full_query = " UNION ALL ".join(queries) if offset: full_query += ' OFFSET %s ' all_params.append(offset) if limit: full_query += ' LIMIT %s ' all_params.append(limit) return (full_query, all_params) def _get_initial_balance_values(self, report, account_ids, options): """ Get sums for the initial balance. """ queries = [] params = [] for column_group_key, options_group in report._split_options_per_column_group(options).items(): new_options = self._get_options_initial_balance(options_group) ct_query = report._get_query_currency_table(new_options) domain = [('account_id', 'in', account_ids)] if new_options.get('include_current_year_in_unaff_earnings'): domain += [('account_id.include_initial_balance', '=', True)] tables, where_clause, where_params = report._query_get(new_options, 'normal', domain=domain) params.append(column_group_key) params += where_params queries.append(f""" SELECT account_move_line.account_id AS groupby, 'initial_balance' AS key, NULL AS max_date, %s AS column_group_key, COALESCE(SUM(account_move_line.amount_currency), 0.0) AS amount_currency, SUM(ROUND(account_move_line.debit * currency_table.rate, currency_table.precision)) AS debit, SUM(ROUND(account_move_line.credit * currency_table.rate, currency_table.precision)) AS credit, SUM(ROUND(account_move_line.balance * currency_table.rate, currency_table.precision)) AS balance FROM {tables} LEFT JOIN {ct_query} ON currency_table.company_id = account_move_line.company_id WHERE {where_clause} GROUP BY account_move_line.account_id """) self._cr.execute(" UNION ALL ".join(queries), params) init_balance_by_col_group = { account_id: {column_group_key: {} for column_group_key in options['column_groups']} for account_id in account_ids } for result in self._cr.dictfetchall(): init_balance_by_col_group[result['groupby']][result['column_group_key']] = result accounts = self.env['account.account'].browse(account_ids) return { account.id: (account, init_balance_by_col_group[account.id]) for account in accounts } def _get_options_initial_balance(self, options): """ Create options used to compute the initial balances. The initial balances depict the current balance of the accounts at the beginning of the selected period in the report. The resulting dates domain will be: [ ('date' <= options['date_from'] - 1), '|', ('date' >= fiscalyear['date_from']), ('account_id.include_initial_balance', '=', True) ] :param options: The report options. :return: A copy of the options. """ #pylint: disable=sql-injection new_options = options.copy() date_to = new_options['comparison']['periods'][-1]['date_from'] if new_options.get('comparison', {}).get('periods') else new_options['date']['date_from'] new_date_to = fields.Date.from_string(date_to) - timedelta(days=1) # Date from computation # We have two case: # 1) We are choosing a date that starts at the beginning of a fiscal year and we want the initial period to be # the previous fiscal year # 2) We are choosing a date that starts in the middle of a fiscal year and in that case we want the initial period # to be the beginning of the fiscal year date_from = fields.Date.from_string(new_options['date']['date_from']) current_fiscalyear_dates = self.env.company.compute_fiscalyear_dates(date_from) if date_from == current_fiscalyear_dates['date_from']: # We want the previous fiscal year previous_fiscalyear_dates = self.env.company.compute_fiscalyear_dates(date_from - timedelta(days=1)) new_date_from = previous_fiscalyear_dates['date_from'] include_current_year_in_unaff_earnings = True else: # We want the current fiscal year new_date_from = current_fiscalyear_dates['date_from'] include_current_year_in_unaff_earnings = False new_options['date'] = { 'mode': 'range', 'date_from': fields.Date.to_string(new_date_from), 'date_to': fields.Date.to_string(new_date_to), } new_options['include_current_year_in_unaff_earnings'] = include_current_year_in_unaff_earnings return new_options def _get_options_sum_balance(self, options): new_options = options.copy() if not options.get('general_ledger_strict_range'): # Date from date_from = fields.Date.from_string(new_options['date']['date_from']) current_fiscalyear_dates = self.env.company.compute_fiscalyear_dates(date_from) new_date_from = current_fiscalyear_dates['date_from'] new_date_to = new_options['date']['date_to'] new_options['date'] = { 'mode': 'range', 'date_from': fields.Date.to_string(new_date_from), 'date_to': new_date_to, } return new_options #################################################### # COLUMN/LINE HELPERS #################################################### def _get_account_title_line(self, report, options, account, has_lines, eval_dict): line_columns = [] for column in options['columns']: col_value = eval_dict.get(column['column_group_key'], {}).get(column['expression_label']) col_expr_label = column['expression_label'] value = None if col_value is None or (col_expr_label == 'amount_currency' and not account.currency_id) else col_value line_columns.append(report._build_column_dict( value, column, options=options, currency=account.currency_id if col_expr_label == 'amount_currency' else None, )) line_id = report._get_generic_line_id('account.account', account.id) is_in_unfolded_lines = any( report._get_res_id_from_line_id(line_id, 'account.account') == account.id for line_id in options.get('unfolded_lines') ) return { 'id': line_id, 'name': f'{account.code} {account.name}', 'columns': line_columns, 'level': 1, 'unfoldable': has_lines, 'unfolded': has_lines and (is_in_unfolded_lines or options.get('unfold_all')), 'expand_function': '_report_expand_unfoldable_line_general_ledger', } def _get_aml_line(self, report, parent_line_id, options, eval_dict, init_bal_by_col_group): line_columns = [] for column in options['columns']: col_expr_label = column['expression_label'] col_value = eval_dict[column['column_group_key']].get(col_expr_label) col_currency = None if col_value is not None: if col_expr_label == 'amount_currency': col_currency = self.env['res.currency'].browse(eval_dict[column['column_group_key']]['currency_id']) col_value = None if col_currency == self.env.company.currency_id else col_value elif col_expr_label == 'balance': col_value += (init_bal_by_col_group[column['column_group_key']] or 0) line_columns.append(report._build_column_dict( col_value, column, options=options, currency=col_currency, )) aml_id = None move_name = None caret_type = None for column_group_dict in eval_dict.values(): aml_id = column_group_dict.get('id', '') if aml_id: if column_group_dict.get('payment_id'): caret_type = 'account.payment' else: caret_type = 'account.move.line' move_name = column_group_dict['move_name'] date = str(column_group_dict.get('date', '')) break return { 'id': report._get_generic_line_id('account.move.line', aml_id, parent_line_id=parent_line_id, markup=date), 'caret_options': caret_type, 'parent_id': parent_line_id, 'name': move_name, 'columns': line_columns, 'level': 3, } @api.model def _get_total_line(self, report, options, eval_dict): line_columns = [] for column in options['columns']: col_value = eval_dict[column['column_group_key']].get(column['expression_label']) col_value = None if col_value is None else col_value line_columns.append(report._build_column_dict(col_value, column, options=options)) return { 'id': report._get_generic_line_id(None, None, markup='total'), 'name': _('Total'), 'level': 1, 'columns': line_columns, } def caret_option_audit_tax(self, options, params): return self.env['account.generic.tax.report.handler'].caret_option_audit_tax(options, params) def _report_expand_unfoldable_line_general_ledger(self, line_dict_id, groupby, options, progress, offset, unfold_all_batch_data=None): def init_load_more_progress(line_dict): return { column['column_group_key']: line_col.get('no_format', 0) for column, line_col in zip(options['columns'], line_dict['columns']) if column['expression_label'] == 'balance' } report = self.env.ref('account_reports.general_ledger_report') model, model_id = report._get_model_info_from_id(line_dict_id) if model != 'account.account': raise UserError(_("Wrong ID for general ledger line to expand: %s", line_dict_id)) lines = [] # Get initial balance if offset == 0: if unfold_all_batch_data: account, init_balance_by_col_group = unfold_all_batch_data['initial_balances'][model_id] else: account, init_balance_by_col_group = self._get_initial_balance_values(report, [model_id], options)[model_id] initial_balance_line = report._get_partner_and_general_ledger_initial_balance_line(options, line_dict_id, init_balance_by_col_group, account.currency_id) if initial_balance_line: lines.append(initial_balance_line) # For the first expansion of the line, the initial balance line gives the progress progress = init_load_more_progress(initial_balance_line) # Get move lines limit_to_load = report.load_more_limit + 1 if report.load_more_limit and options['export_mode'] != 'print' else None if unfold_all_batch_data: aml_results = unfold_all_batch_data['aml_results'][model_id] has_more = unfold_all_batch_data['has_more'].get(model_id, False) else: aml_results, has_more = self._get_aml_values(report, options, [model_id], offset=offset, limit=limit_to_load) aml_results = aml_results[model_id] next_progress = progress for aml_result in aml_results.values(): new_line = self._get_aml_line(report, line_dict_id, options, aml_result, next_progress) lines.append(new_line) next_progress = init_load_more_progress(new_line) return { 'lines': lines, 'offset_increment': report.load_more_limit, 'has_more': has_more, 'progress': next_progress, }