Ticketing joins the notification platform (closes #8)

No ticketing.* category existed, so the shared Notification Log offered a filter for
every other app and nothing for this one. Verified after this change: 2 categories
register, 39 total.

THIS WAS NOT A MAIL SWEEP. Every other app's integration redirected an existing Mail::
call; here there was nothing to redirect. TicketMessageController::notifySubmitter()
was an empty stub:

    // Placeholder for email notification
    // In production: Mail::to($submitterEmail)->send(new TicketReplyMail(…));

So nobody has ever been told their ticket was answered. The reporter read the absence
of a Notifications directory as "not wired up", and it was more literal than that.

TWO CATEGORIES, matching the two moments somebody is waiting on another person:

  ticketing.replied  → the submitter, when an agent replies. Internal notes are
    excluded at the call site, which already distinguishes them — the entire point of
    an internal note is that the submitter does not see it.

  ticketing.assigned → the agent a ticket is handed to. The previous assignee is
    captured BEFORE the write so a real hand-off is distinguishable from a save that
    merely touched other fields, and assigning to yourself sends nothing.

Neither is mandatory: a reply notice is a convenience, not a security or financial
record, so it is opt-out in a person's preferences.

Bridge shape copied from Dashboard\Tools\Support\ToolsNotifier: one class owning the
platform contact, available() false when the platform is absent or dark, and a queue
failure logged rather than thrown — sending a reply must succeed even when the email
cannot be queued. DELIBERATELY NOT a manifest dependency, for the documented reason:
requires.apps is a HARD requirement and Notifications is still beta and ships dark.

There is no direct-mail fallback, unlike the swept apps, because there was never any
direct mail to fall back to. With Notifications dark, ticketing behaves exactly as it
does today: silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDA3fQbsa3gaHCFoV1Q8Gg
This commit is contained in:
2026-07-28 12:58:24 -04:00
parent 1175c20a57
commit 4719cbab8b
4 changed files with 188 additions and 2 deletions

View File

@@ -493,8 +493,33 @@ class TicketController extends Controller
}
}
// Issue #8: capture the previous assignee BEFORE the write, so a genuine
// hand-off can be told from a save that merely touched other fields.
$previousAssignee = $ticket->assigned_to;
$ticket->update($validated);
$newAssignee = $ticket->assigned_to;
$handedOver = $newAssignee
&& $newAssignee !== $previousAssignee
&& $newAssignee !== Auth::id(); // assigning to yourself needs no telling
if ($handedOver) {
$agent = \App\Models\User::find($newAssignee);
$email = trim((string) ($agent?->email ?? ''));
if ($email !== '') {
\Dashboard\Ticketing\Support\TicketingNotifier::deliver(
\Dashboard\Ticketing\Support\TicketingNotifier::ASSIGNED,
$email,
"[{$ticket->number}] Assigned to you: {$ticket->title}",
'<p>'.e(Auth::user()?->name ?? 'Somebody').' assigned this ticket to you.</p>'
.'<p><a href="'.e(route('ticketing.show', $ticket->id)).'">View the ticket</a></p>',
$ticket,
);
}
}
return back()->with('success', 'Ticket updated.');
}