Files
Odoo-Modules/fusion_helpdesk/models/fusion_helpdesk_ticket_seen.py
gsinghpal 6c15a7b1cf feat(fusion_helpdesk): customer follow-up + embedded ticket inbox
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>
2026-05-27 09:23:33 -04:00

67 lines
2.4 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1
"""Per-user read-tracking for the embedded ticket inbox.
Stores ONLY metadata — which central ticket a user has seen and up to
which message id. No ticket content is replicated locally; this exists
purely so the systray unread badge can work without re-fetching the
whole inbox on every page load. Tickets themselves remain a live RPC
view of the central Odoo.
"""
from odoo import api, fields, models
class FusionHelpdeskTicketSeen(models.Model):
_name = 'fusion.helpdesk.ticket.seen'
_description = 'Fusion Helpdesk — per-user read tracking (metadata only)'
user_id = fields.Many2one(
'res.users', required=True, index=True, ondelete='cascade',
default=lambda self: self.env.uid,
)
central_ticket_id = fields.Integer(
string='Central Ticket ID', required=True, index=True,
help='helpdesk.ticket id on the central Odoo.',
)
last_seen_message_id = fields.Integer(
string='Last Seen Message ID', default=0,
help='Highest central mail.message id this user has viewed for '
'the ticket. Drives the unread badge.',
)
_user_ticket_uniq = models.Constraint(
'UNIQUE(user_id, central_ticket_id)',
'One seen-row per user per ticket.',
)
@api.model
def _mark_seen(self, central_ticket_id, last_message_id):
"""Upsert the current user's last-seen marker for a ticket.
Monotonic — never moves the marker backwards (a stale client
reporting an older id can't resurrect an unread badge)."""
rec = self.search([
('user_id', '=', self.env.uid),
('central_ticket_id', '=', central_ticket_id),
], limit=1)
if rec:
if (last_message_id or 0) > rec.last_seen_message_id:
rec.last_seen_message_id = last_message_id
else:
self.create({
'central_ticket_id': central_ticket_id,
'last_seen_message_id': last_message_id or 0,
})
return True
@api.model
def _seen_map(self, central_ticket_ids):
"""Return {central_ticket_id: last_seen_message_id} for the
current user across the given ticket ids."""
rows = self.search([
('user_id', '=', self.env.uid),
('central_ticket_id', 'in', list(central_ticket_ids)),
])
return {r.central_ticket_id: r.last_seen_message_id for r in rows}