diff --git a/spp_case_base/README.rst b/spp_case_base/README.rst index 3f27621dc..237fc7a28 100644 --- a/spp_case_base/README.rst +++ b/spp_case_base/README.rst @@ -151,6 +151,15 @@ Dependencies Changelog ========= +19.0.2.0.1 +~~~~~~~~~~ + +- fix(case): completing an intervention plan now clears ``is_current``, + so a finished plan stops being the case's current plan. Previously + only the revision path released the flag, leaving ``current_plan_id`` + pointing at completed work while ``has_active_plan`` read False, and + blocking any new plan from being marked current. + 19.0.2.0.0 ~~~~~~~~~~ diff --git a/spp_case_base/__manifest__.py b/spp_case_base/__manifest__.py index 1ce233749..b49f65259 100644 --- a/spp_case_base/__manifest__.py +++ b/spp_case_base/__manifest__.py @@ -1,7 +1,7 @@ # pylint: disable=pointless-statement { "name": "OpenSPP Case Management Base", - "version": "19.0.2.0.0", + "version": "19.0.2.0.1", "category": "OpenSPP/Monitoring", "summary": "Core case management functionality for OpenSPP", "author": "OpenSPP.org", diff --git a/spp_case_base/migrations/19.0.2.0.1/post-migration.py b/spp_case_base/migrations/19.0.2.0.1/post-migration.py new file mode 100644 index 000000000..042483691 --- /dev/null +++ b/spp_case_base/migrations/19.0.2.0.1/post-migration.py @@ -0,0 +1,56 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +"""Release ``is_current`` on intervention plans that already finished. + +``action_complete`` never cleared ``is_current`` (#458), and the only other +writer of ``is_current = False`` is the revision path, so a plan that finished +NORMALLY stayed its case's current plan forever. The code fix covers future +completions only; the rows already in a released database keep the incoherent +pair — ``case.current_plan_id`` pointing at completed work while +``has_active_plan`` reads False — and keep tripping the one-current-plan-per-case +constraint, so they are cleared here. + +Deliberately narrow: only ``completed``. ``action_create_revision`` writes +``is_current = False`` together with ``revised``, so that state has no +equivalent stale population, and widening the predicate would demote plans this +fix makes no claim about. + +Affected cases are left with NO current plan, which is the intended end state — +the plan is finished, and marking a fresh plan current is now possible again +(that constraint failure is the user-facing half of #458). The plan's own record +is untouched: ``state``, ``actual_end_date`` and its interventions all stay. + +Raw SQL with no ORM invalidation, matching +``spp_grm_cel/migrations/19.0.2.0.2/post-migration.py``: nothing reads +``is_current`` through the ORM later in this transaction, and a flush-and- +invalidate would risk writing a cached value back over the UPDATE. Callers that +DO read back in the same transaction — the test for this script, for one — must +invalidate on their side. +""" + +import logging + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + if not version: + return + + cr.execute( + "UPDATE spp_case_intervention_plan SET is_current = false " + "WHERE state = 'completed' AND is_current = true " + "RETURNING case_id" + ) + # One row per demoted plan; the one-current-plan constraint means a case + # cannot appear twice, but sort and de-duplicate so the log is stable. + case_ids = sorted({row[0] for row in cr.fetchall()}) + + if case_ids: + _logger.warning( + "Released is_current on %s completed intervention plan(s) that were still " + "flagged as their case's current plan. Cases affected (spp.case ids): %s. " + "Those cases now report no current plan; where case work is continuing, " + "mark the successor plan current on the case's Intervention Plans tab.", + len(case_ids), + ", ".join(str(cid) for cid in case_ids), + ) diff --git a/spp_case_base/models/case_intervention_plan.py b/spp_case_base/models/case_intervention_plan.py index ef90f67c7..fe7330af1 100644 --- a/spp_case_base/models/case_intervention_plan.py +++ b/spp_case_base/models/case_intervention_plan.py @@ -195,6 +195,7 @@ def action_complete(self): { "state": "completed", "actual_end_date": fields.Date.context_today(self), + "is_current": False, } ) return True diff --git a/spp_case_base/readme/HISTORY.md b/spp_case_base/readme/HISTORY.md index 4aaf9afef..743623c07 100644 --- a/spp_case_base/readme/HISTORY.md +++ b/spp_case_base/readme/HISTORY.md @@ -1,3 +1,10 @@ +### 19.0.2.0.1 + +- fix(case): completing an intervention plan now clears `is_current`, so a finished plan + stops being the case's current plan. Previously only the revision path released the + flag, leaving `current_plan_id` pointing at completed work while `has_active_plan` read + False, and blocking any new plan from being marked current. + ### 19.0.2.0.0 - Initial migration to OpenSPP2 diff --git a/spp_case_base/static/description/index.html b/spp_case_base/static/description/index.html index 4ecf78438..44399ec34 100644 --- a/spp_case_base/static/description/index.html +++ b/spp_case_base/static/description/index.html @@ -540,6 +540,16 @@

Changelog

+

19.0.2.0.1

+ +
+

19.0.2.0.0

+

19.0.2.0.1

+ +
+

19.0.2.0.0