- Triggers (Admin > Wyzwalacze): event-driven rules that fire immediately on
  a ticket lifecycle event (created/updated/status/priority/assignee/team/
  category changed, new reply), with AND-conditions and ordered actions
  (set status/priority/team/assignee, send e-mail). Ships its own dedicated,
  freely add/edit/delete-able e-mail templates, kept separate from the fixed
  system templates.
- Ticket watching: operators can star/"Obserwuj" any ticket to follow it
  regardless of assignment/team.
- Real-time notification bell (private per-user broadcast channel, 30s
  fallback poll) with an opt-in in-tab browser push notification.
- Per-user notification preferences (/settings/notifications): scope
  (mine/unassigned/watched/all) and e-mail toggle per event category.
- Admin > Integracje: new tab for LDAP/AD + BookStack config, split out of
  Konfiguracja.
- Operator queue: Podkategoria/Zespół/Utworzono columns (off by default).
- Obserwuj button moved next to the auto-refresh countdown; trigger
  condition builder shows subcategory/zgłaszający as name dropdowns instead
  of raw IDs; /settings/notifications got a back link, full-width push
  card, and a bordered table container; admin panel tab and operator queue
  view now persist across a plain page refresh.
- Docs: README/ARCHITECTURE/wiki updated for all of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 23:43:01 +02:00
parent 0b06687ea1
commit ab90abcaa3
47 changed files with 2480 additions and 139 deletions

View File

@@ -4,6 +4,7 @@ namespace App\Notifications;
use App\Models\EmailTemplate;
use App\Models\Ticket;
use App\Models\TriggerEmailTemplate;
use App\Support\Settings;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\AnonymousNotifiable;
@@ -20,8 +21,25 @@ class TicketNotification extends Notification
* user can hold both roles at once, so this can't be inferred from the
* notifiable itself; it decides which ticket URL (client vs operator
* area) both the e-mail link and the in-app notification point to.
*
* $channels lets a caller with a real per-recipient preference (see
* TicketService::notifyStaffForCategory()) send only 'database' (bell,
* no e-mail) for a given recipient defaults to the original
* unconditional "both" behaviour so every existing call site is
* unaffected.
*
* $templateSource picks which table $emailTemplateId is looked up in:
* 'email_template' (the fixed, built-in templates) or
* 'trigger_email_template' (the freely add/edit/delete-able templates
* used by trigger "send_notification" actions see TriggerEngine).
*/
public function __construct(protected Ticket $ticket, protected int $emailTemplateId, protected string $recipientRole = 'client') {}
public function __construct(
protected Ticket $ticket,
protected int $emailTemplateId,
protected string $recipientRole = 'client',
protected array $channels = ['mail', 'database'],
protected string $templateSource = 'email_template',
) {}
/**
* A guest customer with no account is routed anonymously (see
@@ -30,7 +48,7 @@ class TicketNotification extends Notification
*/
public function via(object $notifiable): array
{
return $notifiable instanceof AnonymousNotifiable ? ['mail'] : ['mail', 'database'];
return $notifiable instanceof AnonymousNotifiable ? ['mail'] : $this->channels;
}
protected function ticketUrl(): string
@@ -51,7 +69,9 @@ class TicketNotification extends Notification
public function toMail(object $notifiable): MailMessage
{
$template = EmailTemplate::query()->find($this->emailTemplateId);
$template = $this->templateSource === 'trigger_email_template'
? TriggerEmailTemplate::query()->find($this->emailTemplateId)
: EmailTemplate::query()->find($this->emailTemplateId);
$firstName = trim(explode(' ', $this->ticket->name)[0] ?? $this->ticket->name);