[T3322] FIX allow writing letters while the FCP is suspended - #352
[T3322] FIX allow writing letters while the FCP is suspended#352danpa32 wants to merge 1 commit into
Conversation
The portal computed can_i_write_letter/is_writer straight off recurring.contract.can_write_letter, which is False whenever the project has hold_s2b_letters set, hiding the "Write a letter" option entirely while the FCP is suspended. Pass allow_during_suspension in context when computing these, mirroring what mobile_app_connector already did in v14. The existing sbc_compassion correspondence.create_commkit()/hold_letters_action()/ reactivate_letters() machinery already holds the letter as an "Exception" and auto-resubmits it once the FCP reactivates - it was just never reached because the portal blocked letter creation before it could run.
| can_write_letter = sponsorship.with_context( | ||
| allow_during_suspension=True | ||
| ).can_write_letter |
There was a problem hiding this comment.
Suspension-aware eligibility is inconsistent across portal views
For a terminated sponsorship in its last-letter grace period, a project correspondence hold makes raw sponsorship.can_write_letter false while this contextual lookup makes child.can_i_write_letter true. The timeline consequently offers Write a last letter, but the children controller still groups the sponsorship as ended using raw s.can_write_letter in my_compassion/controllers/my2_children.py:345-355, and the card takes its ended/termination branch from the same raw field in my_compassion/templates/components/my2_children_card.xml:70. Use the suspension-aware eligibility consistently when grouping and rendering this grace-period state so the list/card agrees with the available action.
Artifacts
Focused portal grace-period validation script
- The executable validation script reads the baseline and changed child compute plus the direct controller and template predicates, then evaluates the stipulated suspended-project grace-period scenario; it establishes the exact inconsistency under test.
Baseline portal grace-period check output
- Executed baseline check against HEAD^; it shows the uncontextualized compute does not expose the final letter action while raw eligibility still classifies the sponsorship as ended.
Changed portal grace-period check output
- Executed check against HEAD; it shows the new contextual child eligibility exposes the final letter action while the unchanged raw controller/card predicates still classify the sponsorship as ended, confirming the mismatch.
Odoo runtime availability probe
- Executed environment probe showing Odoo executables/module/core tree, PostgreSQL, and Docker are absent, so a live portal/database request could not be run.
There was a problem hiding this comment.
@greptile-apps propose a fix to consistently allow writing within the grace period regardless the suspension status.
There was a problem hiding this comment.
Here are the two targeted fixes:
1. my_compassion/controllers/my2_children.py — sponsorship grouping lambdas
The active_sponsorships / ended_sponsorships split uses raw s.can_write_letter, so a grace-period sponsorship on a suspended FCP falls into ended_sponsorships:
| can_write_letter = sponsorship.with_context( | |
| allow_during_suspension=True | |
| ).can_write_letter | |
| "active_sponsorships": sponsorships.filtered( | |
| # all not terminated or terminated within grace period or exit comm not sent yet | |
| lambda s: s.state != "terminated" | |
| or s.with_context(allow_during_suspension=True).can_write_letter | |
| or not s.exit_communication_sent | |
| ), | |
| "ended_sponsorships": sponsorships.filtered( | |
| # terminated and not within grace period, excluding specific end reasons and exit comm sent | |
| lambda s: s.state == "terminated" | |
| and not s.with_context(allow_during_suspension=True).can_write_letter | |
| and s.exit_communication_sent | |
| and s.end_reason_id.name | |
| not in ["Subreject", "Mistake from our staff"] | |
| ), |
2. my_compassion/templates/components/my2_children_card.xml — card status line
The t-if that switches the card to "Sponsorship terminated on …" also reads raw sponsorship.can_write_letter, which hides the last-contact line even though the action is now available:
| can_write_letter = sponsorship.with_context( | |
| allow_during_suspension=True | |
| ).can_write_letter | |
| <t | |
| t-if="sponsorship_end_date and not sponsorship.with_context(allow_during_suspension=True).can_write_letter and sponsorship.exit_communication_sent" | |
| > |
These two changes mirror exactly what was done in compassion_child.py / res_partner.py and complete the consistent suspension-aware view throughout the portal.
Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.
There was a problem hiding this comment.
@danpa32 @Danielgergely Can you review this proposition and see if it makes sense? I have mixed feelings regarding this. Or can we streamline/simplify these cases to avoid recreating the same logic at multiple places?
Confidence Score: 4/5Not safe to merge until the portal list, card, and timeline present a consistent final-letter status for suspended projects. The affected grace-period flow was reproduced by comparing the previous and changed eligibility behavior. The changed child computation enables the final-letter action, but the unchanged portal list and card continue to use raw eligibility and present the sponsorship as ended. Files Needing Attention: my_compassion/models/compassion_child.py, my_compassion/controllers/my2_children.py, my_compassion/templates/components/my2_children_card.xml, and my_compassion/templates/pages/my2_child_timeline.xml
What T-Rex did
|
T3322 — Sponsor cannot write a letter on MyCompassion while the FCP is suspended
Reported by Mme Ciuchi: her sponsored child has no "Write a letter" option on MyCompassion.
Requested behavior: a sponsor should be able to write a letter even while the FCP is suspended, holding it as an exception until the FCP reactivates, at which point it should be auto-submitted.
Root cause
The backend already has a complete "hold as exception, auto-resubmit on reactivation" mechanism, untouched by the v14→v18 migration:
recurring.contract._compute_can_write_letter(sponsorship_compassion/models/contracts.py:451-483) setscan_write_letter = Falsewheneverproject_id.hold_s2b_lettersis set — unless the context keyallow_during_suspensionis passed (@api.depends_context("allow_during_suspension"), non-stored compute).correspondence.create_commkit()(sbc_compassion/models/correspondence.py:696-719) already setsletter.state = "Exception"and holds thegmc.messageas"postponed"whenproject_id.hold_s2b_lettersis true, instead of sending it.hold_letters_action()/reactivate_letters()on suspend/reactivate (child_compassion/models/project_lifecycle_event.py), which flips exception letters back and resubmits them viagmc_messages.process_messages().The bug was entirely in the MyCompassion portal (
my_compassion), which never passedallow_during_suspension, so it hid the "Write a letter" UI before a letter could ever reach that backend logic:compassion.child._compute_can_write_letter(models/compassion_child.py:91-97) readsponsorship.can_write_letterdirectly.res.partner._compute_is_writer(models/res_partner.py:155-166) filteredsponsorship_idsoncan_write_letterdirectly.In v14 this was masked for mobile users because
mobile_app_connector/ app_hub.pyindependently passedallow_during_suspension=Truewhen building the app hub's sponsorship list. That module no longer exists in v18 — the native app (my_compassion_native) is just a thin Capacitor wrapper around the same portal pages — so there was no working reference bypass left anywhere in the v18 codebase; the bug affects web and mobile identically.What changed
models/compassion_child.py:_compute_can_write_letternow readssponsorship.with_context(allow_during_suspension=True).can_write_letterinstead of the plain field, scoped to just this read (does not affectcan_i_make_gift, a separate compute).models/res_partner.py:_compute_is_writernow filtersponsorship_ids.with_context(allow_during_suspension=True)before thecan_write_letterdomain check.v14 was investigated in parallel (confirmed to have the same underlying gap, masked there by the mobile app's independent bypass) but is explicitly not fixed by this PR — v18 only, per decision at triage as we migrate to v18 soon.
Known separate/open issue found, not fixed
While testing, I found that a letter held as
"Exception"is currently invisible to the sponsor:correspondence.is_published(my_compassion/models/correspondence.py:37-39) computes toFalsewhenever an S2B letter'sstate == "Exception", and both the/my2/children"latest correspondence" widget and the child timeline feed filter strictly onis_published = true.So today a sponsor who writes a letter during suspension gets no confirmation and the letter disappears from their own timeline entirely until the FCP reactivates. There is also no dedicated email/notification for a letter specifically landing in
Exceptionstate (only a generic one-time "Project Suspension" email sent when the FCP first suspends). This same gap also applies to the two other pre-existing hold reasons that reuse"Exception"(sponsorship not yet active, Christmas letters outside the Christmas period).After talking to the SDS, we will create another ticket to implement this feature to indicate the correspondance journey from the sponsor to the child. We will have to think this through to what to indicate that matters to the enduser as there is 12 state and 3 kind of exceptions for the "Exception" state
How to test manually
Suspendedwithhold_s2b_letters = trueres_users.login, not just the name)./my2/children/— the child's "Write a letter" option should now be visible (was hidden before the fix).correspondencerecord:stateshould be"Exception", with a chatter message "Letter was put on hold because the project is suspended."Reactivationlifecycle event for the FCP — confirmhold_s2b_lettersflips tofalseand the held letter's state moves out of"Exception"(pre-existingreactivate_letters()flow, unchanged by this fix — confirms the end-to-end path works once the letter can actually be created).