from odoo import models class AccountReport(models.Model): _inherit = 'account.report' def _get_lines(self, options, all_column_groups_expression_totals=None, warnings=None): # Get the standard lines lines = super()._get_lines(options, all_column_groups_expression_totals, warnings=warnings) # 1. Filter Logic & Collection of Seen Accounts filtered_lines = [] seen_account_ids = set() # We need a template line to construct missing account lines later line_template = None for line in lines: keep_line = True is_account_line = line.get('caret_options') in ('account.account', 'trial_balance') if is_account_line: # Store potential template (first valid account line we find) if not line_template: line_template = line # Attempt to get the code name = line.get('name', '').strip() parts = name.split(' ', 1) code_part = parts[0] if parts else '' # Logic: Hide if code ends with '0' AND has no balance if code_part.isdigit() and code_part.endswith('0'): has_balance = False for col in line.get('columns', []): val = col.get('no_format') if val is None or val == '': val = 0.0 if not self.env.company.currency_id.is_zero(val): has_balance = True break if not has_balance: keep_line = False # If we keep it, track the account ID # Usually report lines for accounts have 'res_id' pointing to account.account id if keep_line and line.get('res_model') == 'account.account': seen_account_ids.add(line.get('res_id')) elif keep_line: # Fallback for complex IDs or different formats line_id = line.get('id', '') # 1. Standard Odoo 'account.account_ID' if line_id.startswith('account.account_'): try: acc_id = int(line_id.split('_')[-1]) seen_account_ids.add(acc_id) except: pass # 2. Complex Report Engine ID: e.g. ~account.report~12|~account.account~3743 elif '~account.account~' in line_id: try: # Split by | and find the part with account.account # This is a bit manual but robust enough for this format parts = line_id.split('|') for p in parts: if '~account.account~' in p: # p might be '~account.account~3743' acc_id_str = p.split('~account.account~')[-1] acc_id = int(acc_id_str) seen_account_ids.add(acc_id) except: pass if keep_line: filtered_lines.append(line) # 2. Add Missing Accounts ("Show All") # Find accounts that are NOT in seen_account_ids and do NOT end in '0' # (Accounts ending in '0' are "view" accounts, we don't want to force show them if they are empty) if line_template: domain = [ ('company_id', 'in', self.env.companies.ids), ('id', 'not in', list(seen_account_ids)), ('code', 'not like', '%0') ] missing_accounts = self.env['account.account'].search(domain) # Create a zero-column structure zero_columns = [] for col in line_template.get('columns', []): # reconstruct a zero column zero_col = {'name': '', 'no_format': 0.0, 'class': 'number'} zero_columns.append(zero_col) # Separate existing lines into accounts and others (e.g. Total) # The 'Total' line usually comes last. # We want to insert our new accounts into the account list and sort them, # but keep the Total at the bottom. account_lines = [] other_lines = [] # Headers, Totals, Sections for line in filtered_lines: if line.get('caret_options') in ('account.account', 'trial_balance') and not line.get('class') == 'total': account_lines.append(line) else: other_lines.append(line) # Add missing accounts to account_lines for account in missing_accounts: new_line = { 'id': f'account.account_{account.id}', 'name': f'{account.code} {account.name}', 'columns': zero_columns, 'level': line_template.get('level', 2), 'caret_options': 'account.account', 'res_id': account.id, 'res_model': 'account.account', 'parent_id': line_template.get('parent_id'), 'unfoldable': False, 'unfolded': False, } account_lines.append(new_line) # Sort account lines by code def get_code(l): n = l.get('name', '').strip() return n.split(' ')[0] account_lines.sort(key=get_code) # Reassemble: Account Lines first, then Total/Other lines # NOTE: If 'other_lines' contains headers that should be at top, this logic might be too simple. # But usually Trial Balance is simple. # If GL has sections, this whole "Show All" logic is risky without knowing section structure. # Assuming Trial Balance for now as per user request context. # If 'other_lines' are at the end (Total), appending is correct. # If 'other_lines' are at start, they need to be prepended. # Heuristic: Check where they came from. # Re-construct based on original position roughly? # Creating a list of (index, line) and sorting might work if we knew where to put new ones. # But new ones belong in the "middle". # Safer Approach for TB: # - Group headers/top lines # - Group account lines # - Group total/bottom lines # Simple Heuristic: 'Total' line usually has class='total' or similar. # Let's assume other_lines are footer/total for TB. # However, check if any 'other_lines' appeared BEFORE the first account line. passed_first_account = False top_lines = [] bottom_lines = [] # Re-scan filtered_lines to split into top/account/bottom for line in filtered_lines: is_acc = line.get('caret_options') in ('account.account', 'trial_balance') and not line.get('class') == 'total' if is_acc: passed_first_account = True # account_lines already collected above (but we need to clear it and re-collect to be safe? # No, we have account_lines populated above. # Use the logic: # If we haven't seen an account yet, it's a top line. # If we have, and it's not an account, it's a bottom line? NOT ALWAYS (Sections). pass else: if not passed_first_account: top_lines.append(line) else: bottom_lines.append(line) # Re-sort the account_lines (which includes the newly added ones + existing ones) account_lines.sort(key=get_code) filtered_lines = top_lines + account_lines + bottom_lines return filtered_lines