first commit
This commit is contained in:
commit
56b44d621e
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal 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
44
README.md
Normal 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
1
__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import models
|
||||
16
__manifest__.py
Normal file
16
__manifest__.py
Normal 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
2
models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import account_general_ledger
|
||||
from . import account_report
|
||||
116
models/account_general_ledger.py
Normal file
116
models/account_general_ledger.py
Normal 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
40
models/account_report.py
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user