Commit Graph

15 Commits

Author SHA1 Message Date
gsinghpal
fe98fadf61 fix(fusion_helpdesk_central): engagement now posts public chatter for employee
Sending an engagement triggered template.send_mail(), which logged the
outbound email to the chatter as a `notification` message with the
internal `Note` subtype. That's correct for nexa-side bookkeeping (we
don't want the raw email body propagating to the customer), but it
meant nothing public was posted — so the entech-side My Tickets inbox
showed no activity. The employee couldn't tell their request had been
escalated for approval.

_fc_reset_engagement now posts a follow-up public message via
message_post (subtype mail.mt_comment, message_type='comment') with:

   Awaiting owner approval from <owner_name>.
  Their decision will appear here when they reply.

  Our reply:
  > <findings text>

This survives the entech _public_messages filter (comment +
non-internal subtype) and propagates to the employee's My Tickets
thread, giving them context AND the engineer's reply without exposing
the raw outbound email or the owner's email address.

Smoke-tested live on ticket #54: re-engaged with the same owner, the
new mail.message (id=348213) is subtype=Discussions / internal=False /
message_type=comment, and contains both the awaiting-approval notice
and the findings text. _public_messages would surface it.

Bumps fusion_helpdesk_central to 19.0.2.4.1.
2026-05-27 15:31:58 -04:00
gsinghpal
32c7026558 feat(fusion_helpdesk_central): owner email shows 3 sections — Request / Reply / Summary
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 <details> 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 <data noupdate="1"> 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.
2026-05-27 15:26:26 -04:00
gsinghpal
76866a7c76 fix(fusion_helpdesk_central): wizard dialog closed on Generate Summary click
Previous fix (return True from action_generate_summary) prevented the
self-id crash but introduced a worse regression: Odoo's web client
auto-closes target='new' modals on any non-action return — the
"wizard done" convention. So Generate Summary updated the field and
then immediately killed the dialog, leaving the user with no chance
to click Send Engagement.

The only Odoo-19 idiom that reliably keeps a wizard dialog open
across a button click is to return an act_window dict that re-opens
the same wizard record (res_id=self.id). That was the original
approach — it crashed because of the active_id self-id collision in
default_get. With the active_model='helpdesk.ticket' guard now in
place (from 0104e877), the re-open is safe.

Belt-and-suspenders: the re-open action passes context={} explicitly,
so even if a future change to default_get drops the active_model
guard, there's no parent-form active_id leaking in to confuse the
ticket lookup. The wizard record is loaded by res_id directly; Odoo
19 doesn't call default_get for record loads, only for new-record
creation.

Centralised the re-open logic in _reopen_action so single + bulk
modes share the same code path.

Bumps fusion_helpdesk_central to 19.0.2.3.4.
2026-05-27 15:12:33 -04:00
gsinghpal
0104e87750 fix(fusion_helpdesk_central): Generate Summary crashed wizard with self-id collision
Repro: open the engagement wizard on a ticket, write findings, click
'Generate Summary from Findings'. Notification: "Ticket N no longer
exists" and the whole dialog closes — even though the ticket clearly
exists in the DB.

Root cause was two compounding bugs:

1. action_generate_summary returned an act_window dict with
   res_id=self.id to "stay open after writing the summary field". The
   web client honoured that by opening a NEW act_window — and the new
   action's context inherited active_id=<wizard_id> (because that's
   the res_id of the action being opened). Wizard ids are not ticket
   ids, but our default_get didn't know the difference.

2. default_get read ctx.get('active_id') unconditionally, without
   first checking ctx.get('active_model') == 'helpdesk.ticket'. So
   when active_id pointed at the wizard's own id, default_get fed
   that to _default_get_single, which raised "Ticket <wizard_id> no
   longer exists" — and the user saw a confusing error about a
   ticket that obviously DID exist (just not with that id).

Two fixes:

(a) action_generate_summary + action_generate_all_summaries now
    return True. The form field write is visible to the client via
    the call response; the wizard re-renders with the new
    ai_summary populated. No spurious navigation, no context
    pollution.

(b) default_get only consults active_id / active_ids when
    active_model is helpdesk.ticket. Explicit
    default_ticket_id[s] context keys still take precedence and
    aren't gated by active_model (they're the caller's strong
    signal).

Verified live: opening the wizard with active_id=99999 and NO
active_model no longer raises 'Ticket 99999 no longer exists' —
just creates the wizard cleanly. The normal flow (default_ticket_id
+ active_model='helpdesk.ticket') still works as before.

Bumps fusion_helpdesk_central to 19.0.2.3.3.
2026-05-27 14:30:53 -04:00
gsinghpal
1f818096db fix(fusion_helpdesk_central): findings + summary actually span full width
Previous fix (col=1 on the group) didn't work — Odoo still rendered
the group's string as a left-column label inside the form sheet's
flow, so the textarea got pushed into a narrow right column. The
summary field looked entirely missing because its content split
between the button row (on the right) and the textarea (collapsed
nowhere visible).

Right idiom (lifted from Odoo's own mail.compose.message wizard):
WIDE textareas live directly at the form level, not inside <group>.
Section titles use <separator string="…"> which renders as a
horizontal divider with the label above. The textarea then takes
the full sheet width naturally.

Same pattern applied to bulk mode for consistency.

Also moved Personal Note into the top compact group with Owner /
Owner Email since it's a one-line input that belongs with the
header info, not pretending to be a wide section.

Bumps fusion_helpdesk_central to 19.0.2.3.2.
2026-05-27 14:17:23 -04:00
gsinghpal
fc8963da99 fix(fusion_helpdesk_central): findings + summary textareas span full width
The Findings and Summary fields rendered at half-width because their
enclosing <group> defaulted to col="2" — Odoo reserves a label column
even when the field has nolabel="1", so the textarea was squeezed
into the right half of the dialog while the left half sat empty.

Switch both groups to col="1" so the field uses the entire group
width. Also tag both fields with widget="text" explicitly (it was
inferred from the Text field type, but being explicit makes the
intent obvious to anyone reading the view) and migrate the button
row to a flex div so the helper text aligns with the button vertical
center.

Bumps fusion_helpdesk_central to 19.0.2.3.1.
2026-05-27 13:53:38 -04:00
gsinghpal
c520803c84 feat(fusion_helpdesk_central): findings-first wizard, explicit Generate button
The old flow fired OpenAI on wizard open with just ticket + chatter,
so the AI summary was just a paraphrase of what the user originally
reported — your engineering analysis (scope, limitations, recommended
approach) never made it to the owner. Restructure to a two-step flow:

  1. Open wizard → empty findings + empty summary, NO OpenAI call
  2. You write findings: scope / effort / approach / risk
  3. Click 'Generate Summary from Findings' → OpenAI runs with
     ticket + chatter + findings, where the prompt explicitly tells
     the model to weight findings MORE THAN the original report
  4. Review/edit, then Send

Bulk wizard mirrors the flow per line: each row gets its own
findings + summary, one 'Generate All Summaries' button fans out
parallel OpenAI calls using each line's own findings.

Updated SUMMARY_PROMPT to:
- Tell the model the support engineer's findings are authoritative
- Emit a bullet structure that leads with the recommendation, not
  the user's restated ask
- Side with findings over the original report when they conflict

New tests cover:
- default_get does NOT fire OpenAI (regression guard for auto-AI)
- Findings text actually reaches the OpenAI prompt
- Send works with a manually-typed summary (no AI in the loop)
- Existing bulk + validation paths still pass with the new shape

Also folds in the deferred code-review #7: ThreadPoolExecutor now
explicitly cancels pending futures on timeout via
shutdown(wait=False, cancel_futures=True) so a slow OpenAI day can't
hold the wizard open for ceil(N/workers)*15s.

Bumps fusion_helpdesk_central to 19.0.2.3.0.

Smoke-tested live on nexa: opening the wizard makes zero OpenAI calls;
clicking Generate with findings='My findings: scope is XL, ~8h' makes
exactly one call and the findings text is verifiably in the prompt
body received by call_openai_chat.
2026-05-27 13:49:02 -04:00
gsinghpal
52849777dd feat(fusion_helpdesk_central): expose OpenAI key + cron settings in UI
Adding the 'Fusion Helpdesk Central' block to General Settings so the
three ICP keys the engagement flow reads are configurable from a real
form instead of forcing admins to open Technical → System Parameters.

Three settings, all wired via config_parameter= so the existing read
paths (engagement_wizard, _fc_send_engagement_reminders) keep working
unchanged:

- fusion_helpdesk_central.openai_api_key (password widget — doesn't
  render plaintext on the form)
- fusion_helpdesk_central.openai_model (default 'gpt-4o-mini')
- fusion_helpdesk_central.engagement_reminder_days (default 3, 0
  disables the reminder cron entirely)

Bumps fusion_helpdesk_central to 19.0.2.2.0.

Find under Settings → Fusion Helpdesk Central. The block has two
sub-sections: "Owner Approval — AI Summary" (key + model) and
"Owner Approval — Reminder Cadence" (days).
2026-05-27 13:36:44 -04:00
gsinghpal
8cc02759b8 feat(fusion_helpdesk_central): Owner Contact field + Add-as-Follower button
Adds a one-click 'loop the owner into the chatter' shortcut on the
ticket form — separate from the engagement approval flow, just keeps
the owner in the loop on ongoing communication.

What's new on helpdesk.ticket:

- x_fc_owner_display (computed Char): 'Kris Pathinather <kris@…>',
  read live from fusion.helpdesk.client.key so a change to the owner
  contact reflects immediately on every existing ticket.
- x_fc_owner_email_resolved (computed Char): email-only slice, drives
  view visibility (the field + button only render when an owner is
  configured).
- x_fc_owner_is_follower (computed Boolean): True when a partner with
  the owner email is in message_partner_ids. Swaps the button for a
  green 'Following' badge when the owner is already on the thread.
- action_add_owner_as_follower(): find-or-create the owner partner by
  email and message_subscribe. Idempotent — second call is a no-op,
  no duplicate partner. Raises UserError with a clear message if no
  owner is configured.

View extension on the helpdesk ticket form: injects right after the
existing partner_id ('Customer') field in the customer side group,
so it reads as 'Customer | Owner Contact [Add as Follower]' — same
row, no layout shift when the state flips to 'Following'.

Tests cover the compute display in three states (configured,
no-client-label, no-owner-on-key), the action's three paths
(create-and-subscribe, reuse-existing-partner, idempotent-when-
already-following), and the UserError when nothing is configured.

Smoke-tested live on nexa: ticket with x_fc_client_label='ENTECH'
displays 'Kris Pathinather <kris@enplating.ca>'; first click adds
res.partner #723 to followers and flips owner_is_follower to True;
second click is a no-op.

Bumps fusion_helpdesk_central to 19.0.2.1.0.
2026-05-27 13:28:18 -04:00
gsinghpal
f1a2b300f7 fix(fusion_helpdesk_central): close magic-link race + cron savepoint + avg pivot
Findings from the post-feature code review on commit 396170b4. Addresses
the two CRITICAL + one HIGH + two MEDIUM issues; rest are deferred.

CRITICAL #1 — magic-link token race:
  Two near-simultaneous POSTs on the same /engagement/<token>/approve
  could both SELECT state='pending' under READ COMMITTED, both post
  chatter, and let the last writer flip the outcome. Now the POST path
  does an atomic UPDATE helpdesk_ticket SET token=NULL WHERE token=%s
  AND state='pending' RETURNING id — the loser gets no row back and
  renders the friendly invalid-link page. Verified live: 2 concurrent
  POSTs → 1 wins, 1 loses, exactly 1 chatter row.

CRITICAL #2 — reminder cron without per-row savepoint:
  Per CLAUDE.md rule #14, a DB failure mid-loop aborts the whole
  transaction and silently kills the rest of the batch. Wrap each row's
  send_mail+write in `with self.env.cr.savepoint()`. Also corrected the
  success-count log (was len(stale), now actual sent count).

HIGH #3 — turnaround pivot summed instead of averaged:
  fields.Float defaults to SUM aggregator; meaningless for per-ticket
  decision delays. Added aggregator='avg' so the pivot reads "avg
  turnaround per ticket" not "summed wait time".

HIGH #4 — added test_concurrent_claim_only_one_wins regression test
  that fires two real HTTP POSTs against the same token and asserts
  exactly one wins + exactly one approval chatter row exists.

MEDIUM #6 — cron nextcall pinned to 09:00 tomorrow so reminders land
  in business hours regardless of when the module was last upgraded.

MEDIUM #10 — escalate failed owner-partner-create from WARNING to
  ERROR (via _logger.exception) since silent attribution to the bot
  account is a real audit-trail confusion.

Deferred (follow-up commits): #5, #7 (executor cleanup), #8, #9,
#11–#14 — none are bugs, all spec-drift or hardening.
2026-05-27 13:16:20 -04:00
gsinghpal
396170b438 feat(fusion_helpdesk): owner-approval engagement flow + AI summary + reporting
Ships the design spec at docs/superpowers/specs/2026-05-27-owner-approval-flow-design.md.

What's new on central (fusion_helpdesk_central 19.0.1.2.0 -> 19.0.2.0.0):

- Engagement model: 8 new fields on helpdesk.ticket (state, snapshotted
  owner email/name, single-use UUID4 token, sent/reminded/decided
  timestamps, AI summary, stored-computed turnaround hours).
- Wizard: single + bulk modes on one fusion.helpdesk.engagement.wizard
  TransientModel with a child wizard.line for per-ticket bulk summaries.
  default_get pulls the OpenAI summary on open; AI fan-out for bulk is
  parallel via ThreadPoolExecutor (max 5 workers, 30s overall cap).
- OpenAI client in utils.py — stdlib urllib, 15s per-call timeout, every
  failure collapses to '' so the wizard's manual-summary fallback fires.
- Public portal: /fusion_helpdesk/engagement/<token>/<decision> GET +
  POST, four branded standalone QWeb pages (confirm/done/invalid/error).
  Token is single-use, cleared on confirm. Decision posts a public
  comment attributed to the resolved owner partner; chatter propagates
  to the employee's My Tickets thread per the "fully visible" UX choice.
- Mail templates (single + bulk) with magic-link buttons. Bulk template
  renders one card per ticket, each with its own approve/reject URL.
- Reminder cron: daily, single-shot per engagement, configurable via
  fusion_helpdesk_central.engagement_reminder_days ICP (default 3, 0
  disables).
- Reporting dashboard: pivot/graph/list/kanban over helpdesk.ticket
  filtered to engaged ones, with avg-turnaround measure. Menu lives
  under Helpdesk > Reporting > Owner Engagements.
- Client_key extended with owner_email/owner_name fields; ticket.create
  upserts them from the client-side piggyback (no new sync endpoint).
- 100% coverage on utils + integration tests on wizard, controllers,
  re-engagement, cron, computed turnaround. OpenAI mocked in CI.

What's new on client (fusion_helpdesk 19.0.1.7.1 -> 19.0.2.0.0):

- Two new ICP settings: fusion_helpdesk.owner_email / .owner_name with
  a new "Owner Approval" block in Settings > Fusion Helpdesk.
- controllers/main.py::submit piggybacks both keys on every ticket
  payload so central keeps client_key.owner_email/name fresh
  automatically.

Verified live end-to-end on entech -> nexa: payload upsert, wizard with
mocked AI, action_send, portal GET/POST/GET-again cycle, second click
hits the friendly invalid-token page. Token entropy = 122 bits (UUID4).
2026-05-27 13:03:23 -04:00
gsinghpal
d7ec91b0f1 feat(fusion_helpdesk): Critical flag, KPI cards, colored stage pills
Three coordinated changes on top of the section grouping:

1. **Mark as Critical** — a red chip on the New tab sets priority='3'
   when submitted. The central post-create hook auto-applies a "Critical"
   helpdesk.tag (shipped via fusion_helpdesk_central data XML, noupdate=1
   so support can recolor without losing it on upgrade), giving support
   a kanban-groupable signal that doesn't rely on remembering what
   priority='3' means. Scoped to in-app-channel tickets only, so a
   support agent manually setting Urgent on their own ticket isn't
   silently tagged.

2. **KPI cards above the sections** — Total / Open / Closed / Critical
   in a 4-up grid (auto-collapses to 2x2 under 540px). Each card uses
   its own saturated gradient so it reads on both light and dark mode —
   the dialog backdrop is irrelevant because the gradient brings its
   own background. Counts are computed in JS from state.tickets so they
   always match what's rendered below.

3. **Colored stage pills** — red Critical, green Solved, dark-yellow New,
   orange Cancelled, blue for In Progress / Testing / On Hold. Critical
   priority gets a *separate* red pill alongside the stage pill so you
   keep stage info even on escalated tickets. Stage matching is
   substring-based (lowercased) so a renamed "Resolved" or "Done" stage
   on central still maps to the green pill.

Tests cover the new is_critical=True → priority='3' wiring and the
default omission so SLA / stage defaults keep working for normal
tickets. Bumps fusion_helpdesk to 19.0.1.7.0 and
fusion_helpdesk_central to 19.0.1.2.0. End-to-end smoke test verified
live: priority=3 + x_fc_client_label triggers the Critical tag.
2026-05-27 11:21:11 -04:00
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
gsinghpal
a713ec2fd3 changes 2026-05-04 02:17:47 -04:00
gsinghpal
586f05d567 chnages 2026-05-04 02:14:34 -04:00