Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions spp_programs/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,35 @@ Dependencies
Changelog
=========

19.0.2.4.0
~~~~~~~~~~

- feat(spp_programs): **the Program Configuration tab is one consistent
set of cards.** Every manager category is now configured the same way
-- a card showing what is set up, ``+ Add`` opening a dialog that asks
which method and what to call it, and a cog on each row to open it.
Previously some categories were cards and others were bare editable
lists whose only column was a Reference field, so adding one meant
picking a model and then finding or creating a record of it.
Notifications was the last such list and is now a card like the rest
(#1172)
- feat(spp_programs): a shared manager setup dialog backs all of those
cards, replacing the per-category wiring. Selecting a method a program
already has is refused with a message naming it rather than a
duplicate-record error (#1172)
- fix(spp_programs): the entitlement amount item no longer requires a
formula. A fixed sum is a normal entitlement and should not oblige
anyone to write it as an expression; leaving the formula empty pays
the Base Amount unchanged. The **Base Amount** field is also no longer
hidden -- formulas are documented to build on it as ``base_amount``,
so hiding it left nothing for them to multiply (#1172)
- fix(spp_programs): the entitlement formula box no longer offers
symbols the evaluator never receives. It advertised the entitlements
profile, whose record is the entitlement itself, while the field is
evaluated with ``me`` for the beneficiary and ``base_amount`` for the
fixed amount -- so a formula built from the browser failed to compile.
The placeholder and help now name the real vocabulary (#1172)

19.0.2.3.4
~~~~~~~~~~

Expand Down
3 changes: 2 additions & 1 deletion spp_programs/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.3.4",
"version": "19.0.2.4.0",
"sequence": 1,
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
Expand Down Expand Up @@ -115,6 +115,7 @@
"wizard/enrollment_wizard_views.xml",
"wizard/exit_membership_wizard.xml",
"wizard/prepare_entitlement_confirm_wizard.xml",
"wizard/manager_setup_wizard.xml",
],
"assets": {
"web.assets_backend": [
Expand Down
83 changes: 83 additions & 0 deletions spp_programs/models/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,86 @@
"spp.compliance.manager": "spp.compliance.manager.default",
},
}

# The cards on a program's Configuration tab (OP#1172). Each names the field on
# spp.program, the wrapper model behind it, and the wording the Add dialog uses.
# The keys match MANAGER_TYPE_INFO's "category" so the two can be read together:
# this map says where a category lives, MANAGER_TYPE_INFO describes the methods
# inside it.
#
# The concrete methods themselves are deliberately absent. They come from the
# wrapper's `_selection_manager_ref_id()`, which is what other modules extend
# when they add one — spp_program_geofence adds an eligibility method that way,
# and a hard-coded list here would never see it.
MANAGER_CATEGORIES = {
"eligibility": {
"field": "eligibility_manager_ids",
"wrapper": "spp.eligibility.manager",
"label": "Eligibility Method",
},
"entitlement": {
"field": "entitlement_manager_ids",
"wrapper": "spp.program.entitlement.manager",
"label": "Entitlement Type",
# One per program, and not by choice of this dialog: spp.program's
# check_managers_limit refuses a second entitlement manager, and the
# cycle machinery reaches for exactly one — get_manager() calls
# ensure_one(), and get_managers() raises NotImplementedError for this
# kind. QA asked for several of the same kind (OP#1172 round 1); that
# needs the entitlement engine to iterate managers first, so the dialog
# says what the program can actually do rather than accepting a second
# method the cycle would then choke on.
"single_manager": True,
},
"cycle": {
"field": "cycle_manager_ids",
"wrapper": "spp.cycle.manager",
"label": "Cycle Schedule",
# Capped at one by spp.program's check_managers_limit, same as
# entitlement. Unreachable through the dialog while this category has a
# single concrete method in-repo -- the already-configured check fires
# first -- but a module registering a second one (as spp_program_geofence
# does for eligibility) would otherwise get the constraint's after-the-fact
# wording, which is the experience this dialog exists to remove.
"single_manager": True,
},
"compliance": {
"field": "compliance_manager_ids",
"wrapper": "spp.compliance.manager",
"label": "Compliance Criteria",
},
"payment": {
"field": "payment_manager_ids",
"wrapper": "spp.program.payment.manager",
"label": "Payment Method",
# Capped at one by spp.program's check_managers_limit, same as
# entitlement. Unreachable through the dialog while this category has a
# single concrete method in-repo -- the already-configured check fires
# first -- but a module registering a second one (as spp_program_geofence
# does for eligibility) would otherwise get the constraint's after-the-fact
# wording, which is the experience this dialog exists to remove.
"single_manager": True,
},
"deduplication": {
"field": "deduplication_manager_ids",
"wrapper": "spp.deduplication.manager",
"label": "Deduplication Method",
},
"notification": {
"field": "notification_manager_ids",
"wrapper": "spp.program.notification.manager",
"label": "Notification Channel",
},
"program": {
"field": "program_manager_ids",
"wrapper": "spp.program.manager",
"label": "Program Manager",
# Capped at one by spp.program's check_managers_limit, same as
# entitlement. Unreachable through the dialog while this category has a
# single concrete method in-repo -- the already-configured check fires
# first -- but a module registering a second one (as spp_program_geofence
# does for eligibility) would otherwise get the constraint's after-the-fact
# wording, which is the experience this dialog exists to remove.
"single_manager": True,
},
}
123 changes: 58 additions & 65 deletions spp_programs/models/program_manager_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"""

from odoo import _, api, fields, models
from odoo.exceptions import UserError

from .constants import MANAGER_CATEGORIES


def _format_recurrence(duration, rrule_type):
Expand Down Expand Up @@ -241,6 +244,11 @@ class ProgramManagerUI(models.Model):
payment_manager_display = fields.Char(compute="_compute_banner_layout_helpers")
payment_manager_detail = fields.Text(compute="_compute_banner_layout_helpers")

# OP#1172: Notifications became a card like the rest, so it needs the same
# single-vs-multi helpers the other cards use.
notification_manager_count = fields.Integer(compute="_compute_banner_layout_helpers")
notification_manager_display = fields.Char(compute="_compute_banner_layout_helpers")
notification_manager_detail = fields.Text(compute="_compute_banner_layout_helpers")
deduplication_manager_count = fields.Integer(compute="_compute_banner_layout_helpers")
deduplication_manager_display = fields.Char(compute="_compute_banner_layout_helpers")
deduplication_manager_detail = fields.Text(compute="_compute_banner_layout_helpers")
Expand Down Expand Up @@ -407,6 +415,8 @@ def _compute_compliance_summary(self):
"compliance_manager_ids.manager_ref_id",
"payment_manager_ids",
"payment_manager_ids.manager_ref_id",
"notification_manager_ids",
"notification_manager_ids.manager_ref_id",
"deduplication_manager_ids",
"deduplication_manager_ids.manager_ref_id",
)
Expand All @@ -419,6 +429,7 @@ def _compute_banner_layout_helpers(self):
("cycle_manager_ids", "cycle"),
("compliance_manager_ids", "compliance"),
("payment_manager_ids", "payment"),
("notification_manager_ids", "notification"),
("deduplication_manager_ids", "deduplication"),
)
for rec in self:
Expand Down Expand Up @@ -653,73 +664,58 @@ def action_configure_compliance(self):
return self._open_manager_setup_wizard("compliance")
return False

def action_add_compliance_manager(self):
"""Open the default compliance manager form in create mode.

The program form's compliance banner shows a `+ Add` zero-state
button when no compliance manager is configured. We open the
concrete model (`spp.compliance.manager.default`) in create mode
with `default_program_id` and `_spp_wrapper_model` in context.
Saving the dialog runs the source-mixin's `create()` override,
which auto-creates the wrapper (see source_mixin.py). Dismissing
the dialog with `X` leaves nothing in the DB — that's the whole
point of #953.
def action_add_manager(self):
"""Open the Add dialog for one Configuration card (OP#1172).

One action serves every card: the button passes its category in the
context, so adding an eligibility method and adding a payment method
are the same gesture instead of one bespoke action per section.

The methods on offer come from the wrapper, so a category whose module
is not installed says so rather than opening a dialog with an empty
list — notifications have no channel at all until a bridge module such
as SMS is installed.
"""
self.ensure_one()
if not self.can_edit_configuration:
if not self.can_edit_configuration or self.state == "ended":
return False
if self.compliance_manager_ids:
return self.action_configure_compliance()
Concrete = self.env["spp.compliance.manager.default"]
category = self.env.context.get("manager_category")
info = MANAGER_CATEGORIES.get(category)
if not info:
raise UserError(_("Unknown configuration category %s.") % category)
wizard = self.env["spp.manager.setup.wizard"]
methods = wizard._methods_for_category(category)
if not methods:
raise UserError(
_("No %s is available. Install a module that provides one, then add it here.") % info["label"].lower()
)
return {
"type": "ir.actions.act_window",
"name": _("Compliance Criteria"),
"res_model": Concrete._name,
"name": _("Add a %s") % info["label"],
"res_model": wizard._name,
"view_mode": "form",
"views": [(Concrete.get_manager_view_id(), "form")],
"views": [(False, "form")],
"target": "new",
"context": {
"default_program_id": self.id,
# The mixin's create() will create the wrapper and rely
# on its `program_id` inverse to populate the program's
# One2many `compliance_manager_ids` automatically — no
# m2m write needed.
"_spp_wrapper_model": "spp.compliance.manager",
"default_category": category,
"default_method": methods[0][0],
"default_name": methods[0][1],
},
}

def action_add_payment_manager(self):
"""Open the default payment manager form in create mode.

Mirrors `action_add_compliance_manager`. The concrete model's
`create()` override auto-creates the default batch tag if the
form was saved with `create_batch=True` and no tag selected —
so we don't have to pre-create it here (which would orphan the
tag if the user dismisses the dialog). The source-mixin's
`create()` override creates the wrapper, then writes it into
the program's `payment_manager_ids` Many2many because that
field doesn't auto-resolve via the wrapper's `program_id`
inverse. See #953.
def action_add_compliance_manager(self):
"""Compliance's Add button, kept for callers that predate OP#1172.

Compliance opened its concrete form directly (#952) and payment did the
same (#953), while the other cards had no Add at all. Every card now
goes through one dialog, so all this does is name the category.
"""
self.ensure_one()
if not self.can_edit_configuration:
return False
if self.payment_manager_ids:
return self.action_configure_payment()
Concrete = self.env["spp.program.payment.manager.default"]
return {
"type": "ir.actions.act_window",
"name": _("Payment Processing"),
"res_model": Concrete._name,
"view_mode": "form",
"views": [(Concrete.get_manager_view_id(), "form")],
"target": "new",
"context": {
"default_program_id": self.id,
"_spp_wrapper_model": "spp.program.payment.manager",
"_spp_program_m2m_field": "payment_manager_ids",
},
}
return self.with_context(manager_category="compliance").action_add_manager()

def action_add_payment_manager(self):
"""Payment's Add button, kept for callers that predate OP#1172."""
return self.with_context(manager_category="payment").action_add_manager()

def action_add_deduplication_manager(self):
"""Open the two-step dialog for adding a deduplication method (OP#1171).
Expand All @@ -746,17 +742,14 @@ def action_add_deduplication_manager(self):
}

def _open_manager_setup_wizard(self, manager_type):
"""Open wizard to set up a new manager of the specified type."""
return {
"type": "ir.actions.client",
"tag": "display_notification",
"params": {
"title": _("Setup Required"),
"message": _("Please add a %s manager first using the list below.") % manager_type,
"sticky": False,
"type": "warning",
},
}
"""Point a caller at the Add dialog for this category (OP#1172).

This used to pop a warning telling the user to "add a manager using the
list below" — the inline list with the Reference field, which is the
control this ticket removes. The categories it is called with are the
MANAGER_CATEGORIES keys, so it can now open the real thing.
"""
return self.with_context(manager_category=manager_type).action_add_manager()

def get_manager_type_options(self, category):
"""Get available manager type options for a category."""
Expand Down
Loading
Loading