Ships the design spec at docs/superpowers/specs/2026-05-27-owner-approval-flow-design.md.
What's new on central (fusion_helpdesk_central 19.0.1.2.0 -> 19.0.2.0.0):
- Engagement model: 8 new fields on helpdesk.ticket (state, snapshotted
owner email/name, single-use UUID4 token, sent/reminded/decided
timestamps, AI summary, stored-computed turnaround hours).
- Wizard: single + bulk modes on one fusion.helpdesk.engagement.wizard
TransientModel with a child wizard.line for per-ticket bulk summaries.
default_get pulls the OpenAI summary on open; AI fan-out for bulk is
parallel via ThreadPoolExecutor (max 5 workers, 30s overall cap).
- OpenAI client in utils.py — stdlib urllib, 15s per-call timeout, every
failure collapses to '' so the wizard's manual-summary fallback fires.
- Public portal: /fusion_helpdesk/engagement/<token>/<decision> GET +
POST, four branded standalone QWeb pages (confirm/done/invalid/error).
Token is single-use, cleared on confirm. Decision posts a public
comment attributed to the resolved owner partner; chatter propagates
to the employee's My Tickets thread per the "fully visible" UX choice.
- Mail templates (single + bulk) with magic-link buttons. Bulk template
renders one card per ticket, each with its own approve/reject URL.
- Reminder cron: daily, single-shot per engagement, configurable via
fusion_helpdesk_central.engagement_reminder_days ICP (default 3, 0
disables).
- Reporting dashboard: pivot/graph/list/kanban over helpdesk.ticket
filtered to engaged ones, with avg-turnaround measure. Menu lives
under Helpdesk > Reporting > Owner Engagements.
- Client_key extended with owner_email/owner_name fields; ticket.create
upserts them from the client-side piggyback (no new sync endpoint).
- 100% coverage on utils + integration tests on wizard, controllers,
re-engagement, cron, computed turnaround. OpenAI mocked in CI.
What's new on client (fusion_helpdesk 19.0.1.7.1 -> 19.0.2.0.0):
- Two new ICP settings: fusion_helpdesk.owner_email / .owner_name with
a new "Owner Approval" block in Settings > Fusion Helpdesk.
- controllers/main.py::submit piggybacks both keys on every ticket
payload so central keeps client_key.owner_email/name fresh
automatically.
Verified live end-to-end on entech -> nexa: payload upsert, wizard with
mocked AI, action_send, portal GET/POST/GET-again cycle, second click
hits the friendly invalid-token page. Token entropy = 122 bits (UUID4).
The OWL dialog used <t t-out="m.body"/> on message bodies, but t-out
escapes plain strings — it only renders raw when the value is a Markup
instance. Bodies arrive over JSON-RPC as plain strings (Markup is a
client-side type, doesn't cross the wire), so the customer was seeing
literal "<p>This has been fixed.</p>" in the thread instead of the
rendered HTML.
Wrap incoming bodies in `markup()` at the boundary (openTicket +
sendReply call sites) so the template renders them as the sanitised
HTML the central chatter already produced. Trust is fine — the body is
sanitised server-side by mail.thread before it ever leaves nexa.
Bumps fusion_helpdesk to 19.0.1.7.1.
Three coordinated changes on top of the section grouping:
1. **Mark as Critical** — a red chip on the New tab sets priority='3'
when submitted. The central post-create hook auto-applies a "Critical"
helpdesk.tag (shipped via fusion_helpdesk_central data XML, noupdate=1
so support can recolor without losing it on upgrade), giving support
a kanban-groupable signal that doesn't rely on remembering what
priority='3' means. Scoped to in-app-channel tickets only, so a
support agent manually setting Urgent on their own ticket isn't
silently tagged.
2. **KPI cards above the sections** — Total / Open / Closed / Critical
in a 4-up grid (auto-collapses to 2x2 under 540px). Each card uses
its own saturated gradient so it reads on both light and dark mode —
the dialog backdrop is irrelevant because the gradient brings its
own background. Counts are computed in JS from state.tickets so they
always match what's rendered below.
3. **Colored stage pills** — red Critical, green Solved, dark-yellow New,
orange Cancelled, blue for In Progress / Testing / On Hold. Critical
priority gets a *separate* red pill alongside the stage pill so you
keep stage info even on escalated tickets. Stage matching is
substring-based (lowercased) so a renamed "Resolved" or "Done" stage
on central still maps to the green pill.
Tests cover the new is_critical=True → priority='3' wiring and the
default omission so SLA / stage defaults keep working for normal
tickets. Bumps fusion_helpdesk to 19.0.1.7.0 and
fusion_helpdesk_central to 19.0.1.2.0. End-to-end smoke test verified
live: priority=3 + x_fc_client_label triggers the Critical tag.
The flat write_date-sorted list was hard to scan with 50+ tickets — solved
ones were intermixed with active ones, and there was no signal for
priority. Bucket each ticket server-side into 'critical' (open + priority
High/Urgent), 'solved' (stage marked fold=True on central) or 'open'
(everything else), and render three labelled sections in the dialog with
sticky headers, count badges, and per-group accent colours. Backend keeps
its write_date desc order so latest is always at top within each bucket.
Bucketing uses helpdesk.stage.fold (not the stage name) so renaming
"Solved" to "Done" on the central won't quietly mis-categorise rows.
Adds bucket_ticket() in utils.py with unit tests covering the
folded-wins-over-priority precedence and the missing-priority fallback.
Also surfaces a small Urgent (triangle) / High (arrow) icon on each row
so a critical ticket reads at a glance even after a user scrolls past
the section header.
Bumps fusion_helpdesk to 19.0.1.6.0.
The customer-followup ship left two papercuts that hid 51 historical
tickets from the entech owner:
1. group_reporter_admin had zero members on install — the new XML record
created the group but never granted it. Extend base.group_system's
implied_ids so every system administrator transparently inherits the
admin view of the embedded inbox on install / upgrade. (4, id) tuple
is additive — never replaces base's existing implications.
2. Tickets created before this feature shipped had NULL
x_fc_client_label and NULL partner_email, so the scope filter
excluded them all. The reporter identity was still recoverable from
the description HTML's diag block. Backfill recipe is captured in
CLAUDE.md so future deployments can apply the same one-shot UPDATE
without re-deriving the regex.
Bumps fusion_helpdesk to 19.0.1.5.0. Verified live on entech: all six
base.group_system members now return True for
has_group('fusion_helpdesk.group_reporter_admin').
Squash-merge of feat/helpdesk-customer-followup. The billing and
fusion_login_audit work from that branch is already on main (landed
separately); this lands only the helpdesk feature.
- Identity keystone: submit() forwards partner_email/partner_name/
x_fc_client_label so the central Helpdesk find-or-creates the customer
partner and subscribes them as a follower (enables reply emails + magic link).
- Embedded in-app 'My Tickets' inbox: server-side scoped read/reply RPC
endpoints, per-user seen tracking (fusion.helpdesk.ticket.seen), systray
unread badge. Defense-in-depth scope domain + _norm_email normalisation
(wildcard emails cannot widen scope).
- fusion_helpdesk_central: x_fc_client_label field + list/search views +
branded acknowledgement email template.
- Deployed and smoke-tested live: nexa central 19.0.1.1.0, entech client
19.0.1.4.1 (requires Contact Creation on the central service account).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>