50
-
+
@@ -121,16 +129,20 @@
50
-
+
diff --git a/spp_hazard_programs/models/program.py b/spp_hazard_programs/models/program.py
index 4d48edc0e..0778b030d 100644
--- a/spp_hazard_programs/models/program.py
+++ b/spp_hazard_programs/models/program.py
@@ -84,8 +84,12 @@ def _compute_affected_registrant_count(self):
# Build domain for qualifying damage levels
damage_domain = rec._get_damage_level_domain()
- # Count unique registrants with qualifying impacts
- impacts = self.env["spp.hazard.impact"].search(
+ # Count unique registrants with qualifying impacts.
+ # sudo: emergency-program eligibility must consider all qualifying
+ # impacts regardless of the viewing user's hazard access; impact rows
+ # are not exposed, only the aggregate count.
+ impact_sudo = self.env["spp.hazard.impact"].sudo() # nosemgrep: odoo-sudo-without-context
+ impacts = impact_sudo.search(
[
("incident_id", "in", rec.target_incident_ids.ids),
("verification_status", "=", "verified"),
@@ -123,8 +127,11 @@ def get_emergency_eligible_registrants(self):
damage_domain = self._get_damage_level_domain()
- # Find qualifying impacts
- impacts = self.env["spp.hazard.impact"].search(
+ # Find qualifying impacts.
+ # sudo: eligibility must consider all qualifying impacts regardless of the
+ # viewing user's hazard access; only the resulting registrants are returned.
+ impact_sudo = self.env["spp.hazard.impact"].sudo() # nosemgrep: odoo-sudo-without-context
+ impacts = impact_sudo.search(
[
("incident_id", "in", self.target_incident_ids.ids),
("verification_status", "=", "verified"),
diff --git a/spp_hazard_programs/tests/__init__.py b/spp_hazard_programs/tests/__init__.py
index 66fbca0e0..669012f1b 100644
--- a/spp_hazard_programs/tests/__init__.py
+++ b/spp_hazard_programs/tests/__init__.py
@@ -1,3 +1,5 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
from . import test_hazard_programs
+
+from . import test_program_user_access
diff --git a/spp_hazard_programs/tests/test_program_user_access.py b/spp_hazard_programs/tests/test_program_user_access.py
new file mode 100644
index 000000000..bdfe7bed1
--- /dev/null
+++ b/spp_hazard_programs/tests/test_program_user_access.py
@@ -0,0 +1,49 @@
+# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
+"""Regression: emergency-eligibility logic must work for non-hazard program users.
+
+After tightening the hazard-impact ACL (removing the broad ``base.group_user``
+read grant), the program eligibility computes read ``spp.hazard.impact`` via
+``sudo`` so a program user without any hazard group can still use them. Without
+that sudo, ``affected_registrant_count`` / ``get_emergency_eligible_registrants``
+would raise ``AccessError`` for such users.
+"""
+
+from odoo import Command
+from odoo.tests import tagged
+
+from .common import HazardProgramsTestCase
+
+
+@tagged("post_install", "-at_install")
+class TestProgramUserHazardAccess(HazardProgramsTestCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.program_user = cls.env["res.users"].create(
+ {
+ "name": "Program Manager (no hazard group)",
+ "login": "program_mgr_no_hazard_test",
+ "group_ids": [
+ Command.link(cls.env.ref("base.group_user").id),
+ Command.link(cls.env.ref("spp_programs.group_programs_manager").id),
+ ],
+ }
+ )
+ cls.program.write(
+ {
+ "target_incident_ids": [Command.link(cls.incident_active.id)],
+ "qualifying_damage_levels": "any",
+ }
+ )
+
+ def test_program_user_without_hazard_group_can_compute_eligibility(self):
+ """A program user with no hazard group must still compute emergency
+ eligibility (the impact reads are sudo'd)."""
+ self.assertFalse(self.program_user.has_group("spp_hazard.group_hazard_read"))
+ program = self.program.with_user(self.program_user)
+ # Non-stored compute -> runs live as this user; reads impact via sudo.
+ self.assertEqual(program.affected_registrant_count, 2)
+ # Method -> runs live as this user; reads impact via sudo.
+ eligible = program.get_emergency_eligible_registrants()
+ self.assertIn(self.registrant_1, eligible)
+ self.assertIn(self.registrant_2, eligible)