first commit

This commit is contained in:
Suherdy Yacob 2026-01-19 16:54:43 +07:00
commit 56b44d621e
7 changed files with 239 additions and 0 deletions

20
.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Python
*.py[cod]
*$py.class
# Odoo
*.hot-update.js
*.hot-update.json
.odoo_image_cache/
# Editor/IDE
.vscode/
.idea/
*.swp
*.swo
# MacOS
.DS_Store
# Temporary files
*.tmp

44
README.md Normal file
View File

@ -0,0 +1,44 @@
# Account Report Show All
**Version:** 17.0.1.0.0
**Author:** Suherdy Yacob
**Category:** Accounting/Reporting
**License:** LGPL-3
## Overview
This module enhances Odoo's standard accounting reports (General Ledger, Trial Balance, Balance Sheet, Profit & Loss) to display **all accounts**, even those with zero balance and no transaction history in the selected period.
By default, Odoo hides accounts that have no activity or balance. This module ensures that all accounts defined in your Chart of Accounts are visible, providing a complete view of your financial structure.
## Features
1. **Show All Accounts**: Automatically injects accounts with zero balance into reports if they are missing from the standard view.
2. **Smart Filtering for Summary Accounts**:
* Accounts ending in `0` (e.g., `100000`, `111000`) are treated as "View" or "Summary" accounts.
* These accounts are **excluded** if they have no balance and no activity.
* They are **included** only if they have a non-zero balance or activity in the selected period.
* This prevents report clutter while keeping necessary headers visible when active.
3. **Global Compatibility**:
* Works with **General Ledger** and **Trial Balance** via a specialized handler.
* Works with **Balance Sheet** and generic reports via a global report extension.
## Usage
1. Install the module.
2. Go to **Accounting > Reporting**.
3. Open any report (e.g., Trial Balance).
4. You will see all accounts listed.
* Empty accounts will show `0.00`.
* Accounts like `110100` will be hidden if they are completely flat (0 balance, 0 activity), but will appear if they have data.
5. Use the standard **"Hide lines at 0"** option in the report filter if you want to revert to the standard behavior (hiding empty lines).
## Technical Details
* **Models Extended**:
* `account.general.ledger.report.handler`: Overrides `_query_values` to inject missing empty accounts.
* `account.report`: Overrides `_get_lines` to post-filter summary accounts (ending in '0') that may have been included by standard logic but are actually zeroed out.
## Dependencies
* `account_reports` (Odoo Enterprise)

1
__init__.py Normal file
View File

@ -0,0 +1 @@
from . import models

16
__manifest__.py Normal file
View File

@ -0,0 +1,16 @@
{
'name': 'Account Report Show All',
'version': '17.0.1.0.0',
'category': 'Accounting/Reporting',
'summary': 'Show all accounts in General Ledger and Trial Balance',
'description': """
This module modifies the General Ledger and Trial Balance reports to include all accounts,
even those with zero balance and no journal entries.
""",
'author': 'Suherdy Yacob',
'depends': ['account_reports'],
'data': [],
'installable': True,
'auto_install': False,
'license': 'OPL-1',
}

2
models/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from . import account_general_ledger
from . import account_report

View File

@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
from odoo import models, api
class GeneralLedgerCustomHandler(models.AbstractModel):
_inherit = 'account.general.ledger.report.handler'
def _query_values(self, report, options):
# 1. Get existing values from super (accounts with moves/balances)
results = super()._query_values(report, options)
# 2. Filter out accounts ending in '0' that have 0.0 balance.
filtered_results = []
possible_removals = 0
for account, values in results:
if account.code and account.code.endswith('0'):
has_balance = False
for col_group_key, col_group_values in values.items():
# Check if this column group is strict range (period activity) or cumulative (balance sheet)
col_group_options = options.get('column_groups', {}).get(col_group_key, {})
forced_options = col_group_options.get('forced_options', {})
is_strict_range = forced_options.get('general_ledger_strict_range') or options.get('general_ledger_strict_range')
for key in ['sum', 'initial_balance', 'unaffected_earnings']:
if key in col_group_values:
# 1. Always check Balance
if not self.env.company.currency_id.is_zero(col_group_values[key].get('balance', 0.0)):
has_balance = True
break
# 2. If Strict Range (Period Activity), check Debit/Credit too
# If Cumulative, we ignore Debit/Credit because they are lifetime sums which might be non-zero even if balance is zero.
if is_strict_range:
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
if not has_balance:
continue # Skip this account
filtered_results.append((account, values))
results = filtered_results
# Extract IDs of accounts already in results
existing_account_ids = {account.id for account, _ in results}
# 2. Find missing accounts
# We need all accounts for the current company(ies)
# We respect the search filter if present (handled in super via existing_account_ids check essentially,
# but we need to re-apply filter to find *unused* accounts matching the filter)
domain = [
*self.env['account.account']._check_company_domain(report.get_report_company_ids(options)),
('id', 'not in', list(existing_account_ids)),
('code', 'not like', '%0') # Filter out unused accounts ending with 0. Used accounts (with balance) are already included by super().
]
if options.get('filter_search_bar'):
domain.append(('name', 'ilike', options['filter_search_bar']))
# 3. Create empty result structure for missing accounts
# The structure expected is:
# values_by_column_group is a dict {column_group_key: values}
# values is a dict with keys like 'sum', 'initial_balance', 'unaffected_earnings'
# 'sum': {'debit': 0.0, 'credit': 0.0, 'balance': 0.0, 'amount_currency': 0.0}
missing_accounts = self.env['account.account'].search(domain)
if not missing_accounts:
return results
# Construct the empty value dictionary structure
# We need to replicate the structure for each column group
empty_values_template = {
'sum': {
'debit': 0.0,
'credit': 0.0,
'balance': 0.0,
'amount_currency': 0.0,
# 'max_date': None # Optional
},
'initial_balance': {
'debit': 0.0,
'credit': 0.0,
'balance': 0.0,
'amount_currency': 0.0,
},
'unaffected_earnings': {
'debit': 0.0,
'credit': 0.0,
'balance': 0.0,
'amount_currency': 0.0,
}
}
for account in missing_accounts:
# We need a separate copy of the values for each account/column_group to avoid reference issues
# (though strictly speaking they are all 0 so it might not matter, but safer to copy)
account_values = {}
for column_group_key in options['column_groups']:
# deeply copy the template
account_values[column_group_key] = {
key: val.copy() for key, val in empty_values_template.items()
}
results.append((account, account_values))
# Re-sort results by account code to ensure correct order
# The original results are sorted, we appended new ones at the end.
results.sort(key=lambda x: x[0].code)
return results

40
models/account_report.py Normal file
View File

@ -0,0 +1,40 @@
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)
# Filter logic to hide accounts ending in '0' if they have zero balance
filtered_lines = []
for line in lines:
keep_line = True
# Only check lines that represent accounts
if line.get('caret_options') == 'account.account':
# Attempt to get the code from the name.
# Account names usually start with the code (e.g., "110100 Stock Valuation")
name = line.get('name', '').strip()
code_part = name.split(' ')[0]
# Check if code appears to be numeric and ends with '0'
# We interpret this as a summary/view account that should be hidden if unused
if code_part.isdigit() and code_part.endswith('0'):
has_balance = False
for col in line.get('columns', []):
val = col.get('no_format', 0.0)
if val is None: 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 keep_line:
filtered_lines.append(line)
return filtered_lines