feat(fusion_accounting_core): add computed coexistence group + recompute hooks

group_fusion_show_when_enterprise_absent has membership = all internal
users iff no Enterprise accounting module is installed. Membership is
recomputed on module install/uninstall via overrides on ir.module.module.
Used by Phase 1 fusion_bank_rec menus to auto-hide when Enterprise is
active and auto-appear after Enterprise uninstall.

Made-with: Cursor
This commit is contained in:
gsinghpal
2026-04-19 10:02:19 -04:00
parent 3f4fdeffce
commit 78a481f3f4
8 changed files with 111 additions and 1 deletions

View File

@@ -1 +1,6 @@
from . import models
def post_init_hook(env):
"""Initialize coexistence group membership based on current Enterprise install state."""
env['res.users']._fusion_recompute_coexistence_group()

View File

@@ -1,6 +1,6 @@
{
'name': 'Fusion Accounting Core',
'version': '19.0.1.0.1',
'version': '19.0.1.0.2',
'category': 'Accounting/Accounting',
'sequence': 24,
'summary': 'Shared base for the Fusion Accounting sub-module suite (security, shared schema, runtime helpers).',
@@ -30,4 +30,5 @@ Built by Nexa Systems Inc.
'installable': True,
'application': False,
'license': 'OPL-1',
'post_init_hook': 'post_init_hook',
}

View File

@@ -1,4 +1,5 @@
from . import ir_module_module
from . import res_users
from . import account_move
from . import account_reconcile_model
from . import account_bank_statement_line

View File

@@ -30,3 +30,26 @@ class IrModuleModule(models.Model):
('name', '=', module_name),
('state', '=', 'installed'),
]))
def button_immediate_install(self):
"""Recompute the coexistence group after install state changes."""
result = super().button_immediate_install()
self.env['res.users']._fusion_recompute_coexistence_group()
return result
def button_immediate_uninstall(self):
"""Recompute the coexistence group after uninstall state changes.
The MRO chains into fusion_accounting_migration's override (which runs
the safety guard before calling super); we recompute only after the
whole chain completes.
"""
result = super().button_immediate_uninstall()
self.env['res.users']._fusion_recompute_coexistence_group()
return result
def module_uninstall(self):
"""Recompute the coexistence group after the lower-level uninstall."""
result = super().module_uninstall()
self.env['res.users']._fusion_recompute_coexistence_group()
return result

View File

@@ -0,0 +1,27 @@
"""Coexistence group membership recomputation."""
from odoo import api, models
class ResUsers(models.Model):
_inherit = "res.users"
@api.model
def _fusion_recompute_coexistence_group(self):
"""Set group membership = all internal users iff Enterprise absent.
Called from ir.module.module.button_immediate_install / uninstall
overrides. Idempotent; safe to call multiple times.
"""
group = self.env.ref(
'fusion_accounting_core.group_fusion_show_when_enterprise_absent',
raise_if_not_found=False,
)
if not group:
return
enterprise_installed = self.env['ir.module.module']._fusion_is_enterprise_accounting_installed()
if enterprise_installed:
group.sudo().write({'user_ids': [(5, 0, 0)]})
else:
all_internal = self.sudo().search([('share', '=', False)])
group.sudo().write({'user_ids': [(6, 0, all_internal.ids)]})

View File

@@ -43,4 +43,10 @@
<record id="account.group_account_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('group_fusion_accounting_admin'))]"/>
</record>
<!-- Phase 1: dynamic coexistence group -->
<record id="group_fusion_show_when_enterprise_absent" model="res.groups">
<field name="name">Fusion: Show menus when Enterprise absent</field>
<field name="comment">Computed group. Membership: all internal users when no Enterprise accounting module is installed. Used to hide fusion sub-module menus that would conflict with Enterprise UIs.</field>
</record>
</odoo>

View File

@@ -1,3 +1,4 @@
from . import test_enterprise_detection
from . import test_shared_field_ownership
from . import test_shared_field_bank_statement
from . import test_coexistence_group

View File

@@ -0,0 +1,46 @@
from odoo.tests.common import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestCoexistenceGroup(TransactionCase):
"""The 'show when Enterprise absent' group must exist and have computed membership."""
def test_group_exists(self):
group = self.env.ref(
'fusion_accounting_core.group_fusion_show_when_enterprise_absent',
raise_if_not_found=False,
)
self.assertTrue(group, "Coexistence group must exist")
def test_membership_matches_enterprise_state(self):
"""A user is in the group iff Enterprise accounting is NOT installed.
We can't toggle Enterprise mid-test, so just assert the current state
matches: if Enterprise is installed, group should have 0 members; if
not, the group should include all internal users.
"""
group = self.env.ref(
'fusion_accounting_core.group_fusion_show_when_enterprise_absent'
)
enterprise_installed = self.env['ir.module.module']._fusion_is_enterprise_accounting_installed()
all_internal = self.env['res.users'].sudo().search([('share', '=', False)])
if enterprise_installed:
self.assertEqual(
len(group.user_ids), 0,
"Enterprise installed -> coexistence group should be empty",
)
else:
self.assertEqual(
set(group.user_ids.ids), set(all_internal.ids),
"Enterprise absent -> coexistence group should contain all internal users",
)
def test_recompute_method_exists(self):
"""The recompute helper must be callable on res.users."""
self.assertTrue(
callable(getattr(
self.env['res.users'],
'_fusion_recompute_coexistence_group',
None,
))
)