refactor: replace global record rule with ORM overrides on account.journal to enforce journal access restrictions safely
This commit is contained in:
parent
983e966f1a
commit
4505c7c486
11
README.md
11
README.md
@ -8,14 +8,17 @@ This module allows restricting users to specific journals in Odoo's Accounting a
|
|||||||
- **Access Control:**
|
- **Access Control:**
|
||||||
- If "Allowed Journals" is **empty**, the user has access to **ALL** journals (standard behavior).
|
- If "Allowed Journals" is **empty**, the user has access to **ALL** journals (standard behavior).
|
||||||
- If "Allowed Journals" is **populated**, the user can **ONLY** access the selected journals.
|
- If "Allowed Journals" is **populated**, the user can **ONLY** access the selected journals.
|
||||||
- Implements a global Record Rule to enforce this restriction across the system (Views, Search, Create, Write).
|
- Implements an ORM-level `_search`, `write`, and `unlink` override on `account.journal` to safely and recursively filter journals without crashing on standard views.
|
||||||
|
- **Context-Aware Bypass:** Automatically detects and bypasses restrictions during critical Point of Sale (POS) operations and administrative automated/clearing background contexts.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
1. Go to **Settings** > **Users & Companies** > **Users**.
|
1. Go to **Settings** > **Users & Companies** > **Users**.
|
||||||
2. Open the user you want to restrict.
|
2. Open the user you want to restrict.
|
||||||
3. Go to the **Allowed Journals** tab.
|
3. In the **Allowed Journals** field, select the journals the user is allowed to access.
|
||||||
4. Add the journals the user is allowed to access.
|
4. Save.
|
||||||
5. Save.
|
|
||||||
|
## Author
|
||||||
|
Suherdy Yacob
|
||||||
|
|
||||||
## License
|
## License
|
||||||
LGPL-3
|
LGPL-3
|
||||||
|
|||||||
@ -9,10 +9,9 @@
|
|||||||
- If "Allowed Journals" is set, the user can only access those journals.
|
- If "Allowed Journals" is set, the user can only access those journals.
|
||||||
- If "Allowed Journals" is empty, the user can access all journals.
|
- If "Allowed Journals" is empty, the user can access all journals.
|
||||||
""",
|
""",
|
||||||
'author': 'Antigravity',
|
'author': 'Suherdy Yacob',
|
||||||
'depends': ['base', 'account'],
|
'depends': ['base', 'account'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/account_security.xml',
|
|
||||||
'views/res_users_views.xml',
|
'views/res_users_views.xml',
|
||||||
],
|
],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
from . import res_users
|
from . import res_users
|
||||||
|
from . import account_journal
|
||||||
|
|||||||
74
models/account_journal.py
Normal file
74
models/account_journal.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
class AccountJournal(models.Model):
|
||||||
|
_inherit = 'account.journal'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _search(self, domain, offset=0, limit=None, order=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Override _search to filter journals based on the allowed_journal_ids configuration on res.users.
|
||||||
|
This provides a secure, recursive-safe, and context-aware filtering mechanism.
|
||||||
|
"""
|
||||||
|
user = self.env.user
|
||||||
|
|
||||||
|
# Determine if we should bypass the journal visibility restrictions:
|
||||||
|
# 1. Superuser / system context (env.su) is always bypassed.
|
||||||
|
# 2. Skip if user has no allowed journals configured (empty means access to all).
|
||||||
|
# 3. Explicit bypass requested in the context (bypass_allowed_journal).
|
||||||
|
# 4. Point of Sale contexts:
|
||||||
|
# - pos_session_id or pos_config_id is present.
|
||||||
|
# - pos_last_server_date is present (POS frontend loading data).
|
||||||
|
# 5. During standard module installations/upgrades (install_mode).
|
||||||
|
if not self.env.su and user.sudo().allowed_journal_ids:
|
||||||
|
bypass = (
|
||||||
|
self.env.context.get('bypass_allowed_journal') or
|
||||||
|
self.env.context.get('pos_session_id') or
|
||||||
|
self.env.context.get('pos_config_id') or
|
||||||
|
'pos_last_server_date' in self.env.context or
|
||||||
|
self.env.context.get('install_mode')
|
||||||
|
)
|
||||||
|
if not bypass:
|
||||||
|
allowed_ids = user.sudo().allowed_journal_ids.ids
|
||||||
|
domain = [('id', 'in', allowed_ids)] + list(domain)
|
||||||
|
|
||||||
|
return super(AccountJournal, self)._search(domain, offset=offset, limit=limit, order=order, **kwargs)
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
"""
|
||||||
|
Restrict write access to allowed journals only.
|
||||||
|
"""
|
||||||
|
if not self.env.su:
|
||||||
|
user = self.env.user
|
||||||
|
if user.sudo().allowed_journal_ids:
|
||||||
|
bypass = (
|
||||||
|
self.env.context.get('bypass_allowed_journal') or
|
||||||
|
self.env.context.get('pos_session_id') or
|
||||||
|
self.env.context.get('pos_config_id')
|
||||||
|
)
|
||||||
|
if not bypass:
|
||||||
|
allowed_ids = user.sudo().allowed_journal_ids.ids
|
||||||
|
unallowed = self.filtered(lambda j: j.id not in allowed_ids)
|
||||||
|
if unallowed:
|
||||||
|
raise UserError(_("You are not allowed to modify the following journal(s): %s") % ", ".join(unallowed.mapped('name')))
|
||||||
|
return super(AccountJournal, self).write(vals)
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
"""
|
||||||
|
Restrict delete access to allowed journals only.
|
||||||
|
"""
|
||||||
|
if not self.env.su:
|
||||||
|
user = self.env.user
|
||||||
|
if user.sudo().allowed_journal_ids:
|
||||||
|
bypass = (
|
||||||
|
self.env.context.get('bypass_allowed_journal') or
|
||||||
|
self.env.context.get('pos_session_id') or
|
||||||
|
self.env.context.get('pos_config_id')
|
||||||
|
)
|
||||||
|
if not bypass:
|
||||||
|
allowed_ids = user.sudo().allowed_journal_ids.ids
|
||||||
|
unallowed = self.filtered(lambda j: j.id not in allowed_ids)
|
||||||
|
if unallowed:
|
||||||
|
raise UserError(_("You are not allowed to delete the following journal(s): %s") % ", ".join(unallowed.mapped('name')))
|
||||||
|
return super(AccountJournal, self).unlink()
|
||||||
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
<record id="journal_comp_rule_allowed_journals" model="ir.rule">
|
|
||||||
<field name="name">Journal Multi-Company with Allowed Journals</field>
|
|
||||||
<field name="model_id" ref="account.model_account_journal"/>
|
|
||||||
<field name="domain_force">[(1, '=', 1)] if not user.allowed_journal_ids else [('id', 'in', user.allowed_journal_ids.ids)]</field>
|
|
||||||
<field name="global" eval="True"/>
|
|
||||||
</record>
|
|
||||||
</odoo>
|
|
||||||
Loading…
Reference in New Issue
Block a user