feat(fusion_helpdesk_central): client-label field, views, branded ack email
x_fc_client_label on helpdesk.ticket (+ list/search exposure), create() override sends a branded acknowledgement (magic link via get_portal_url) for in-app-channel tickets only. 3 Enterprise tests (run on nexa). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
# License OPL-1
|
# License OPL-1
|
||||||
{
|
{
|
||||||
'name': 'Fusion Helpdesk Central — Client API Keys',
|
'name': 'Fusion Helpdesk Central — Client API Keys',
|
||||||
'version': '19.0.1.0.2',
|
'version': '19.0.1.1.0',
|
||||||
'category': 'Productivity',
|
'category': 'Productivity',
|
||||||
'summary': 'Admin UI on the central Odoo for issuing per-client API '
|
'summary': 'Admin UI on the central Odoo for issuing per-client API '
|
||||||
'keys used by fusion_helpdesk client deployments.',
|
'keys used by fusion_helpdesk client deployments.',
|
||||||
@@ -28,7 +28,9 @@ Depends only on `helpdesk`. No client-side install needed.
|
|||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'data/ir_config_parameter_data.xml',
|
'data/ir_config_parameter_data.xml',
|
||||||
|
'data/mail_template_ack.xml',
|
||||||
'views/fusion_helpdesk_client_key_views.xml',
|
'views/fusion_helpdesk_client_key_views.xml',
|
||||||
|
'views/helpdesk_ticket_views.xml',
|
||||||
],
|
],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'auto_install': False,
|
'auto_install': False,
|
||||||
|
|||||||
55
fusion_helpdesk_central/data/mail_template_ack.xml
Normal file
55
fusion_helpdesk_central/data/mail_template_ack.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2026 Nexa Systems Inc.
|
||||||
|
License OPL-1
|
||||||
|
|
||||||
|
Branded acknowledgement sent to the customer when an in-app-channel
|
||||||
|
ticket is created. Carries the portal magic link (object.get_portal_url()
|
||||||
|
embeds the access token) so the customer can track + reply without an
|
||||||
|
account. Button colours follow the company email branding, like Odoo's
|
||||||
|
own helpdesk templates.
|
||||||
|
-->
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<record id="mail_template_ticket_ack" model="mail.template">
|
||||||
|
<field name="name">Helpdesk: Ticket Acknowledgement (Fusion)</field>
|
||||||
|
<field name="model_id" ref="helpdesk.model_helpdesk_ticket"/>
|
||||||
|
<field name="subject">We received your request [{{ object.ticket_ref or object.id }}]</field>
|
||||||
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
||||||
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
||||||
|
<field name="auto_delete" eval="True"/>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<div style="margin:0; padding:0; font-family:Arial, Helvetica, sans-serif; color:#21252b; font-size:14px;">
|
||||||
|
<p>Hello <t t-out="object.partner_name or 'there'"/>,</p>
|
||||||
|
<p>
|
||||||
|
Thanks for reaching out — we've received your request and our
|
||||||
|
support team will be in touch. Here are the details:
|
||||||
|
</p>
|
||||||
|
<table style="margin:12px 0; font-size:14px;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding:2px 12px 2px 0; color:#6c757d;">Reference</td>
|
||||||
|
<td style="padding:2px 0;"><strong t-out="object.ticket_ref or object.id"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:2px 12px 2px 0; color:#6c757d;">Subject</td>
|
||||||
|
<td style="padding:2px 0;"><t t-out="object.name or ''"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p style="margin:18px 0;">
|
||||||
|
<a t-att-href="object.get_base_url() + object.get_portal_url()"
|
||||||
|
target="_blank"
|
||||||
|
t-attf-style="background-color: {{ object.company_id.email_secondary_color or '#2c89e9' }}; padding:10px 18px; text-decoration:none; color: {{ object.company_id.email_primary_color or '#ffffff' }}; border-radius:5px; font-size:14px; display:inline-block;">
|
||||||
|
View & track your ticket
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p style="color:#6c757d; font-size:13px;">
|
||||||
|
You can reply directly to this email to add information, follow up
|
||||||
|
from the link above, or sign up for an account from that page to
|
||||||
|
manage all of your requests in one place.
|
||||||
|
</p>
|
||||||
|
<p style="color:#6c757d; font-size:13px;">— <t t-out="object.company_id.name or 'Support'"/></p>
|
||||||
|
</div>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from . import fusion_helpdesk_client_key
|
from . import fusion_helpdesk_client_key
|
||||||
|
from . import helpdesk_ticket
|
||||||
|
|||||||
57
fusion_helpdesk_central/models/helpdesk_ticket.py
Normal file
57
fusion_helpdesk_central/models/helpdesk_ticket.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2026 Nexa Systems Inc.
|
||||||
|
# License OPL-1
|
||||||
|
"""Central-side helpdesk.ticket extensions for the customer follow-up flow.
|
||||||
|
|
||||||
|
Adds the `x_fc_client_label` deployment tag (set by the in-app reporter so
|
||||||
|
the embedded inbox can scope per client) and sends a branded acknowledgement
|
||||||
|
email — carrying the portal magic link — when an in-app ticket is created.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HelpdeskTicket(models.Model):
|
||||||
|
_inherit = 'helpdesk.ticket'
|
||||||
|
|
||||||
|
x_fc_client_label = fields.Char(
|
||||||
|
string='Client Deployment', index=True, copy=False,
|
||||||
|
help='Deployment tag (e.g. ENTECH) set by the fusion_helpdesk in-app '
|
||||||
|
'reporter. Scopes the embedded "My Tickets" inbox per client and '
|
||||||
|
'lets support filter tickets by originating deployment.',
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
tickets = super().create(vals_list)
|
||||||
|
tickets._fc_send_ack_email()
|
||||||
|
return tickets
|
||||||
|
|
||||||
|
def _fc_send_ack_email(self):
|
||||||
|
"""Send the branded acknowledgement (with magic link) to the customer.
|
||||||
|
|
||||||
|
Only fires for in-app-channel tickets (those tagged with a client
|
||||||
|
label) that have a customer email — external web-form submissions
|
||||||
|
rely on the native website confirmation, so this won't double-send.
|
||||||
|
The whole thing is best-effort: a template/mail failure must never
|
||||||
|
block ticket creation, so we log and move on.
|
||||||
|
"""
|
||||||
|
template = self.env.ref(
|
||||||
|
'fusion_helpdesk_central.mail_template_ticket_ack',
|
||||||
|
raise_if_not_found=False,
|
||||||
|
)
|
||||||
|
if not template:
|
||||||
|
return
|
||||||
|
for ticket in self:
|
||||||
|
if not (ticket.x_fc_client_label and ticket.partner_email):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
template.send_mail(ticket.id, force_send=False)
|
||||||
|
except Exception: # noqa: BLE001 — ack must never block create
|
||||||
|
_logger.exception(
|
||||||
|
'fusion_helpdesk_central: acknowledgement email failed '
|
||||||
|
'for ticket %s (%s)', ticket.id, ticket.x_fc_client_label,
|
||||||
|
)
|
||||||
2
fusion_helpdesk_central/tests/__init__.py
Normal file
2
fusion_helpdesk_central/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import test_identity
|
||||||
68
fusion_helpdesk_central/tests/test_identity.py
Normal file
68
fusion_helpdesk_central/tests/test_identity.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2026 Nexa Systems Inc.
|
||||||
|
# License OPL-1
|
||||||
|
"""Identity-keystone tests for the central helpdesk extensions.
|
||||||
|
|
||||||
|
Runs on an Enterprise environment (helpdesk installed) — e.g. odoo-nexa or
|
||||||
|
odoo-trial. Validates that passing partner_email resolves the customer +
|
||||||
|
follower (native), that the client label is stored, and that the branded
|
||||||
|
acknowledgement only fires for in-app-channel tickets.
|
||||||
|
"""
|
||||||
|
from odoo.tests import TransactionCase, tagged
|
||||||
|
|
||||||
|
|
||||||
|
@tagged('post_install', '-at_install', 'fusion_helpdesk_central')
|
||||||
|
class TestTicketIdentity(TransactionCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.team = cls.env['helpdesk.team'].search([], limit=1)
|
||||||
|
|
||||||
|
def _ack_mails(self, ticket):
|
||||||
|
return self.env['mail.mail'].search([
|
||||||
|
('model', '=', 'helpdesk.ticket'),
|
||||||
|
('res_id', '=', ticket.id),
|
||||||
|
]).filtered(lambda m: 'received your request' in (m.subject or ''))
|
||||||
|
|
||||||
|
def test_partner_resolution_follower_and_label(self):
|
||||||
|
ticket = self.env['helpdesk.ticket'].create({
|
||||||
|
'name': 'Keystone test',
|
||||||
|
'team_id': self.team.id,
|
||||||
|
'partner_email': 'keystone.newperson@example.com',
|
||||||
|
'partner_name': 'Key Stone',
|
||||||
|
'x_fc_client_label': 'ENTECH',
|
||||||
|
})
|
||||||
|
self.assertEqual(ticket.x_fc_client_label, 'ENTECH')
|
||||||
|
self.assertTrue(
|
||||||
|
ticket.partner_id,
|
||||||
|
"native create() should find-or-create a partner from partner_email")
|
||||||
|
self.assertEqual(ticket.partner_id.email, 'keystone.newperson@example.com')
|
||||||
|
self.assertIn(
|
||||||
|
ticket.partner_id, ticket.message_partner_ids,
|
||||||
|
"the customer must be subscribed as a follower")
|
||||||
|
|
||||||
|
def test_ack_email_for_inapp_ticket(self):
|
||||||
|
ticket = self.env['helpdesk.ticket'].create({
|
||||||
|
'name': 'Ack test',
|
||||||
|
'team_id': self.team.id,
|
||||||
|
'partner_email': 'ack.person@example.com',
|
||||||
|
'partner_name': 'Ack Person',
|
||||||
|
'x_fc_client_label': 'ENTECH',
|
||||||
|
})
|
||||||
|
self.assertTrue(
|
||||||
|
self._ack_mails(ticket),
|
||||||
|
"an in-app ticket with a customer email should get our ack email")
|
||||||
|
|
||||||
|
def test_no_ack_without_client_label(self):
|
||||||
|
# Simulates an external web-form ticket — no client label, so our
|
||||||
|
# acknowledgement must NOT fire (avoids double-acknowledgement).
|
||||||
|
ticket = self.env['helpdesk.ticket'].create({
|
||||||
|
'name': 'External web ticket',
|
||||||
|
'team_id': self.team.id,
|
||||||
|
'partner_email': 'external.web@example.com',
|
||||||
|
'partner_name': 'External Web',
|
||||||
|
})
|
||||||
|
self.assertFalse(
|
||||||
|
self._ack_mails(ticket),
|
||||||
|
"no client label => our acknowledgement should not send")
|
||||||
34
fusion_helpdesk_central/views/helpdesk_ticket_views.xml
Normal file
34
fusion_helpdesk_central/views/helpdesk_ticket_views.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2026 Nexa Systems Inc.
|
||||||
|
License OPL-1
|
||||||
|
Surface the client-deployment tag in the agent backend so support can
|
||||||
|
see + filter tickets by the deployment they came from.
|
||||||
|
-->
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="fhc_ticket_list_label" model="ir.ui.view">
|
||||||
|
<field name="name">fhc.helpdesk.ticket.list.client_label</field>
|
||||||
|
<field name="model">helpdesk.ticket</field>
|
||||||
|
<field name="inherit_id" ref="helpdesk.helpdesk_tickets_view_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//list" position="inside">
|
||||||
|
<field name="x_fc_client_label" optional="show"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="fhc_ticket_search_label" model="ir.ui.view">
|
||||||
|
<field name="name">fhc.helpdesk.ticket.search.client_label</field>
|
||||||
|
<field name="model">helpdesk.ticket</field>
|
||||||
|
<field name="inherit_id" ref="helpdesk.helpdesk_tickets_view_search"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//search" position="inside">
|
||||||
|
<field name="x_fc_client_label"/>
|
||||||
|
<filter string="Client Deployment" name="group_client_label"
|
||||||
|
context="{'group_by': 'x_fc_client_label'}"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user