Files
Odoo-Modules/fusion_helpdesk/static/src/js/fusion_helpdesk_systray.js
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

62 lines
2.0 KiB
JavaScript

/** @odoo-module **/
// Fusion Helpdesk — top systray icon with an unread-reply badge.
// Sequence 99 places it just left of the attendance check-in button.
import { Component, useState, onWillStart, onWillUnmount } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { rpc } from "@web/core/network/rpc";
import { useService } from "@web/core/utils/hooks";
import { FusionHelpdeskDialog } from "./fusion_helpdesk_dialog";
const POLL_MS = 120000; // refresh the unread badge every 2 minutes
class FusionHelpdeskSystray extends Component {
static template = "fusion_helpdesk.SystrayItem";
static props = {};
setup() {
this.dialog = useService("dialog");
this.state = useState({ unread: 0 });
onWillStart(async () => {
await this._refreshUnread();
});
// Poll so a reply that lands while the user is working still
// surfaces without a page reload. Errors are swallowed server-side
// (the endpoint always returns a count) so the badge never breaks.
this._timer = setInterval(() => this._refreshUnread(), POLL_MS);
onWillUnmount(() => clearInterval(this._timer));
}
async _refreshUnread() {
try {
const res = await rpc("/fusion_helpdesk/unread_count", {});
this.state.unread = (res && res.count) || 0;
} catch {
// Network/config hiccup — leave the badge as-is, don't throw.
}
}
onClick() {
// If there are unread replies, drop straight into the inbox;
// otherwise open the New report form (the primary action).
const initialTab = this.state.unread > 0 ? "list" : "new";
this.dialog.add(
FusionHelpdeskDialog,
{ initialTab },
{ onClose: () => this._refreshUnread() }
);
}
}
export const fusionHelpdeskSystrayItem = {
Component: FusionHelpdeskSystray,
};
registry
.category("systray")
.add("fusion_helpdesk.report_button", fusionHelpdeskSystrayItem, {
sequence: 99,
});