From 32c7026558a1fd3215bc8da5fec6fd80e1426e1c Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Wed, 27 May 2026 15:26:26 -0400 Subject: [PATCH] =?UTF-8?q?feat(fusion=5Fhelpdesk=5Fcentral):=20owner=20em?= =?UTF-8?q?ail=20shows=203=20sections=20=E2=80=94=20Request=20/=20Reply=20?= =?UTF-8?q?/=20Summary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The owner only saw the AI summary, which was a paraphrase of the user report — they couldn't see the actual request OR what we said back. Restructure the engagement email into three sections so the owner can read the conversation and not just the AI's take: 1. Original Request (from the reporter) — ticket.description, no longer buried in a
collapsible at the bottom 2. Our Reply — the wizard's "Your Findings" text, now persisted on the ticket so the email template can render it directly. This is the engineer's analysis / response to the request. 3. Summary for the Decision — the AI-generated brief Approve / Reject buttons stay below all three. Bulk email mirrors the same per-card structure. New ticket field x_fc_engagement_findings (Text, copy=False) stores the findings at send-time so they survive as audit history. Wizard's _action_send_single / _action_send_bulk pass findings into _fc_reset_engagement; bulk uses per-line findings + per-line summary. Mail templates are in so a plain -u doesn't re-import them. Pre-migration in migrations/19.0.2.4.0/pre-migration.py deletes the existing template records + ir_model_data so the upgrade's data load re-creates them with the new body_html. Pre- (not post-) because data load happens between the two phases. Smoke-tested live on nexa: rendered template HTML contains all three section headers at the expected positions with their expected content markers (ORIGINAL FROM RIYA in Original Request, REPLY-FROM-GURPREET in Our Reply, the summary text in Summary for the Decision). Bumps fusion_helpdesk_central to 19.0.2.4.0. --- fusion_helpdesk_central/__manifest__.py | 2 +- .../data/mail_template_engagement.xml | 79 ++++++++++++++----- .../migrations/19.0.2.4.0/pre-migration.py | 68 ++++++++++++++++ .../models/engagement_wizard.py | 14 +++- .../models/helpdesk_ticket.py | 17 +++- 5 files changed, 154 insertions(+), 26 deletions(-) create mode 100644 fusion_helpdesk_central/migrations/19.0.2.4.0/pre-migration.py diff --git a/fusion_helpdesk_central/__manifest__.py b/fusion_helpdesk_central/__manifest__.py index 004e39f8..c3ac1091 100644 --- a/fusion_helpdesk_central/__manifest__.py +++ b/fusion_helpdesk_central/__manifest__.py @@ -3,7 +3,7 @@ # License OPL-1 { 'name': 'Fusion Helpdesk Central — Client API Keys', - 'version': '19.0.2.3.4', + 'version': '19.0.2.4.0', 'category': 'Productivity', 'summary': 'Admin UI on the central Odoo for issuing per-client API ' 'keys used by fusion_helpdesk client deployments.', diff --git a/fusion_helpdesk_central/data/mail_template_engagement.xml b/fusion_helpdesk_central/data/mail_template_engagement.xml index d139e2c3..61073537 100644 --- a/fusion_helpdesk_central/data/mail_template_engagement.xml +++ b/fusion_helpdesk_central/data/mail_template_engagement.xml @@ -48,22 +48,46 @@

Your team at has filed a request that needs your sign-off before - our team proceeds. A quick AI-prepared summary is - below; the full thread expands at the bottom if you - want the detail. + our team proceeds. The original request, our reply, + and a quick AI-prepared summary are below.

-
-
Summary
-
+
+
-
-
Request
-
+ +
+
+ Original Request — from +
+
+ +
- + +
+
+ Our Reply — from Nexa Systems Support +
+
+ +
+
+ + +
+
+ Summary for the Decision +
+
+ +
+
+ +
-
- View original request & full thread -
- -
-
-

This Approve / Reject link is single-use and will stop working once you've clicked it. @@ -129,10 +146,32 @@

Request of
-
-
- +
+ +
+
+ Original Request — from +
+
+ +
+ +
+
Our Reply
+
+ +
+
+ +
+
Summary for the Decision
+
+ +
+
+
diff --git a/fusion_helpdesk_central/migrations/19.0.2.4.0/pre-migration.py b/fusion_helpdesk_central/migrations/19.0.2.4.0/pre-migration.py new file mode 100644 index 00000000..fdbac075 --- /dev/null +++ b/fusion_helpdesk_central/migrations/19.0.2.4.0/pre-migration.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 +"""Force re-import of the engagement mail templates on 19.0.2.4.0. + +The mail templates ship in `` so support's later +tweaks (different reply-to, custom signature, etc.) aren't blown away +by every routine module upgrade. The flip side: a structural change +to the template body in our own XML is silently skipped — the next +`-u` reads the new XML but Odoo refuses to overwrite the existing +row because of the noupdate flag. + +This release introduces the 3-section email layout (Original Request / +Our Reply / Summary) and we DELIBERATELY want it to land. So this +pre-migration deletes the existing template records + their +ir_model_data anchors BEFORE the upgrade's data load runs. The XML +import then re-creates them with the new body_html. + +Pre-migration > post-migration here because data load happens between +the two. If we deleted in post-migration, the data load would already +have skipped the update — too late. +""" +import logging + +_logger = logging.getLogger(__name__) + + +_TEMPLATE_XMLIDS = ( + 'mail_template_engagement', + 'mail_template_engagement_bulk', +) + + +def migrate(cr, version): + cr.execute( + """ + SELECT name, res_id FROM ir_model_data + WHERE module = 'fusion_helpdesk_central' + AND name IN %s + """, + (_TEMPLATE_XMLIDS,), + ) + rows = cr.fetchall() + if not rows: + _logger.info( + 'fusion_helpdesk_central 19.0.2.4.0 pre-migration: no ' + 'existing engagement templates to delete; nothing to do.' + ) + return + template_ids = [r[1] for r in rows] + cr.execute( + "DELETE FROM mail_template WHERE id IN %s", + (tuple(template_ids),), + ) + cr.execute( + """ + DELETE FROM ir_model_data + WHERE module = 'fusion_helpdesk_central' + AND name IN %s + """, + (_TEMPLATE_XMLIDS,), + ) + _logger.info( + 'fusion_helpdesk_central 19.0.2.4.0 pre-migration: dropped %s ' + 'engagement mail template(s) so the upgrade re-imports them ' + 'with the new 3-section layout: %s', + len(template_ids), ', '.join(r[0] for r in rows), + ) diff --git a/fusion_helpdesk_central/models/engagement_wizard.py b/fusion_helpdesk_central/models/engagement_wizard.py index 1ae5e438..d4aaface 100644 --- a/fusion_helpdesk_central/models/engagement_wizard.py +++ b/fusion_helpdesk_central/models/engagement_wizard.py @@ -416,7 +416,10 @@ class FusionHelpdeskEngagementWizard(models.TransientModel): if not email: raise UserError(_('Owner contact disappeared since you opened ' 'the wizard. Refresh and try again.')) - ticket._fc_reset_engagement(email, name, self.ai_summary or '') + ticket._fc_reset_engagement( + email, name, self.ai_summary or '', + findings=self.findings or '', + ) template = self.env.ref( 'fusion_helpdesk_central.mail_template_engagement', raise_if_not_found=False, @@ -442,11 +445,14 @@ class FusionHelpdeskEngagementWizard(models.TransientModel): if not email: raise UserError(_('Owner contact disappeared since you opened ' 'the wizard. Refresh and try again.')) - summary_by_id = {line.ticket_id.id: line.ai_summary or '' - for line in self.line_ids} + by_id = { + line.ticket_id.id: (line.ai_summary or '', line.findings or '') + for line in self.line_ids + } for ticket in self.ticket_ids: + summary, findings = by_id.get(ticket.id, ('', '')) ticket._fc_reset_engagement( - email, name, summary_by_id.get(ticket.id, ''), + email, name, summary, findings=findings, ) template = self.env.ref( 'fusion_helpdesk_central.mail_template_engagement_bulk', diff --git a/fusion_helpdesk_central/models/helpdesk_ticket.py b/fusion_helpdesk_central/models/helpdesk_ticket.py index b1ca7509..e1ce4c72 100644 --- a/fusion_helpdesk_central/models/helpdesk_ticket.py +++ b/fusion_helpdesk_central/models/helpdesk_ticket.py @@ -82,6 +82,14 @@ class HelpdeskTicket(models.Model): help='OpenAI-generated brief shown to the owner in the approval ' 'email. Editable in the wizard before sending; frozen after.', ) + x_fc_engagement_findings = fields.Text( + string='Engagement Findings', copy=False, + help='The support engineer\'s reply / analysis (typed in the ' + 'wizard\'s Findings field). Sent to the owner in the ' + 'approval email alongside the original request and the AI ' + 'summary so they see the back-and-forth context, not just ' + 'a paraphrase. Frozen at send-time.', + ) x_fc_engagement_turnaround_hours = fields.Float( string='Owner Turnaround (h)', compute='_compute_engagement_turnaround', @@ -238,7 +246,8 @@ class HelpdeskTicket(models.Model): monkeypatch it for deterministic assertions.""" return uuid.uuid4().hex - def _fc_reset_engagement(self, owner_email, owner_name, ai_summary): + def _fc_reset_engagement(self, owner_email, owner_name, ai_summary, + findings=''): """Stamp a fresh pending engagement on this ticket — invalidates any previous token + clears decided/reminded timestamps so the cron and the reporting view see a clean slate. @@ -248,6 +257,11 @@ class HelpdeskTicket(models.Model): up as the snapshot. If normalisation fails, we still proceed using the raw value — the email will probably bounce but state is consistent and re-engaging fixes it. + + `findings` is the support engineer's reply text from the wizard — + stored on the ticket so the mail template can show it as the + "My Reply" section without context-magic, and so it survives as + audit history once the engagement is decided. """ self.ensure_one() normalised = email_normalize(owner_email or '') or (owner_email or '') @@ -260,6 +274,7 @@ class HelpdeskTicket(models.Model): 'x_fc_engagement_reminded_at': False, 'x_fc_engagement_decided_at': False, 'x_fc_ai_summary': ai_summary or '', + 'x_fc_engagement_findings': findings or '', }) def action_add_owner_as_follower(self):