fix: workflow editor Phase 5/6 sign-off — save guard, conflict detection, dispatch pre-check

- Save button disabled for non-admins (canSave prop threaded through
  WorkflowEditor → WorkflowCanvas → WorkflowCanvasToolbar); tooltip
  explains why when hovered
- Optimistic concurrency: migration 072 adds updated_at to
  workflow_definitions; PUT /workflows/:id returns 409 when client sends
  a stale updated_at; frontend shows a specific reload-prompt toast
- _legacy_dispatch now routes through dispatch_order_line_render instead
  of calling render_order_line_task directly, so cancelled/rejected
  order lines are skipped before queueing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 11:39:02 +02:00
co-authored by Claude Sonnet 4.6
parent 53b19f2ba1
commit e8b4580608
9 changed files with 71 additions and 10 deletions
@@ -284,6 +284,7 @@ async def _workflow_to_out(db: AsyncSession, wf: WorkflowDefinition) -> Workflow
),
is_active=wf.is_active,
created_at=wf.created_at,
updated_at=wf.updated_at,
)
@@ -938,6 +939,15 @@ async def update_workflow(
if not wf:
raise HTTPException(status_code=404, detail="Workflow definition not found")
if body.updated_at is not None:
stored_ts = wf.updated_at.replace(tzinfo=None) if wf.updated_at.tzinfo else wf.updated_at
client_ts = body.updated_at.replace(tzinfo=None) if body.updated_at.tzinfo else body.updated_at
if abs((stored_ts - client_ts).total_seconds()) > 1:
raise HTTPException(
status_code=409,
detail="Workflow was modified by someone else. Reload and try again.",
)
if body.name is not None:
wf.name = body.name
if body.config is not None: