diff --git a/account_move_group_restriction/README.rst b/account_move_group_restriction/README.rst new file mode 100644 index 00000000000..e61f679fea3 --- /dev/null +++ b/account_move_group_restriction/README.rst @@ -0,0 +1,111 @@ +========================================= +Account Move Restricted by Account Groups +========================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:933c2ca3272ccf0dd85bb6c838cee69ba99b8a2366f20cbb46fe18b36a1652ad + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-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%2Faccount--financial--tools-lightgray.png?logo=github + :target: https://github.com/OCA/account-financial-tools/tree/16.0/account_move_group_restriction + :alt: OCA/account-financial-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-financial-tools-16-0/account-financial-tools-16-0-account_move_group_restriction + :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/account-financial-tools&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds an Access Groups field on accounts. When set, only +users in the selected groups can view journal entries and journal items +that use those accounts. + +In standard Odoo, accounting users can generally access all journal +entries and items. In some cases, even certain accounting managers +should not see specific entries because they contain confidential +information. This module fills that gap by enforcing account-based +visibility rules. + +Note: The groups set on accounts are intended to be additional to the +standard account.group_account_user, so users still keep basic +accounting access. For this reason, the module also provides a default +group, Accounting / View Restricted Accounts, which implies +account.group_account_user. + +**Table of contents** + +.. contents:: + :local: + +Known issues / Roadmap +====================== + +If a journal entry includes lines on accounts restricted by different +access groups, the entry is accessible to any user who belongs to at +least one (not all) of those groups. + +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 +------- + +* Quartile + +Contributors +------------ + +- `Quartile `__: + + - Tatsuki Kanda + - Aung Ko Ko Lin + +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-kanda999| image:: https://github.com/kanda999.png?size=40px + :target: https://github.com/kanda999 + :alt: kanda999 +.. |maintainer-aungkokolin1997| image:: https://github.com/aungkokolin1997.png?size=40px + :target: https://github.com/aungkokolin1997 + :alt: aungkokolin1997 + +Current `maintainers `__: + +|maintainer-kanda999| |maintainer-aungkokolin1997| + +This module is part of the `OCA/account-financial-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_move_group_restriction/__init__.py b/account_move_group_restriction/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/account_move_group_restriction/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_move_group_restriction/__manifest__.py b/account_move_group_restriction/__manifest__.py new file mode 100644 index 00000000000..003f62bdb7e --- /dev/null +++ b/account_move_group_restriction/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2025 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Account Move Restricted by Account Groups", + "summary": "Restrict visibility of journal entries by security groups on accounts.", + "version": "16.0.1.0.0", + "category": "Accounting", + "author": "Quartile, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/account-financial-tools", + "license": "AGPL-3", + "depends": ["account"], + "data": [ + "security/account_move_group_restriction_security.xml", + "views/account_account_view.xml", + ], + "maintainers": ["kanda999", "aungkokolin1997"], + "installable": True, + "application": False, +} diff --git a/account_move_group_restriction/models/__init__.py b/account_move_group_restriction/models/__init__.py new file mode 100644 index 00000000000..541790b173a --- /dev/null +++ b/account_move_group_restriction/models/__init__.py @@ -0,0 +1,2 @@ +from . import account_account +from . import account_move diff --git a/account_move_group_restriction/models/account_account.py b/account_move_group_restriction/models/account_account.py new file mode 100644 index 00000000000..e06e491a997 --- /dev/null +++ b/account_move_group_restriction/models/account_account.py @@ -0,0 +1,22 @@ +# Copyright 2025 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class AccountAccount(models.Model): + _inherit = "account.account" + + security_group_ids = fields.Many2many( + comodel_name="res.groups", + relation="account_account_res_groups_rel", + column1="account_id", + column2="group_id", + string="Access Groups", + help=( + "If set, only users belonging to at least one of these groups " + "will be able to see journal items and journal entries " + "posting on this account. " + "If left empty, the account is visible to all accounting users." + ), + ) diff --git a/account_move_group_restriction/models/account_move.py b/account_move_group_restriction/models/account_move.py new file mode 100644 index 00000000000..dc67c14a7b0 --- /dev/null +++ b/account_move_group_restriction/models/account_move.py @@ -0,0 +1,31 @@ +# Copyright 2025 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import Command, api, fields, models + + +class AccountMove(models.Model): + _inherit = "account.move" + + account_security_group_ids = fields.Many2many( + comodel_name="res.groups", + relation="account_move_res_groups_rel", + column1="move_id", + column2="group_id", + string="Account Access Groups", + compute="_compute_account_security_group_ids", + compute_sudo=True, + store=True, + help=( + "Union of the security groups configured on the accounts used " + "on this journal entry. Used by record rules to restrict access: " + "if any line uses a restricted account, the whole move becomes " + "visible only to users in at least one of these groups." + ), + ) + + @api.depends("line_ids.account_id.security_group_ids") + def _compute_account_security_group_ids(self): + for move in self: + groups = move.mapped("line_ids.account_id.security_group_ids") + move.account_security_group_ids = [Command.set(groups.ids)] diff --git a/account_move_group_restriction/readme/CONTRIBUTORS.md b/account_move_group_restriction/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..a8660f9a12e --- /dev/null +++ b/account_move_group_restriction/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [Quartile](https://www.quartile.co): + - Tatsuki Kanda + - Aung Ko Ko Lin diff --git a/account_move_group_restriction/readme/DESCRIPTION.md b/account_move_group_restriction/readme/DESCRIPTION.md new file mode 100644 index 00000000000..2d064a65bfe --- /dev/null +++ b/account_move_group_restriction/readme/DESCRIPTION.md @@ -0,0 +1,12 @@ +This module adds an Access Groups field on accounts. When set, only users in the +selected groups can view journal entries and journal items that use those accounts. + +In standard Odoo, accounting users can generally access all journal entries and items. +In some cases, even certain accounting managers should not see specific entries because +they contain confidential information. This module fills that gap by enforcing +account-based visibility rules. + +Note: The groups set on accounts are intended to be additional to the standard +account.group_account_user, so users still keep basic accounting access. For this +reason, the module also provides a default group, Accounting / View Restricted Accounts, +which implies account.group_account_user. diff --git a/account_move_group_restriction/readme/ROADMAP.md b/account_move_group_restriction/readme/ROADMAP.md new file mode 100644 index 00000000000..cff854a8e2d --- /dev/null +++ b/account_move_group_restriction/readme/ROADMAP.md @@ -0,0 +1,2 @@ +If a journal entry includes lines on accounts restricted by different access groups, the +entry is accessible to any user who belongs to at least one (not all) of those groups. diff --git a/account_move_group_restriction/security/account_move_group_restriction_security.xml b/account_move_group_restriction/security/account_move_group_restriction_security.xml new file mode 100644 index 00000000000..dd304d5ad80 --- /dev/null +++ b/account_move_group_restriction/security/account_move_group_restriction_security.xml @@ -0,0 +1,41 @@ + + + + Accounting / View Restricted Accounts + + + + + + Account Move Line by Account Group + + + ['|', + ('move_id.account_security_group_ids', '=', False), + ('move_id.account_security_group_ids', 'in', user.groups_id.ids) + ] + + + + + + + + + Account Move by Account Group + + + ['|', + ('account_security_group_ids', '=', False), + ('account_security_group_ids', 'in', user.groups_id.ids) + ] + + + + + + + diff --git a/account_move_group_restriction/static/description/index.html b/account_move_group_restriction/static/description/index.html new file mode 100644 index 00000000000..ea852170b50 --- /dev/null +++ b/account_move_group_restriction/static/description/index.html @@ -0,0 +1,448 @@ + + + + + +Account Move Restricted by Account Groups + + + +
+

Account Move Restricted by Account Groups

+ + +

Beta License: AGPL-3 OCA/account-financial-tools Translate me on Weblate Try me on Runboat

+

This module adds an Access Groups field on accounts. When set, only +users in the selected groups can view journal entries and journal items +that use those accounts.

+

In standard Odoo, accounting users can generally access all journal +entries and items. In some cases, even certain accounting managers +should not see specific entries because they contain confidential +information. This module fills that gap by enforcing account-based +visibility rules.

+

Note: The groups set on accounts are intended to be additional to the +standard account.group_account_user, so users still keep basic +accounting access. For this reason, the module also provides a default +group, Accounting / View Restricted Accounts, which implies +account.group_account_user.

+

Table of contents

+ +
+

Known issues / Roadmap

+

If a journal entry includes lines on accounts restricted by different +access groups, the entry is accessible to any user who belongs to at +least one (not all) of those groups.

+
+
+

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

+
    +
  • Quartile
  • +
+
+
+

Contributors

+
    +
  • Quartile:
      +
    • Tatsuki Kanda
    • +
    • Aung Ko Ko Lin
    • +
    +
  • +
+
+
+

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 maintainers:

+

kanda999 aungkokolin1997

+

This module is part of the OCA/account-financial-tools project on GitHub.

+

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

+
+
+
+ + diff --git a/account_move_group_restriction/tests/__init__.py b/account_move_group_restriction/tests/__init__.py new file mode 100644 index 00000000000..e4ceda61951 --- /dev/null +++ b/account_move_group_restriction/tests/__init__.py @@ -0,0 +1 @@ +from . import test_account_move_group_restriction diff --git a/account_move_group_restriction/tests/test_account_move_group_restriction.py b/account_move_group_restriction/tests/test_account_move_group_restriction.py new file mode 100644 index 00000000000..23bf52157ca --- /dev/null +++ b/account_move_group_restriction/tests/test_account_move_group_restriction.py @@ -0,0 +1,173 @@ +# Copyright 2025 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + + +from odoo.fields import Command +from odoo.tests.common import TransactionCase, tagged + + +@tagged("post_install", "-at_install") +class TestAccountMoveGroupRestriction(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.env.company + cls.group_account_user = cls.env.ref("account.group_account_user") + cls.group_restricted_account_management = cls.env.ref( + "account_move_group_restriction.group_restricted_account_management" + ) + Users = cls.env["res.users"].with_context(no_reset_password=True) + cls.user_basic = Users.create( + { + "name": "Basic Accounting User", + "login": "basic_account_user", + "email": "basic@example.com", + "company_id": cls.company.id, + "company_ids": [Command.set([cls.company.id])], + "groups_id": [Command.set([cls.group_account_user.id])], + } + ) + cls.user_management = Users.create( + { + "name": "Accounting Management User", + "login": "management_account_user", + "email": "management@example.com", + "company_id": cls.company.id, + "company_ids": [Command.set([cls.company.id])], + "groups_id": [ + Command.set([cls.group_restricted_account_management.id]) + ], + } + ) + cls.account_public = cls.env["account.account"].create( + { + "name": "Public Account", + "code": "PUB001", + "account_type": "asset_current", + "company_id": cls.company.id, + } + ) + cls.account_restricted = cls.env["account.account"].create( + { + "name": "Restricted Account", + "code": "RES001", + "account_type": "asset_current", + "company_id": cls.company.id, + "security_group_ids": [ + Command.set([cls.group_restricted_account_management.id]) + ], + } + ) + cls.journal = cls.env["account.journal"].create( + { + "name": "Miscellaneous", + "code": "TEST MISC", + "type": "general", + "company_id": cls.company.id, + } + ) + cls.move_public = cls._create_move(cls.account_public, cls.account_public) + cls.move_restricted = cls._create_move( + cls.account_restricted, cls.account_restricted + ) + cls.move_mixed = cls._create_move(cls.account_restricted, cls.account_public) + + @classmethod + def _create_move(cls, debit_account, credit_account): + Move = cls.env["account.move"] + move = Move.create( + { + "move_type": "entry", + "journal_id": cls.journal.id, + "company_id": cls.company.id, + "line_ids": [ + Command.create( + { + "name": "Debit line", + "account_id": debit_account.id, + "debit": 100, + "credit": 0.0, + } + ), + Command.create( + { + "name": "Credit line", + "account_id": credit_account.id, + "debit": 0.0, + "credit": 100, + } + ), + ], + } + ) + move.action_post() + return move + + def test_account_security_group_ids_computed_from_accounts(self): + self.assertFalse(self.move_public.account_security_group_ids) + self.assertEqual( + self.move_restricted.account_security_group_ids, + self.group_restricted_account_management, + ) + self.assertEqual( + self.move_mixed.account_security_group_ids, + self.group_restricted_account_management, + ) + + def test_basic_user_cannot_see_restricted_moves(self): + Move_basic = self.env["account.move"].with_user(self.user_basic) + public_move = Move_basic.search([("id", "=", self.move_public.id)]) + restricted_move = Move_basic.search([("id", "=", self.move_restricted.id)]) + mixed_move = Move_basic.search([("id", "=", self.move_mixed.id)]) + self.assertEqual( + public_move, + self.move_public, + "Basic user should see public move.", + ) + self.assertFalse( + restricted_move, + "Basic user should not see restricted move.", + ) + self.assertFalse( + mixed_move, + "Basic user should not see mixed move using restricted account.", + ) + + def test_management_user_can_see_all_moves(self): + Move_mgmt = self.env["account.move"].with_user(self.user_management) + for move in (self.move_public, self.move_restricted, self.move_mixed): + result = Move_mgmt.search([("id", "=", move.id)]) + self.assertEqual( + result, + move, + "Management user should see move %s." % move.id, + ) + + def test_basic_user_cannot_see_restricted_move_lines(self): + Line_basic = self.env["account.move.line"].with_user(self.user_basic) + public_lines = Line_basic.search([("move_id", "=", self.move_public.id)]) + restricted_lines = Line_basic.search( + [("move_id", "=", self.move_restricted.id)] + ) + mixed_lines = Line_basic.search([("move_id", "=", self.move_mixed.id)]) + self.assertTrue( + public_lines, + "Basic user should see lines of unrestricted move.", + ) + self.assertFalse( + restricted_lines, + "Basic user should not see lines of restricted move.", + ) + self.assertFalse( + mixed_lines, + "Basic user should not see lines of mixed restricted move.", + ) + + def test_management_user_can_see_all_move_lines(self): + Line_mgmt = self.env["account.move.line"].with_user(self.user_management) + for move in (self.move_public, self.move_restricted, self.move_mixed): + lines = Line_mgmt.search([("move_id", "=", move.id)]) + self.assertTrue( + lines, + "Management user should see lines of move %s." % move.id, + ) diff --git a/account_move_group_restriction/views/account_account_view.xml b/account_move_group_restriction/views/account_account_view.xml new file mode 100644 index 00000000000..7e525687db1 --- /dev/null +++ b/account_move_group_restriction/views/account_account_view.xml @@ -0,0 +1,18 @@ + + + + account.account.form.group.ids + account.account + + + + + + + + diff --git a/setup/account_move_group_restriction/odoo/addons/account_move_group_restriction b/setup/account_move_group_restriction/odoo/addons/account_move_group_restriction new file mode 120000 index 00000000000..fcdea11808c --- /dev/null +++ b/setup/account_move_group_restriction/odoo/addons/account_move_group_restriction @@ -0,0 +1 @@ +../../../../account_move_group_restriction \ No newline at end of file diff --git a/setup/account_move_group_restriction/setup.py b/setup/account_move_group_restriction/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/account_move_group_restriction/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)