Skip to content

Commit 130c734

Browse files
committed
fix(spp_change_request_v2): fix batch approval wizard line deletion
Replace act_window bindings with server actions that create the wizard record before opening the form, so One2many line operations work without "Please save your changes first" errors.
1 parent 45d9e69 commit 130c734

3 files changed

Lines changed: 81 additions & 74 deletions

File tree

spp_change_request_v2/tests/test_ux_wizards.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -254,16 +254,26 @@ def setUp(self):
254254
cr.action_submit_for_approval()
255255
self.pending_crs |= cr
256256

257+
def _create_wizard(self, cr_ids, action_type="approve", **kwargs):
258+
"""Helper to create a batch wizard via create_from_selection."""
259+
result = (
260+
self.env["spp.cr.batch.approval.wizard"].with_context(active_ids=cr_ids).create_from_selection(action_type)
261+
)
262+
wizard = self.env["spp.cr.batch.approval.wizard"].browse(result["res_id"])
263+
if kwargs:
264+
wizard.write(kwargs)
265+
return wizard
266+
257267
def test_batch_wizard_initialization(self):
258268
"""Test batch wizard initializes with selected CRs."""
259-
wizard = self.env["spp.cr.batch.approval.wizard"].with_context(active_ids=self.pending_crs.ids).create({})
269+
wizard = self._create_wizard(self.pending_crs.ids)
260270

261271
self.assertEqual(wizard.total_count, 3)
262272
self.assertEqual(len(wizard.line_ids), 3)
263273

264274
def test_batch_wizard_counts(self):
265275
"""Test batch wizard computes valid/invalid counts correctly."""
266-
wizard = self.env["spp.cr.batch.approval.wizard"].with_context(active_ids=self.pending_crs.ids).create({})
276+
wizard = self._create_wizard(self.pending_crs.ids)
267277

268278
# All should be valid if user can approve
269279
self.assertEqual(wizard.total_count, wizard.valid_count + wizard.invalid_count)
@@ -279,7 +289,7 @@ def test_batch_approve_requires_valid_crs(self):
279289
}
280290
)
281291

282-
wizard = self.env["spp.cr.batch.approval.wizard"].with_context(active_ids=[draft_cr.id]).create({})
292+
wizard = self._create_wizard([draft_cr.id])
283293

284294
# Should have 0 valid CRs
285295
self.assertEqual(wizard.valid_count, 0)
@@ -289,16 +299,7 @@ def test_batch_approve_requires_valid_crs(self):
289299

290300
def test_batch_reject_requires_comment(self):
291301
"""Test batch reject requires a reason."""
292-
wizard = (
293-
self.env["spp.cr.batch.approval.wizard"]
294-
.with_context(active_ids=self.pending_crs.ids)
295-
.create(
296-
{
297-
"action_type": "reject",
298-
"comment": "",
299-
}
300-
)
301-
)
302+
wizard = self._create_wizard(self.pending_crs.ids, action_type="reject", comment="")
302303

303304
# Should fail without comment
304305
with self.assertRaises(UserError):

spp_change_request_v2/views/batch_approval_wizard_views.xml

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
<!-- Selected requests -->
9696
<notebook>
9797
<page string="Selected Requests" name="requests">
98-
<field name="line_ids" nolabel="1" readonly="1">
98+
<field name="line_ids" nolabel="1">
9999
<list
100100
decoration-muted="not can_process"
101101
decoration-danger="not can_process"
@@ -115,12 +115,6 @@
115115
invisible="not can_process"
116116
title="Ready to process"
117117
/>
118-
<button
119-
name="action_remove_line"
120-
type="object"
121-
icon="fa-trash"
122-
title="Remove from batch"
123-
/>
124118
</list>
125119
</field>
126120
</page>
@@ -211,39 +205,40 @@
211205
</record>
212206

213207
<!-- ══════════════════════════════════════════════════════════════════════════
214-
BATCH APPROVAL - SERVER ACTION (for list view context menu)
208+
BATCH APPROVAL - SERVER ACTIONS (for list view context menu)
209+
Creates the wizard record before opening it, so One2many line
210+
operations (delete, buttons) work without "Please save first" errors.
215211
══════════════════════════════════════════════════════════════════════════ -->
216-
<!-- Note: Security is enforced via ir.model.access.csv - only validators can access wizard -->
217-
<record id="action_batch_approve" model="ir.actions.act_window">
212+
<record id="server_action_batch_approve" model="ir.actions.server">
218213
<field name="name">Batch Approve</field>
219-
<field name="res_model">spp.cr.batch.approval.wizard</field>
220-
<field name="view_mode">form</field>
221-
<field name="view_id" ref="view_batch_approval_wizard_form" />
222-
<field name="target">new</field>
214+
<field name="model_id" ref="model_spp_change_request" />
223215
<field name="binding_model_id" ref="model_spp_change_request" />
224216
<field name="binding_view_types">list</field>
225-
<field name="context">{'default_action_type': 'approve'}</field>
217+
<field name="state">code</field>
218+
<field name="code">
219+
action = env["spp.cr.batch.approval.wizard"].with_context(active_ids=records.ids).create_from_selection("approve")
220+
</field>
226221
</record>
227222

228-
<record id="action_batch_reject" model="ir.actions.act_window">
223+
<record id="server_action_batch_reject" model="ir.actions.server">
229224
<field name="name">Batch Decline</field>
230-
<field name="res_model">spp.cr.batch.approval.wizard</field>
231-
<field name="view_mode">form</field>
232-
<field name="view_id" ref="view_batch_approval_wizard_form" />
233-
<field name="target">new</field>
225+
<field name="model_id" ref="model_spp_change_request" />
234226
<field name="binding_model_id" ref="model_spp_change_request" />
235227
<field name="binding_view_types">list</field>
236-
<field name="context">{'default_action_type': 'reject'}</field>
228+
<field name="state">code</field>
229+
<field name="code">
230+
action = env["spp.cr.batch.approval.wizard"].with_context(active_ids=records.ids).create_from_selection("reject")
231+
</field>
237232
</record>
238233

239-
<record id="action_batch_request_changes" model="ir.actions.act_window">
234+
<record id="server_action_batch_request_changes" model="ir.actions.server">
240235
<field name="name">Batch Request Changes</field>
241-
<field name="res_model">spp.cr.batch.approval.wizard</field>
242-
<field name="view_mode">form</field>
243-
<field name="view_id" ref="view_batch_approval_wizard_form" />
244-
<field name="target">new</field>
236+
<field name="model_id" ref="model_spp_change_request" />
245237
<field name="binding_model_id" ref="model_spp_change_request" />
246238
<field name="binding_view_types">list</field>
247-
<field name="context">{'default_action_type': 'revision'}</field>
239+
<field name="state">code</field>
240+
<field name="code">
241+
action = env["spp.cr.batch.approval.wizard"].with_context(active_ids=records.ids).create_from_selection("revision")
242+
</field>
248243
</record>
249244
</odoo>

spp_change_request_v2/wizards/batch_approval_wizard.py

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -79,44 +79,60 @@ class SPPCRBatchApprovalWizard(models.TransientModel):
7979
)
8080

8181
# ══════════════════════════════════════════════════════════════════════════
82-
# DEFAULT
82+
# CREATION
8383
# ══════════════════════════════════════════════════════════════════════════
8484

8585
@api.model
86-
def default_get(self, fields_list):
87-
"""Initialize wizard with selected change requests."""
88-
res = super().default_get(fields_list)
86+
def create_from_selection(self, action_type="approve"):
87+
"""Create and populate wizard from selected change requests.
8988
89+
Called by server actions to create a saved wizard record before
90+
opening the form, so that One2many operations (delete lines, etc.)
91+
work without 'Please save your changes first' errors.
92+
"""
9093
active_ids = self.env.context.get("active_ids", [])
91-
if active_ids and "line_ids" in fields_list:
92-
crs = self.env["spp.change.request"].browse(active_ids)
93-
# Prefetch all needed fields in a single query to avoid N+1
94-
crs.read(["approval_state", "can_approve", "display_state"])
95-
lines = []
96-
for cr in crs:
97-
can_process = cr.approval_state == "pending" and cr.can_approve
98-
lines.append(
99-
Command.create(
100-
{
101-
"change_request_id": cr.id,
102-
"can_process": can_process,
103-
"error_message": "" if can_process else self._get_error_message(cr),
104-
}
105-
)
94+
if not active_ids:
95+
raise UserError(_("No change requests selected."))
96+
97+
crs = self.env["spp.change.request"].browse(active_ids)
98+
crs.read(["approval_state", "can_approve", "display_state"])
99+
100+
lines = []
101+
for change_request in crs:
102+
can_process = change_request.approval_state == "pending" and change_request.can_approve
103+
lines.append(
104+
Command.create(
105+
{
106+
"change_request_id": change_request.id,
107+
"can_process": can_process,
108+
"error_message": "" if can_process else self._get_error_message(change_request),
109+
}
106110
)
107-
res["line_ids"] = lines
111+
)
108112

109-
return res
113+
wizard = self.create(
114+
{
115+
"action_type": action_type,
116+
"line_ids": lines,
117+
}
118+
)
110119

111-
def _get_error_message(self, cr):
112-
"""Get error message explaining why CR cannot be processed.
120+
return {
121+
"type": "ir.actions.act_window",
122+
"name": _("Batch Approval"),
123+
"res_model": "spp.cr.batch.approval.wizard",
124+
"res_id": wizard.id,
125+
"view_mode": "form",
126+
"view_id": self.env.ref("spp_change_request_v2.view_batch_approval_wizard_form").id,
127+
"target": "new",
128+
}
113129

114-
Note: Plain strings are used here instead of _() to avoid translation
115-
context issues during default_get() in test scenarios.
116-
"""
117-
if cr.approval_state != "pending":
118-
return f"Not pending approval (state: {cr.display_state})"
119-
if not cr.can_approve:
130+
@staticmethod
131+
def _get_error_message(change_request):
132+
"""Get error message explaining why CR cannot be processed."""
133+
if change_request.approval_state != "pending":
134+
return f"Not pending approval (state: {change_request.display_state})"
135+
if not change_request.can_approve:
120136
return "You are not authorized to approve this request"
121137
return ""
122138

@@ -311,11 +327,6 @@ class SPPCRBatchApprovalLine(models.TransientModel):
311327
)
312328
result_message = fields.Char()
313329

314-
def action_remove_line(self):
315-
"""Remove this line from the batch wizard."""
316-
self.ensure_one()
317-
self.unlink()
318-
319330
@api.depends("change_request_id")
320331
def _compute_document_count(self):
321332
# Batch prefetch document_ids to avoid N+1 queries

0 commit comments

Comments
 (0)