diff --git a/spp_area/README.rst b/spp_area/README.rst
index bd8dfabb..d186bc1c 100644
--- a/spp_area/README.rst
+++ b/spp_area/README.rst
@@ -140,6 +140,17 @@ Dependencies
Changelog
=========
+19.0.2.0.1
+~~~~~~~~~~
+
+- fix(security): add a global ``ir.rule`` on ``res.partner`` that
+ filters registrants by ``area_id`` for users with ``center_area_ids``
+ set (OP#989). Replaces the limited ``search_read`` /
+ ``web_search_read`` override in ``models/registrant.py`` which missed
+ ``name_search`` (Many2one dropdowns), ``search_count``,
+ ``read_group``, and related-field traversal. The rule's conditional
+ domain is a no-op for users without center areas (global roles).
+
19.0.2.0.0
~~~~~~~~~~
diff --git a/spp_area/__manifest__.py b/spp_area/__manifest__.py
index 29ef826e..2a50339b 100644
--- a/spp_area/__manifest__.py
+++ b/spp_area/__manifest__.py
@@ -6,7 +6,7 @@
"name": "OpenSPP Area Management",
"summary": "Establishes direct associations between OpenSPP registrants, beneficiary groups, and their corresponding geographical administrative areas. It validates registrant-area linkages against official area types, ensuring data integrity and enabling targeted program delivery and analysis.",
"category": "OpenSPP/Core",
- "version": "19.0.2.0.0",
+ "version": "19.0.2.0.1",
"sequence": 1,
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
@@ -33,6 +33,7 @@
"security/privileges.xml",
"security/groups.xml",
"security/ir.model.access.csv",
+ "security/rules.xml",
"wizard/area_import_language_wizard_views.xml",
"views/area_base.xml",
"views/area_tag.xml",
diff --git a/spp_area/readme/HISTORY.md b/spp_area/readme/HISTORY.md
index 4aaf9afe..73e6d978 100644
--- a/spp_area/readme/HISTORY.md
+++ b/spp_area/readme/HISTORY.md
@@ -1,3 +1,7 @@
+### 19.0.2.0.1
+
+- fix(security): add a global `ir.rule` on `res.partner` that filters registrants by `area_id` for users with `center_area_ids` set (OP#989). Replaces the limited `search_read` / `web_search_read` override in `models/registrant.py` which missed `name_search` (Many2one dropdowns), `search_count`, `read_group`, and related-field traversal. The rule's conditional domain is a no-op for users without center areas (global roles).
+
### 19.0.2.0.0
- Initial migration to OpenSPP2
diff --git a/spp_area/security/rules.xml b/spp_area/security/rules.xml
new file mode 100644
index 00000000..b82bef5b
--- /dev/null
+++ b/spp_area/security/rules.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ Registrants: visible only within user's center areas
+
+ ['|', ('is_registrant', '=', False), ('area_id', 'child_of', user.center_area_ids.ids)] if user.center_area_ids else []
+
+
+
+
+
+
+
diff --git a/spp_area/static/description/index.html b/spp_area/static/description/index.html
index 131f7568..1505db84 100644
--- a/spp_area/static/description/index.html
+++ b/spp_area/static/description/index.html
@@ -537,6 +537,18 @@
+
19.0.2.0.1
+
+- fix(security): add a global ir.rule on res.partner that
+filters registrants by area_id for users with center_area_ids
+set (OP#989). Replaces the limited search_read /
+web_search_read override in models/registrant.py which missed
+name_search (Many2one dropdowns), search_count,
+read_group, and related-field traversal. The rule’s conditional
+domain is a no-op for users without center areas (global roles).
+
+
+
19.0.2.0.0
- Initial migration to OpenSPP2
diff --git a/spp_change_request_v2/README.rst b/spp_change_request_v2/README.rst
index 346551e7..ae0a3054 100644
--- a/spp_change_request_v2/README.rst
+++ b/spp_change_request_v2/README.rst
@@ -853,6 +853,18 @@ Before declaring a new CR type complete:
Changelog
=========
+19.0.2.0.5
+~~~~~~~~~~
+
+- fix(security): add a global ``ir.rule`` on ``spp.change.request`` that
+ filters by ``registrant_id.area_id`` against the user's
+ ``center_area_ids`` (OP#989 round-2). The earlier ``_prepare_domain``
+ override only caught ``search_read`` / ``web_search_read`` and missed
+ the registrant Many2one picker (which uses ``name_search`` →
+ ``_search``), so users could still select out-of-area registrants. The
+ conditional domain is a no-op for users with no center areas (global
+ roles).
+
19.0.2.0.3
~~~~~~~~~~
diff --git a/spp_change_request_v2/__manifest__.py b/spp_change_request_v2/__manifest__.py
index c565429a..1fae5f0d 100644
--- a/spp_change_request_v2/__manifest__.py
+++ b/spp_change_request_v2/__manifest__.py
@@ -1,6 +1,6 @@
{
"name": "OpenSPP Change Request V2",
- "version": "19.0.2.0.3",
+ "version": "19.0.2.0.5",
"sequence": 50,
"category": "OpenSPP",
"summary": "Configuration-driven change request system with UX improvements, conflict detection and duplicate prevention",
@@ -13,6 +13,7 @@
"mail",
"spp_base_common",
"spp_registry",
+ "spp_area",
"spp_security",
"spp_approval",
"spp_event_data",
@@ -24,6 +25,7 @@
"security/privileges.xml",
"security/groups.xml",
"security/rules.xml",
+ "security/area_filter_rules.xml",
"security/ir.model.access.csv",
# Views (loaded before data that references them)
"views/dms_file_views.xml",
diff --git a/spp_change_request_v2/readme/HISTORY.md b/spp_change_request_v2/readme/HISTORY.md
index 387d84da..1976b5fc 100644
--- a/spp_change_request_v2/readme/HISTORY.md
+++ b/spp_change_request_v2/readme/HISTORY.md
@@ -1,3 +1,7 @@
+### 19.0.2.0.5
+
+- fix(security): add a global `ir.rule` on `spp.change.request` that filters by `registrant_id.area_id` against the user's `center_area_ids` (OP#989 round-2). The earlier `_prepare_domain` override only caught `search_read` / `web_search_read` and missed the registrant Many2one picker (which uses `name_search` → `_search`), so users could still select out-of-area registrants. The conditional domain is a no-op for users with no center areas (global roles).
+
### 19.0.2.0.3
- fix: add HTML escaping to all computed Html fields with `sanitize=False` to prevent stored XSS (#50)
diff --git a/spp_change_request_v2/security/area_filter_rules.xml b/spp_change_request_v2/security/area_filter_rules.xml
new file mode 100644
index 00000000..1a352d5c
--- /dev/null
+++ b/spp_change_request_v2/security/area_filter_rules.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ Change Request: visible only within user's center areas
+
+ [('registrant_id.area_id', 'child_of', user.center_area_ids.ids)] if user.center_area_ids else []
+
+
+
+
+
+
+
diff --git a/spp_change_request_v2/static/description/index.html b/spp_change_request_v2/static/description/index.html
index f8bf3e1a..202a441b 100644
--- a/spp_change_request_v2/static/description/index.html
+++ b/spp_change_request_v2/static/description/index.html
@@ -1339,26 +1339,39 @@ Changelog
+
19.0.2.0.5
+
+- fix(security): add a global ir.rule on spp.change.request that
+filters by registrant_id.area_id against the user’s
+center_area_ids (OP#989 round-2). The earlier _prepare_domain
+override only caught search_read / web_search_read and missed
+the registrant Many2one picker (which uses name_search →
+_search), so users could still select out-of-area registrants. The
+conditional domain is a no-op for users with no center areas (global
+roles).
+
+
+
19.0.2.0.3
- fix: add HTML escaping to all computed Html fields with
sanitize=False to prevent stored XSS (#50)
-
+
19.0.2.0.2
- fix: fix batch approval wizard line deletion (#130)
-
+
19.0.2.0.1
- fix: skip field types before getattr and isolate detail prefetch
(#129)
-
+
19.0.2.0.0
- Initial migration to OpenSPP2
diff --git a/spp_programs/README.rst b/spp_programs/README.rst
index 0a60c52c..b0c117bf 100644
--- a/spp_programs/README.rst
+++ b/spp_programs/README.rst
@@ -254,6 +254,19 @@ Dependencies
Changelog
=========
+19.0.2.1.2
+~~~~~~~~~~
+
+- fix(security): add global ``ir.rule`` records on
+ ``spp.program.membership`` and ``spp.cycle.membership`` that filter by
+ ``partner_id.area_id`` against the user's ``center_area_ids`` (OP#989
+ round-2). The earlier Python ``_prepare_domain`` override on program
+ memberships only caught ``search_read`` / ``web_search_read`` and
+ missed counts (``search_count``, ``read_group``), dropdowns
+ (``name_search``), and related-field traversal — and cycle memberships
+ had no filter at all. Both rules use a conditional domain that's a
+ no-op for users with no center areas (global roles).
+
19.0.2.1.1
~~~~~~~~~~
diff --git a/spp_programs/__manifest__.py b/spp_programs/__manifest__.py
index 2b85f890..1e5f41a1 100644
--- a/spp_programs/__manifest__.py
+++ b/spp_programs/__manifest__.py
@@ -4,7 +4,7 @@
"name": "OpenSPP Programs",
"summary": "Manage programs, cycles, beneficiary enrollment, entitlements (cash and in-kind), payments, and fund tracking for social protection.",
"category": "OpenSPP/Core",
- "version": "19.0.2.1.1",
+ "version": "19.0.2.1.2",
"sequence": 1,
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
@@ -39,6 +39,7 @@
"security/program_security.xml",
"security/ir.model.access.csv",
"security/registrant_rule.xml",
+ "security/area_filter_rules.xml",
# Data files
"data/sequences.xml",
"data/queue_data.xml",
diff --git a/spp_programs/readme/HISTORY.md b/spp_programs/readme/HISTORY.md
index bac79104..f04b5b84 100644
--- a/spp_programs/readme/HISTORY.md
+++ b/spp_programs/readme/HISTORY.md
@@ -1,3 +1,7 @@
+### 19.0.2.1.2
+
+- fix(security): add global `ir.rule` records on `spp.program.membership` and `spp.cycle.membership` that filter by `partner_id.area_id` against the user's `center_area_ids` (OP#989 round-2). The earlier Python `_prepare_domain` override on program memberships only caught `search_read` / `web_search_read` and missed counts (`search_count`, `read_group`), dropdowns (`name_search`), and related-field traversal — and cycle memberships had no filter at all. Both rules use a conditional domain that's a no-op for users with no center areas (global roles).
+
### 19.0.2.1.1
- fix(views): apply `spp_registry.x2many_no_padding` widget to the Programs and Entitlements lists on registrant forms and to Program Membership inline lines — removes the four empty placeholder rows Odoo 19 inserts on inline list-in-form views (#943).
diff --git a/spp_programs/security/area_filter_rules.xml b/spp_programs/security/area_filter_rules.xml
new file mode 100644
index 00000000..abc34217
--- /dev/null
+++ b/spp_programs/security/area_filter_rules.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ Program Membership: visible only within user's center areas
+
+ [('partner_id.area_id', 'child_of', user.center_area_ids.ids)] if user.center_area_ids else []
+
+
+
+
+
+
+
+
+ Cycle Membership: visible only within user's center areas
+
+ [('partner_id.area_id', 'child_of', user.center_area_ids.ids)] if user.center_area_ids else []
+
+
+
+
+
+
+
diff --git a/spp_programs/static/description/index.html b/spp_programs/static/description/index.html
index a2c4be96..6ca70d87 100644
--- a/spp_programs/static/description/index.html
+++ b/spp_programs/static/description/index.html
@@ -658,6 +658,20 @@
+
19.0.2.1.2
+
+- fix(security): add global ir.rule records on
+spp.program.membership and spp.cycle.membership that filter by
+partner_id.area_id against the user’s center_area_ids (OP#989
+round-2). The earlier Python _prepare_domain override on program
+memberships only caught search_read / web_search_read and
+missed counts (search_count, read_group), dropdowns
+(name_search), and related-field traversal — and cycle memberships
+had no filter at all. Both rules use a conditional domain that’s a
+no-op for users with no center areas (global roles).
+
+
+
19.0.2.1.1
- fix(views): apply spp_registry.x2many_no_padding widget to the
@@ -666,7 +680,7 @@
19.0.2.1.1
19 inserts on inline list-in-form views (#943).
-
+
19.0.2.0.11
- Fix TypeError: 'NoneType' object is not iterable when clicking
@@ -677,7 +691,7 @@
19.0.2.0.11
omit the state filter instead of crashing on tuple(None)
-
+
19.0.2.0.10
- Increase parallel-safe channel limits (cycle, eligibility_manager,
@@ -690,7 +704,7 @@
19.0.2.0.10
submission on double-click
-
+
19.0.2.0.9
- Add context flags (skip_registrant_statistics,
@@ -703,7 +717,7 @@
19.0.2.0.9
_compute_has_members
-
+
19.0.2.0.8
- Replace OFFSET pagination with NTILE-based ID-range batching in all
@@ -714,7 +728,7 @@
19.0.2.0.8
program and cycle
-
+
19.0.2.0.7
- Bulk membership creation using raw SQL INSERT ON CONFLICT DO NOTHING
@@ -723,7 +737,7 @@
19.0.2.0.7
_add_beneficiaries with bulk SQL path
-
+
19.0.2.0.6
- Remove unused entitlement_base_model.py (dead code, never imported)
@@ -732,34 +746,34 @@ 19.0.2.0.6
payment, and fund tests (172 → 492 tests)
-
+
19.0.2.0.5
- Batch create entitlements and payments instead of one-by-one ORM
creates
-
+
19.0.2.0.4
- Fetch fund balance once per approval batch instead of per entitlement
-
+
19.0.2.0.3
- Replace cycle computed fields (total_amount, entitlements_count,
approval flags) with SQL aggregation queries
-
+
19.0.2.0.2
- Add composite indexes for frequent query patterns on entitlements and
program memberships
-
+
19.0.2.0.1
- Replace Python-level uniqueness checks with SQL UNIQUE constraints for
@@ -768,7 +782,7 @@
19.0.2.0.1
constraint creation
-
+
19.0.2.0.0
- Initial migration to OpenSPP2