Compare commits

...

2 Commits

Author SHA1 Message Date
gsinghpal
34a65f9c4a fix(fusion_helpdesk_central): chatter notice no longer collapsed; adds summary
Previous engagement notice used <blockquote> to style the findings
quote. Odoo's mail.thread renderer auto-tags every <blockquote> with
data-o-mail-quote-node="1" and the chatter UI then HIDES the content
behind a "..." widget — exactly the wrong UX since the findings are
the load-bearing content, not throwaway quoted text. Swapped both
quote blocks for styled <div>s with the same visual treatment (left
border, light background, padding) so they render fully inline with
no toggle.

Also expanded the notice to mirror more of what the owner sees in the
engagement email: now includes BOTH "Our reply" (the findings) and
"Summary sent to the owner" (the AI summary). The employee can see
the full context being used for the decision, not just the engineer's
reply. Skipped the Original Request section because the employee
wrote it themselves — would just clutter the thread.

white-space:pre-wrap preserves multi-line findings/summaries that the
engineer typed with line breaks. The two sections are visually
distinct: findings in light blue (matching the email's "Our Reply"
treatment), summary in light grey (matching "Summary for the
Decision" in the email).

Verified live on ticket #54: new message body has no <blockquote>,
no data-o-mail-quote attribute, and contains both section headers
with their content rendered inline.

Bumps fusion_helpdesk_central to 19.0.2.4.2.
2026-05-27 15:36:46 -04:00
gsinghpal
97cce8c755 docs(billing): record NexaCloud surgical deploy (inert; .env + Cursor WIP preserved) 2026-05-27 15:32:15 -04:00
3 changed files with 46 additions and 21 deletions

View File

@@ -120,11 +120,18 @@ All four chunks of #2 are now built. The brainstorm "which slice" question resol
`nexacloud_dsn`, all charges `plan_id` NULL (rating cron no-op), no webhooks queued
(dispatch cron no-op), inbound API 401s with no key configured. Synced to
`/opt/odoo/custom-addons` + `-i` via the restart-safe recipe.
- **NexaCloud: NOT deployed (deliberately).** `deploy.sh` does `rsync --delete` of the
**local working tree** then `--no-cache` rebuild — it would ship the concurrent
**uncommitted Cursor WIP** (7 files) to prod. My 2b/2c is also feature-flagged OFF and
the app wasn't boot-tested. Deploy only AFTER: Cursor's WIP is committed/finished, the
app boots clean, and `ODOO_BILLING_*` config is set (otherwise it's inert anyway).
- **NexaCloud (prod, `vps.nexasystems.ca` / 192.168.1.250): DEPLOYED — INERT.** Did NOT
use `./deploy.sh` (it `rsync --delete`s the working tree → would have shipped the
concurrent **uncommitted Cursor WIP** (7 files) AND wiped the gitignored prod `.env`
files). Instead deployed **surgically**: rsync of ONLY my 6 committed billing files (no
`--delete`; `.env` + Cursor's files untouched), `docker compose build backend`,
**boot-tested in a throwaway container** (`run --rm --no-deps backend python -c "import
app.main"` → BOOT_OK) before swapping, then `up -d backend`. `nexacloud-api` healthy,
`/health` OK. Feature OFF: `ODOO_BILLING_ENABLED` unset → `/billing/webhooks/central`
returns 404 and no usage is pushed. Activate later by setting `ODOO_BILLING_*` in
`/opt/nexacloud/.env` (+ compose env passthrough) once the Odoo side is wired.
**NOTE:** Cursor's 7-file WIP remains uncommitted locally and was never deployed — when
Cursor finishes, a normal `./deploy.sh` will ship it (and re-sync `.env`).
**Remaining before go-live (gated on infra / ops you do):**
1. Grant the read-only DSN (`fusion_billing.nexacloud_dsn`) — see the module README — then

View File

@@ -3,7 +3,7 @@
# License OPL-1
{
'name': 'Fusion Helpdesk Central — Client API Keys',
'version': '19.0.2.4.1',
'version': '19.0.2.4.2',
'category': 'Productivity',
'summary': 'Admin UI on the central Odoo for issuing per-client API '
'keys used by fusion_helpdesk client deployments.',

View File

@@ -289,18 +289,23 @@ class HelpdeskTicket(models.Model):
def _fc_post_engagement_notice(self, owner_name, findings):
"""Public chatter message posted when an engagement is sent.
Two purposes:
- Tells the employee (via the entech My Tickets inbox, which only
reads public comments) that their request has been escalated
for approval and who's deciding.
- Captures our reply (findings) on the public thread so the
employee can see what support said — same content the owner
receives in the engagement email, just without the AI summary
(which is a decision-aid for the owner, not the employee).
Mirrors the engagement email body so the chatter shows the same
context the owner is reading: the engineer's reply (findings)
and the summary that's being used for the decision. We skip the
Original Request section that's in the email — the employee
filed the ticket, they already know what they asked for.
Posted via message_post with subtype_xmlid='mail.mt_comment' so
it survives the entech-side _public_messages filter
(message_type='comment' + non-internal subtype).
Critically: uses styled <div>s instead of <blockquote>. Odoo
auto-flags <blockquote> content as "quoted" (data-o-mail-quote-
node="1") and the chatter UI then collapses it behind a "..."
widget — exactly the wrong UX here, since the findings + summary
are the load-bearing content, not throwaway quotation. Plain
divs with the same visual treatment render fully inline.
Posted with subtype_xmlid='mail.mt_comment' so it passes the
entech-side _public_messages filter (message_type='comment' +
non-internal subtype) and reaches the employee's My Tickets
inbox.
"""
from markupsafe import Markup, escape
self.ensure_one()
@@ -309,14 +314,27 @@ class HelpdeskTicket(models.Model):
'<p>⏳ <b>Awaiting owner approval from %s.</b> '
'Their decision will appear here when they reply.</p>'
) % name_html
# Section 1: our reply (the wizard's findings)
text = (findings or '').strip()
if text:
body += (
'<p><b>Our reply:</b></p>'
'<blockquote style="margin:6px 0 0 0; padding:6px 12px; '
'<p style="margin:12px 0 4px 0;"><b>Our reply:</b></p>'
'<div style="margin:0; padding:10px 14px; '
'border-left:3px solid #c7dcfa; color:#1e3a5f; '
'background:#f4f8ff;">%s</blockquote>'
) % escape(text).replace('\n', '<br/>')
'background:#f4f8ff; border-radius:4px; '
'white-space:pre-wrap;">%s</div>'
) % escape(text)
# Section 2: the AI summary the owner is seeing for the decision
summary = (self.x_fc_ai_summary or '').strip()
if summary:
body += (
'<p style="margin:14px 0 4px 0;">'
'<b>Summary sent to the owner:</b></p>'
'<div style="margin:0; padding:10px 14px; '
'border-left:3px solid #e5e7eb; color:#444; '
'background:#f9fafb; border-radius:4px; '
'white-space:pre-wrap;">%s</div>'
) % escape(summary)
self.message_post(
body=Markup(body),
message_type='comment',