feat(fusion_helpdesk): embedded ticket inbox UI + unread badge

Dialog gains New/My Tickets tabs, ticket list (with admin Mine/All toggle + unread dots), thread view with inline reply, confirmed reply-email field, and an Open-full-ticket magic link. Systray shows an unread-reply badge (polled). Controller returns portal_url + per-message attachment counts. Assets compile clean (light+dark).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-27 03:32:48 -04:00
parent 304e19ce45
commit b6e7ad3485
6 changed files with 566 additions and 115 deletions

View File

@@ -442,7 +442,8 @@ class FusionHelpdeskController(http.Controller):
[[('model', '=', 'helpdesk.ticket'),
('res_id', '=', ticket_id),
('message_type', 'in', ['comment', 'email'])]],
{'fields': ['id', 'date', 'body', 'author_id', 'subtype_id'],
{'fields': ['id', 'date', 'body', 'author_id', 'subtype_id',
'attachment_ids'],
'order': 'id asc'},
)
internal = self._internal_subtype_map(
@@ -456,6 +457,7 @@ class FusionHelpdeskController(http.Controller):
'body': m['body'] or '',
'author': (m['author_id'][1] if m['author_id'] else ''),
'author_id': (m['author_id'][0] if m['author_id'] else False),
'attachment_count': len(m.get('attachment_ids') or []),
'subtype_is_internal': internal.get(st[0], False) if st else False,
}
if is_public_message(msg):
@@ -549,7 +551,8 @@ class FusionHelpdeskController(http.Controller):
) + [('id', '=', ticket_id)]
try:
found = self._rpc(cfg, 'helpdesk.ticket', 'search_read', [domain],
{'fields': ['id', 'name', 'stage_id'], 'limit': 1})
{'fields': ['id', 'name', 'stage_id',
'access_token'], 'limit': 1})
if not found:
return {'ok': False, 'error': 'not_found',
'message': _('Ticket not found or not accessible.')}
@@ -560,9 +563,16 @@ class FusionHelpdeskController(http.Controller):
request.env['fusion.helpdesk.ticket.seen']._mark_seen(
ticket_id, max(m['id'] for m in messages))
t = found[0]
# Magic link: the customer's own access-token URL on central, so they
# can open the full ticket (incl. attachments) in the portal if needed.
portal_url = ''
if t.get('access_token'):
portal_url = '%s/my/ticket/%s/%s' % (
cfg['url'].rstrip('/'), t['id'], t['access_token'])
return {'ok': True, 'ticket': {
'id': t['id'], 'subject': t['name'],
'stage': t['stage_id'][1] if t['stage_id'] else '',
'portal_url': portal_url,
'messages': messages}}
@http.route('/fusion_helpdesk/ticket/<int:ticket_id>/reply',