Skip to content

OpenSPP v2 - 2026.09

Latest

Choose a tag to compare

@gonzalesedwin1123 gonzalesedwin1123 released this 15 Sep 03:13
Immutable release. Only release title and notes can be modified.
e85e79e

Introduction

This OpenSPP V2 release comprises 135 commits and 58 merged pull requests since 2026.08. It carries two security hardening batches and a grievance-automation security cluster that close privilege escalations and access-control gaps across DCI, key management, OAuth, change requests, programs and GRM; a complete review of the disaster relief inventory (DRIMS) workflow from incident through delivery; a new family of PII protection modules (data classification, field-level encryption, masked registrant identifiers); consolidated Registry and Program configuration screens; and a Playwright end-to-end test suite running post-merge in CI.

Several security fixes remove rights that were granted by accident. Read Upgrade guidance before upgrading — in particular the administrator lockout check and the OAuth signing-key rotation.

Upgrade guidance

Supported upgrade path

This release upgrades from OpenSPP v2 2026.08 only. The migration scripts in this release are written and tested against the 2026.08 schema.

Deployments on 2026.07 must upgrade to 2026.08 first, following that release's upgrade guidance. Deployments on v2.0.0 (v19.0.2.0.0) must go through 2026.07 and 2026.08. OpenSPP v1.x (Odoo 17) remains unsupported as a starting point.

Automatic migrations

Twelve migration scripts run during the module upgrade. All are idempotent.

  • spp_dci/migrations/19.0.2.0.2 — removes the system-administrator implication from the DCI Administrator group and logs a warning with the number of users who lose transitively granted rights (#399)
  • spp_key_management/migrations/19.0.2.0.1 — the same for the Key Management Admin group (#399)
  • spp_programs/migrations/19.0.2.3.2 — moves the Program Viewer role from the Tier-2 registry viewer group to Tier-3 registry read and re-syncs already-assigned users (#353, #422)
  • spp_change_request_v2/migrations/19.0.3.1.5 — the same for the CR Requestor, CR Local Validator and CR HQ Validator roles (#353, #422)
  • spp_grm_cel/migrations/19.0.2.0.2 — backfills the owner (eval_as_user_id) of every GRM routing and escalation rule from its creator, and logs a warning listing rules owned by the superuser (#415)
  • spp_starter_sp_mis/migrations/19.0.2.1.0 — stops the "Restrict Registry Edits to Admin Only" setting from being reset on every upgrade (#411)
  • spp_drims/migrations/19.0.3.1.0 — recomputes the three incident stock/distribution KPIs for every incident, including closed ones, because their meaning changed (#416)
  • spp_registry/migrations/19.0.2.2.2 — drops the unconditional uniqueness constraint on registrant IDs so a partial index that ignores Invalid rows can replace it (#437)
  • spp_case_base/migrations/19.0.2.0.1 — clears the "current plan" flag on intervention plans that are already completed and logs the affected cases (#478)
  • spp_hide_menus_base/migrations/19.0.2.1.0 — de-duplicates hidden-menu configuration rows before the new uniqueness constraint is applied (#409)
  • spp_studio/migrations/19.0.2.0.2 — removes 20 shipped logic-pack items whose CEL expressions could never evaluate and rewrites 3 others; locally modified items are left untouched and logged (#444)
  • spp_attendance/migrations/19.0.2.0.0 — hashes stored API client secrets (only relevant where the legacy spp_attendance was already installed) (#435)

Before upgrading

🔴 Administrator lockout check. On each target database, confirm at least one operator other than admin holds system administration directly, not only through the "DCI Administrator" or "Key Management Admin" groups. Both stop granting system administration in this upgrade, and reverting the release does not restore the DCI link (#399).

🔴 Plan to rotate the OAuth signing keypair. Every internal user could read the private RSA signing key before this release. Treat it as compromised. After upgrading, generate a new 4096-bit RSA keypair and, as a system administrator, paste it into Settings → SPP OAuth Settings → OAuth Settings (4096 bits RSA keys) (Private Key and Public Key). Tokens signed with the old key stop validating once it is replaced. Rotation now requires system administration (#399).

Also:

  • Rebuild the container image (docker compose build), not just the modules: rlPyCairo was added to the image requirements; without it every report containing a barcode or QR code fails with HTTP 500 (#433). The rebuilt image also fixes multi-record PDF printing (#407) and requires OCA server-backend at or after its role_ids → user_role_ids rename: spp_user_roles and spp_area now extend the renamed field, so an older checkout fails at view load and holding back is not supported. For reproducible builds, pin OCA_SERVER_BACKEND_REF to a commit at or after the rename (#507, #508)

  • List the users holding "DCI Administrator" and "Key Management Admin" and decide who receives system administration explicitly (#399)

  • Grant the API user the Change Request Manager role if any integration applies change requests through API v2 or RPC (#365, #422)

  • Inventory GRM routing and escalation rules and who created them; rules created from a shell, import or data load will be owned by the superuser after migration and need an owner (#415)

  • List users holding Program Viewer, CR Requestor, CR Local Validator or CR HQ Validator who rely on the Registry Search menu; give them a registry role if they need it (#353, #422)

  • Complete any in-flight dynamic-approval change requests you intend to edit — they become read-only after submission (#264, #422)

  • SP-MIS starter deployments: decide the intended state of "Restrict Registry Edits to Admin Only" — it ships enabled and is now enforced (#411)

  • Review configured change-request Expression transforms: they have silently passed raw values through since the Odoo 19 port and will now transform them, refusing the apply if the expression cannot be evaluated (#459)

  • Finish or cancel in-flight DRIMS dispatches whose Operations tab was edited beyond the request (products added, demand raised) — validation now refuses them (#391). Record departure on any dispatch you intend to mark delivered right after the upgrade (#393)

  • On very large registries, pre-create the new membership ended_date index concurrently so the upgrade skips its write-blocking CREATE INDEX (#418):

    CREATE INDEX CONCURRENTLY IF NOT EXISTS spp_group_membership__ended_date_index
      ON spp_group_membership (ended_date) WHERE ended_date IS NOT NULL;
  • API clients of the change-request endpoints and DCI clients of the alias search stubs: adapt status-code handling (see Breaking changes) (#460, #471, #303)

After upgrading, verify

  • Upgrade log warnings from spp_dci, spp_key_management, spp_grm_cel, spp_case_base, spp_hide_menus_base and spp_studio — each names records or users that need a decision
  • A user holding only Key Management Admin can open the new top-level Key Management app and its key records; DCI server operators (tokens, sender registries, subscriptions, settings) hold system administration explicitly (#399)
  • The OAuth keypair has been rotated and old tokens invalidated (#399)
  • Portal grievance submission works and a portal user sees only their own tickets; each GRM rule has the intended owner (use Take Ownership on superuser-owned rules) and new unassigned tickets are still routed (#415)
  • A Program Viewer / CR-role user can open a registrant form (consents, cycles, irrigation assets render) and no longer sees Registry Search (#353, #454)
  • A system administrator can Force Unlock; a non-admin async import or eligibility job still completes and releases its lock (#336, #422)
  • API v2 $apply succeeds with the manager-role API user (#365, #422)
  • Settings → Registry is reachable for OpenSPP Administrator and Registry Configuration Admin; "Restrict Registry Edits to Admin Only" matches the intended state and survives a reload (#440, #411)
  • Incident dashboards: spot-check a closed incident's stock and distributed KPIs, which were recomputed (#416); a request dispatch shows only Ready → Done, has no "Add a Product", and keeps Quantity editable (#391, #392)
  • Program Configuration renders every manager category as a card (#472)
  • Studio: programs whose eligibility logic was installed from one of the 20 removed pack items still hold that never-evaluable expression — decide whether to replace or delete it (#444)
  • Multi-record PDF printing and a barcode-bearing report both render on the rebuilt image (#407, #433)
  • The hourly "sweep pending scans" cron is active and the pending attachment count drains over the first hours (#470)

Breaking changes

DCI Administrator and Key Management Admin no longer grant system administration

Modules: spp_dci, spp_key_management · Affects: every deployment that assigned either group

Both groups listed the system-administrator group among their implied groups, which in Odoo grants it: assigning either domain role silently made the holder a full system administrator. The implication is removed and stripped from existing databases (#399).

Consequences: all DCI server administration (API tokens, sender registries, subscriptions, server keys, DCI settings) is system-gated, so "DCI Administrator" now grants only visibility of DCI payload and identifier data. No group implies "DCI Administrator" any more, so an administrator who needs raw DCI payloads on screen must be granted it explicitly. Key Management moves from Settings to a top-level app so key administrators can still reach it; they keep read on the KMS-wrapped key material (ciphertext only). Reverting the release does not restore the DCI link.

Action required: grant system administration explicitly to whoever operates the DCI server or rotates OAuth keys; grant "DCI Administrator" explicitly to administrators who need DCI PII on screen. Run the lockout check under Upgrade guidance before upgrading.

OAuth signing keys restricted to system administrators

Modules: spp_oauth · Affects: deployments issuing OAuth tokens

Only system administrators can read or rotate the signing keypair; a settings save by any other user now fails with an access error instead of silently deleting the stored keys (#399).

Action required: rotate the RSA keypair and invalidate outstanding tokens (see Upgrade guidance).

Four roles lose the Registry Search menu

Modules: spp_programs, spp_change_request_v2, spp_consent, spp_irrigation · Affects: users holding Program Viewer, CR Requestor, CR Local Validator or CR HQ Validator

These roles move from the Tier-2 registry viewer group, which gates the standalone Registry Search menu — a broad registrant-PII enumeration surface they do not need — to Tier-3 registry read. Migrations revoke the viewer group from already-assigned users. Registrant read for program and change-request cross-references is unchanged, and a follow-up restores Tier-3 read on the models the registrant form depends on so these roles can still open a registrant (#353, #422, #454).

Action required: users who genuinely need the Registry app must be given a registry role (for example Registry Viewer) explicitly. Users who also hold Program Manager or Program Validator are unaffected.

Force Unlock requires system administration

Modules: spp_programs, spp_program_geofence, spp_farmer_registry_demo · Affects: program officers, managers and cycle approvers who used Force Unlock; custom code writing the lock fields

Direct writes to is_locked / locked_reason on cycles and programs (on create or write) and action_force_unlock raise an access error unless the caller is a system administrator or runs as superuser. The async pipeline manages the lock through internal helpers (#336, #422).

Action required: custom modules that acquire or release the lock must use _acquire_operation_lock / _release_operation_lock. Clearing a stuck lock now needs a system administrator.

Applying a change request requires the Change Request Manager role

Modules: spp_change_request_v2, spp_api_v2_change_request · Affects: integrations applying change requests through API v2 or direct RPC

The public action_apply() is gated server-side on the manager group; previously the restriction lived only on the review button and the apply ran under sudo(). Approval-driven auto-apply is unaffected (#365, #422).

Action required: grant the API user the Change Request Manager role before upgrading, or the apply endpoint returns an authorization error.

Dynamic-approval change requests are frozen after submission

Modules: spp_change_request_v2, spp_cr_type_assign_program · Affects: deployments that enabled dynamic approval on a change-request type (no shipped type does); integrations writing detail rows over RPC

After submission the routed field, its old and new values and the detail pointer are read-only server-side; editing requires a reset to draft, which re-routes the approval. Only the routed field is applied. Detail rows and Create-Group wizard rows are visible only to their owner (or validators and managers) within area scope, and applying a request that would write nothing is rejected instead of being stamped applied (#264, #261, #343, #422).

Action required: integrations that edited submitted requests must reset them to draft first; integrations that read other users' detail rows as a plain CR user need a validator or manager role.

Change-request transform expressions fail closed and are administrator-only

Modules: spp_change_request_v2 · Affects: deployments with field mappings using the Expression transform

Expression transforms are evaluated again (they had silently passed raw values through since the Odoo 19 port). An expression that cannot be evaluated now refuses the apply and records the error, instead of writing the untransformed, requester-controlled value. The expression field is restricted to system administrators, and expressions see only read-only scalar snapshots of the detail and registrant — no environment, sudo(), relation traversal or database cursor (#459).

Action required: review each configured expression before upgrading (see Upgrade guidance).

GRM automation rules evaluate as their owner; portal ticket access scoped

Modules: spp_grm_cel, spp_grm · Affects: deployments with routing or escalation rules; portal grievance users; custom code calling the rule engine

Rules now run with their owner's identity and can act only within the owner's ticket visibility — an officer-authored rule no longer routes brand-new unassigned tickets, so cross-team rules must be owned by a manager or administrator. A user who owns rules cannot be deleted (archive instead); rules owned by an archived user stop firing until someone uses the new Take Ownership button. Portal users lose write and create on rules (#399) and read on both rule models, and see only their own tickets. The engine entry points are no longer RPC-callable and "Check Escalation" requires write access on the ticket (#415).

Action required: review rule ownership after upgrading (see Upgrade guidance). Custom code invoking the engine over RPC must move server-side.

Registry access restriction in the SP-MIS starter now applies

Modules: spp_starter_sp_mis · Affects: SP-MIS bundle deployments

"Restrict Registry Edits to Admin Only" ships enabled and is now enforced through the registrant access check — over the UI, RPC and import — where it was previously enforced in JavaScript only and could not be persisted as "off". Registrars therefore lose registrant create, edit and delete by default; plain contacts are unaffected. The toggle now lives under Registry Settings (#411, #440).

Action required: decide before upgrading whether the restriction should be on. A database where an administrator had previously turned it off is re-locked once by the upgrade; turn it off again and the choice now persists.

Change-request API error statuses

Modules: spp_api_v2_change_request · Affects: API clients that branch on HTTP status codes

Condition Endpoints Before After
Authorization failure $submit, $approve, $reject, $request-revision, $apply, $reset 409 403
Authorization failure create 500 403
Validation error (e.g. missing rejection reason) the six transition endpoints 409 422
Record gone mid-transition the six transition endpoints 409 404
403 response body all of the above raw Odoo message naming models and rules fixed generic message

A plain state-machine refusal (for example "only pending change requests can be rejected") still returns 409 (#460, #471).

Action required: clients that retried on 409 to recover from permission errors must handle 403 instead.

DCI registry alias search stubs answer DCI-style

Modules: spp_dci_server · Affects: DCI clients calling the disability, CRVS or farmer /sync/search aliases

The stubs accept a DCI envelope and return a signed on-search envelope with one per-item ACTION_NOT_SUPPORTED rejection per request item (status 200) instead of HTTP 501; a bare search request without the envelope wrapper now fails validation with 422 (#303).

DRIMS request: single Source Warehouse field removed

Modules: spp_drims · Affects: custom code, imports or reports referencing source_warehouse_id on requests

Allocation is now recorded per source warehouse on a new spp.drims.request.allocation model, with each stock move linked to its allocation. The request's source_warehouse_id field is removed; a computed "Source Warehouse(s)" summary replaces it on the list (#396).

Action required: move any reference to source_warehouse_id onto the allocation records.

DRIMS incidents and donations start in a Draft state

Modules: spp_hazard, spp_drims · Affects: integrations creating incidents or donations; downstream modules extending the incident form

A new incident defaults to Draft (previously Active) and is not treated as a live incident until classified with Flag As Alert or Set Active; a draft cannot be closed and a closed incident cannot be reopened, enforced server-side. Donations likewise start in a new Draft state, and DRIMS operations against a closed incident are refused. Existing records keep their current state (#416, #414).

Action required: API clients or imports that create incidents and expect them active immediately must set the state explicitly. Upgrade spp_drims (do not merely restart): the donation default state is looked up by vocabulary code, and without the data record new donations are created with no state.

Other DRIMS contract changes

Modules: spp_drims · Affects: downstream code and custom views

  • The donation line description field is removed in favour of product and quantity columns; the database column is retained but no longer readable through the ORM (#414)
  • Priority and type constants are renamed after the codes the vocabulary actually ships: PRIORITY_LOW/MEDIUM/HIGH → PRIORITY_ROUTINE/URGENT/CRITICAL, DRIMS_TYPE_TRANSFER → DRIMS_TYPE_INTERNAL_TRANSFER (#438)
  • action_confirm_pod refuses when departure has not been recorded; the form routes through the new delivery-confirmation wizard (#393)

Registry configuration menus and settings sections moved

Modules: spp_registry, spp_api_v2, spp_import_match, spp_farmer_registry, spp_starter_sp_mis · Affects: operators, documentation, downstream modules hanging menus off Registry → Configuration

Registry → Configuration and the API V2 and Import Match menus under it move to Settings → Registry, gated to OpenSPP Administrator and Registry Configuration Admin. The separate Farmer Registry Settings and SP-MIS Settings sections and their settings fields are removed in favour of one Registry Settings section; both legacy storage parameters keep being written, so enforcement is unchanged (#440).

Action required: update runbooks and screenshots; downstream modules that attached menus under the old parent must re-parent them.

Program managers are bound to one program

Modules: spp_programs · Affects: API clients, imports or data files that link an existing manager record into a second program

Creating or writing a program refuses a manager link whose manager was created for another program. Links already in the database stay loadable and removable; only additions are checked (#472).

Action required: create a manager per program instead of sharing one record.

Notes for module authors

  • spp_hide_menus_base: a module that seeds a hidden-menu record for a menu that already has a configuration row now fails at install with an integrity error, instead of inserting the duplicate that used to abort the next registry load (#409)
  • spp_change_request_v2: downstream action_apply overrides now also run for auto-apply-on-approve, as superuser (#461)
  • spp_case_base: completed intervention plans no longer match is_current; downstream domains that used it as "the plan that matters" must widen to is_current OR state = completed (#478)
  • spp_user_roles, spp_area: follow the OCA base_user_role rename of res.users.role_ids to user_role_ids; downstream views extending that field must follow (#508)

Security fixes

Two staged hardening batches and a grievance-automation cluster. Several items remove rights that were granted by accident — see Breaking changes and Upgrade guidance for the operator actions.

Privilege escalation

  • Two administrative groups silently granted full system administration: "DCI Administrator" and "Key Management Admin" each implied the system-administrator group. Both links are removed and stripped from existing databases by migration (#399)
  • OAuth RSA signing keys were readable by every internal user: spp_oauth widened settings access to all internal users and the settings form exposed the private key. The widening is removed and key fields are gated to system administrators (#399)
  • Portal grievance users could author GRM routing and escalation rules that executed as superuser. Portal access to rules is reduced to read (#399) and then removed (#415)
  • Change-request users could apply their own approved request over RPC: the apply ran under sudo() and its manager restriction lived only on the review button. The public method now requires the manager group server-side (#365, #422)
  • Program officers, managers and cycle approvers could clear an active async operation lock on cycles and programs, enabling double entitlements and payments. Lock fields are guarded on write and create and require system administration (#336, #422)

Access control and record-level isolation

  • Change-request detail models had access-control entries but no record rules, so any change-request user could read or tamper with other users' pending request details over RPC. Ownership and area rules mirroring the parent request are added to every detail model and to the Create-Group member wizards (#261, #422)
  • Assign-program change requests validated the program's state but not the user's access to it; a write-time constraint now rejects inaccessible programs and the apply re-checks access against the requester's company scope (#338, #422)
  • Program Viewer and the three change-request roles carried the Tier-2 registry viewer group, which gates the standalone Registry Search menu — a broad registrant-PII enumeration surface they do not need. They are re-pointed to Tier-3 registry read (#353, #422), with the registrant form's dependent models restored to Tier-3 read (#454)
  • Portal users could read and rewrite every grievance ticket over RPC. A record rule scopes tickets to the user's own partner and the portal access entry is reduced to read-only (#415)
  • The SP-MIS "Restrict Registry Edits to Admin Only" setting neither persisted nor enforced beyond JavaScript; it is now stored explicitly and enforced server-side for registrants (#411)

Change-request approval integrity

  • Dynamic-approval requests routed on one selected field but applied every changed mapped field, letting a requester obtain a weak approval for a low-risk field and have high-risk fields written alongside it. Apply and preview now operate only on the routed field, and the request is frozen server-side once it leaves draft (#264, #422)
  • Conflict and duplicate detection trusted the user-writable selected-field label, so re-pointing it to an unchanged field cleared a configured conflict. Detection now derives the changed-field set from the detail-versus-registrant diff and scores duplicates over the shared changed fields (#343, #422)

Grievance automation

  • Routing and escalation rules ran as superuser from the hourly cron, so a GRM officer could author an always-match rule and seize every open ticket. Rules now evaluate with their owner's identity, act only within the owner's ticket scope, apply at most once per ticket and are atomic per escalation; the engine entry points are no longer RPC-callable (#415)

Other

  • Change-request Expression transforms fail closed and are administrator-only, with expressions confined to read-only scalar snapshots (#459)
  • Change-request API endpoints no longer leak Odoo's raw access-error text naming models and record rules; 403 bodies are a fixed generic message (#471)

Known issues

  • Security follow-ups tracked as issues: async completion callbacks in spp_programs remain public and clear the operation lock (#337); approval_state on the shared approval mixin has no field-level protection (#369); custom-strategy change requests freeze only the routing selector (#403), the assign-program program_id remains re-pointable after submission (#347), and the writable selected-field label still steers dynamic-approval routing in draft (#344); plain internal users hold unscoped read on GRM tickets (#486) and officers can disable or reorder manager-owned rules (#488); "DCI Administrator" is still named as if it administered DCI — a rename is tracked (#398)
  • spp_farmer_registry_cr and spp_studio_change_requests detail models have the same missing-record-rule gap that #422 closed for the core detail models; tracked in #423 and #320
  • Reverting this release does not restore the DCI Administrator escalation and neither de-escalation migration has a down-migration. If administrators lose Settings access after upgrading, grant system administration explicitly rather than reverting (#399)
  • PII encryption (spp_pii_encryption) has no adopter of the encryption mixin in the platform yet; design-level findings (ciphertext framing and key rotation, copy() double-encryption, non-read() access paths, blind-index linkage) are tracked in #451 and gate the first adopting model. Masking in spp_registry_encryption is display de-emphasis, not an access control: plaintext still reaches the browser through the normal record read. The Data Migration wizard runs synchronously; an async variant is tracked in #453 (#232, #452)
  • spp_data_classification ships the registry only; PII-aware read masking, data-subject requests, retention and consent integration from the legacy module are deferred (#233)
  • spp_attendance: API v2 alignment, RFC 9457 error bodies and token-endpoint rate limiting are deferred to #434 (#435)
  • Studio: 25 of the 27 field-backed standard variables point at fields that exist on no model (#446), and the CEL translator lets numeric comparisons on nonexistent fields through unvalidated (#444)
  • DRIMS: a fully delivered request stays Dispatched even at 100% fulfilment because nothing yet sets the delivered/fulfilled states (#393); allocation reserves no Odoo stock, so two requests can allocate the same units and the second dispatch lands in Waiting (#392); editing a closed incident's own fields is blocked in the UI only, deliberately (#416); the incident Impact tab is hidden pending its own review (#416)
  • Programs: deduplication still flags a paused membership as duplicated — a pause is protected from Enroll Eligible, not from Deduplicate (#436); the entitlement-formula symbol browser is switched off until an entitlement-amount CEL profile exists (#472)
  • Approvals: approval managers retain delete rights on reviews, so an approval trail can still be removed (#447)
  • Registry memberships: active is not archived when the clock crosses a future ended_date, and start_date is ignored by the status computes (#420, #421)
  • Case management: action_complete has no state guard, so an RPC call can complete a draft plan and strand the case (#497); Create Revision ignores which plan is current and collides with a successor plan (#498); nothing constrains is_current against state, so a completed plan can be re-marked current (#499); is_current changes are not tracked in the chatter (#500); a per-record write cleanup in the same model is open (#501)
  • spp_api_v2_gis: some raw-SQL statistics sites remain unprotected against aborted transactions (#375)
  • Docker: BuildKit's cache never refreshes a branch tarball once downloaded, so an unpinned addon *_REF may still serve stale code on a cached builder (#507)
  • Attachments stored by system models carrying source-controlled binaries and attachments with no res_model are deliberately excluded from the pending-scan sweep (#470)
  • The demo birth-verification flow (spp_dci_demo), the all-or-nothing metric breakdown expansion (spp_metric_service) and the hidden import-match conditional-gate columns (spp_import_match) listed in 2026.08 remain as described there

New features

Disaster relief inventory management (DRIMS): full workflow review

spp_drims moves from 19.0.2.0.0 to 19.0.4.0.4 after a QA-driven review of every stage of the relief workflow — incident, request, allocation, dispatch, delivery and donations.

  • Incidents are classified deliberately. A new incident is saved as Draft and offers Flag As Alert and Set Active side by side (Draft → Alert or Active → Recovery → Closed). Drafts do not count as live incidents anywhere. Closed incidents are inert: submitting, approving or allocating requests, accepting donations and deploying personnel are refused. Warehouses can be linked to an incident, and the warehouse pickers on donations and requests narrow to them (#416)
  • Incident dashboard counts incident-related stock net of allocations and distributed value net of confirmed returns; KPI boxes no longer navigate away, and the stock-value KPI refreshes when a warehouse's incidents change (#416)
  • Per-warehouse allocation. The Allocate Stock wizard auto-splits each requested line across the warehouses that hold stock, with editable rows, distinguishes no-stock, shortfall and deliberate partial allocation, and shows fulfilment %. Dispatch creates one picking per source warehouse (#396)
  • Reviewer notes and resubmission. Request Changes opens a wizard that captures a required note instead of silently bouncing the request, and a request sent back for revision can be resubmitted for approval. Request Details were realigned, filler rows removed and the items and allocation tabs merged (#396)
  • Dispatch page and waybill. A Dispatch & Delivery tab leads the dispatch form; everything the request already decided is locked, with Quantity left editable so a partial dispatch is still possible; the waybill fits one page with the signature block intact, and barcodes render (#433)
  • A dispatch ships only what its request approved. Products cannot be added and quantities cannot exceed the allocation, enforced on the model so imports and API callers are covered (#391)
  • Short dispatches and backorders stay inside the request. A backorder is announced on the request with a to-do for the area's coordinators, the request reopens as Ready for Dispatch until nothing is outstanding, and dispatched totals are rebuilt from the moves that still stand. Per-shipment facts are no longer copied onto the backorder, which also stops beneficiaries being double-counted on the incident (#390)
  • Proof of delivery in a popup. Confirm Delivery is hidden until departure is recorded and opens a wizard for receiver, signature, photos, GPS and what actually arrived per line. Delivered quantities are now recorded against the request (they were never written before, so Fulfillment % always read 0) and accumulate across dispatches (#393)
  • Dispatch status bar shows only reachable states: Draft and Waiting no longer appear as future steps on a request dispatch (#392)
  • Donations reviewed end to end. Donations start in Draft; at least one item and a positive pledge are required; Mark Received opens a wizard for what actually arrived; line columns appear progressively (Received and Variance, then Condition and Action); items returned, disposed of or quarantined at inspection are tracked to resolution; the donor list is limited to donor organisations (#414)
  • Request priorities behave as labelled. An Urgent request's approval SLA is 8 hours (it silently fell back to the 24-hour routine default), the priority badge and list highlighting appear, and the priority filters return records (#438)

PII protection: data classification, field-level encryption and masked identifiers

Three modules ported from the legacy openspp-modules repository give operators a foundation for protecting personally identifiable information. All are optional installs at Alpha status.

  • spp_data_classification is the registry: it seeds four sensitivity levels (Public, Internal, Confidential, Restricted) and 15 detection patterns, and lets administrators map model fields to a level and a PII category. Classified fields carry an is_pii flag that downstream consumers query. Menu: Settings → Data Classification (#233)
  • spp_pii_encryption provides the runtime: a mixin that encrypts char and text fields transparently with AES-256-GCM and maintains HMAC blind indexes (exact, last-4 or phonetic) so encrypted values stay searchable; per-field configuration from the UI; an audit log of reveal, export and decrypt events; and a masked-field widget. Keys and salts come from spp_key_management. Menu: Key Management → PII Encryption (#232)
  • The Data Migration wizard scans the classification registry for PII fields on encryption-capable models, previews the workload with a dry run, then encrypts existing plaintext in place batch by batch. It ships without in-app rollback — take a database snapshot before migrating (#452)
  • spp_registry_encryption is the first consumer: registrant ID numbers render masked (••••-••••-1234) on individual and group forms, the reveal control requires the PII full-access group, and every reveal is audited (#452)

Setup: install spp_data_classification first, then spp_pii_encryption (requires a configured spp_key_management key provider), configure fields under Key Management → PII Encryption → Field Configuration, and run the Data Migration wizard only after a snapshot. No model adopts the encryption mixin out of the box. The encryption and classification admin groups deliberately do not imply system administration.

Registry

  • Registry configuration is consolidated into one Registry Settings section in the Settings app, in the style of Programs Settings, carrying Restrict Registry Edits to Admin Only; the duplicate Farmer Registry and SP-MIS settings sections are removed (#440)
  • Latitude and Longitude are editable fields on the individual and group forms (spp_registrant_gis), kept in sync with the map in both directions, so coordinates can be entered without a MapTiler key. Values outside ±90 / ±180 are refused, including on import (#324)

Programs

  • Program Configuration is one consistent set of cards. Every manager category is a card with + Add opening a dialog that asks which method and what to call it, and a cog per row. Selecting a method the program already has is refused with a message naming it (#472)
  • Entitlement amounts no longer require a formula: a fixed Base Amount pays as-is, the Base Amount field is visible again, and the formula help names the real vocabulary (#472)
  • Duplicate Detection is a card with an Add dialog. Choosing ID document also asks which ID types to compare (without them the method matched nothing). Deduplicate now recomputes rather than accumulates, so a membership whose clash was fixed returns to draft, and validators can return duplicated memberships to draft in bulk (#445)

Attendance service and CEL load testing

  • spp_attendance (Alpha) arrives from the legacy repository as a standalone, API-first attendance service: its own participant registry, OAuth-secured REST endpoints for external submission, registry sync and CSV import. Relative to the legacy version, API client secrets are stored hashed and shown once, the Viewer group loses read on credentials, the REST API works on Odoo 19 again, and gender is never fabricated when unknown (#435)
  • spp_cel_load_testing (Alpha) provides performance benchmarks and validation suites for CEL expressions and Studio logic, plus query-analysis tooling. It is a test and tooling package, not for production instances (#432)

Improvements

Bug fixes

  • Registry: an ID type can be used again after its ID was removed. Removing an ID through a change request keeps the row as Invalid, and the old uniqueness rule counted it; uniqueness now applies to live IDs only, and is refused before the write so the message names the ID type rather than surfacing a database error (#437)
  • Registry: stored membership status and is_ended no longer go stale when the clock crosses a future-dated ended_date — rosters, metrics, API search and downstream gates previously kept treating the departed member as active indefinitely. Writing a future end date schedules a repair in the minute after it, and a daily sweep self-heals everything else, including rows already stale in existing databases (#418)
  • Programs: Enroll Eligible no longer undoes a deliberate pause; only Resume reverses one (#436)
  • Approvals (spp_approval): the pending-approvals and review lists no longer offer New — reviews are created by the approval flow (#447)
  • Change requests: group-scope conflict rules work again; resolving a household's members crashed because the traversal used field names the membership model does not have, so any type with an active group-scope rule failed on creation for group registrants. Ended memberships are excluded from conflict candidates (#477)
  • Change requests: auto-apply-on-approve runs through the public action_apply again, so downstream overrides fire on approval; the applying user is still recorded as the approver (#461)
  • Change requests: a selectable field on a dynamic-approval type can be applied through several mappings via a new routing_field on the mapping (defaults to the source field, so existing configurations are unchanged) (#462)
  • Case management (spp_case_base): completing an intervention plan clears is_current, so a finished plan stops being the case's current plan and a successor plan can be created; released databases are repaired by migration (#478)
  • Studio (spp_studio): 24 shipped logic-pack filter items referenced registrant fields that exist on no model. One is fixed by adding the missing in_institutional_care variable, three are rewritten to the working part of their expression, and 20 are removed (#444)
  • Hidden menus (spp_hide_menus_base): a second hidden-menu row for the same menu made every registry load fail — a total outage (#408). The menu is now unique, the hook reads a single governing row, and existing duplicates are cleaned up by migration (#409)
  • GIS API (spp_api_v2_gis): statistics, batch-statistics and proximity queries run their coordinate attempt and area fallback inside savepoints, so one database error no longer aborts the transaction and every later query; coordinate parameters are bound in the right order when a group filter is present; geofence and incident are selectable client scope actions (#375)
  • Attachment malware scanning (spp_attachment_av_scan): scans are never queued while the registry is loading, which could abort server boot on a serialization conflict (#464); attachments stranded at pending after a failed enqueue are re-queued by an hourly cron, bounded by batch size, minimum age and max attempts (#470)
  • Docker: the Odoo install no longer pulls legacy PyPDF2 3.x next to the pinned pypdf, which crashed PDF printing for more than one record (#407)

Demo data

  • Farmer registry demo: the Input Subsidy program uses manual entitlement approval so a demo can walk the full approval chain (#412); farm groups are typed Farm and cooperatives Cooperative (#322); a 50 ha commercial farm with idle land is seeded so the Climate Resilience targeting rule can be shown rejecting a non-smallholder (#323)
  • DRIMS Sierra Leone demo (spp_drims_sl_demo): donations are generated through the reworked lifecycle (#414)
  • Case management demo (spp_case_demo): finished demo plans are completed through the real lifecycle and no longer seeded as both completed and current (#478)

Infrastructure & tooling

  • A Playwright GUI end-to-end suite lives under e2e/ (SP-MIS starter and Farmer Registry starter specs with import fixtures) and runs post-merge on every push to 19.0, uploading the HTML report and posting results to Discord when configured; follow-ups added change-request and import coverage and made the Discord summary surface failures instead of truncating them (#442, #474, #480, #481)
  • Docker: the six third-party addon repositories (OCA server-ux, server-tools, server-backend, rest-framework, odoo-job-worker, muk-it/odoo-modules) can be pinned to a commit SHA through *_REF build arguments (default 19.0; a branch name or SHA is accepted, a tag name is not), and a bad ref fails at download rather than later in tar (#507)
  • spp_user_roles and spp_area follow the OCA base_user_role rename of role_ids to user_role_ids, which had broken every fresh image build (#508)
  • A Contributor Covenant 2.1 Code of Conduct is published at CODE_OF_CONDUCT.md (#377)

New modules

All five ship at Alpha status and are optional installs; nothing installs them unless an operator does.

Module Description
spp_data_classification Data-sensitivity classification registry: Public → Restricted levels with policy flags, per-field classification with PII categories, and pattern-based detection
spp_pii_encryption Field-level AES-256-GCM encryption for PII with searchable blind indexes, UI-driven field configuration, PII access audit log, masked-field widget and a batch migration wizard
spp_registry_encryption Masks registrant ID numbers on the registrant forms with a group-gated, audited reveal control
spp_attendance Standalone, API-first attendance service: own participant registry, OAuth-secured REST endpoints for external submission, registry sync and CSV import
spp_cel_load_testing Performance benchmarks and validation suites for CEL expressions and Studio logic, with query-analysis tooling