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