Skip to content

feat(spp_programs): show cycle compliance status on registrant form#152

Open
emjay0921 wants to merge 1 commit into19.0from
feat/cycle-compliance-on-registrant
Open

feat(spp_programs): show cycle compliance status on registrant form#152
emjay0921 wants to merge 1 commit into19.0from
feat/cycle-compliance-on-registrant

Conversation

@emjay0921
Copy link
Copy Markdown
Contributor

Why is this change needed?

When a registrant is marked as non-compliant on a cycle membership, there is no way to see this from the registrant form. Users have to navigate through programs and cycles manually to find compliance issues. This makes it difficult to quickly identify beneficiaries with compliance problems.

How was the change implemented?

Latest Cycle Status column on program membership list

  • Added latest_cycle_state computed field on spp.program.membership that reads the most recent cycle membership state per registrant+program
  • Displayed as a badge column ("Latest Cycle Status") on both individual and group registrant forms
  • Non-compliant in red, enrolled in green, paused in yellow

View Cycles button

  • Added "View Cycles" link button on each program membership row
  • Opens all cycle memberships filtered by that registrant and that specific program
  • Shows cycle name, state, and compliance criteria

Compliance Criteria column on cycle membership list

  • Added compliance_criteria computed field on spp.cycle.membership
  • When non-compliant, shows the program's compliance CEL expression (e.g., per_capita_income < poverty_line)
  • Empty for enrolled/other states

Smart button on registrant form

  • Added cycle_membership_count and non_compliant_cycle_count on res.partner
  • Smart button shows total cycle count and non-compliant count in red when > 0

Search filter

  • Added "Non-Compliant" filter to cycle membership search view

New unit tests

N/A — existing 579 spp_programs tests all pass (0 failures)

Unit tests executed by the author

  • spp_programs: 685 tests run, 0 failed, 0 errors

How to test manually

  1. Open a registrant enrolled in a program with compliance criteria
  2. Verify "Latest Cycle Status" badge column shows on program enrollment list
  3. Click "View Cycles" to see all cycles for that program with states
  4. For non-compliant cycles, verify "Compliance Criteria" column shows the CEL expression
  5. Verify smart button shows cycle count and non-compliant count

Related links

Add visibility into cycle-level compliance status from the registrant
form. Users can now see at a glance which programs have non-compliant
cycles and drill down to the specific cycle details.

- Add latest_cycle_state computed field on spp.program.membership
  showing the most recent cycle state per registrant+program
- Add "View Cycles" button on program membership rows opening
  filtered cycle memberships for that registrant+program
- Add compliance_criteria computed field on spp.cycle.membership
  showing the program CEL expression when non-compliant
- Add cycle_membership_count and non_compliant_cycle_count on
  res.partner with smart button
- Add Non-Compliant filter to cycle membership search view

Closes #860
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces features to track and display compliance information and cycle membership status for registrants. Key additions include a compliance_criteria field on cycle memberships, a latest_cycle_state field on program memberships, and new statistics and action buttons on the registrant view. A performance issue was identified in the _compute_latest_cycle_state method, where an N+1 query pattern should be replaced with a batch query using _read_group to improve efficiency.

Comment on lines +101 to +121
def _compute_latest_cycle_state(self):
"""Get the latest cycle membership state per registrant+program."""
if not self:
return

for rec in self:
rec.latest_cycle_state = False

# Batch query: find latest cycle membership for each program membership
CycleMembership = self.env["spp.cycle.membership"]
for rec in self:
cycle_mem = CycleMembership.search(
[
("partner_id", "=", rec.partner_id.id),
("cycle_id.program_id", "=", rec.program_id.id),
],
order="id desc",
limit=1,
)
if cycle_mem:
rec.latest_cycle_state = cycle_mem.state
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of _compute_latest_cycle_state performs a search() inside a loop, which leads to an N+1 query pattern. This will significantly degrade performance when viewing a list of program memberships (e.g., on the registrant form). It is highly recommended to use _read_group to fetch the latest cycle membership IDs for all records in the batch in a single query.

    def _compute_latest_cycle_state(self):
        """Get the latest cycle membership state per registrant+program."""
        for rec in self:
            rec.latest_cycle_state = False

        if not self:
            return

        # Batch query: find latest cycle membership ID for each partner+program pair
        data = self.env["spp.cycle.membership"]._read_group(
            domain=[
                ("partner_id", "in", self.partner_id.ids),
                ("cycle_id.program_id", "in", self.program_id.ids),
            ],
            groupby=["partner_id", "cycle_id.program_id"],
            aggregates=["id:max"],
        )
        # Map (partner_id, program_id) -> latest_membership_id
        latest_ids = {(p.id, prog.id): max_id for p, prog, max_id in data}

        # Fetch states for all latest memberships in one query
        mems = self.env["spp.cycle.membership"].browse(latest_ids.values())
        mem_states = {m.id: m.state for m in mems}

        for rec in self:
            mem_id = latest_ids.get((rec.partner_id.id, rec.program_id.id))
            if mem_id:
                rec.latest_cycle_state = mem_states.get(mem_id)

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 6, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.05%. Comparing base (f738582) to head (e729c6c).

Files with missing lines Patch % Lines
spp_programs/models/program_membership.py 21.42% 11 Missing ⚠️
spp_programs/models/cycle_membership.py 16.66% 10 Missing ⚠️
spp_programs/models/registrant.py 86.36% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #152      +/-   ##
==========================================
- Coverage   71.06%   71.05%   -0.02%     
==========================================
  Files         925      925              
  Lines       54704    54774      +70     
==========================================
+ Hits        38876    38919      +43     
- Misses      15828    15855      +27     
Flag Coverage Δ
spp_api_v2 80.10% <ø> (ø)
spp_api_v2_change_request 66.85% <ø> (ø)
spp_api_v2_cycles 71.12% <ø> (ø)
spp_api_v2_data 64.41% <ø> (ø)
spp_api_v2_entitlements 70.19% <ø> (ø)
spp_api_v2_gis 71.52% <ø> (ø)
spp_api_v2_products 66.27% <ø> (ø)
spp_api_v2_service_points 70.94% <ø> (ø)
spp_api_v2_simulation 71.12% <ø> (ø)
spp_api_v2_vocabulary 57.26% <ø> (ø)
spp_audit 64.44% <ø> (+0.60%) ⬆️
spp_base_common 90.26% <ø> (ø)
spp_case_entitlements 97.61% <ø> (ø)
spp_case_programs 97.14% <ø> (ø)
spp_cel_event 85.11% <ø> (ø)
spp_claim_169 58.11% <ø> (ø)
spp_dci_client_dr 55.87% <ø> (ø)
spp_dci_client_ibr 60.17% <ø> (ø)
spp_programs 62.15% <50.00%> (-0.09%) ⬇️
spp_security 66.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_programs/models/registrant.py 83.17% <86.36%> (+0.82%) ⬆️
spp_programs/models/cycle_membership.py 51.72% <16.66%> (-9.15%) ⬇️
spp_programs/models/program_membership.py 39.88% <21.42%> (-1.68%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant