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>
62 lines
2.0 KiB
JavaScript
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,
|
|
});
|