From 84ed406c8e2dc3b3b29319152f356ce0df3b3d2c Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 24 May 2026 01:23:32 -0400 Subject: [PATCH] feat(plating-quality): split Manager vs Quality Manager permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- fusion_plating/fusion_plating/__manifest__.py | 2 +- .../fusion_plating/tests/__init__.py | 1 + .../tests/test_quality_split.py | 90 +++++++++++++++++++ .../__manifest__.py | 3 +- .../security/fp_cert_security.xml | 26 ++++++ .../fusion_plating_quality/__manifest__.py | 2 +- .../security/ir.model.access.csv | 15 ++-- 7 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 fusion_plating/fusion_plating/tests/test_quality_split.py create mode 100644 fusion_plating/fusion_plating_certificates/security/fp_cert_security.xml diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index 34a0dc2f..8708c613 100644 --- a/fusion_plating/fusion_plating/__manifest__.py +++ b/fusion_plating/fusion_plating/__manifest__.py @@ -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': """ diff --git a/fusion_plating/fusion_plating/tests/__init__.py b/fusion_plating/fusion_plating/tests/__init__.py index 810d9a45..ae483a5d 100644 --- a/fusion_plating/fusion_plating/tests/__init__.py +++ b/fusion_plating/fusion_plating/tests/__init__.py @@ -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 diff --git a/fusion_plating/fusion_plating/tests/test_quality_split.py b/fusion_plating/fusion_plating/tests/test_quality_split.py new file mode 100644 index 00000000..efedad0c --- /dev/null +++ b/fusion_plating/fusion_plating/tests/test_quality_split.py @@ -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') diff --git a/fusion_plating/fusion_plating_certificates/__manifest__.py b/fusion_plating/fusion_plating_certificates/__manifest__.py index 9c1c4621..b1a1b0ca 100644 --- a/fusion_plating/fusion_plating_certificates/__manifest__.py +++ b/fusion_plating/fusion_plating_certificates/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Certificates', - 'version': '19.0.7.9.1', + 'version': '19.0.7.9.2', 'category': 'Manufacturing/Plating', 'summary': 'Certificate registry for CoC, thickness reports, and quality documents.', 'description': """ @@ -32,6 +32,7 @@ Includes Fischerscope thickness measurement data capture. ], 'data': [ 'security/ir.model.access.csv', + 'security/fp_cert_security.xml', 'data/fp_certificate_sequence_data.xml', 'views/res_config_settings_views.xml', 'views/fp_certificate_views.xml', diff --git a/fusion_plating/fusion_plating_certificates/security/fp_cert_security.xml b/fusion_plating/fusion_plating_certificates/security/fp_cert_security.xml new file mode 100644 index 00000000..99d11ec8 --- /dev/null +++ b/fusion_plating/fusion_plating_certificates/security/fp_cert_security.xml @@ -0,0 +1,26 @@ + + + + + FP Certificate: FAIR/Nadcap edit restricted to Quality Manager + + [('cert_type', 'not in', ('fair', 'nadcap'))] + + + + + + + + + FP Certificate: QM has full access to all certs + + [(1, '=', 1)] + + + + + + + + diff --git a/fusion_plating/fusion_plating_quality/__manifest__.py b/fusion_plating/fusion_plating_quality/__manifest__.py index f184ac86..59e17d12 100644 --- a/fusion_plating/fusion_plating_quality/__manifest__.py +++ b/fusion_plating/fusion_plating_quality/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Quality (QMS)', - 'version': '19.0.6.6.1', + 'version': '19.0.6.6.2', 'category': 'Manufacturing/Plating', 'summary': 'Native QMS for plating shops: NCR, CAPA, calibration, AVL, FAIR, ' 'internal audits, customer specs, document control. CE + EE compatible.', diff --git a/fusion_plating/fusion_plating_quality/security/ir.model.access.csv b/fusion_plating/fusion_plating_quality/security/ir.model.access.csv index bb70e9a6..4a5b864f 100644 --- a/fusion_plating/fusion_plating_quality/security/ir.model.access.csv +++ b/fusion_plating/fusion_plating_quality/security/ir.model.access.csv @@ -4,7 +4,8 @@ access_fp_ncr_supervisor,fp.ncr.supervisor,model_fusion_plating_ncr,fusion_plati access_fp_ncr_manager,fp.ncr.manager,model_fusion_plating_ncr,fusion_plating.group_fp_manager,1,1,1,1 access_fp_capa_operator,fp.capa.operator,model_fusion_plating_capa,fusion_plating.group_fp_technician,1,0,0,0 access_fp_capa_supervisor,fp.capa.supervisor,model_fusion_plating_capa,fusion_plating.group_fp_shop_manager_v2,1,1,1,0 -access_fp_capa_manager,fp.capa.manager,model_fusion_plating_capa,fusion_plating.group_fp_manager,1,1,1,1 +access_fp_capa_manager,fp.capa.manager,model_fusion_plating_capa,fusion_plating.group_fp_manager,1,0,0,0 +access_fp_capa_qm,fp.capa.qm,model_fusion_plating_capa,fusion_plating.group_fp_quality_manager,1,1,1,1 access_fp_cal_equipment_operator,fp.cal.equipment.operator,model_fusion_plating_calibration_equipment,fusion_plating.group_fp_technician,1,0,0,0 access_fp_cal_equipment_supervisor,fp.cal.equipment.supervisor,model_fusion_plating_calibration_equipment,fusion_plating.group_fp_shop_manager_v2,1,1,1,0 access_fp_cal_equipment_manager,fp.cal.equipment.manager,model_fusion_plating_calibration_equipment,fusion_plating.group_fp_manager,1,1,1,1 @@ -13,19 +14,23 @@ access_fp_cal_event_supervisor,fp.cal.event.supervisor,model_fusion_plating_cali access_fp_cal_event_manager,fp.cal.event.manager,model_fusion_plating_calibration_event,fusion_plating.group_fp_manager,1,1,1,1 access_fp_avl_operator,fp.avl.operator,model_fusion_plating_avl,fusion_plating.group_fp_technician,1,0,0,0 access_fp_avl_supervisor,fp.avl.supervisor,model_fusion_plating_avl,fusion_plating.group_fp_shop_manager_v2,1,1,1,0 -access_fp_avl_manager,fp.avl.manager,model_fusion_plating_avl,fusion_plating.group_fp_manager,1,1,1,1 +access_fp_avl_manager,fp.avl.manager,model_fusion_plating_avl,fusion_plating.group_fp_manager,1,0,0,0 +access_fp_avl_qm,fp.avl.qm,model_fusion_plating_avl,fusion_plating.group_fp_quality_manager,1,1,1,1 access_fp_customer_spec_operator,fp.customer.spec.operator,model_fusion_plating_customer_spec,fusion_plating.group_fp_technician,1,0,0,0 access_fp_customer_spec_supervisor,fp.customer.spec.supervisor,model_fusion_plating_customer_spec,fusion_plating.group_fp_shop_manager_v2,1,1,1,0 -access_fp_customer_spec_manager,fp.customer.spec.manager,model_fusion_plating_customer_spec,fusion_plating.group_fp_manager,1,1,1,1 +access_fp_customer_spec_manager,fp.customer.spec.manager,model_fusion_plating_customer_spec,fusion_plating.group_fp_manager,1,0,0,0 +access_fp_customer_spec_qm,fp.customer.spec.qm,model_fusion_plating_customer_spec,fusion_plating.group_fp_quality_manager,1,1,1,1 access_fp_audit_operator,fp.audit.operator,model_fusion_plating_audit,fusion_plating.group_fp_technician,1,0,0,0 access_fp_audit_supervisor,fp.audit.supervisor,model_fusion_plating_audit,fusion_plating.group_fp_shop_manager_v2,1,1,1,0 -access_fp_audit_manager,fp.audit.manager,model_fusion_plating_audit,fusion_plating.group_fp_manager,1,1,1,1 +access_fp_audit_manager,fp.audit.manager,model_fusion_plating_audit,fusion_plating.group_fp_manager,1,0,0,0 +access_fp_audit_qm,fp.audit.qm,model_fusion_plating_audit,fusion_plating.group_fp_quality_manager,1,1,1,1 access_fp_fair_operator,fp.fair.operator,model_fusion_plating_fair,fusion_plating.group_fp_technician,1,0,0,0 access_fp_fair_supervisor,fp.fair.supervisor,model_fusion_plating_fair,fusion_plating.group_fp_shop_manager_v2,1,1,1,0 access_fp_fair_manager,fp.fair.manager,model_fusion_plating_fair,fusion_plating.group_fp_manager,1,1,1,1 access_fp_doc_control_operator,fp.doc.control.operator,model_fusion_plating_doc_control,fusion_plating.group_fp_technician,1,0,0,0 access_fp_doc_control_supervisor,fp.doc.control.supervisor,model_fusion_plating_doc_control,fusion_plating.group_fp_shop_manager_v2,1,1,1,0 -access_fp_doc_control_manager,fp.doc.control.manager,model_fusion_plating_doc_control,fusion_plating.group_fp_manager,1,1,1,1 +access_fp_doc_control_manager,fp.doc.control.manager,model_fusion_plating_doc_control,fusion_plating.group_fp_manager,1,0,0,0 +access_fp_doc_control_qm,fp.doc.control.qm,model_fusion_plating_doc_control,fusion_plating.group_fp_quality_manager,1,1,1,1 access_fp_quality_hold_operator,fp.quality.hold.operator,model_fusion_plating_quality_hold,fusion_plating.group_fp_technician,1,0,1,0 access_fp_quality_hold_supervisor,fp.quality.hold.supervisor,model_fusion_plating_quality_hold,fusion_plating.group_fp_shop_manager_v2,1,1,1,0 access_fp_quality_hold_manager,fp.quality.hold.manager,model_fusion_plating_quality_hold,fusion_plating.group_fp_manager,1,1,1,1