feat: Implement 'Show All Accounts' in reports by adding missing non-summary accounts and refining general ledger balance detection logic.
This commit is contained in:
parent
56b44d621e
commit
d56be74d87
@ -14,31 +14,33 @@ class GeneralLedgerCustomHandler(models.AbstractModel):
|
|||||||
for account, values in results:
|
for account, values in results:
|
||||||
if account.code and account.code.endswith('0'):
|
if account.code and account.code.endswith('0'):
|
||||||
has_balance = False
|
has_balance = False
|
||||||
|
|
||||||
|
# Check ALL column groups
|
||||||
for col_group_key, col_group_values in values.items():
|
for col_group_key, col_group_values in values.items():
|
||||||
# Check if this column group is strict range (period activity) or cumulative (balance sheet)
|
# For GL, we usually care if there is ANY activity or balance in the period/range displayed.
|
||||||
col_group_options = options.get('column_groups', {}).get(col_group_key, {})
|
|
||||||
forced_options = col_group_options.get('forced_options', {})
|
# Check 'sum' (Period Activity/Balance for the column)
|
||||||
is_strict_range = forced_options.get('general_ledger_strict_range') or options.get('general_ledger_strict_range')
|
sum_vals = col_group_values.get('sum', {})
|
||||||
|
if not self.env.company.currency_id.is_zero(sum_vals.get('balance', 0.0)) or \
|
||||||
|
not self.env.company.currency_id.is_zero(sum_vals.get('debit', 0.0)) or \
|
||||||
|
not self.env.company.currency_id.is_zero(sum_vals.get('credit', 0.0)):
|
||||||
|
has_balance = True
|
||||||
|
break
|
||||||
|
|
||||||
for key in ['sum', 'initial_balance', 'unaffected_earnings']:
|
# Check 'initial_balance'
|
||||||
if key in col_group_values:
|
init_vals = col_group_values.get('initial_balance', {})
|
||||||
# 1. Always check Balance
|
if not self.env.company.currency_id.is_zero(init_vals.get('balance', 0.0)):
|
||||||
if not self.env.company.currency_id.is_zero(col_group_values[key].get('balance', 0.0)):
|
has_balance = True
|
||||||
has_balance = True
|
break
|
||||||
break
|
|
||||||
|
# Check 'unaffected_earnings' (current year earnings usually)
|
||||||
# 2. If Strict Range (Period Activity), check Debit/Credit too
|
unaff_vals = col_group_values.get('unaffected_earnings', {})
|
||||||
# If Cumulative, we ignore Debit/Credit because they are lifetime sums which might be non-zero even if balance is zero.
|
if not self.env.company.currency_id.is_zero(unaff_vals.get('balance', 0.0)):
|
||||||
if is_strict_range:
|
has_balance = True
|
||||||
if not self.env.company.currency_id.is_zero(col_group_values[key].get('debit', 0.0)) or \
|
|
||||||
not self.env.company.currency_id.is_zero(col_group_values[key].get('credit', 0.0)):
|
|
||||||
has_balance = True
|
|
||||||
break
|
|
||||||
if has_balance:
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if not has_balance:
|
if not has_balance:
|
||||||
continue # Skip this account
|
continue # Skip this account if no relevant numbers found
|
||||||
|
|
||||||
filtered_results.append((account, values))
|
filtered_results.append((account, values))
|
||||||
|
|
||||||
|
|||||||
@ -7,25 +7,34 @@ class AccountReport(models.Model):
|
|||||||
# Get the standard lines
|
# Get the standard lines
|
||||||
lines = super()._get_lines(options, all_column_groups_expression_totals, warnings=warnings)
|
lines = super()._get_lines(options, all_column_groups_expression_totals, warnings=warnings)
|
||||||
|
|
||||||
# Filter logic to hide accounts ending in '0' if they have zero balance
|
# 1. Filter Logic & Collection of Seen Accounts
|
||||||
filtered_lines = []
|
filtered_lines = []
|
||||||
|
seen_account_ids = set()
|
||||||
|
|
||||||
|
# We need a template line to construct missing account lines later
|
||||||
|
line_template = None
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
keep_line = True
|
keep_line = True
|
||||||
|
is_account_line = line.get('caret_options') in ('account.account', 'trial_balance')
|
||||||
|
|
||||||
# Only check lines that represent accounts
|
if is_account_line:
|
||||||
if line.get('caret_options') == 'account.account':
|
# Store potential template (first valid account line we find)
|
||||||
# Attempt to get the code from the name.
|
if not line_template:
|
||||||
# Account names usually start with the code (e.g., "110100 Stock Valuation")
|
line_template = line
|
||||||
name = line.get('name', '').strip()
|
|
||||||
code_part = name.split(' ')[0]
|
|
||||||
|
|
||||||
# Check if code appears to be numeric and ends with '0'
|
# Attempt to get the code
|
||||||
# We interpret this as a summary/view account that should be hidden if unused
|
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'):
|
if code_part.isdigit() and code_part.endswith('0'):
|
||||||
has_balance = False
|
has_balance = False
|
||||||
for col in line.get('columns', []):
|
for col in line.get('columns', []):
|
||||||
val = col.get('no_format', 0.0)
|
val = col.get('no_format')
|
||||||
if val is None: val = 0.0
|
if val is None or val == '':
|
||||||
|
val = 0.0
|
||||||
|
|
||||||
if not self.env.company.currency_id.is_zero(val):
|
if not self.env.company.currency_id.is_zero(val):
|
||||||
has_balance = True
|
has_balance = True
|
||||||
@ -33,8 +42,141 @@ class AccountReport(models.Model):
|
|||||||
|
|
||||||
if not has_balance:
|
if not has_balance:
|
||||||
keep_line = False
|
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:
|
if keep_line:
|
||||||
filtered_lines.append(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
|
return filtered_lines
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user