Skip to content
Merged
4 changes: 3 additions & 1 deletion spp_base_common/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{
"name": "OpenSPP Base (Common)",
"category": "OpenSPP/Core",
"version": "19.0.2.0.0",
"version": "19.0.2.0.1",
"sequence": 1,
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
Expand Down Expand Up @@ -38,6 +38,8 @@
"spp_base_common/static/src/xml/custom_list_create_template.xml",
"spp_base_common/static/src/js/filterable_radio_field.js",
"spp_base_common/static/src/xml/filterable_radio_field.xml",
"spp_base_common/static/src/xml/pager_hide_single.xml",
"spp_base_common/static/src/scss/pager_hide_single.scss",
],
"web._assets_primary_variables": [
"spp_base_common/static/src/scss/colors.scss",
Expand Down
10 changes: 10 additions & 0 deletions spp_base_common/static/src/scss/pager_hide_single.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// See OP#920 + spp_base_common/static/src/xml/pager_hide_single.xml.
// The control-panel right-side toolbar (refresh button, embedded
// actions, view switcher) had no vertical alignment of its own — it
// relied on the pager's h-100 nav to anchor the row's height.
// Once the pager is hidden, those siblings collapse to top-aligned.
// Set align-items: center explicitly so they stay vertically centred
// whether the pager is visible or not.
.o_control_panel_navigation {
align-items: center;
}
26 changes: 26 additions & 0 deletions spp_base_common/static/src/xml/pager_hide_single.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Hide the form-view "1/1" pager when there's only one record to navigate.

The standard web.Pager renders the counter + chevrons even when
`total === 1`, leaving a disabled "1/1 < >" widget that confuses
users. We drop the entire <nav> with `t-if` and pair the change
with an `align-items: center` rule on the parent
`o_control_panel_navigation` (see pager_hide_single.scss) so the
sibling refresh button stays vertically centred without the
pager's old h-100 anchor. `1/1+` (updateTotal set) stays visible
because the count may still grow.

See OP#920.
-->
<templates xml:space="preserve">
<t
t-name="spp_base_common.PagerHideSingle"
t-inherit="web.Pager"
t-inherit-mode="extension"
>
<xpath expr="//nav[hasclass('o_pager')]" position="attributes">
<attribute name="t-if">props.total &gt; 1 or props.updateTotal</attribute>
</xpath>
</t>
</templates>
14 changes: 14 additions & 0 deletions spp_change_request_v2/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,20 @@ Before declaring a new CR type complete:
Changelog
=========

19.0.2.0.6
~~~~~~~~~~

- fix(views): route post-submit CRs (pending / approved / applied /
rejected) through the stage review form when opened from the list,
matching the Edit Details → Upload Documents → Review & Submit
breadcrumb workflow used for fresh CRs (#920 round-2). Demo-generated
CRs in "Applied" state previously landed on the legacy main form view
from the list — now they open in ``spp_change_request_review_form``
like manually-created CRs. Adds the missing
``_action_open_review_form`` / ``_action_open_documents_form`` helpers
and wires ``action="action_open_stage_form" type="object"`` on the CR
list so row-click goes through the stage router.

19.0.2.0.5
~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion spp_change_request_v2/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "OpenSPP Change Request V2",
"version": "19.0.2.0.5",
"version": "19.0.2.0.6",
"sequence": 50,
"category": "OpenSPP",
"summary": "Configuration-driven change request system with UX improvements, conflict detection and duplicate prevention",
Expand Down
71 changes: 53 additions & 18 deletions spp_change_request_v2/models/change_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,7 @@ def _on_submit(self):
action = "resubmitted" if old_state == "revision" else "submitted"
self._create_audit_event("submitted", old_state, "pending")
self._create_log(action)
self.stage = "review"

def _on_request_revision(self, notes):
super()._on_request_revision(notes)
Expand Down Expand Up @@ -1523,29 +1524,63 @@ def action_upload_document(self):
def action_open_stage_form(self):
"""Open the appropriate form view based on the current stage.

For draft/revision CRs: routes to the stage-specific form.
For other states: opens the main CR form (for validators/managers).
- **Draft / revision**: route by `stage` to the editable stage form
(details / documents / review).
- **Submitted+ (pending, approved, applied, rejected)**: always open
the review-stage form. That form already renders state-aware
headers (Approve/Reject for validators, Apply for managers,
Applied ribbon for completed, Start Over for rejected) and shows
the same Edit Details → Upload Documents → Review & Submit
breadcrumb. Without this, validators/managers (and demo-applied
CRs opened from the list) landed on the legacy main form view
which lacks the breadcrumb and the pager-hide treatment. See
OP#920 round-2.
"""
self.ensure_one()

if self.approval_state not in ("draft", "revision"):
return {
"type": "ir.actions.act_window",
"name": self.name,
"res_model": "spp.change.request",
"res_id": self.id,
"view_mode": "form",
"views": [[False, "form"]],
"target": "current",
}
if self.approval_state in ("draft", "revision"):
if self.stage == "documents":
return self._action_open_documents_form()
if self.stage == "review":
return self._action_open_review_form()
return self.action_open_detail()

# pending / approved / applied / rejected
return self._action_open_review_form()

if self.stage == "documents":
return self._action_open_documents_form()
if self.stage == "review":
return self._action_open_review_form()
def _action_open_review_form(self):
"""Open the CR in the Review & Submit stage form view."""
self.ensure_one()
view = self.env.ref(
"spp_change_request_v2.spp_change_request_review_form",
raise_if_not_found=False,
)
return {
"type": "ir.actions.act_window",
"name": self.name,
"res_model": "spp.change.request",
"res_id": self.id,
"view_mode": "form",
"views": [[view.id if view else False, "form"]],
"target": "current",
}

# Default: details stage
return self.action_open_detail()
def _action_open_documents_form(self):
"""Open the CR in the Upload Documents stage form view."""
self.ensure_one()
view = self.env.ref(
"spp_change_request_v2.spp_change_request_documents_form",
raise_if_not_found=False,
)
return {
"type": "ir.actions.act_window",
"name": self.name,
"res_model": "spp.change.request",
"res_id": self.id,
"view_mode": "form",
"views": [[view.id if view else False, "form"]],
"target": "current",
}
Comment on lines +1551 to +1583
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The methods _action_open_review_form and _action_open_documents_form are almost identical. Refactoring them into a single helper method reduces code duplication and improves maintainability. Additionally, using self._name instead of a hardcoded model string is more idiomatic and robust for inheritance, aligning with the practice of preferring the _name attribute to avoid synchronization edge cases.

    def _action_open_stage_view(self, view_xml_id):
        """Helper to open the CR in a specific stage form view."""
        self.ensure_one()
        view = self.env.ref(view_xml_id, raise_if_not_found=False)
        return {
            "type": "ir.actions.act_window",
            "name": self.name,
            "res_model": self._name,
            "res_id": self.id,
            "view_mode": "form",
            "views": [[view.id if view else False, "form"]],
            "target": "current",
        }

    def _action_open_review_form(self):
        """Open the CR in the Review & Submit stage form view."""
        return self._action_open_stage_view("spp_change_request_v2.spp_change_request_review_form")

    def _action_open_documents_form(self):
        """Open the CR in the Upload Documents stage form view."""
        return self._action_open_stage_view("spp_change_request_v2.spp_change_request_documents_form")
References
  1. To avoid synchronization edge cases with related fields, prefer accessing a record's model name via its _name attribute rather than from a related field on a parent record.


def action_goto_details(self):
"""Navigate to the details stage (replaces breadcrumb via client action)."""
Expand Down
4 changes: 4 additions & 0 deletions spp_change_request_v2/readme/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 19.0.2.0.6

- fix(views): route post-submit CRs (pending / approved / applied / rejected) through the stage review form when opened from the list, matching the Edit Details → Upload Documents → Review & Submit breadcrumb workflow used for fresh CRs (#920 round-2). Demo-generated CRs in "Applied" state previously landed on the legacy main form view from the list — now they open in `spp_change_request_review_form` like manually-created CRs. Adds the missing `_action_open_review_form` / `_action_open_documents_form` helpers and wires `action="action_open_stage_form" type="object"` on the CR list so row-click goes through the stage router.

### 19.0.2.0.5

- fix(security): add a global `ir.rule` on `spp.change.request` that filters by `registrant_id.area_id` against the user's `center_area_ids` (OP#989 round-2). The earlier `_prepare_domain` override only caught `search_read` / `web_search_read` and missed the registrant Many2one picker (which uses `name_search` → `_search`), so users could still select out-of-area registrants. The conditional domain is a no-op for users with no center areas (global roles).
Expand Down
23 changes: 19 additions & 4 deletions spp_change_request_v2/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,21 @@ <h2>Changelog</h2>
</div>
</div>
<div class="section" id="section-1">
<h1>19.0.2.0.6</h1>
<ul class="simple">
<li>fix(views): route post-submit CRs (pending / approved / applied /
rejected) through the stage review form when opened from the list,
matching the Edit Details → Upload Documents → Review &amp; Submit
breadcrumb workflow used for fresh CRs (#920 round-2). Demo-generated
CRs in “Applied” state previously landed on the legacy main form view
from the list — now they open in <tt class="docutils literal">spp_change_request_review_form</tt>
like manually-created CRs. Adds the missing
<tt class="docutils literal">_action_open_review_form</tt> / <tt class="docutils literal">_action_open_documents_form</tt> helpers
and wires <tt class="docutils literal"><span class="pre">action=&quot;action_open_stage_form&quot;</span> <span class="pre">type=&quot;object&quot;</span></tt> on the CR
list so row-click goes through the stage router.</li>
</ul>
</div>
<div class="section" id="section-2">
<h1>19.0.2.0.5</h1>
<ul class="simple">
<li>fix(security): add a global <tt class="docutils literal">ir.rule</tt> on <tt class="docutils literal">spp.change.request</tt> that
Expand All @@ -1351,27 +1366,27 @@ <h1>19.0.2.0.5</h1>
roles).</li>
</ul>
</div>
<div class="section" id="section-2">
<div class="section" id="section-3">
<h1>19.0.2.0.3</h1>
<ul class="simple">
<li>fix: add HTML escaping to all computed Html fields with
<tt class="docutils literal">sanitize=False</tt> to prevent stored XSS (#50)</li>
</ul>
</div>
<div class="section" id="section-3">
<div class="section" id="section-4">
<h1>19.0.2.0.2</h1>
<ul class="simple">
<li>fix: fix batch approval wizard line deletion (#130)</li>
</ul>
</div>
<div class="section" id="section-4">
<div class="section" id="section-5">
<h1>19.0.2.0.1</h1>
<ul class="simple">
<li>fix: skip field types before getattr and isolate detail prefetch
(#129)</li>
</ul>
</div>
<div class="section" id="section-5">
<div class="section" id="section-6">
<h1>19.0.2.0.0</h1>
<ul class="simple">
<li>Initial migration to OpenSPP2</li>
Expand Down
6 changes: 4 additions & 2 deletions spp_change_request_v2/tests/test_stage_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,16 @@ def test_action_open_stage_form_draft_review(self):
cr.action_open_stage_form()

def test_action_open_stage_form_pending(self):
"""Pending CR opens main form (not stage form)."""
"""Pending CR opens the Review & Submit stage form so post-submit
states share the breadcrumb-based UI with draft / revision."""
cr = self._create_cr()
cr.approval_state = "pending"
result = cr.action_open_stage_form()
self.assertEqual(result["type"], "ir.actions.act_window")
self.assertEqual(result["res_model"], "spp.change.request")
self.assertEqual(result["res_id"], cr.id)
self.assertEqual(result["views"], [[False, "form"]])
review_view = self.env.ref("spp_change_request_v2.spp_change_request_review_form")
self.assertEqual(result["views"], [[review_view.id, "form"]])

def test_action_start_over_creates_new_cr(self):
"""action_start_over() creates a new CR with same type and registrant."""
Expand Down
2 changes: 2 additions & 0 deletions spp_change_request_v2/views/change_request_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
decoration-warning="display_state == 'revision'"
create="0"
sample="1"
action="action_open_stage_form"
type="object"
>
<field name="name" decoration-bf="1" />
<field name="request_type_id" />
Expand Down
Loading