From 366008e8d8f6360d65b615ad9152550059c4e4eb Mon Sep 17 00:00:00 2001 From: Carlos Dauden Date: Fri, 31 Jul 2026 17:15:46 +0200 Subject: [PATCH 1/2] [IMP] Copier update --- .copier-answers.yml | 4 +- .gitattributes | 1 + .github/workflows/pre-commit.yml | 9 +++- .github/workflows/test.yml | 11 +++++ .pre-commit-config.yaml | 12 ++++-- .pylintrc | 73 +++++++++++++++++--------------- .pylintrc-mandatory | 56 ++++++++++++------------ README.md | 5 ++- renovate.json | 6 +++ 9 files changed, 109 insertions(+), 68 deletions(-) create mode 100644 .gitattributes create mode 100644 renovate.json diff --git a/.copier-answers.yml b/.copier-answers.yml index 4a85ee7..308d24b 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,7 +1,6 @@ # Do NOT update manually; changes here will be overwritten by Copier -_commit: v1.29 +_commit: v1.44 _src_path: https://github.com/OCA/oca-addons-repo-template.git -ci: GitHub convert_readme_fragments_to_markdown: false enable_checklog_odoo: false generate_requirements_txt: true @@ -16,6 +15,7 @@ odoo_test_flavor: Both odoo_version: 15.0 org_name: Odoo Community Association (OCA) org_slug: OCA +postgres_image: '' rebel_module_groups: [] repo_description: 'TODO: add repo description.' repo_name: mis-builder-contrib diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e0d5668 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +test-requirements.txt merge=union diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index ccf084d..487429f 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -1,3 +1,4 @@ + name: pre-commit on: @@ -9,6 +10,10 @@ on: - "15.0" - "15.0-ocabot-*" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: pre-commit: runs-on: ubuntu-22.04 @@ -16,7 +21,9 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.8" + cache: 'pip' + cache-dependency-path: '.pre-commit-config.yaml' - name: Get python version run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV - uses: actions/cache@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0434bac..3edd9f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,10 @@ on: - "15.0" - "15.0-ocabot-*" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: unreleased-deps: runs-on: ubuntu-latest @@ -64,6 +68,13 @@ jobs: run: oca_init_test_database - name: Run tests run: oca_run_tests + - name: Upload screenshots from JS tests + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: Screenshots of failed JS tests - ${{ matrix.name }}${{ join(matrix.include) }} + path: /tmp/odoo_tests/${{ env.PGDATABASE }} + if-no-files-found: ignore - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5b6bb09..07c120c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ exclude: | # You don't usually want a bot to modify your legal texts (LICENSE.*|COPYING.*) default_language_version: - python: python3 + python: python3.8 node: "14.18.0" repos: - repo: local @@ -38,8 +38,13 @@ repos: entry: found a en.po file language: fail files: '[a-zA-Z0-9_]*/i18n/en\.po$' + - id: obsolete dotfiles + name: obsolete dotfiles + entry: found obsolete files; remove them + files: '^(\.travis\.yml|\.t2d\.yml|CONTRIBUTING\.md)$' + language: fail - repo: https://github.com/oca/maintainer-tools - rev: d5fab7ee87fceee858a3d01048c78a548974d935 + rev: 31bdbd8e4cb94be6534f0e82b1cafb84a455f671 hooks: # update the NOT INSTALLABLE ADDONS section above - id: oca-update-pre-commit-excluded-addons @@ -104,6 +109,7 @@ repos: additional_dependencies: - "eslint@v7.32.0" - "eslint-plugin-jsdoc@" + - "globals@" - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: @@ -140,7 +146,7 @@ repos: - --settings=. exclude: /__init__\.py$ - repo: https://github.com/acsone/setuptools-odoo - rev: 3.1.8 + rev: 3.3.2 hooks: - id: setuptools-odoo-make-default - id: setuptools-odoo-get-requirements diff --git a/.pylintrc b/.pylintrc index 449108e..525b1d1 100644 --- a/.pylintrc +++ b/.pylintrc @@ -4,6 +4,9 @@ load-plugins=pylint_odoo score=n +[IMPORTS] +deprecated-modules=pdb,pudb,ipdb,bs4 + [ODOOLINT] readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" manifest_required_authors=Odoo Community Association (OCA) @@ -25,19 +28,26 @@ disable=all enable=anomalous-backslash-in-string, api-one-deprecated, api-one-multi-together, - assignment-from-none, - attribute-deprecated, class-camelcase, - dangerous-default-value, dangerous-view-replace-wo-priority, - development-status-allowed, duplicate-id-csv, - duplicate-key, duplicate-xml-fields, duplicate-xml-record-id, eval-referenced, - eval-used, incoherent-interpreter-exec-perm, + openerp-exception-warning, + redundant-modulename-xml, + relative-import, + rst-syntax-error, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + assignment-from-none, + attribute-deprecated, + dangerous-default-value, + deprecated-module, + development-status-allowed, + duplicate-key, + eval-used, license-allowed, manifest-author-string, manifest-deprecated-key, @@ -48,72 +58,67 @@ enable=anomalous-backslash-in-string, method-inverse, method-required-super, method-search, - openerp-exception-warning, pointless-statement, pointless-string-statement, print-used, redundant-keyword-arg, - redundant-modulename-xml, reimported, - relative-import, return-in-init, - rst-syntax-error, sql-injection, too-few-format-args, translation-field, translation-required, unreachable, use-vim-comment, - wrong-tabs-instead-of-spaces, - xml-syntax-error, - attribute-string-redundant, character-not-valid-in-resource-link, - consider-merging-classes-inherited, - context-overridden, create-user-wo-reset-password, dangerous-filter-wo-user, dangerous-qweb-replace-wo-priority, deprecated-data-xml-node, deprecated-openerp-xml-node, duplicate-po-message-definition, - except-pass, file-not-used, + missing-newline-extrafiles, + old-api7-method-defined, + po-msgstr-variables, + po-syntax-error, + str-format-used, + unnecessary-utf8-coding-comment, + xml-attribute-translatable, + xml-deprecated-qweb-directive, + xml-deprecated-tree-attribute, + attribute-string-redundant, + consider-merging-classes-inherited, + context-overridden, + except-pass, invalid-commit, manifest-maintainers-list, - missing-newline-extrafiles, missing-readme, missing-return, odoo-addons-relative-import, - old-api7-method-defined, - po-msgstr-variables, - po-syntax-error, renamed-field-parameter, resource-not-exist, - str-format-used, test-folder-imported, translation-contains-variable, translation-positional-used, - unnecessary-utf8-coding-comment, website-manifest-key-not-valid-uri, - xml-attribute-translatable, - xml-deprecated-qweb-directive, - xml-deprecated-tree-attribute, - # messages that do not cause the lint step to fail - consider-merging-classes-inherited, + missing-manifest-dependency, + too-complex, create-user-wo-reset-password, dangerous-filter-wo-user, - deprecated-module, file-not-used, - invalid-commit, - missing-manifest-dependency, missing-newline-extrafiles, - missing-readme, no-utf8-coding-comment, - odoo-addons-relative-import, old-api7-method-defined, + unnecessary-utf8-coding-comment, + # messages that do not cause the lint step to fail + consider-merging-classes-inherited, + deprecated-module, + invalid-commit, + missing-readme, + odoo-addons-relative-import, redefined-builtin, - too-complex, - unnecessary-utf8-coding-comment + manifest-external-assets [REPORTS] diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory index 164c82f..733e859 100644 --- a/.pylintrc-mandatory +++ b/.pylintrc-mandatory @@ -3,6 +3,9 @@ load-plugins=pylint_odoo score=n +[IMPORTS] +deprecated-modules=pdb,pudb,ipdb,bs4 + [ODOOLINT] readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" manifest_required_authors=Odoo Community Association (OCA) @@ -17,19 +20,26 @@ disable=all enable=anomalous-backslash-in-string, api-one-deprecated, api-one-multi-together, - assignment-from-none, - attribute-deprecated, class-camelcase, - dangerous-default-value, dangerous-view-replace-wo-priority, - development-status-allowed, duplicate-id-csv, - duplicate-key, duplicate-xml-fields, duplicate-xml-record-id, eval-referenced, - eval-used, incoherent-interpreter-exec-perm, + openerp-exception-warning, + redundant-modulename-xml, + relative-import, + rst-syntax-error, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + assignment-from-none, + attribute-deprecated, + dangerous-default-value, + deprecated-module, + development-status-allowed, + duplicate-key, + eval-used, license-allowed, manifest-author-string, manifest-deprecated-key, @@ -40,56 +50,50 @@ enable=anomalous-backslash-in-string, method-inverse, method-required-super, method-search, - openerp-exception-warning, pointless-statement, pointless-string-statement, print-used, redundant-keyword-arg, - redundant-modulename-xml, reimported, - relative-import, return-in-init, - rst-syntax-error, sql-injection, too-few-format-args, translation-field, translation-required, unreachable, use-vim-comment, - wrong-tabs-instead-of-spaces, - xml-syntax-error, - attribute-string-redundant, character-not-valid-in-resource-link, - consider-merging-classes-inherited, - context-overridden, create-user-wo-reset-password, dangerous-filter-wo-user, dangerous-qweb-replace-wo-priority, deprecated-data-xml-node, deprecated-openerp-xml-node, duplicate-po-message-definition, - except-pass, file-not-used, + missing-newline-extrafiles, + old-api7-method-defined, + po-msgstr-variables, + po-syntax-error, + str-format-used, + unnecessary-utf8-coding-comment, + xml-attribute-translatable, + xml-deprecated-qweb-directive, + xml-deprecated-tree-attribute, + attribute-string-redundant, + consider-merging-classes-inherited, + context-overridden, + except-pass, invalid-commit, manifest-maintainers-list, - missing-newline-extrafiles, missing-readme, missing-return, odoo-addons-relative-import, - old-api7-method-defined, - po-msgstr-variables, - po-syntax-error, renamed-field-parameter, resource-not-exist, - str-format-used, test-folder-imported, translation-contains-variable, translation-positional-used, - unnecessary-utf8-coding-comment, - website-manifest-key-not-valid-uri, - xml-attribute-translatable, - xml-deprecated-qweb-directive, - xml-deprecated-tree-attribute + website-manifest-key-not-valid-uri [REPORTS] msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} diff --git a/README.md b/README.md index b6ac984..a4e5555 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ +[![Support the OCA](https://odoo-community.org/readme-banner-image)](https://odoo-community.org/get-involved?utm_source=repo-readme) + +# mis-builder-contrib [![Runboat](https://img.shields.io/badge/runboat-Try%20me-875A7B.png)](https://runboat.odoo-community.org/builds?repo=OCA/mis-builder-contrib&target_branch=15.0) [![Pre-commit Status](https://github.com/OCA/mis-builder-contrib/actions/workflows/pre-commit.yml/badge.svg?branch=15.0)](https://github.com/OCA/mis-builder-contrib/actions/workflows/pre-commit.yml?query=branch%3A15.0) [![Build Status](https://github.com/OCA/mis-builder-contrib/actions/workflows/test.yml/badge.svg?branch=15.0)](https://github.com/OCA/mis-builder-contrib/actions/workflows/test.yml?query=branch%3A15.0) @@ -7,8 +10,6 @@ -# mis-builder-contrib - TODO: add repo description. diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..ad53daa --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended", ":disableDependencyDashboard"], + "baseBranchPatterns": ["16.0", "15.0", "14.0", "13.0", "12.0"], + "enabledManagers": ["copier"] +} From eaf97105ce13271aab46fbc8759e29d0c151e9f2 Mon Sep 17 00:00:00 2001 From: Carlos Dauden Date: Fri, 31 Jul 2026 16:23:47 +0200 Subject: [PATCH 2/2] [ADD] mis_report_account_coverage_check: New module --- mis_report_account_coverage_check/README.rst | 149 ++++++ mis_report_account_coverage_check/__init__.py | 1 + .../__manifest__.py | 18 + mis_report_account_coverage_check/i18n/es.po | 209 ++++++++ .../mis_report_account_coverage_check.pot | 205 ++++++++ .../readme/CONFIGURE.rst | 2 + .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 12 + .../readme/USAGE.rst | 38 ++ .../security/ir.model.access.csv | 3 + .../static/description/icon.png | Bin 0 -> 10254 bytes .../static/description/index.html | 492 ++++++++++++++++++ .../tests/__init__.py | 1 + .../test_mis_report_account_coverage_check.py | 215 ++++++++ .../views/mis_report_instance_views.xml | 19 + .../wizard/__init__.py | 2 + .../mis_report_account_coverage_check.py | 151 ++++++ .../mis_report_account_coverage_check_line.py | 58 +++ ...is_report_account_coverage_check_views.xml | 79 +++ .../addons/mis_report_account_coverage_check | 1 + .../setup.py | 6 + 21 files changed, 1664 insertions(+) create mode 100644 mis_report_account_coverage_check/README.rst create mode 100644 mis_report_account_coverage_check/__init__.py create mode 100644 mis_report_account_coverage_check/__manifest__.py create mode 100644 mis_report_account_coverage_check/i18n/es.po create mode 100644 mis_report_account_coverage_check/i18n/mis_report_account_coverage_check.pot create mode 100644 mis_report_account_coverage_check/readme/CONFIGURE.rst create mode 100644 mis_report_account_coverage_check/readme/CONTRIBUTORS.rst create mode 100644 mis_report_account_coverage_check/readme/DESCRIPTION.rst create mode 100644 mis_report_account_coverage_check/readme/USAGE.rst create mode 100644 mis_report_account_coverage_check/security/ir.model.access.csv create mode 100644 mis_report_account_coverage_check/static/description/icon.png create mode 100644 mis_report_account_coverage_check/static/description/index.html create mode 100644 mis_report_account_coverage_check/tests/__init__.py create mode 100644 mis_report_account_coverage_check/tests/test_mis_report_account_coverage_check.py create mode 100644 mis_report_account_coverage_check/views/mis_report_instance_views.xml create mode 100644 mis_report_account_coverage_check/wizard/__init__.py create mode 100644 mis_report_account_coverage_check/wizard/mis_report_account_coverage_check.py create mode 100644 mis_report_account_coverage_check/wizard/mis_report_account_coverage_check_line.py create mode 100644 mis_report_account_coverage_check/wizard/mis_report_account_coverage_check_views.xml create mode 120000 setup/mis_report_account_coverage_check/odoo/addons/mis_report_account_coverage_check create mode 100644 setup/mis_report_account_coverage_check/setup.py diff --git a/mis_report_account_coverage_check/README.rst b/mis_report_account_coverage_check/README.rst new file mode 100644 index 0000000..4639cc8 --- /dev/null +++ b/mis_report_account_coverage_check/README.rst @@ -0,0 +1,149 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +================================= +MIS Report Account Coverage Check +================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:d15b6b79010e83433bb3d45bca81fecc88cd9c794b0dc69f54db09a7b5b3784e + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmis--builder--contrib-lightgray.png?logo=github + :target: https://github.com/OCA/mis-builder-contrib/tree/15.0/mis_report_account_coverage_check + :alt: OCA/mis-builder-contrib +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/mis-builder-contrib-15-0/mis-builder-contrib-15-0-mis_report_account_coverage_check + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/mis-builder-contrib&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +A MIS Builder report (e.g. a balance sheet) only shows what its KPI +expressions explicitly reference. If an account within the report's range +receives postings during the period but no KPI expression covers it, the +report silently omits it -- typically the symptom is a balance sheet that +does not balance, with no obvious way to find out why. + +This module adds a wizard that, given a MIS report instance and an account +code range, finds accounts in that range which have journal entries posted +during the report's period(s) but are not referenced by any KPI expression +of the report (including subreport KPIs). Each uncovered account is shown +with its period debit/credit/balance, with a direct drill-down to the +underlying journal items. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +No configuration is needed. The wizard is available to users in the +``Accounting / Account User`` group. + +Usage +===== + +#. Open a MIS Report Instance (*Accounting > Reporting > MIS Reporting > + MIS Reports*) and click the *Check Account Coverage* button, or go to + *Accounting > Reporting > MIS Reporting > Account Coverage Check* and + pick the report instance manually. +#. Enter the account code range to check, e.g. ``100000`` to ``599999``. +#. Optionally restrict the periods to check; leave empty to check every + period column defined on the report instance. +#. Click *Check*. Any account in the range that has postings in a period + but is not referenced by any KPI expression of the report is listed with + its debit, credit and balance for that period. +#. Click *View Journal Items* on a line to open the underlying journal + entries causing that account to appear. + +.. note:: + The account range is compared as text (``account.account.code``), so it + is only reliable when the codes being compared have the same number of + digits (as is normally the case, e.g. 6-digit Spanish PGC codes). A range + like ``7`` to ``12`` would not behave as expected since ``"7" <= "12"`` + is false as a string comparison. + +.. note:: + Coverage means "referenced by *any* KPI expression of the report", + including aggregate/rollup KPIs (e.g. a "Result for the year" line that + nets a whole P&L range like ``6%,7%`` into a single equity figure). That + is intentional: if an account is correctly summed into such a KPI it does + not put the report out of balance, so it should not be reported as + uncovered even though it has no individual detail line of its own. + +.. note:: + Without a chart's usual closing/opening entries, a balance-sheet + account's balance keeps accumulating from postings dated before the + period being checked. To match how mis_builder itself computes a + balance-sheet KPI ("ending balance" mode), an uncovered account's + debit/credit/balance are summed since the beginning of time for + balance-sheet accounts (asset/liability/equity), and since the start of + the fiscal year only for P&L accounts (income/expense), which do reset + every year. So a posting from a previous period can still make an + account show up as uncovered today. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_ + + * Carlos Dauden + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-carlosdauden| image:: https://github.com/carlosdauden.png?size=40px + :target: https://github.com/carlosdauden + :alt: carlosdauden + +Current `maintainer `__: + +|maintainer-carlosdauden| + +This module is part of the `OCA/mis-builder-contrib `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mis_report_account_coverage_check/__init__.py b/mis_report_account_coverage_check/__init__.py new file mode 100644 index 0000000..4027237 --- /dev/null +++ b/mis_report_account_coverage_check/__init__.py @@ -0,0 +1 @@ +from . import wizard diff --git a/mis_report_account_coverage_check/__manifest__.py b/mis_report_account_coverage_check/__manifest__.py new file mode 100644 index 0000000..ad60552 --- /dev/null +++ b/mis_report_account_coverage_check/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2026 Tecnativa - Carlos Dauden +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "MIS Report Account Coverage Check", + "version": "15.0.1.0.0", + "category": "Accounting", + "author": "Tecnativa, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/mis-builder-contrib", + "license": "AGPL-3", + "depends": ["account", "mis_builder"], + "data": [ + "security/ir.model.access.csv", + "wizard/mis_report_account_coverage_check_views.xml", + "views/mis_report_instance_views.xml", + ], + "installable": True, + "maintainers": ["carlosdauden"], +} diff --git a/mis_report_account_coverage_check/i18n/es.po b/mis_report_account_coverage_check/i18n/es.po new file mode 100644 index 0000000..d258e21 --- /dev/null +++ b/mis_report_account_coverage_check/i18n/es.po @@ -0,0 +1,209 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mis_report_account_coverage_check +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-07-24 15:06+0000\n" +"PO-Revision-Date: 2026-07-24 15:06+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__account_id +msgid "Account" +msgstr "Cuenta" + +#. module: mis_report_account_coverage_check +#: model:ir.ui.menu,name:mis_report_account_coverage_check.mis_report_account_coverage_check_menu +msgid "Account Coverage Check" +msgstr "Comprobación de cobertura de cuentas" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__account_name +msgid "Account Name" +msgstr "Nombre de la cuenta" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__account_code_from +msgid "Account code from" +msgstr "Código de cuenta desde" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__account_code_to +msgid "Account code to" +msgstr "Código de cuenta hasta" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__balance +msgid "Balance" +msgstr "Saldo" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__check_id +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Check" +msgstr "Comprobar" + +#. module: mis_report_account_coverage_check +#: model:ir.actions.act_window,name:mis_report_account_coverage_check.action_mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_instance_view_form +msgid "Check Account Coverage" +msgstr "Comprobar cobertura de cuentas" + +#. module: mis_report_account_coverage_check +#: model:ir.model,name:mis_report_account_coverage_check.model_mis_report_account_coverage_check +msgid "Check accounts used in postings but not covered by a MIS report" +msgstr "" +"Comprueba las cuentas usadas en apuntes pero no cubiertas por un informe MIS" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Close" +msgstr "Cerrar" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__account_code +msgid "Code" +msgstr "Código" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields.selection,name:mis_report_account_coverage_check.selection__mis_report_account_coverage_check__state__init +msgid "Configuration" +msgstr "Configuración" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__create_uid +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__create_date +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__credit +msgid "Credit" +msgstr "Haber" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__currency_id +msgid "Currency" +msgstr "Moneda" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__debit +msgid "Debit" +msgstr "Debe" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__display_name +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__id +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__id +msgid "ID" +msgstr "ID" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__move_line_count +msgid "Journal Items" +msgstr "Apuntes contables" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check____last_update +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line____last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__write_uid +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__write_date +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,help:mis_report_account_coverage_check.field_mis_report_account_coverage_check__period_ids +msgid "Leave empty to check all periods of the instance." +msgstr "Déjelo vacío para comprobar todos los periodos de la instancia." + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__report_instance_id +msgid "MIS Report Instance" +msgstr "Instancia de informe MIS" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Modify criteria" +msgstr "Modificar criterios" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__period_id +msgid "Period" +msgstr "Periodo" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__period_ids +msgid "Periods to check" +msgstr "Periodos a comprobar" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields.selection,name:mis_report_account_coverage_check.selection__mis_report_account_coverage_check__state__done +msgid "Results" +msgstr "Resultados" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__state +msgid "Status" +msgstr "Estado" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Total Balance" +msgstr "Saldo total" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Total Credit" +msgstr "Total haber" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Total Debit" +msgstr "Total debe" + +#. module: mis_report_account_coverage_check +#: model:ir.model,name:mis_report_account_coverage_check.model_mis_report_account_coverage_check_line +msgid "Uncovered account found during a MIS report coverage check" +msgstr "" +"Cuenta no cubierta encontrada durante una comprobación de cobertura de un " +"informe MIS" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__line_ids +msgid "Uncovered accounts" +msgstr "Cuentas no cubiertas" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "View Journal Items" +msgstr "Ver apuntes contables" diff --git a/mis_report_account_coverage_check/i18n/mis_report_account_coverage_check.pot b/mis_report_account_coverage_check/i18n/mis_report_account_coverage_check.pot new file mode 100644 index 0000000..3b2ef1c --- /dev/null +++ b/mis_report_account_coverage_check/i18n/mis_report_account_coverage_check.pot @@ -0,0 +1,205 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mis_report_account_coverage_check +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-07-24 15:06+0000\n" +"PO-Revision-Date: 2026-07-24 15:06+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__account_id +msgid "Account" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.ui.menu,name:mis_report_account_coverage_check.mis_report_account_coverage_check_menu +msgid "Account Coverage Check" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__account_name +msgid "Account Name" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__account_code_from +msgid "Account code from" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__account_code_to +msgid "Account code to" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__balance +msgid "Balance" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__check_id +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Check" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.actions.act_window,name:mis_report_account_coverage_check.action_mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_instance_view_form +msgid "Check Account Coverage" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model,name:mis_report_account_coverage_check.model_mis_report_account_coverage_check +msgid "Check accounts used in postings but not covered by a MIS report" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Close" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__account_code +msgid "Code" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields.selection,name:mis_report_account_coverage_check.selection__mis_report_account_coverage_check__state__init +msgid "Configuration" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__create_uid +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__create_uid +msgid "Created by" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__create_date +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__create_date +msgid "Created on" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__credit +msgid "Credit" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__currency_id +msgid "Currency" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__debit +msgid "Debit" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__display_name +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__display_name +msgid "Display Name" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__id +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__id +msgid "ID" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__move_line_count +msgid "Journal Items" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check____last_update +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__write_uid +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__write_date +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__write_date +msgid "Last Updated on" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,help:mis_report_account_coverage_check.field_mis_report_account_coverage_check__period_ids +msgid "Leave empty to check all periods of the instance." +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__report_instance_id +msgid "MIS Report Instance" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Modify criteria" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check_line__period_id +msgid "Period" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__period_ids +msgid "Periods to check" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields.selection,name:mis_report_account_coverage_check.selection__mis_report_account_coverage_check__state__done +msgid "Results" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__state +msgid "Status" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Total Balance" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Total Credit" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "Total Debit" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model,name:mis_report_account_coverage_check.model_mis_report_account_coverage_check_line +msgid "Uncovered account found during a MIS report coverage check" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model:ir.model.fields,field_description:mis_report_account_coverage_check.field_mis_report_account_coverage_check__line_ids +msgid "Uncovered accounts" +msgstr "" + +#. module: mis_report_account_coverage_check +#: model_terms:ir.ui.view,arch_db:mis_report_account_coverage_check.mis_report_account_coverage_check_view_form +msgid "View Journal Items" +msgstr "" diff --git a/mis_report_account_coverage_check/readme/CONFIGURE.rst b/mis_report_account_coverage_check/readme/CONFIGURE.rst new file mode 100644 index 0000000..5ba3170 --- /dev/null +++ b/mis_report_account_coverage_check/readme/CONFIGURE.rst @@ -0,0 +1,2 @@ +No configuration is needed. The wizard is available to users in the +``Accounting / Account User`` group. diff --git a/mis_report_account_coverage_check/readme/CONTRIBUTORS.rst b/mis_report_account_coverage_check/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..e0af8f6 --- /dev/null +++ b/mis_report_account_coverage_check/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Tecnativa `_ + + * Carlos Dauden diff --git a/mis_report_account_coverage_check/readme/DESCRIPTION.rst b/mis_report_account_coverage_check/readme/DESCRIPTION.rst new file mode 100644 index 0000000..f0fa06a --- /dev/null +++ b/mis_report_account_coverage_check/readme/DESCRIPTION.rst @@ -0,0 +1,12 @@ +A MIS Builder report (e.g. a balance sheet) only shows what its KPI +expressions explicitly reference. If an account within the report's range +receives postings during the period but no KPI expression covers it, the +report silently omits it -- typically the symptom is a balance sheet that +does not balance, with no obvious way to find out why. + +This module adds a wizard that, given a MIS report instance and an account +code range, finds accounts in that range which have journal entries posted +during the report's period(s) but are not referenced by any KPI expression +of the report (including subreport KPIs). Each uncovered account is shown +with its period debit/credit/balance, with a direct drill-down to the +underlying journal items. diff --git a/mis_report_account_coverage_check/readme/USAGE.rst b/mis_report_account_coverage_check/readme/USAGE.rst new file mode 100644 index 0000000..4a9539b --- /dev/null +++ b/mis_report_account_coverage_check/readme/USAGE.rst @@ -0,0 +1,38 @@ +#. Open a MIS Report Instance (*Accounting > Reporting > MIS Reporting > + MIS Reports*) and click the *Check Account Coverage* button, or go to + *Accounting > Reporting > MIS Reporting > Account Coverage Check* and + pick the report instance manually. +#. Enter the account code range to check, e.g. ``100000`` to ``599999``. +#. Optionally restrict the periods to check; leave empty to check every + period column defined on the report instance. +#. Click *Check*. Any account in the range that has postings in a period + but is not referenced by any KPI expression of the report is listed with + its debit, credit and balance for that period. +#. Click *View Journal Items* on a line to open the underlying journal + entries causing that account to appear. + +.. note:: + The account range is compared as text (``account.account.code``), so it + is only reliable when the codes being compared have the same number of + digits (as is normally the case, e.g. 6-digit Spanish PGC codes). A range + like ``7`` to ``12`` would not behave as expected since ``"7" <= "12"`` + is false as a string comparison. + +.. note:: + Coverage means "referenced by *any* KPI expression of the report", + including aggregate/rollup KPIs (e.g. a "Result for the year" line that + nets a whole P&L range like ``6%,7%`` into a single equity figure). That + is intentional: if an account is correctly summed into such a KPI it does + not put the report out of balance, so it should not be reported as + uncovered even though it has no individual detail line of its own. + +.. note:: + Without a chart's usual closing/opening entries, a balance-sheet + account's balance keeps accumulating from postings dated before the + period being checked. To match how mis_builder itself computes a + balance-sheet KPI ("ending balance" mode), an uncovered account's + debit/credit/balance are summed since the beginning of time for + balance-sheet accounts (asset/liability/equity), and since the start of + the fiscal year only for P&L accounts (income/expense), which do reset + every year. So a posting from a previous period can still make an + account show up as uncovered today. diff --git a/mis_report_account_coverage_check/security/ir.model.access.csv b/mis_report_account_coverage_check/security/ir.model.access.csv new file mode 100644 index 0000000..c1db0a1 --- /dev/null +++ b/mis_report_account_coverage_check/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_mis_report_account_coverage_check,mis.report.account.coverage.check,model_mis_report_account_coverage_check,account.group_account_user,1,1,1,1 +access_mis_report_account_coverage_check_line,mis.report.account.coverage.check.line,model_mis_report_account_coverage_check_line,account.group_account_user,1,1,1,1 diff --git a/mis_report_account_coverage_check/static/description/icon.png b/mis_report_account_coverage_check/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 GIT binary patch literal 10254 zcmbt)WmufcvhH9Zc!C8B?l8#UE&&o;gF7=g3=D(IAOS+K1lK^25Zv7%L4sRw_uvvF z*qyAk?>c**=lnR&y+1yw{;I3Hy6Ua2{<d0kcR+VvBo; zA_X`>;1;xAPL9rQqFxd#f5{a^zW*uaW+r3+U{|fRunu`GZhy$X z8_|Zi{zd#vIokczl8Xh*4Wi@i0+C?Rg1AB5VOEg8B>buLFCi~r5DPd2ED7QP2>^LO zKpr7+?*I1bPaFSLLEa0l2$tj*;u8Qtc=&(RUc*VK@ zjIN{I--GfO@vl+&r^eqy_BZ3dndN_PDzMc*W^!?dIsWAWU@LBjBg6^f4F6*!-hUYh zY$Xb}gF8b0%S1Ac@c%Rs()UCiEu3v6SiFE>h_!{gBb-H2{e=wB5o!YkT0>#LKZFw$ z?CuD0Gvfsb(|XbVxx0AL0%`gG2X+6|f;jiTHU9shtjoW-{2!| zMN*WuOj6elhD4zqgjNpX>F#JP{)hAbenX<+FPr>7jXM&q{|x+pbj8cU<=>Ej zWE1_%qoFVzDAZB%g@v<+1ud%<#2E~ML11jOV5pUZoXktGmzB38%te^i-3o9i$lge>z>tBcK|P2K0H9w{l#|i%$~egM)Ys{q>p<9yaE*%v2cy1wXE{AXqG1_b znfyg@Fq*e@yC)^(@$R*j^E;skyEM6pmL$1ctg*mWiWM&q1{nj>E^)Odw$RPr zhjesSk}k}@-e_%uZTy0t_*TJD&6%*HV0KH>xE@oBex6CL@`Ty3nH_2OF#M?6j(j|9 znRKGSfp3Q2i+|>}w?>8g$>r`|OcvG5r;p)z8DO8+O>EvYQ=_~`p}9!ReUEjUnNL@6 z+C*aoo67(sd|7QgW54@V9Y8PnBW$Q+7ZsRFA}Vj*viA!yWUfb!s*yJi6JKsXZCH4j z*B%nJpad-DDvJ8d>xrxkkh6A}i7V3nULqHCiG~|)YY6{NE3M}c^s#PQhzhsJUf^QW zR+F;up-dN*!)M1ZYl@d0HoqfVD2PNiQcPdzq4NDKO!8mUl{!t*ntBg_+-+lRlI0~Lr>5v!PiQj|hD7B-YFIs~6hIY*R6USZA zlb}=UxqxpSzIsL3pPmiuixCN|3LFBd?0Ih8Y6GWQ;U>dkdXtQaQ&8H|TGAQbuHY=F z_R83&B{1_hP7L#$^eAe?GPB_83y#HZKTwD>e-@E2P>Gk$BBb9|Ivfmdp za~s>3=aj(;xmz8n)sI}uFO$|C>0CZbcTY$Bq6~L-Bc9=vl@X#0S~Q@j8iKzuPeQE_ zQSI)wNz~CvJ>!%QszoCfUm9}h^DL!WYAN|FtMO#kpDXq74sYC87(uvv*jiCjV?Ta& zgO1D0OP3TEN3YnBpD6GnmsEolzEbGM{&VlTz_)J(o{nl0+TmNt{xL%L6G&UR$^aYC zQOA#W7R%9JsC5oTZJE>_?!Ci}mNH{0ObyUd%Q!k%5J8Z`8sR!m`~|Taje`(bLD7=a z-{-=d7w;k@DIrgU{I@K}eN`>S**Lg<@ChAf$M(&kV9TLUixqFQ>YoYHrI!K#R6`S> z%?d5hQ@&;Gje<|uRQZb%Hhibocl9(buI?=0aZW{JYXx?ZS@Lr%G8L<d+riEi2~+{HfHK{K^VrGYNi{2-WJOiC>Pz?f*)cxKCl>1H1=$jb!^ zpmYw>eoiM0Hy7$xbbX_e5o*+{7T2&-t%-h4i7MMo;k|tSqQAeNkwHS9hWY#EV7r3| zTmOmN{;b9OUZpp`LP(I9Wo%R#$b6YdH7GD4*p6>a2N2A04pQ*n;INQMh%+mj;x7>S z_(H?uJ^n!r1)kJH1*s+%$al#?C^Cw{H@RA^QGB=Dubyc)XUaY>f`(VKTlIO-YNCp{1n zOl*>jT?Dtf5fD$DY-j&B*Xmn|2-u2OB zBL@-lFs5lhcQKXBR*cIXmi%~EJcc^5#Xpg!E^A6sXf1#$qJGRpmU~A zcdj-cvBfx(fIRAMU(1obztJR%I7v3R-%$#~r!0sS^I(iC*5i6296*88A7I=_JhU3p zya!aCti0R5*RFT%LW0R|;u&oJ6=P-c$le4J0bi}u!!@;xzao|l6fJ{;Mld9hGhrJg zr_B)=4yktp)yPB@tCC_L9h1>GzXD6DA!W7xt{1)8!07~gONkEWC8@y%lciB{9ojy) zWm$drJ_9uVJ>Q$-`@q%OM7_S>(K=__CGYB~@@mE^Z=eT|x0Rv?Z-N)LLWR zod*Zy3v)iMX@usPX-OKBDgC8yq?fMhqf8H)A&C)Hi29YFn!NVf5!J0-F{wC&L5-3`#id=4?=2>Zp6Pdu4N6#bG&atu7 z8IET&ciXy_Tp4YjMx3yIAbw#_e2#jgGJ~ogkv-|M7|%Gio%2@mnS89NKUOM#Bzg4_ z9e9oN;^m>G*#?)AawODi6YckRPmkSKD_4b4WFpj|@|eS!B0WN@?QscYzTH`~6e%iz z!z1>ps)CG37%(E=kZ_>re)@ODv^0^=rWU^*m;6M&gD10EYImO98JVabRe5{#wrogYUKPB@_(#e7Ej9_x;n1oHDj5GawU)A&1hWj|HzJB(q{vMTX>jOW;Jz zBsW&SqTaR7!NXXg_A}$XnFpg_n)Zi;{e9eb*k|b(y$a}12boJ7rqQXQpVhU8HxHTl zt8Ln!KLFyfq!%}hdMXle^qajw2g6S{z&7tQ6J(w9 z3+!HTO{_TqM{9o$RR~lKFf4b4(xLUP?QG;McNFQc_Yd_mig9Ejy9%q~Ye>rIn3};U z)w&1@QCK;cC(;x0G&YuSad+>{c@ZsFJcUdcs@PP-x{mrO)|6_#CjMlXsMJx;Cr?FF zVFrlt@$Z-Ll^*7d0#`5Uez@bb{Xn(BQLhScBhF!6+aIso0=l{PP7P(6-ru>nVy%AP z+|eZpY(ooMU7rtG$l#14v=Z?@ebOjm(A2)5k_${|wAA$oq+;42wiS78ezjgWWnTrF z`1!i2h{fM91aD8uxz?tZpE(PsL37e3$*I6%un5Bzzpn10p`j72R;3=Oaug_|Z(y)@ z9$SJN@-5d1tNIy0=7|d&_HAnDx!yDd-u#qmfuDh)0a_CVje{hvQz9rDFHJTpQ0Dg@ zGQ3t*gZlcFSXfx%OG@Cds&NDROxd^osY_)abmo^dKMUY!R~kGH%*;rutPF@Mx$zrv z6Q1soKnYYRW#;Bi-!H)>Br0<`y+Wy~p7_<>{ljuG`Dpje=v1x}-ND<)bWBr|<}v6B zkDTUZ^@VsH>CyR}ml4j2rB{}0q8eGwX>ExkI9yZN0)(P}$N(yi$AxmBY#Xj`(7zs{ zJbn2&jE`-*0lww_r;|fNaWm_xp;c9JHIv|RExZGKP%18qjgYa);`N-^VqXNVz{~)~ z?^&D;ouy!pKPy?%@xH`A zSR z7x%N3@o&{YEjfa|1;*eW_4TU{ zt;qCcY3Hj(<0DJuny*QL!y!StcG{>bhpUP%eVMq=1xcR>yZT8X9)1;rXOmQjPcANs zr>&Qb{rr66;s|4v3iGmQlMjr9j;G6pqNs%;TsyVNd3{i~hpDX8ugdcnd&UQJzj)rH zh>S6#n`cCJ9CwHv<2Ht$o`R5(h#r||VB?%J?s5W48;^o)b`Pi1^~}5{Y19lg{&W@LfHt*gc1`w$RfLrK{~H?A1$5 z;5v?AIhpN%gQsR6+Act9-3y z8>jCTMnWQq-^s3#Lb|WalgB$k3F>}lyCxs<2&A;LS0}s#<|hPx9kM#B+Lu2DiD_3P zelg;N!80(j@HNc2pXs}re%sHi+{aqBt~qUOy86?zN>7)yiCEJqy@2Gh#gzJE6j6Rx zBQK{77zW?gLWtQ20Dzntu16k9^N>DQ@Nmbx*mOg=F=k)8VJfM%y(Xu41;8YCz+@K| z9u7vhlT`BOnk_oMTeC;u@OhhoTeA`^34^iMihCLM_uVD>rI-9@4l7ocZl@DJ8FWZU zB0lRBIqkHj4#pE&mD(X!e!~;G$`7f47k* zOznM2@`&KM(|f5}sz)z%2}yJ5YmMj5Zwzr-W?v3R&@KuJ+l0zo==N@)nsbMHqHV}w z7#_ntMGCNM21RuH^SYG+RH0sHUsF2z7ams57@2xbPj0y5)8h+caqv@P^q!do+}>+X zzUBx|mikTawzXWYzJ4(AqAJpBF4ObmD_@gyg->oFGB6`k(8+?rFRV5P1yDkFM=8(c z%RI)iG(rKtq-^V%B_(R9;tk6WIzA?x@cESTXg zWYDBxkoNB5v6J8BP&n@HVtBNb@r+XYpjgub zR4oE*$ffXJuh2g8TCaLnpNoSxJ~Jx@ayx9z5Osa)=AI#bg^5eQb<6gpR%c+Qs#N*e z@XE4pAmjdI#0%pV7sIN>mNa^jTkd=<==2_#t-}9Ju&Z^|Lp$%B92@eN%=MRc)LK$% z@!XAg;dQ8bt=@ZNey7+a(dy^o;QKGP@Rb5NJYQRrGEC{J=FB(Irw-MAfoP(9RK;)&jlxSCT=W;ODCf($WqRFhqN#LR^qVhK zWhEp4`{Nnk;n0FHj}eNCZpRM`Y-@MIM&pvr7zQOZ3Ik5;CmZbR99b&22(!-07YNF) z$o0MKej-jnvQV39{TH4r2R5univa1{ASc|VOTi4c@`t2FId|xkh5typ-rdU;1j){adk@*+( zkHj{5B~eSy&HrPOOvl_FJ98)0V;^d`0-u0FTslgiLBQVGSTiSyu zgMGAu&R}SbNa-DgKJb?;fe3Qys$?=;5?V`eRiq*Kj$I`}Z*x4rC~eNM=DsOq(=nUW>(+7o@O8K-_U(X? zTyg032nXKax5W~SF5|eBj%r8Fa>i!ejC72*sd}zJ)t7Xy!gFvM`c4@*Iw>z$u)j_l zR-Uqxymg}>Ti>i%9j*4kwfC33i~kyIQ``n)r(L z!|H2*)Mwj4dk%e*L0tgFdW185>j4<7YwLXwcOsed`%6mS{+=&d@d!B}GkbDV*0 zNIWzW^|trz!&;qeI&mPiVDOUL70xpqVv0fpN9tjpu)@1LD9D<9}9{57j9!W$`zC6&i zl9lKkmPh`x)5+h>>JtiRNNBW5$_)%-)#+SVSGsjX2T=+SRX05>yJZd`1hyk<@{%1+ zDu^k>J$d*Qz6BZMwHx!@O**^Tx&fsHDw%$@J0nfj^je^Ihy*aIx{B(hkBvSvh46Z9 zRO)BjjXL_IHXKo~$4es=8Wxk;Y+&nVBCXA;=MVuLgVn8Mk(*y^+kP3f?Pr~4^A}hXj9UHS}qeI%XKD3KhHnkrNH0(Y20BWl&!Kfm`EVh2;i5C zpirU^K0nc2-I{cqvjZKVx z=&hH#-d=gDWjVE}cMNAPJf;#NYdQ=h`twjX6yquXuCNgGx1~uk{YHAmFpQF`ZLGC=~ukEyj?cFDI zH=@XvV#AY1EY4qb`y*;Ki>KuFB|2|toL7__Cr0S1Dl{s#y0=~7HSq~&7lpBc*VLua zvv3r&-LM*{hq%IYP7<@)dG-G$kMrZaqs(MYoZ zugEeJ@u(ip9rMoVtoFe;dF`^Br5x7v!rr5`hb5mJ#ocGqXHnm9m`yILjd0>UQSMv) z^v}l5^bM6RZ6M%{mkI) zHOoSp&dX)*xUt+kXscna#a`XxI;Ul2Sxa^i5sZc=(Q)oA^2-_;!pfYHAul+oA@Ilelm;rw@FYR+SIaWS?;_ zUdw<|qqaYq(nqu>rG48E9dYAoT6GH;QRuBYK1}W#C_Z_?7~k*pJ3?MzVt&rhZTsBy zw?nN$_Z>kimtwWcy`0?G#!)&7GjOcxCQps@p&ml8>~z(t=sjhR$6aFh!Vw5GA(lTh z5GM)jCwloa6a}7mdfqNYE7oi`Jv$m5>5qR%9eZ=)=a z+K4j5NpcDHHdepCS+P*{@o=yNp&TE(Sd4b0Notqso-Kt_mhDk1<-fa>T4KdY2N`U) zxu41vD%T&k$Gl?CW81%7r#-o1TZ0&PCcy}L4TPiV;sz`|S!&w8-s$rLdM zF&)>@`7=)65PWn#oi|8tXNb|((2ojf9d0fNZ^l7xY~dX~%*Xf-v2W-2n$i~s!4?H; z2qbQscFN21tqB{|x1+(^G~xQSrvX&Y;V-%?b1}zjBQX{GOFcVYTcwm>>}>6^HA=$x zn+z^Biv_5}0!#@7z1~YXJFCT2?D^jm+kH7jAqBo?M@ZdMl|2|66oLnSJXUOJtVLxe z0vH)N^t*qrjq=eFRMV>BFEfS)-2RzKlt973;d3D}4edwIE>kGc5-o=JV56ird)RlS z{Jg@0t-b#Ife80%!E~(7`qkZ8O~Q-8_{j7G&tqwX&&>^tm-#*{v7j-f1n0}mCR#7P z-4FkajD2$9?4Fc7-C_|0Z_G^bxIs%tWk|aFgSQ(qkM+5PRh=g&ZeAZg35$-kn~}_;~&fP-dCNCzg>{gyW!~LZpn?aZ~Va3~H0Ta)z z<4XPVk@;#%1S@fq<(2#8T04#8$mz>vM;(jek0>Qh!K%t5*4tU(fVYwD3Ri~=D!AmI zV$Dt#TEDX7{lpW%tF&DOlTO)vZodn_%wYu~)ZQ}Qo^cBbDHd{YajkzNxttQW>ST<^ z2~^xhB_y1sjIF5;xchvCn{QVugIE2eYZDZ!-Y-4lJdb34*k({@M zJ5!9Di^||~(IZ4iOoAbtggao+CaYvJynmB^;4r-tY2gS_*P!?U?hlEX;l+^*{%B2n z)|1j9wOHQQ^5Xha>{Cu8_w^8=#6;Dz7kU~RgTqn;ynDm6{xdlkf2vk0UK^oS3yVy4 zE+v&qnlYtPHBk#X&2}r7`@K`J@^e~Qm?iRJ*tbAaZDZTmB&mWMkZp7Kj7^kth#_uX z5z>gC(8Xz|Ie(+#&wiF3;Aey|Db(R*-U)!6;l_5@u?-$>j0SgEl5+c}Lfe-$p-dFH zB_$bC<)x6#A_2Uuo8=^l1@}vK!gvbF#b&MoH8ac3xMxUz$LFb8KU(x$YhtHanM_sw zYOFMBX2iNNSe&a}!;G9nv(tsW4@%3iQcqczOCF*JOBQ@4Orw=o?_vc(9$hfO`>U6& zyY_CUa9pASiJpmv`@oR!k;&$`h8!)$uS=}d-fPddfIdMDUW@%3y1LI(1Q=e$)sz(QC*E;Nfl99YTgk+|@jl`+iF?<_D?4YqV0Zl)lO8YWC@1ZWW^mi{5ePQN<~FQ2NMG$|K{py5akJa zkezmqhN)>MGMp$7=sOo2(7ppv``dCIwf&MaQQis7S596kkiw8Do(jO?EY4iJ4Hec6 z4Hymzu`w)cI9Pbq6GPtTP)x&Lmk;FT=ZCB4>(5}c0?;2l`p&?>&<;2(P8a3lOTNP# zdEzF5qDpkRR&PZC&cS{7xD@qV;(g5X%xI?m$9Q + + + + +MIS Report Account Coverage Check + + + +
+ + +Odoo Community Association +
+

MIS Report Account Coverage Check

+ +

Beta License: AGPL-3 OCA/mis-builder-contrib Translate me on Weblate Try me on Runboat

+

A MIS Builder report (e.g. a balance sheet) only shows what its KPI +expressions explicitly reference. If an account within the report’s range +receives postings during the period but no KPI expression covers it, the +report silently omits it – typically the symptom is a balance sheet that +does not balance, with no obvious way to find out why.

+

This module adds a wizard that, given a MIS report instance and an account +code range, finds accounts in that range which have journal entries posted +during the report’s period(s) but are not referenced by any KPI expression +of the report (including subreport KPIs). Each uncovered account is shown +with its period debit/credit/balance, with a direct drill-down to the +underlying journal items.

+

Table of contents

+ +
+

Configuration

+

No configuration is needed. The wizard is available to users in the +Accounting / Account User group.

+
+
+

Usage

+
    +
  1. Open a MIS Report Instance (Accounting > Reporting > MIS Reporting > +MIS Reports) and click the Check Account Coverage button, or go to +Accounting > Reporting > MIS Reporting > Account Coverage Check and +pick the report instance manually.
  2. +
  3. Enter the account code range to check, e.g. 100000 to 599999.
  4. +
  5. Optionally restrict the periods to check; leave empty to check every +period column defined on the report instance.
  6. +
  7. Click Check. Any account in the range that has postings in a period +but is not referenced by any KPI expression of the report is listed with +its debit, credit and balance for that period.
  8. +
  9. Click View Journal Items on a line to open the underlying journal +entries causing that account to appear.
  10. +
+
+

Note

+

The account range is compared as text (account.account.code), so it +is only reliable when the codes being compared have the same number of +digits (as is normally the case, e.g. 6-digit Spanish PGC codes). A range +like 7 to 12 would not behave as expected since "7" <= "12" +is false as a string comparison.

+
+
+

Note

+

Coverage means “referenced by any KPI expression of the report”, +including aggregate/rollup KPIs (e.g. a “Result for the year” line that +nets a whole P&L range like 6%,7% into a single equity figure). That +is intentional: if an account is correctly summed into such a KPI it does +not put the report out of balance, so it should not be reported as +uncovered even though it has no individual detail line of its own.

+
+
+

Note

+

Without a chart’s usual closing/opening entries, a balance-sheet +account’s balance keeps accumulating from postings dated before the +period being checked. To match how mis_builder itself computes a +balance-sheet KPI (“ending balance” mode), an uncovered account’s +debit/credit/balance are summed since the beginning of time for +balance-sheet accounts (asset/liability/equity), and since the start of +the fiscal year only for P&L accounts (income/expense), which do reset +every year. So a posting from a previous period can still make an +account show up as uncovered today.

+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

carlosdauden

+

This module is part of the OCA/mis-builder-contrib project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/mis_report_account_coverage_check/tests/__init__.py b/mis_report_account_coverage_check/tests/__init__.py new file mode 100644 index 0000000..b321026 --- /dev/null +++ b/mis_report_account_coverage_check/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mis_report_account_coverage_check diff --git a/mis_report_account_coverage_check/tests/test_mis_report_account_coverage_check.py b/mis_report_account_coverage_check/tests/test_mis_report_account_coverage_check.py new file mode 100644 index 0000000..678a693 --- /dev/null +++ b/mis_report_account_coverage_check/tests/test_mis_report_account_coverage_check.py @@ -0,0 +1,215 @@ +# Copyright 2026 Tecnativa - Carlos Dauden +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import datetime + +from odoo import fields +from odoo.tests import common + + +class TestMisReportAccountCoverageCheck(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # use a dedicated company so the test is not affected by any chart + # of accounts / demo postings that other installed modules may have + # set up on the default company + cls.company = cls.env["res.company"].create({"name": "Coverage Check Co"}) + type_ar = cls.env.ref("account.data_account_type_receivable") + type_in = cls.env.ref("account.data_account_type_revenue") + type_asset = cls.env.ref("account.data_account_type_current_assets") + cls.account_ar = cls.env["account.account"].create( + { + "company_id": cls.company.id, + "code": "400AR", + "name": "Receivable", + "user_type_id": type_ar.id, + "reconcile": True, + } + ) + cls.account_uncovered = cls.env["account.account"].create( + { + "company_id": cls.company.id, + "code": "500UN", + "name": "Uncovered account", + "user_type_id": type_in.id, + } + ) + cls.account_in = cls.env["account.account"].create( + { + "company_id": cls.company.id, + "code": "700IN", + "name": "Income", + "user_type_id": type_in.id, + } + ) + cls.account_out_of_range = cls.env["account.account"].create( + { + "company_id": cls.company.id, + "code": "900OU", + "name": "Out of range account", + "user_type_id": type_in.id, + } + ) + # balance-sheet account (no posting yet): used to check that, with + # no closing/opening entries, its balance keeps accumulating from + # before the period being checked + cls.account_bs_old = cls.env["account.account"].create( + { + "company_id": cls.company.id, + "code": "450OL", + "name": "Old balance-sheet account", + "user_type_id": type_asset.id, + } + ) + # P&L account (no posting yet): used to check that, unlike balance + # -sheet accounts, its balance resets every fiscal year + cls.account_pl_old = cls.env["account.account"].create( + { + "company_id": cls.company.id, + "code": "480PL", + "name": "Old P&L account", + "user_type_id": type_in.id, + } + ) + cls.journal = cls.env["account.journal"].create( + { + "company_id": cls.company.id, + "name": "Sale journal", + "code": "VEN", + "type": "sale", + } + ) + cls.date_from = datetime.date.today().replace(day=1) + next_month = cls.date_from.replace(day=28) + datetime.timedelta(days=4) + cls.date_to = next_month - datetime.timedelta(days=next_month.day) + # covered postings: only account_ar/account_in appear in the KPI expr + cls._create_move(cls.account_ar, cls.account_in, 100) + # uncovered-but-used posting, in range: must be detected + cls._create_move(cls.account_ar, cls.account_uncovered, 50) + # posting on an out-of-range account: must never be detected + cls._create_move(cls.account_ar, cls.account_out_of_range, 20) + + cls.report = cls.env["mis.report"].create({"name": "Test report"}) + cls.env["mis.report.kpi"].create( + { + "report_id": cls.report.id, + "name": "balance", + "description": "Balance", + "expression_ids": [(0, 0, {"name": "balp[400AR,700IN]"})], + } + ) + cls.instance = cls.env["mis.report.instance"].create( + { + "name": "Test instance", + "report_id": cls.report.id, + "company_id": cls.company.id, + "date_from": fields.Date.to_string(cls.date_from), + "date_to": fields.Date.to_string(cls.date_to), + "period_ids": [(0, 0, {"name": "Default"})], + } + ) + + @classmethod + def _create_move(cls, debit_account, credit_account, amount, date=None): + move = cls.env["account.move"].create( + { + "journal_id": cls.journal.id, + "date": fields.Date.to_string(date or cls.date_from), + "line_ids": [ + ( + 0, + 0, + {"name": "/", "debit": amount, "account_id": debit_account.id}, + ), + ( + 0, + 0, + { + "name": "/", + "credit": amount, + "account_id": credit_account.id, + }, + ), + ], + } + ) + move._post() + return move + + def _create_wizard(self): + return self.env["mis.report.account.coverage.check"].create( + { + "report_instance_id": self.instance.id, + "account_code_from": "400AR", + "account_code_to": "700IN", + } + ) + + def test_uncovered_account_detected(self): + wizard = self._create_wizard() + wizard.action_check() + self.assertEqual(wizard.state, "done") + self.assertEqual(len(wizard.line_ids), 1) + line = wizard.line_ids + self.assertEqual(line.account_id, self.account_uncovered) + self.assertEqual(line.debit, 0) + self.assertEqual(line.credit, 50) + self.assertEqual(line.balance, -50) + + def test_old_posting_on_balance_sheet_account_detected(self): + # without closing/opening entries, a balance-sheet account's balance + # keeps accumulating: a posting from a year before the period being + # checked must still surface the account as uncovered + old_date = self.date_from.replace(year=self.date_from.year - 1) + self._create_move(self.account_bs_old, self.account_ar, 40, date=old_date) + wizard = self._create_wizard() + wizard.action_check() + result_accounts = wizard.line_ids.mapped("account_id") + self.assertIn(self.account_bs_old, result_accounts) + line = wizard.line_ids.filtered( + lambda line_: line_.account_id == self.account_bs_old + ) + self.assertEqual(line.debit, 40) + self.assertEqual(line.balance, 40) + + def test_old_posting_on_pl_account_not_detected(self): + # unlike balance-sheet accounts, P&L accounts reset every fiscal + # year: a posting from the previous fiscal year must NOT make the + # account show up when checking the current period + old_date = self.date_from.replace(year=self.date_from.year - 1) + self._create_move(self.account_ar, self.account_pl_old, 40, date=old_date) + wizard = self._create_wizard() + wizard.action_check() + self.assertNotIn(self.account_pl_old, wizard.line_ids.mapped("account_id")) + + def test_covered_accounts_not_listed(self): + wizard = self._create_wizard() + wizard.action_check() + result_accounts = wizard.line_ids.mapped("account_id") + self.assertNotIn(self.account_ar, result_accounts) + self.assertNotIn(self.account_in, result_accounts) + + def test_out_of_range_account_not_listed(self): + wizard = self._create_wizard() + wizard.action_check() + self.assertNotIn( + self.account_out_of_range, wizard.line_ids.mapped("account_id") + ) + + def test_view_move_lines_action_domain(self): + wizard = self._create_wizard() + wizard.action_check() + line = wizard.line_ids + action = line.action_view_move_lines() + move_lines = self.env[action["res_model"]].search(action["domain"]) + self.assertTrue(move_lines) + self.assertEqual( + set(move_lines.mapped("account_id.id")), {self.account_uncovered.id} + ) + + def test_action_back_resets_state(self): + wizard = self._create_wizard() + wizard.action_check() + wizard.action_back() + self.assertEqual(wizard.state, "init") + self.assertFalse(wizard.line_ids) diff --git a/mis_report_account_coverage_check/views/mis_report_instance_views.xml b/mis_report_account_coverage_check/views/mis_report_instance_views.xml new file mode 100644 index 0000000..4ad3af4 --- /dev/null +++ b/mis_report_account_coverage_check/views/mis_report_instance_views.xml @@ -0,0 +1,19 @@ + + + + mis.report.instance.form.coverage.check + mis.report.instance + + +
+
+
+
+
diff --git a/mis_report_account_coverage_check/wizard/__init__.py b/mis_report_account_coverage_check/wizard/__init__.py new file mode 100644 index 0000000..da4c51d --- /dev/null +++ b/mis_report_account_coverage_check/wizard/__init__.py @@ -0,0 +1,2 @@ +from . import mis_report_account_coverage_check +from . import mis_report_account_coverage_check_line diff --git a/mis_report_account_coverage_check/wizard/mis_report_account_coverage_check.py b/mis_report_account_coverage_check/wizard/mis_report_account_coverage_check.py new file mode 100644 index 0000000..d2e4ae2 --- /dev/null +++ b/mis_report_account_coverage_check/wizard/mis_report_account_coverage_check.py @@ -0,0 +1,151 @@ +# Copyright 2026 Tecnativa - Carlos Dauden +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models + + +class MisReportAccountCoverageCheck(models.TransientModel): + _name = "mis.report.account.coverage.check" + _description = "Check accounts used in postings but not covered by a MIS report" + + state = fields.Selection( + selection=[("init", "Configuration"), ("done", "Results")], + string="Status", + readonly=True, + default="init", + ) + report_instance_id = fields.Many2one( + comodel_name="mis.report.instance", + string="MIS Report Instance", + required=True, + ondelete="cascade", + ) + period_ids = fields.Many2many( + comodel_name="mis.report.instance.period", + relation="mis_report_account_coverage_check_period_rel", + string="Periods to check", + domain="[('report_instance_id', '=', report_instance_id)]", + help="Leave empty to check all periods of the instance.", + ) + account_code_from = fields.Char(string="Account code from", required=True) + account_code_to = fields.Char(string="Account code to", required=True) + line_ids = fields.One2many( + comodel_name="mis.report.account.coverage.check.line", + inverse_name="check_id", + string="Uncovered accounts", + ) + + @api.onchange("report_instance_id") + def _onchange_report_instance_id(self): + self.period_ids = self.report_instance_id.period_ids + + def _get_account_range_domain(self): + self.ensure_one() + return [ + ("code", ">=", self.account_code_from), + ("code", "<=", self.account_code_to), + ] + + def _get_covered_account_ids(self, aep): + """Account ids referenced by at least one KPI expression of the + report template, including subreport KPIs (``all_kpi_ids``, unlike + the core ``get_kpis_by_account_id`` which only looks at ``kpi_ids``). + + Every KPI counts, including aggregate/rollup ones (e.g. a "Result + for the year" line that nets the whole P&L with a ``6%,7%`` wildcard + into a single equity figure): if an account is correctly summed into + such a KPI, it does not put the report out of balance, so it must + not be reported as uncovered even though it has no individual detail + line of its own. + """ + self.ensure_one() + report = self.report_instance_id.report_id + covered_ids = set() + for kpi in report.all_kpi_ids: + for expression in kpi.expression_ids: + if expression.name: + covered_ids.update(aep.get_account_ids_for_expr(expression.name)) + return covered_ids + + def _reopen(self): + self.ensure_one() + return { + "type": "ir.actions.act_window", + "res_model": self._name, + "res_id": self.id, + "view_mode": "form", + "target": "new", + } + + def action_check(self): + self.ensure_one() + self.line_ids.unlink() + instance = self.report_instance_id + report = instance.report_id + account_model = self.env[report.account_model or "account.account"] + aml_model = self.env[report.sudo().move_lines_source.model] + aep = report._prepare_aep(instance.query_company_ids, instance.currency_id) + + range_accounts = account_model.search( + self._get_account_range_domain() + + [("company_id", "in", instance.query_company_ids.ids)] + ) + covered_ids = self._get_covered_account_ids(aep) + uncovered_accounts = range_accounts.filtered( + lambda account: account.id not in covered_ids + ) + + line_vals = [] + if uncovered_accounts: + periods = self.period_ids or instance.period_ids + for period in periods: + if not (period.date_from and period.date_to): + continue + # MODE_END ("ending balance", what every balance-sheet KPI + # expression uses): balance-sheet accounts (asset/liability + # /equity, "include_initial_balance") are summed since the + # beginning of time, since without formal closing/opening + # entries their balance keeps accumulating across fiscal + # years; P&L accounts are summed since the start of the + # fiscal year only, since those reset every year. A plain + # ("date", ">=", period.date_from) window would miss + # balance-sheet accounts whose contributing postings are + # older than the period being checked. + domain = [ + ("account_id", "in", uncovered_accounts.ids), + ("company_id", "in", instance.query_company_ids.ids), + ] + domain += aep.get_aml_domain_for_dates( + period.date_from, period.date_to, aep.MODE_END + ) + domain += period._get_additional_move_line_filter() + groups = aml_model.read_group( + domain, + ["account_id", "debit", "credit", "balance"], + ["account_id"], + ) + for group in groups: + if not group["account_id"]: + continue + line_vals.append( + ( + 0, + 0, + { + "period_id": period.id, + "account_id": group["account_id"][0], + "debit": group["debit"], + "credit": group["credit"], + "balance": group["balance"], + "move_line_count": group["account_id_count"], + }, + ) + ) + self.line_ids = line_vals + self.state = "done" + return self._reopen() + + def action_back(self): + self.ensure_one() + self.line_ids.unlink() + self.state = "init" + return self._reopen() diff --git a/mis_report_account_coverage_check/wizard/mis_report_account_coverage_check_line.py b/mis_report_account_coverage_check/wizard/mis_report_account_coverage_check_line.py new file mode 100644 index 0000000..fda6fd9 --- /dev/null +++ b/mis_report_account_coverage_check/wizard/mis_report_account_coverage_check_line.py @@ -0,0 +1,58 @@ +# Copyright 2026 Tecnativa - Carlos Dauden +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + +from odoo.addons.mis_builder.models.aep import AccountingExpressionProcessor as AEP + + +class MisReportAccountCoverageCheckLine(models.TransientModel): + _name = "mis.report.account.coverage.check.line" + _description = "Uncovered account found during a MIS report coverage check" + + check_id = fields.Many2one( + comodel_name="mis.report.account.coverage.check", + required=True, + ondelete="cascade", + ) + period_id = fields.Many2one( + comodel_name="mis.report.instance.period", string="Period", required=True + ) + account_id = fields.Many2one( + comodel_name="account.account", string="Account", required=True + ) + account_code = fields.Char(related="account_id.code") + account_name = fields.Char(related="account_id.name") + currency_id = fields.Many2one(related="account_id.company_id.currency_id") + debit = fields.Monetary(currency_field="currency_id") + credit = fields.Monetary(currency_field="currency_id") + balance = fields.Monetary(currency_field="currency_id") + move_line_count = fields.Integer(string="Journal Items") + + def action_view_move_lines(self): + self.ensure_one() + instance = self.check_id.report_instance_id + report = instance.report_id + aml_model_name = report.sudo().move_lines_source.model + # bare AEP just to reuse get_aml_domain_for_dates (no KPI expression + # parsing needed here), same pattern as mis.report.instance.drilldown() + aep = AEP( + instance.query_company_ids, instance.currency_id, report.account_model + ) + domain = [ + ("account_id", "=", self.account_id.id), + ("company_id", "in", instance.query_company_ids.ids), + ] + domain += aep.get_aml_domain_for_dates( + self.period_id.date_from, self.period_id.date_to, aep.MODE_END + ) + domain += self.period_id._get_additional_move_line_filter() + return { + "name": self.account_id.display_name, + "type": "ir.actions.act_window", + "res_model": aml_model_name, + "domain": domain, + "views": [[False, "list"], [False, "form"]], + "view_mode": "list", + "target": "current", + "context": {"active_test": False}, + } diff --git a/mis_report_account_coverage_check/wizard/mis_report_account_coverage_check_views.xml b/mis_report_account_coverage_check/wizard/mis_report_account_coverage_check_views.xml new file mode 100644 index 0000000..8c2e06e --- /dev/null +++ b/mis_report_account_coverage_check/wizard/mis_report_account_coverage_check_views.xml @@ -0,0 +1,79 @@ + + + + mis.report.account.coverage.check.form + mis.report.account.coverage.check + +
+
+ +
+ + + + + + + + + + + + + + + + +