fix(simple-editor): HTML in chatter + library form + expand per-step inline edit

Three fixes from user feedback:

1. Chatter posting raw HTML
   _AUDIT_BODY in migration 19.0.18.8.0 was a plain str with <p>
   tags. message_post escaped it for safety, so the chatter pill
   rendered '<p><strong>...</strong></p>' literally to the recipe
   author. Wrapped in markupsafe.Markup so Odoo recognises it as
   safe HTML. Going forward: ANY message_post body containing HTML
   tags MUST be wrapped in Markup() — most callers already do this,
   the migration script was the outlier.

2. Library template editor showed raw <p> tags
   onOpenLibraryEdit was JSON-cloning the payload directly without
   running description through the existing _htmlToText helper that
   the per-step editor uses. Added the conversion. Save path
   (onSaveLibraryEditor + library_save) already wraps via
   _textToHtml so storage stays HTML-compatible.

3. Per-step inline form was missing critical fields — user had to
   delete + re-add a step to change Type/workflow trigger/parallel/signoff
   onToggleEdit now also captures default_kind, triggers_workflow_state_id,
   parallel_start, requires_signoff into the edit state. onSaveStep
   sends them in the write vals. Added _fpResetStepEdit helper to
   keep open/cancel/save reset paths in sync.

   New per-step form has:
     * Step Type (Default Kind) dropdown — drives workflow milestone
       triggers + step-kind routing (e.g. contract_review opens QA-005)
     * Triggers Workflow State dropdown (Sub 14) — per-step override
     * Parallel Start checkbox (Sub 13)
     * Require QA Sign-off checkbox

   step_write controller endpoint also gained a field whitelist —
   was previously accepting any vals dict from the client (security
   hole + opaque to maintainers).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-04 00:24:40 -04:00
parent d6bd43b76e
commit 3cc393454d
5 changed files with 159 additions and 14 deletions

View File

@@ -102,6 +102,15 @@ class SimpleRecipeController(http.Controller):
'work_center_id': step.work_center_id.id if step.work_center_id else False,
'source_template_id': step.source_template_id.id or False,
'collect_measurements': bool(step.collect_measurements),
# Sub 13 — per-step opt-out of the sequential gate
'parallel_start': bool(step.parallel_start),
# Sub 14 — workflow milestone trigger override
'triggers_workflow_state_id': (
step.triggers_workflow_state_id.id
if 'triggers_workflow_state_id' in step._fields
and step.triggers_workflow_state_id
else False
),
'measurements_badge_text': badge_text,
'measurements_badge_class': badge_class,
'inputs': [
@@ -437,8 +446,29 @@ class SimpleRecipeController(http.Controller):
@http.route('/fp/simple_recipe/step/write', type='jsonrpc', auth='user')
def step_write(self, node_id, vals):
node = request.env['fusion.plating.process.node'].browse(node_id)
node.write(vals)
"""Update fields on an existing recipe step (operation node).
Whitelisted to the fields the inline edit panel actually surfaces
— never trust client-provided node_type / parent_id / etc.
"""
node = request.env['fusion.plating.process.node'].browse(int(node_id))
if not node.exists():
return {'ok': False, 'error': 'not_found'}
node.check_access('write')
allowed = {
'name', 'description', 'icon',
'default_kind',
'requires_signoff', 'requires_predecessor_done',
'parallel_start', # Sub 13
'triggers_workflow_state_id', # Sub 14
'requires_rack_assignment',
'requires_transition_form',
'estimated_duration',
'collect_measurements',
}
clean = {k: v for k, v in (vals or {}).items() if k in allowed}
if clean:
node.write(clean)
return {'ok': True}
@http.route('/fp/simple_recipe/step/remove', type='jsonrpc', auth='user')