Manager pattern
Contents
Manager pattern#
For: developers
OpenSPP uses the strategy pattern for program management logic. Instead of hardcoding business rules into the program model, each program delegates decisions to pluggable "managers." This lets different programs use different eligibility criteria, entitlement calculations, payment methods, and cycle behaviors without changing core code.
How it works#
Each program has manager slots connected via Many2many fields. Each slot accepts one or more managers of a specific type. Managers are Odoo models that inherit from a base class and implement the required interface methods.
spp.program
|
+-- eligibility_manager_ids → spp.eligibility.manager (wrapper)
| → spp.program.membership.manager.default
|
+-- entitlement_manager_ids → spp.program.entitlement.manager (wrapper)
| → spp.program.entitlement.manager.cash
| → spp.program.entitlement.manager.inkind
|
+-- cycle_manager_ids → spp.cycle.manager (wrapper)
| → spp.cycle.manager.default
|
+-- payment_manager_ids → spp.program.payment.manager (wrapper)
→ spp.program.payment.manager.default
Wrapper and implementation models#
Each manager type uses a wrapper/implementation pattern:
Wrapper model (e.g.,
spp.program.entitlement.manager) — Holds amanager_ref_idReference field that points to the actual implementation. This is what the program links to via Many2many.Implementation model (e.g.,
spp.program.entitlement.manager.cash) — Contains the actual business logic and configuration fields. Inherits from a base abstract model.
This two-level indirection lets the program hold references to managers of different implementation types through a single Many2many field.
Manager types#
Eligibility managers#
Determine which registrants qualify for a program.
Model |
Description |
|---|---|
|
Wrapper |
|
Base abstract model |
|
Default CEL-based eligibility |
Key methods:
enroll_eligible_registrants(program_memberships)— filters program memberships to those meeting the criteriaverify_cycle_eligibility(cycle, membership)— re-checks eligibility for cycle membersimport_eligible_registrants(state=None)— imports qualifying registrants into the program
Entitlement managers#
Calculate what each beneficiary receives in a cycle.
Model |
Description |
|---|---|
|
Wrapper |
|
Base abstract model |
|
Fixed amount per cycle |
|
CEL-based cash with line items |
|
In-kind (product-based) entitlements |
Cash entitlement items (spp.program.entitlement.manager.cash.item):
Field |
Description |
|---|---|
|
Base amount |
|
CEL expression for conditional inclusion |
|
Field to multiply amount by (e.g., household size) |
|
Maximum multiplier cap |
Cycle managers#
Control how program cycles are created and processed.
Model |
Description |
|---|---|
|
Wrapper |
|
Base abstract model |
|
Default with recurrence support |
Key method: new_cycle(name, new_start_date, sequence) — Creates the next cycle. The manager already knows its own program_id, so it is not passed as an argument.
Payment managers#
Handle payment processing for approved entitlements.
Model |
Description |
|---|---|
|
Wrapper |
|
Base abstract model |
|
Default payment processing |
Other managers#
Type |
Wrapper model |
Purpose |
|---|---|---|
Program |
|
Program lifecycle management |
Deduplication |
|
Duplicate detection strategies |
Notification |
|
Beneficiary communications |
Compliance |
|
Compliance checking |
Base classes#
Each manager type has its own base class:
Manager type |
Base class |
|---|---|
Eligibility |
|
Entitlement |
|
Cycle |
|
Payment |
|
Eligibility and entitlement base classes also inherit from spp.base.programs.manager, which provides common utility methods like _safe_eval() for CEL expression evaluation. Cycle managers define their own standalone base.
Each base class provides name and program_id fields. The mixin spp.manager.mixin provides the manager_ref_id Reference field and display name computation for wrapper models.
When you select a manager implementation from the dropdown (e.g., "CCT Eligibility"), the wrapper's manager_ref_id stores a reference string like spp.program.membership.manager.cct,42. The wrapper then delegates method calls to this implementation record.
Linking managers to programs#
Managers are linked to programs via Many2many fields using the Odoo Command API:
# Set a program's entitlement manager (replaces existing)
program.write({
"entitlement_manager_ids": [(6, 0, [wrapper_id])]
})
Important
Most manager slots are constrained to a single manager per program. Use Command (6, 0, [id]) to replace, not (4, id) to append. See the individual manager type guides for complete examples.
Source code#
All manager models are in spp_programs/models/managers/:
File |
Contents |
|---|---|
|
|
|
|
|
Eligibility wrapper and default implementation |
|
Entitlement wrapper and base implementation |
|
Cash entitlement with CEL items |
|
In-kind entitlement with products |
|
Fixed-amount entitlement |
|
Cycle wrapper and default implementation |
|
Payment wrapper and default implementation |
|
Program lifecycle manager |
|
Deduplication strategies |
|
Notification manager |
|
Compliance checking |
|
Recurrence scheduling mixin |
|
Source tracking mixin for managers |
What's next#
Building a custom manager — step-by-step guide to creating a custom manager, with method references for all manager types
Tutorial: build CCT program managers — build a complete CCT program module with eligibility, entitlement, and cycle managers
openspp.org