diff --git a/spp_studio/README.rst b/spp_studio/README.rst index 0fe4bef88..fcc7b2d00 100644 --- a/spp_studio/README.rst +++ b/spp_studio/README.rst @@ -149,6 +149,38 @@ Dependencies Changelog ========= +19.0.2.0.2 +~~~~~~~~~~ + +- fix(data): repair the 24 shipped logic-pack filter items whose CEL + expressions referenced registrant fields that exist in no module — in + several cases through catalogued variables whose ``source_field`` is + dangling (#446) — so they could never translate or evaluate (#431). 1 + item is fixed properly: Institutional Residence Exclusion keeps its + expression, backed by a new ``in_institutional_care`` standard + variable over the existing ``spp_registry`` field of the same name + (both scoped to individual context). 3 items are rewritten to the + stricter working part of their expression (OVC Child Age Eligibility, + OVC Vulnerable Household Check, Public Works Poverty Status Check). + The other 20 are removed because no working near-equivalent exists, + or, for GMI Residency Requirement, because the only surviving + sub-expression would be more permissive than the shipped rule + (Disability Status Verification, OVC Orphan Status Check, OVC School + Enrollment Requirement, Social Pension No Formal Pension Check, GMI + Employment Barriers Check and Residency Requirement, Public Works + Physical Work Capability and Seasonal Availability, CCT Health + Conditionality Compliance, Geographic Limited Service Access, and 10 + Exclusion Criteria items: Government Employee, Formal Sector, Vehicle + Ownership, Business Ownership, Housing Quality, Pension Receipt, + Duplicate Program, Income Tax Payer, Bank Balance, Livestock + Ownership). Pack data is ``noupdate``, so a migration applies the same + removals/rewrites to existing databases; every migration write is + guarded on the item still carrying the known-broken shipped + expression, so locally repaired items are left untouched. Logic + already installed from removed items (``installed_logic_id``) is + deliberately not deleted — once installed it is the deployment's own + data. + 19.0.2.0.1 ~~~~~~~~~~ diff --git a/spp_studio/__manifest__.py b/spp_studio/__manifest__.py index dab2e9e6d..b6cda1e29 100644 --- a/spp_studio/__manifest__.py +++ b/spp_studio/__manifest__.py @@ -1,6 +1,6 @@ { "name": "OpenSPP Studio", - "version": "19.0.2.0.1", + "version": "19.0.2.0.2", "category": "OpenSPP/Configuration", "summary": "No-code customization interface for OpenSPP", "author": "OpenSPP.org", diff --git a/spp_studio/data/packs/cct_program.xml b/spp_studio/data/packs/cct_program.xml index a206c838f..b5b1712e0 100644 --- a/spp_studio/data/packs/cct_program.xml +++ b/spp_studio/data/packs/cct_program.xml @@ -58,21 +58,6 @@ 30 - - - - Health Conditionality Compliance - group - Children under 5 meeting health checkup and vaccination requirements. - filter - {"cel_expression": "children_0_5 == 0 || (health_checkups_completed && vaccinations_current)"} - 40 - - diff --git a/spp_studio/data/packs/disability_assistance.xml b/spp_studio/data/packs/disability_assistance.xml index e2d6de08c..466f9991f 100644 --- a/spp_studio/data/packs/disability_assistance.xml +++ b/spp_studio/data/packs/disability_assistance.xml @@ -13,21 +13,6 @@ available - - - - Disability Status Verification - individual - Verifies certified disability status from medical assessment. - filter - {"cel_expression": "has_disability && disability_certified"} - 10 - - diff --git a/spp_studio/data/packs/exclusion_criteria.xml b/spp_studio/data/packs/exclusion_criteria.xml index 10e627951..a0d1ccaef 100644 --- a/spp_studio/data/packs/exclusion_criteria.xml +++ b/spp_studio/data/packs/exclusion_criteria.xml @@ -13,49 +13,6 @@ available - - - - Government Employee Exclusion - group - Exclude households with government/public sector employees. - filter - {"cel_expression": "!has_government_employee"} - 10 - - - - - - Formal Sector Exclusion - group - Exclude households with formal sector employment (for informal sector targeting). - filter - {"cel_expression": "!has_formal_employment || formal_income < 5000"} - 20 - - - - - - Vehicle Ownership Exclusion - group - Exclude households owning motorized vehicles (cars, motorcycles above threshold). - filter - {"cel_expression": "!owns_car && motorcycle_count <= 1"} - 30 - - @@ -69,112 +26,11 @@ 40 - - - - Business Ownership Exclusion - both - Exclude households owning registered businesses above threshold. - filter - {"cel_expression": "!owns_registered_business || business_revenue < 50000"} - 50 - - - - - - Housing Quality Exclusion - both - Exclude households with high-quality/permanent housing (proxy for wealth). - filter - {"cel_expression": "housing_quality_score <= 3 || !has_permanent_structure"} - 60 - - - - - - Pension Receipt Exclusion - both - Exclude individuals already receiving contributory pension. - filter - {"cel_expression": "!receives_contributory_pension || pension_amount < 1000"} - 70 - - - - - - Duplicate Program Exclusion - both - Exclude beneficiaries already enrolled in similar programs. - filter - {"cel_expression": "!enrolled_in_similar_program"} - 80 - - - - - - Income Tax Payer Exclusion - both - Exclude households paying income tax (above tax threshold). - filter - {"cel_expression": "!pays_income_tax"} - 90 - - - - - - Bank Balance Exclusion - both - Exclude households with bank balances above threshold. - filter - {"cel_expression": "total_bank_balance <= 50000"} - 100 - - - - - - Livestock Ownership Exclusion - both - Exclude households with livestock above threshold (in Tropical Livestock Units). - filter - {"cel_expression": "livestock_tlu <= 5"} - 110 - - Institutional Residence Exclusion - both + individual Exclude individuals residing in institutions (prisons, care homes with full support). diff --git a/spp_studio/data/packs/geographic_targeting.xml b/spp_studio/data/packs/geographic_targeting.xml index cbaba22db..b55dcd57c 100644 --- a/spp_studio/data/packs/geographic_targeting.xml +++ b/spp_studio/data/packs/geographic_targeting.xml @@ -79,21 +79,6 @@ 40 - - - - Limited Service Access - both - Location has limited access to basic services (health, education, markets). - filter - {"cel_expression": "distance_to_health_facility > 10 || distance_to_school > 5"} - 50 - - diff --git a/spp_studio/data/packs/guaranteed_minimum_income.xml b/spp_studio/data/packs/guaranteed_minimum_income.xml index 8c5818070..8faf95e2d 100644 --- a/spp_studio/data/packs/guaranteed_minimum_income.xml +++ b/spp_studio/data/packs/guaranteed_minimum_income.xml @@ -28,21 +28,6 @@ 10 - - - - Employment Barriers Check - group - Working-age members have legitimate barriers to employment. - filter - {"cel_expression": "has_disabled_member || has_caregiver_responsibilities || is_single_parent || no_working_age_members"} - 20 - - @@ -58,21 +43,6 @@ 30 - - - - Residency Requirement - individual - Legal residency and minimum duration requirement. - filter - {"cel_expression": "is_legal_resident && residency_months >= 12"} - 40 - - diff --git a/spp_studio/data/packs/ovc_support.xml b/spp_studio/data/packs/ovc_support.xml index b56437056..da0b3a58c 100644 --- a/spp_studio/data/packs/ovc_support.xml +++ b/spp_studio/data/packs/ovc_support.xml @@ -13,33 +13,14 @@ available - - - - Orphan Status Check - individual - Child has lost one or both parents (single or double orphan). - filter - {"cel_expression": "is_orphan || (!has_mother && !has_father)"} - 10 - - Child Age Eligibility individual - Child must be under child_age_limit, or under 25 if in full-time education. + Child must be under child_age_limit. filter - {"cel_expression": "age < child_age_limit || (age < 25 && is_in_education)"} + {"cel_expression": "age < child_age_limit"} 20 @@ -50,29 +31,14 @@ group Household meets vulnerability criteria (elderly-headed, child-headed, or skipped-generation). + >Household meets vulnerability criteria (elderly-headed or child-headed). filter {"cel_expression": "is_elderly_headed || is_child_headed || is_skipped_generation || has_chronically_ill_head"} + >{"cel_expression": "is_elderly_headed || is_child_headed"} 30 - - - - School Enrollment Requirement - individual - School-age children must be enrolled in education (soft conditionality). - filter - {"cel_expression": "age < 6 || is_enrolled_in_school || has_enrollment_exemption"} - 40 - - diff --git a/spp_studio/data/packs/public_works.xml b/spp_studio/data/packs/public_works.xml index 0f4dab33b..697118876 100644 --- a/spp_studio/data/packs/public_works.xml +++ b/spp_studio/data/packs/public_works.xml @@ -28,21 +28,6 @@ 10 - - - - Physical Work Capability - individual - Beneficiary is physically able to perform manual labor (with accommodations for light duty). - filter - {"cel_expression": "can_perform_manual_labor || eligible_for_light_duty"} - 20 - - @@ -69,23 +54,10 @@ filter {"cel_expression": "per_capita_income <= poverty_threshold || is_food_insecure"} + >{"cel_expression": "per_capita_income <= poverty_threshold"} 40 - - - - Seasonal Availability - both - Program operates during lean/off-farm season. - filter - {"cel_expression": "is_lean_season || !has_active_farm_work"} - 50 - - diff --git a/spp_studio/data/packs/social_pension.xml b/spp_studio/data/packs/social_pension.xml index e0268fb45..b9f4a0b95 100644 --- a/spp_studio/data/packs/social_pension.xml +++ b/spp_studio/data/packs/social_pension.xml @@ -26,24 +26,6 @@ 10 - - - - No Formal Pension Check - individual - Excludes individuals already receiving formal/contributory pension benefits. - filter - {"cel_expression": "!has_formal_pension || formal_pension_amount == 0"} - 20 - - diff --git a/spp_studio/data/standard_variables.xml b/spp_studio/data/standard_variables.xml index 276e3892a..5566cd238 100644 --- a/spp_studio/data/standard_variables.xml +++ b/spp_studio/data/standard_variables.xml @@ -697,6 +697,22 @@ True + + in_institutional_care + In Institutional Care + Whether the individual currently resides in institutional care (e.g., nursing home, care facility, orphanage) + + boolean + field + res.partner + in_institutional_care + in_institutional_care + individual + True + + diff --git a/spp_studio/migrations/19.0.2.0.2/post-migration.py b/spp_studio/migrations/19.0.2.0.2/post-migration.py new file mode 100644 index 000000000..985e58284 --- /dev/null +++ b/spp_studio/migrations/19.0.2.0.2/post-migration.py @@ -0,0 +1,158 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +"""Fix shipped logic-pack filter items whose CEL expressions cannot evaluate (#431). + +24 ``spp.studio.pack.item`` records shipped with filter expressions referencing +registrant fields that exist in no module (in some cases through catalogued +``spp.cel.variable`` records whose ``source_field`` is dangling - see #446), +so installing them produced logic that could never translate or run. The pack +data files are ``noupdate="1"``, so existing databases keep the broken records +after the XML is corrected - this migration applies the same fix directly: + +- items whose expression has no working near-equivalent are deleted; +- items where a meaningful stricter sub-expression survives are rewritten to + it (matching the corrected pack data files). + +Every write is guarded on the item still carrying the known-broken shipped +expression: ``noupdate`` exists so local changes survive upgrades, and a +deployment may have repaired an item itself (e.g. by defining the missing +variables), so locally-modified items are left untouched and logged. + +Two flagged items need special handling: + +- Institutional Residence Exclusion keeps its shipped expression - it only + lacked a variable definition, and the new ``in_institutional_care`` standard + variable (over the existing ``spp_registry`` field) is created by the + regular data load, which is not ``noupdate`` for ``standard_variables.xml``. + The variable applies to individuals, so the item's shipped + ``context_type="both"`` is narrowed to ``individual`` here. +- Logic already installed FROM a removed item (``installed_logic_id`` -> + ``spp.cel.expression``) is deliberately NOT deleted: once installed it is + the deployment's own data, possibly referenced by programs. Those + expressions were never evaluable, and cleaning them up is left to the + deployment. +""" + +import json +import logging + +from odoo import SUPERUSER_ID, api + +_logger = logging.getLogger(__name__) + +# xmlid -> known-broken shipped cel_expression (deleted only if still intact) +REMOVED_ITEMS = { + "spp_studio.pack_disability_item_status_check": "has_disability && disability_certified", + "spp_studio.pack_ovc_item_orphan_status": "is_orphan || (!has_mother && !has_father)", + "spp_studio.pack_ovc_item_school_enrollment": ("age < 6 || is_enrolled_in_school || has_enrollment_exemption"), + "spp_studio.pack_social_pension_item_no_formal_pension": ("!has_formal_pension || formal_pension_amount == 0"), + "spp_studio.pack_gmi_item_work_barriers": ( + "has_disabled_member || has_caregiver_responsibilities || is_single_parent || no_working_age_members" + ), + "spp_studio.pack_gmi_item_residency": "is_legal_resident && residency_months >= 12", + "spp_studio.pack_pw_item_physical": "can_perform_manual_labor || eligible_for_light_duty", + "spp_studio.pack_pw_item_seasonal": "is_lean_season || !has_active_farm_work", + "spp_studio.pack_cct_item_health_compliance": ( + "children_0_5 == 0 || (health_checkups_completed && vaccinations_current)" + ), + "spp_studio.pack_geo_item_service_access": ("distance_to_health_facility > 10 || distance_to_school > 5"), + "spp_studio.pack_excl_item_govt_employee": "!has_government_employee", + "spp_studio.pack_excl_item_formal_sector": "!has_formal_employment || formal_income < 5000", + "spp_studio.pack_excl_item_vehicle": "!owns_car && motorcycle_count <= 1", + "spp_studio.pack_excl_item_business": "!owns_registered_business || business_revenue < 50000", + "spp_studio.pack_excl_item_housing": "housing_quality_score <= 3 || !has_permanent_structure", + "spp_studio.pack_excl_item_pension": "!receives_contributory_pension || pension_amount < 1000", + "spp_studio.pack_excl_item_other_programs": "!enrolled_in_similar_program", + "spp_studio.pack_excl_item_tax": "!pays_income_tax", + "spp_studio.pack_excl_item_bank_balance": "total_bank_balance <= 50000", + "spp_studio.pack_excl_item_livestock": "livestock_tlu <= 5", +} + +# xmlid -> (known-broken shipped expression, new expression, new description) +REWRITTEN_ITEMS = { + "spp_studio.pack_ovc_item_child_age": ( + "age < child_age_limit || (age < 25 && is_in_education)", + "age < child_age_limit", + "Child must be under child_age_limit.", + ), + "spp_studio.pack_ovc_item_vulnerable_hh": ( + "is_elderly_headed || is_child_headed || is_skipped_generation || has_chronically_ill_head", + "is_elderly_headed || is_child_headed", + "Household meets vulnerability criteria (elderly-headed or child-headed).", + ), + "spp_studio.pack_pw_item_poverty": ( + "per_capita_income <= poverty_threshold || is_food_insecure", + "per_capita_income <= poverty_threshold", + "Household meets poverty criteria for public works targeting.", + ), +} + + +def _current_expression(item): + try: + return json.loads(item.logic_data or "{}").get("cel_expression") + except (ValueError, TypeError): + return None + + +def migrate(cr, version): + if not version: + return + + env = api.Environment(cr, SUPERUSER_ID, {}) + + removed = 0 + for xmlid, shipped_expr in REMOVED_ITEMS.items(): + item = env.ref(xmlid, raise_if_not_found=False) + if not item: + continue + if _current_expression(item) != shipped_expr: + _logger.warning( + "spp_studio 19.0.2.0.2: pack item %s was modified locally; " + "leaving it in place instead of removing it (#431)", + xmlid, + ) + continue + item.unlink() + removed += 1 + + rewritten = 0 + for xmlid, (shipped_expr, new_expr, new_desc) in REWRITTEN_ITEMS.items(): + item = env.ref(xmlid, raise_if_not_found=False) + if not item: + continue + if _current_expression(item) != shipped_expr: + _logger.warning( + "spp_studio 19.0.2.0.2: pack item %s was modified locally; " + "leaving it in place instead of rewriting it (#431)", + xmlid, + ) + continue + try: + logic_data = json.loads(item.logic_data or "{}") + except (ValueError, TypeError): + logic_data = {} + logic_data["cel_expression"] = new_expr + item.write( + { + "logic_data": json.dumps(logic_data), + "description": new_desc, + } + ) + rewritten += 1 + + # Institutional Residence Exclusion: the new in_institutional_care + # variable applies to individuals, so narrow the item's shipped + # context_type accordingly (guarded like the writes above). + institutional = env.ref("spp_studio.pack_excl_item_institutional", raise_if_not_found=False) + if ( + institutional + and institutional.context_type == "both" + and _current_expression(institutional) == "!in_institutional_care" + ): + institutional.write({"context_type": "individual"}) + + _logger.info( + "spp_studio 19.0.2.0.2: removed %d and rewrote %d shipped pack items with non-evaluable CEL expressions (#431)", + removed, + rewritten, + ) diff --git a/spp_studio/readme/HISTORY.md b/spp_studio/readme/HISTORY.md index 1354baa34..633b3e090 100644 --- a/spp_studio/readme/HISTORY.md +++ b/spp_studio/readme/HISTORY.md @@ -1,3 +1,7 @@ +### 19.0.2.0.2 + +- fix(data): repair the 24 shipped logic-pack filter items whose CEL expressions referenced registrant fields that exist in no module — in several cases through catalogued variables whose `source_field` is dangling (#446) — so they could never translate or evaluate (#431). 1 item is fixed properly: Institutional Residence Exclusion keeps its expression, backed by a new `in_institutional_care` standard variable over the existing `spp_registry` field of the same name (both scoped to individual context). 3 items are rewritten to the stricter working part of their expression (OVC Child Age Eligibility, OVC Vulnerable Household Check, Public Works Poverty Status Check). The other 20 are removed because no working near-equivalent exists, or, for GMI Residency Requirement, because the only surviving sub-expression would be more permissive than the shipped rule (Disability Status Verification, OVC Orphan Status Check, OVC School Enrollment Requirement, Social Pension No Formal Pension Check, GMI Employment Barriers Check and Residency Requirement, Public Works Physical Work Capability and Seasonal Availability, CCT Health Conditionality Compliance, Geographic Limited Service Access, and 10 Exclusion Criteria items: Government Employee, Formal Sector, Vehicle Ownership, Business Ownership, Housing Quality, Pension Receipt, Duplicate Program, Income Tax Payer, Bank Balance, Livestock Ownership). Pack data is `noupdate`, so a migration applies the same removals/rewrites to existing databases; every migration write is guarded on the item still carrying the known-broken shipped expression, so locally repaired items are left untouched. Logic already installed from removed items (`installed_logic_id`) is deliberately not deleted — once installed it is the deployment's own data. + ### 19.0.2.0.1 - fix(security): drop the Program Manager → `group_studio_viewer` extension per the OP#951 menu audit (Program Manager should NOT see the Studio top-level menu). Removes `data/user_roles.xml` from the module entirely; System Admin retains Studio visibility via `spp_security.group_spp_admin` → `group_studio_manager` (wired in `spp_studio/security/groups.xml`). diff --git a/spp_studio/static/description/index.html b/spp_studio/static/description/index.html index 52b9095a5..ae507896e 100644 --- a/spp_studio/static/description/index.html +++ b/spp_studio/static/description/index.html @@ -532,6 +532,39 @@

Changelog

+

19.0.2.0.2

+ +
+

19.0.2.0.1

-
+

19.0.2.0.0