# Fusion Accounting - Partner Ledger Report Handler from odoo import api, models, _, fields from odoo.exceptions import UserError from odoo.osv import expression from odoo.tools import SQL from datetime import timedelta from collections import defaultdict class PartnerLedgerCustomHandler(models.AbstractModel): """Generates the Partner Ledger report. Shows journal items grouped by partner, with initial balances and running totals. Also handles indirectly-linked entries (items without a partner that were reconciled with a partner's entry). """ _name = 'account.partner.ledger.report.handler' _inherit = 'account.report.custom.handler' _description = 'Partner Ledger Custom Handler' # ------------------------------------------------------------------ # Display # ------------------------------------------------------------------ def _get_custom_display_config(self): return { 'css_custom_class': 'partner_ledger', 'components': { 'AccountReportLineCell': 'fusion_accounting.PartnerLedgerLineCell', }, 'templates': { 'AccountReportFilters': 'fusion_accounting.PartnerLedgerFilters', 'AccountReportLineName': 'fusion_accounting.PartnerLedgerLineName', }, } # ------------------------------------------------------------------ # Dynamic lines # ------------------------------------------------------------------ def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None): """Build all partner lines and a final total line.""" partner_rows, col_totals = self._assemble_partner_rows(report, options) output = report._regroup_lines_by_name_prefix( options, partner_rows, '_report_expand_unfoldable_line_partner_ledger_prefix_group', 0, ) output = [(0, ln) for ln in output] output.append((0, self._build_total_line(options, col_totals))) return output def _assemble_partner_rows(self, report, options, depth_shift=0): """Query partner sums and return ``(lines, totals_by_column_group)``.""" rows = [] col_totals = { cg: {'debit': 0.0, 'credit': 0.0, 'amount': 0.0, 'balance': 0.0} for cg in options['column_groups'] } partner_data = self._query_partner_sums(options) filter_text = options.get('filter_search_bar', '') accept_unknown = filter_text.lower() in self._unknown_partner_label().lower() for partner_rec, col_vals in partner_data: # When printing with a search filter, skip the Unknown Partner row # unless the filter matches its label. if ( options['export_mode'] == 'print' and filter_text and not partner_rec and not accept_unknown ): continue per_col = defaultdict(dict) for cg in options['column_groups']: psum = col_vals.get(cg, {}) per_col[cg]['debit'] = psum.get('debit', 0.0) per_col[cg]['credit'] = psum.get('credit', 0.0) per_col[cg]['amount'] = psum.get('amount', 0.0) per_col[cg]['balance'] = psum.get('balance', 0.0) for fld in ('debit', 'credit', 'amount', 'balance'): col_totals[cg][fld] += per_col[cg][fld] rows.append( self._build_partner_line(options, partner_rec, per_col, depth_shift=depth_shift) ) return rows, col_totals # ------------------------------------------------------------------ # Prefix-group expand # ------------------------------------------------------------------ def _report_expand_unfoldable_line_partner_ledger_prefix_group( self, line_dict_id, groupby, options, progress, offset, unfold_all_batch_data=None, ): report = self.env['account.report'].browse(options['report_id']) prefix = report._get_prefix_groups_matched_prefix_from_line_id(line_dict_id) prefix_filter = [('partner_id.name', '=ilike', f'{prefix}%')] if self._unknown_partner_label().upper().startswith(prefix): prefix_filter = expression.OR([prefix_filter, [('partner_id', '=', None)]]) filtered_opts = { **options, 'forced_domain': options.get('forced_domain', []) + prefix_filter, } nest_level = len(prefix) * 2 child_lines, _ = self._assemble_partner_rows(report, filtered_opts, depth_shift=nest_level) for child in child_lines: child['id'] = report._build_subline_id(line_dict_id, child['id']) child['parent_id'] = line_dict_id grouped_output = report._regroup_lines_by_name_prefix( options, child_lines, '_report_expand_unfoldable_line_partner_ledger_prefix_group', nest_level, matched_prefix=prefix, parent_line_dict_id=line_dict_id, ) return { 'lines': grouped_output, 'offset_increment': len(grouped_output), 'has_more': False, } # ------------------------------------------------------------------ # Options # ------------------------------------------------------------------ def _custom_options_initializer(self, report, options, previous_options): super()._custom_options_initializer(report, options, previous_options=previous_options) extra_domain = [] company_ids = report.get_report_company_ids(options) fx_journals = self.env['res.company'].browse(company_ids).mapped('currency_exchange_journal_id') if fx_journals: extra_domain += [ '!', '&', '&', '&', ('credit', '=', 0.0), ('debit', '=', 0.0), ('amount_currency', '!=', 0.0), ('journal_id', 'in', fx_journals.ids), ] if options['export_mode'] == 'print' and options.get('filter_search_bar'): extra_domain += [ '|', ('matched_debit_ids.debit_move_id.partner_id.name', 'ilike', options['filter_search_bar']), '|', ('matched_credit_ids.credit_move_id.partner_id.name', 'ilike', options['filter_search_bar']), ('partner_id.name', 'ilike', options['filter_search_bar']), ] options['forced_domain'] = options.get('forced_domain', []) + extra_domain if self.env.user.has_group('base.group_multi_currency'): options['multi_currency'] = True hidden_cols = [] options['hide_account'] = (previous_options or {}).get('hide_account', False) if options['hide_account']: hidden_cols += ['journal_code', 'account_code', 'matching_number'] options['hide_debit_credit'] = (previous_options or {}).get('hide_debit_credit', False) if options['hide_debit_credit']: hidden_cols += ['debit', 'credit'] else: hidden_cols += ['amount'] options['columns'] = [c for c in options['columns'] if c['expression_label'] not in hidden_cols] options['buttons'].append({ 'name': _('Send'), 'action': 'action_send_statements', 'sequence': 90, 'always_show': True, }) # ------------------------------------------------------------------ # Batch unfold # ------------------------------------------------------------------ def _custom_unfold_all_batch_data_generator(self, report, options, lines_to_expand_by_function): partner_ids = [] for ld in lines_to_expand_by_function.get('_report_expand_unfoldable_line_partner_ledger', []): markup, mdl, mid = self.env['account.report']._parse_line_id(ld['id'])[-1] if mdl == 'res.partner': partner_ids.append(mid) elif markup == 'no_partner': partner_ids.append(None) # Prefix-group expansion unknown_label_upper = self._unknown_partner_label().upper() prefix_domains = [] for ld in lines_to_expand_by_function.get( '_report_expand_unfoldable_line_partner_ledger_prefix_group', [], ): pfx = report._get_prefix_groups_matched_prefix_from_line_id(ld['id']) prefix_domains.append([('name', '=ilike', f'{pfx}%')]) if unknown_label_upper.startswith(pfx): partner_ids.append(None) if prefix_domains: partner_ids += self.env['res.partner'].with_context(active_test=False).search( expression.OR(prefix_domains) ).ids return { 'initial_balances': self._fetch_initial_balances(partner_ids, options) if partner_ids else {}, 'aml_values': self._fetch_aml_data(options, partner_ids) if partner_ids else {}, } # ------------------------------------------------------------------ # Actions # ------------------------------------------------------------------ def _get_report_send_recipients(self, options): preset_ids = options.get('partner_ids', []) if not preset_ids: self.env.cr.execute(self._build_partner_sums_sql(options)) preset_ids = [r['groupby'] for r in self.env.cr.dictfetchall() if r['groupby']] return self.env['res.partner'].browse(preset_ids) def action_send_statements(self, options): tpl = self.env.ref('fusion_accounting.email_template_customer_statement', False) return { 'name': _("Send Partner Ledgers"), 'type': 'ir.actions.act_window', 'views': [[False, 'form']], 'res_model': 'account.report.send', 'target': 'new', 'context': { 'default_mail_template_id': tpl.id if tpl else False, 'default_report_options': options, }, } @api.model def action_open_partner(self, options, params): _, rec_id = self.env['account.report']._get_model_info_from_id(params['id']) return { 'type': 'ir.actions.act_window', 'res_model': 'res.partner', 'res_id': rec_id, 'views': [[False, 'form']], 'view_mode': 'form', 'target': 'current', } # ------------------------------------------------------------------ # SQL helpers # ------------------------------------------------------------------ def _query_partner_sums(self, options): """Fetch sums grouped by partner and apply corrections for partnerless entries reconciled with partnered entries.""" comp_cur = self.env.company.currency_id def _assign_if_nonzero(row): check_fields = ['balance', 'debit', 'credit', 'amount'] if any(not comp_cur.is_zero(row[f]) for f in check_fields): by_partner.setdefault(row['groupby'], defaultdict(lambda: defaultdict(float))) for f in check_fields: by_partner[row['groupby']][row['column_group_key']][f] += row[f] by_partner = {} self.env.cr.execute(self._build_partner_sums_sql(options)) for rec in self.env.cr.dictfetchall(): _assign_if_nonzero(rec) # Correction: partnerless entries reconciled with a partner self.env.cr.execute(self._build_partnerless_correction_sql(options)) correction_sums = {f: {cg: 0 for cg in options['column_groups']} for f in ('debit', 'credit', 'amount', 'balance')} for rec in self.env.cr.dictfetchall(): for f in ('debit', 'credit', 'amount', 'balance'): correction_sums[f][rec['column_group_key']] += rec[f] if rec['groupby'] in by_partner: _assign_if_nonzero(rec) # Adjust the Unknown Partner bucket if None in by_partner: for cg in options['column_groups']: by_partner[None][cg]['debit'] += correction_sums['credit'][cg] by_partner[None][cg]['credit'] += correction_sums['debit'][cg] by_partner[None][cg]['amount'] += correction_sums['amount'][cg] by_partner[None][cg]['balance'] -= correction_sums['balance'][cg] if by_partner: partners = self.env['res.partner'].with_context(active_test=False).search_fetch( [('id', 'in', list(by_partner.keys()))], ["id", "name", "trust", "company_registry", "vat"], ) else: partners = self.env['res.partner'] if None in by_partner: partners = list(partners) + [None] return [(p, by_partner[p.id if p else None]) for p in partners] def _build_partner_sums_sql(self, options) -> SQL: """SQL that sums debit / credit / balance by partner.""" parts = [] report = self.env.ref('fusion_accounting.partner_ledger_report') for cg, cg_opts in report._split_options_per_column_group(options).items(): qry = report._get_report_query(cg_opts, 'from_beginning') parts.append(SQL( """ SELECT account_move_line.partner_id AS groupby, %(cg)s AS column_group_key, SUM(%(dr)s) AS debit, SUM(%(cr)s) AS credit, SUM(%(bal)s) AS amount, SUM(%(bal)s) AS balance FROM %(tbl)s %(fx)s WHERE %(cond)s GROUP BY account_move_line.partner_id """, cg=cg, dr=report._currency_table_apply_rate(SQL("account_move_line.debit")), cr=report._currency_table_apply_rate(SQL("account_move_line.credit")), bal=report._currency_table_apply_rate(SQL("account_move_line.balance")), tbl=qry.from_clause, fx=report._currency_table_aml_join(cg_opts), cond=qry.where_clause, )) return SQL(' UNION ALL ').join(parts) def _fetch_initial_balances(self, partner_ids, options): """Compute opening balances for each partner before date_from.""" parts = [] report = self.env.ref('fusion_accounting.partner_ledger_report') for cg, cg_opts in report._split_options_per_column_group(options).items(): init_opts = self._derive_initial_balance_options(cg_opts) qry = report._get_report_query( init_opts, 'from_beginning', domain=[('partner_id', 'in', partner_ids)], ) parts.append(SQL( """ SELECT account_move_line.partner_id, %(cg)s AS column_group_key, SUM(%(dr)s) AS debit, SUM(%(cr)s) AS credit, SUM(%(bal)s) AS amount, SUM(%(bal)s) AS balance FROM %(tbl)s %(fx)s WHERE %(cond)s GROUP BY account_move_line.partner_id """, cg=cg, dr=report._currency_table_apply_rate(SQL("account_move_line.debit")), cr=report._currency_table_apply_rate(SQL("account_move_line.credit")), bal=report._currency_table_apply_rate(SQL("account_move_line.balance")), tbl=qry.from_clause, fx=report._currency_table_aml_join(cg_opts), cond=qry.where_clause, )) self.env.cr.execute(SQL(" UNION ALL ").join(parts)) init_map = { pid: {cg: {} for cg in options['column_groups']} for pid in partner_ids } for row in self.env.cr.dictfetchall(): init_map[row['partner_id']][row['column_group_key']] = row return init_map def _derive_initial_balance_options(self, options): """Return a modified options dict ending the day before ``date_from``.""" cutoff = fields.Date.from_string(options['date']['date_from']) - timedelta(days=1) new_date = dict(options['date'], date_from=False, date_to=fields.Date.to_string(cutoff)) return dict(options, date=new_date) def _build_partnerless_correction_sql(self, options): """SQL for partnerless lines reconciled with a partner's line.""" parts = [] report = self.env.ref('fusion_accounting.partner_ledger_report') for cg, cg_opts in report._split_options_per_column_group(options).items(): qry = report._get_report_query(cg_opts, 'from_beginning') parts.append(SQL( """ SELECT %(cg)s AS column_group_key, linked.partner_id AS groupby, SUM(%(dr)s) AS debit, SUM(%(cr)s) AS credit, SUM(%(bal)s) AS amount, SUM(%(bal)s) AS balance FROM %(tbl)s JOIN account_partial_reconcile pr ON account_move_line.id = pr.debit_move_id OR account_move_line.id = pr.credit_move_id JOIN account_move_line linked ON (linked.id = pr.debit_move_id OR linked.id = pr.credit_move_id) AND linked.partner_id IS NOT NULL %(fx)s WHERE pr.max_date <= %(dt_to)s AND %(cond)s AND account_move_line.partner_id IS NULL GROUP BY linked.partner_id """, cg=cg, dr=report._currency_table_apply_rate(SQL( "CASE WHEN linked.balance > 0 THEN 0 ELSE pr.amount END" )), cr=report._currency_table_apply_rate(SQL( "CASE WHEN linked.balance < 0 THEN 0 ELSE pr.amount END" )), bal=report._currency_table_apply_rate(SQL( "-SIGN(linked.balance) * pr.amount" )), tbl=qry.from_clause, fx=report._currency_table_aml_join(cg_opts, aml_alias=SQL("linked")), dt_to=cg_opts['date']['date_to'], cond=qry.where_clause, )) return SQL(" UNION ALL ").join(parts) # ------------------------------------------------------------------ # AML detail data # ------------------------------------------------------------------ def _get_additional_column_aml_values(self): """Hook for other modules to inject extra SELECT fields into the partner-ledger AML query.""" return SQL() def _fetch_aml_data(self, options, partner_ids, offset=0, limit=None): """Load move lines for the given partners. Returns ``{partner_id: [row, ...]}`` including both directly- and indirectly-linked entries. """ container = {pid: [] for pid in partner_ids} real_ids = [x for x in partner_ids if x] direct_clauses = [] indirect_clause = SQL('linked_aml.partner_id IS NOT NULL') if None in partner_ids: direct_clauses.append(SQL('account_move_line.partner_id IS NULL')) if real_ids: direct_clauses.append(SQL('account_move_line.partner_id IN %s', tuple(real_ids))) indirect_clause = SQL('linked_aml.partner_id IN %s', tuple(real_ids)) direct_filter = SQL('(%s)', SQL(' OR ').join(direct_clauses)) fragments = [] jnl_name = self.env['account.journal']._field_to_sql('journal', 'name') report = self.env.ref('fusion_accounting.partner_ledger_report') extra_cols = self._get_additional_column_aml_values() for cg, grp_opts in report._split_options_per_column_group(options).items(): qry = report._get_report_query(grp_opts, 'strict_range') acct_a = qry.left_join( lhs_alias='account_move_line', lhs_column='account_id', rhs_table='account_account', rhs_column='id', link='account_id', ) code_f = self.env['account.account']._field_to_sql(acct_a, 'code', qry) name_f = self.env['account.account']._field_to_sql(acct_a, 'name') # Direct entries fragments.append(SQL( ''' SELECT account_move_line.id, 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, account_move_line.matching_number, %(extra_cols)s COALESCE(account_move_line.invoice_date, account_move_line.date) AS invoice_date, %(dr)s AS debit, %(cr)s AS credit, %(bal)s AS amount, %(bal)s AS balance, mv.name AS move_name, mv.move_type AS move_type, %(code_f)s AS account_code, %(name_f)s AS account_name, journal.code AS journal_code, %(jnl_name)s AS journal_name, %(cg)s AS column_group_key, 'directly_linked_aml' AS key, 0 AS partial_id FROM %(tbl)s JOIN account_move mv ON mv.id = account_move_line.move_id %(fx)s LEFT JOIN res_company co ON co.id = account_move_line.company_id LEFT JOIN res_partner prt ON prt.id = account_move_line.partner_id LEFT JOIN account_journal journal ON journal.id = account_move_line.journal_id WHERE %(cond)s AND %(direct_filter)s ORDER BY account_move_line.date, account_move_line.id ''', extra_cols=extra_cols, dr=report._currency_table_apply_rate(SQL("account_move_line.debit")), cr=report._currency_table_apply_rate(SQL("account_move_line.credit")), bal=report._currency_table_apply_rate(SQL("account_move_line.balance")), code_f=code_f, name_f=name_f, jnl_name=jnl_name, cg=cg, tbl=qry.from_clause, fx=report._currency_table_aml_join(grp_opts), cond=qry.where_clause, direct_filter=direct_filter, )) # Indirect (reconciled with a partner but no partner on the line) fragments.append(SQL( ''' SELECT account_move_line.id, 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, linked_aml.partner_id, account_move_line.currency_id, account_move_line.amount_currency, account_move_line.matching_number, %(extra_cols)s COALESCE(account_move_line.invoice_date, account_move_line.date) AS invoice_date, %(dr)s AS debit, %(cr)s AS credit, %(bal)s AS amount, %(bal)s AS balance, mv.name AS move_name, mv.move_type AS move_type, %(code_f)s AS account_code, %(name_f)s AS account_name, journal.code AS journal_code, %(jnl_name)s AS journal_name, %(cg)s AS column_group_key, 'indirectly_linked_aml' AS key, pr.id AS partial_id FROM %(tbl)s %(fx)s, account_partial_reconcile pr, account_move mv, account_move_line linked_aml, account_journal journal WHERE (account_move_line.id = pr.debit_move_id OR account_move_line.id = pr.credit_move_id) AND account_move_line.partner_id IS NULL AND mv.id = account_move_line.move_id AND (linked_aml.id = pr.debit_move_id OR linked_aml.id = pr.credit_move_id) AND %(indirect_clause)s AND journal.id = account_move_line.journal_id AND %(acct_alias)s.id = account_move_line.account_id AND %(cond)s AND pr.max_date BETWEEN %(dt_from)s AND %(dt_to)s ORDER BY account_move_line.date, account_move_line.id ''', extra_cols=extra_cols, dr=report._currency_table_apply_rate(SQL( "CASE WHEN linked_aml.balance > 0 THEN 0 ELSE pr.amount END" )), cr=report._currency_table_apply_rate(SQL( "CASE WHEN linked_aml.balance < 0 THEN 0 ELSE pr.amount END" )), bal=report._currency_table_apply_rate(SQL("-SIGN(linked_aml.balance) * pr.amount")), code_f=code_f, name_f=name_f, jnl_name=jnl_name, cg=cg, tbl=qry.from_clause, fx=report._currency_table_aml_join(grp_opts), indirect_clause=indirect_clause, acct_alias=SQL.identifier(acct_a), cond=qry.where_clause, dt_from=grp_opts['date']['date_from'], dt_to=grp_opts['date']['date_to'], )) combined = SQL(" UNION ALL ").join(SQL("(%s)", f) for f in fragments) if offset: combined = SQL('%s OFFSET %s ', combined, offset) if limit: combined = SQL('%s LIMIT %s ', combined, limit) self.env.cr.execute(combined) for row in self.env.cr.dictfetchall(): if row['key'] == 'indirectly_linked_aml': if row['partner_id'] in container: container[row['partner_id']].append(row) if None in container: container[None].append({ **row, 'debit': row['credit'], 'credit': row['debit'], 'amount': row['credit'] - row['debit'], 'balance': -row['balance'], }) else: container[row['partner_id']].append(row) return container # ------------------------------------------------------------------ # Expand handler # ------------------------------------------------------------------ def _report_expand_unfoldable_line_partner_ledger( self, line_dict_id, groupby, options, progress, offset, unfold_all_batch_data=None, ): def _running_balance(line_dict): return { c['column_group_key']: lc.get('no_format', 0) for c, lc in zip(options['columns'], line_dict['columns']) if c['expression_label'] == 'balance' } report = self.env.ref('fusion_accounting.partner_ledger_report') _, mdl, rec_id = report._parse_line_id(line_dict_id)[-1] if mdl != 'res.partner': raise UserError(_("Invalid line ID for partner ledger expansion: %s", line_dict_id)) # Count prefix-group nesting levels nesting = sum( 1 for mk, _, _ in report._parse_line_id(line_dict_id) if isinstance(mk, dict) and 'groupby_prefix_group' in mk ) depth = nesting * 2 lines = [] # Opening balance if offset == 0: if unfold_all_batch_data: init_by_cg = unfold_all_batch_data['initial_balances'][rec_id] else: init_by_cg = self._fetch_initial_balances([rec_id], options)[rec_id] opening_line = report._get_partner_and_general_ledger_initial_balance_line( options, line_dict_id, init_by_cg, level_shift=depth, ) if opening_line: lines.append(opening_line) progress = _running_balance(opening_line) page_size = report.load_more_limit + 1 if report.load_more_limit and options['export_mode'] != 'print' else None if unfold_all_batch_data: aml_rows = unfold_all_batch_data['aml_values'][rec_id] else: aml_rows = self._fetch_aml_data(options, [rec_id], offset=offset, limit=page_size)[rec_id] overflow = False count = 0 running = progress for row in aml_rows: if options['export_mode'] != 'print' and report.load_more_limit and count >= report.load_more_limit: overflow = True break new_line = self._build_aml_line(options, row, line_dict_id, running, depth_shift=depth) lines.append(new_line) running = _running_balance(new_line) count += 1 return { 'lines': lines, 'offset_increment': count, 'has_more': overflow, 'progress': running, } # ------------------------------------------------------------------ # Line builders # ------------------------------------------------------------------ def _build_partner_line(self, options, partner, col_data, depth_shift=0): """Produce the foldable partner-level line.""" comp_cur = self.env.company.currency_id first_vals = next(iter(col_data.values())) can_unfold = not comp_cur.is_zero(first_vals.get('debit', 0) or first_vals.get('credit', 0)) cols = [] report = self.env['account.report'].browse(options['report_id']) for col_def in options['columns']: expr = col_def['expression_label'] raw = col_data[col_def['column_group_key']].get(expr) can_unfold = can_unfold or ( expr in ('debit', 'credit', 'amount') and not comp_cur.is_zero(raw) ) cols.append(report._build_column_dict(raw, col_def, options=options)) if partner: lid = report._get_generic_line_id('res.partner', partner.id) else: lid = report._get_generic_line_id('res.partner', None, markup='no_partner') return { 'id': lid, 'name': (partner.name or '')[:128] if partner else self._unknown_partner_label(), 'columns': cols, 'level': 1 + depth_shift, 'trust': partner.trust if partner else None, 'unfoldable': can_unfold, 'unfolded': lid in options['unfolded_lines'] or options['unfold_all'], 'expand_function': '_report_expand_unfoldable_line_partner_ledger', } def _unknown_partner_label(self): return _('Unknown Partner') @api.model def _format_aml_name(self, line_name, move_ref, move_name=None): """Format the display name for a move line.""" return self.env['account.move.line']._format_aml_name(line_name, move_ref, move_name=move_name) def _build_aml_line(self, options, row, parent_id, running_bal, depth_shift=0): """Build a single move-line row under its partner.""" caret = 'account.payment' if row['payment_id'] else 'account.move.line' cols = [] report = self.env['account.report'].browse(options['report_id']) for col_def in options['columns']: expr = col_def['expression_label'] if expr not in row: raise UserError(_("Column '%s' is unavailable for this report.", expr)) raw = row[expr] if col_def['column_group_key'] == row['column_group_key'] else None if raw is None: cols.append(report._build_column_dict(None, None)) continue cur = False if expr == 'balance': raw += running_bal[col_def['column_group_key']] if expr == 'amount_currency': cur = self.env['res.currency'].browse(row['currency_id']) if cur == self.env.company.currency_id: raw = '' cols.append(report._build_column_dict(raw, col_def, options=options, currency=cur)) return { 'id': report._get_generic_line_id( 'account.move.line', row['id'], parent_line_id=parent_id, markup=row['partial_id'], ), 'parent_id': parent_id, 'name': self._format_aml_name(row['name'], row['ref'], row['move_name']), 'columns': cols, 'caret_options': caret, 'level': 3 + depth_shift, } def _build_total_line(self, options, col_totals): cols = [] report = self.env['account.report'].browse(options['report_id']) for col_def in options['columns']: raw = col_totals[col_def['column_group_key']].get(col_def['expression_label']) cols.append(report._build_column_dict(raw, col_def, options=options)) return { 'id': report._get_generic_line_id(None, None, markup='total'), 'name': _('Total'), 'level': 1, 'columns': cols, } def open_journal_items(self, options, params): params['view_ref'] = 'account.view_move_line_tree_grouped_partner' report = self.env['account.report'].browse(options['report_id']) action = report.open_journal_items(options=options, params=params) action.get('context', {}).update({'search_default_group_by_account': 0}) return action