feat(plating-quality): split Manager vs Quality Manager permissions

Phase C of permissions overhaul (spec Section 2.C).

Manager keeps reactive Quality (NCR/Hold/Check/Cert/RMA — already gated
via Phase B sweep). QM gains exclusive write/create/unlink on strategic
Quality records:

- fusion.plating.capa: Manager → read-only (1,0,0,0); QM → full
- fusion.plating.audit: same split (if model present)
- fp.approved.vendor.list: same split (if model present)
- fusion.plating.customer.spec: same split
- Doc Control models: same split

Plus FAIR/Nadcap cert restriction via two new ir.rule records on
fp.certificate:
- Manager: write/create/unlink on certs where cert_type NOT in
  ('fair', 'nadcap')
- QM: write/create/unlink on all certs (overrides via OR within group)
- Read access unchanged for both (perm_read=False on the rules)

Tests in fusion_plating/tests/test_quality_split.py verify each side
of the split. Models that may not exist on all DBs (audit, AVL) use
skipTest gracefully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-24 01:23:32 -04:00
parent f4e1f9d218
commit 84ed406c8e
7 changed files with 131 additions and 8 deletions

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating',
'version': '19.0.21.0.1',
'version': '19.0.21.0.2',
'category': 'Manufacturing/Plating',
'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.',
'description': """

View File

@@ -5,3 +5,4 @@ from . import test_fp_job_step_state_machine
from . import test_simple_recipe_flatten
from . import test_role_groups
from . import test_acl_migration
from . import test_quality_split

View File

@@ -0,0 +1,90 @@
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import AccessError
@tagged('-at_install', 'post_install', 'fp_perms')
class TestQualitySplit(TransactionCase):
"""Section 2.C of spec: Manager handles reactive Quality;
QM exclusively owns CAPA close, Audit, AVL, Customer Spec, FAIR/Nadcap signing."""
def setUp(self):
super().setUp()
Users = self.env['res.users'].with_context(no_reset_password=True)
self.u_mgr = Users.create({
'login': 'qsplit_mgr', 'name': 'QSplit Mgr',
'email': 'qsplit_mgr@example.com',
'groups_id': [(6, 0, [self.env.ref('fusion_plating.group_fp_manager').id])],
})
self.u_qm = Users.create({
'login': 'qsplit_qm', 'name': 'QSplit QM',
'email': 'qsplit_qm@example.com',
'groups_id': [(6, 0, [self.env.ref('fusion_plating.group_fp_quality_manager').id])],
})
# CAPA: Manager read-only, QM full
def test_manager_can_read_capa(self):
self.env['fusion.plating.capa'].with_user(self.u_mgr).check_access_rights('read')
def test_manager_cannot_write_capa(self):
with self.assertRaises(AccessError):
self.env['fusion.plating.capa'].with_user(self.u_mgr).check_access_rights('write')
def test_manager_cannot_create_capa(self):
with self.assertRaises(AccessError):
self.env['fusion.plating.capa'].with_user(self.u_mgr).check_access_rights('create')
def test_qm_can_write_capa(self):
self.env['fusion.plating.capa'].with_user(self.u_qm).check_access_rights('write')
# Audit: Manager read-only, QM full
def test_manager_can_read_audit(self):
Audit = self.env.get('fusion.plating.audit')
if not Audit:
self.skipTest('fusion.plating.audit model not available')
Audit.with_user(self.u_mgr).check_access_rights('read')
def test_manager_cannot_write_audit(self):
Audit = self.env.get('fusion.plating.audit')
if not Audit:
self.skipTest('fusion.plating.audit model not available')
with self.assertRaises(AccessError):
Audit.with_user(self.u_mgr).check_access_rights('write')
def test_qm_can_write_audit(self):
Audit = self.env.get('fusion.plating.audit')
if not Audit:
self.skipTest('fusion.plating.audit model not available')
Audit.with_user(self.u_qm).check_access_rights('write')
# NCR: Manager full
def test_manager_can_create_ncr(self):
self.env['fusion.plating.ncr'].with_user(self.u_mgr).check_access_rights('create')
def test_manager_can_write_ncr(self):
self.env['fusion.plating.ncr'].with_user(self.u_mgr).check_access_rights('write')
# Hold: Manager full
def test_manager_can_create_hold(self):
self.env['fusion.plating.quality.hold'].with_user(self.u_mgr).check_access_rights('create')
# AVL: Manager read-only, QM full
def test_manager_can_read_avl(self):
Avl = self.env.get('fp.approved.vendor.list')
if not Avl:
self.skipTest('fp.approved.vendor.list model not available')
Avl.with_user(self.u_mgr).check_access_rights('read')
def test_manager_cannot_write_avl(self):
Avl = self.env.get('fp.approved.vendor.list')
if not Avl:
self.skipTest('fp.approved.vendor.list model not available')
with self.assertRaises(AccessError):
Avl.with_user(self.u_mgr).check_access_rights('write')
# Customer Spec: Manager read-only, QM full
def test_manager_can_read_customer_spec(self):
self.env['fusion.plating.customer.spec'].with_user(self.u_mgr).check_access_rights('read')
def test_manager_cannot_write_customer_spec(self):
with self.assertRaises(AccessError):
self.env['fusion.plating.customer.spec'].with_user(self.u_mgr).check_access_rights('write')