From 56b44d621e4de39a1cc04d72f1ca44f8ef168309 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Mon, 19 Jan 2026 16:54:43 +0700 Subject: [PATCH] first commit --- .gitignore | 20 ++++++ README.md | 44 ++++++++++++ __init__.py | 1 + __manifest__.py | 16 +++++ models/__init__.py | 2 + models/account_general_ledger.py | 116 +++++++++++++++++++++++++++++++ models/account_report.py | 40 +++++++++++ 7 files changed, 239 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 models/__init__.py create mode 100644 models/account_general_ledger.py create mode 100644 models/account_report.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1de6f2 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..1395858 --- /dev/null +++ b/README.md @@ -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) diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..dd54d5f --- /dev/null +++ b/__manifest__.py @@ -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', +} diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..d28266e --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,2 @@ +from . import account_general_ledger +from . import account_report diff --git a/models/account_general_ledger.py b/models/account_general_ledger.py new file mode 100644 index 0000000..9a75e98 --- /dev/null +++ b/models/account_general_ledger.py @@ -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 diff --git a/models/account_report.py b/models/account_report.py new file mode 100644 index 0000000..3939043 --- /dev/null +++ b/models/account_report.py @@ -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