admin: Aktivitäts-Verlauf unter Reports ▸ Verlauf
Chronologisches Protokoll der Systemereignisse als neue Admin-Seite (/admin/reports/activity) mit Filter nach Ereignistyp, Zeitraum und Suche. Erfasst ab jetzt fortlaufend: - Registrierungen inkl. Weg (Formular, Google, Magic-Link) - Anmeldungen inkl. Weg (Passwort, Google, Magic-Link, Impersonation) - PM-Statuswechsel mit Ergebnis/Begründung (zentral im Status-Log-Hook; KI-/Scheduler-Wechsel erscheinen als „System") - Zahlungseingänge (Rechnungsspiegelung) und Credit-Aufladungen - Formular-Eingänge: Kontakt, Rechtsanfrage, Widerruf, Kündigung, Newsletter-Anmeldung (Observer auf den Modellen) Schreiben ist Best-Effort über den ActivityLog-Service — ein fehlgeschlagener Verlaufseintrag bricht nie den fachlichen Ablauf. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a574898ca9
commit
3a11963990
19 changed files with 720 additions and 4 deletions
69
app/Observers/ActivityLogObserver.php
Normal file
69
app/Observers/ActivityLogObserver.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Enums\ActivityType;
|
||||
use App\Models\ContactMessage;
|
||||
use App\Models\LegalRequest;
|
||||
use App\Models\NewsletterSubscription;
|
||||
use App\Models\TerminationRequest;
|
||||
use App\Models\WithdrawalRequest;
|
||||
use App\Services\ActivityLog;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Protokolliert Formular-Eingänge im Aktivitäts-Verlauf — überall dort,
|
||||
* wo das Anlegen des Datensatzes selbst das Ereignis ist (Kontakt,
|
||||
* Rechtsanfrage, Widerruf, Kündigung, Newsletter-Anmeldung).
|
||||
*/
|
||||
class ActivityLogObserver
|
||||
{
|
||||
public function __construct(private readonly ActivityLog $activityLog) {}
|
||||
|
||||
public function created(Model $model): void
|
||||
{
|
||||
match (true) {
|
||||
$model instanceof ContactMessage => $this->activityLog->record(
|
||||
ActivityType::ContactRequest,
|
||||
sprintf('Kontaktanfrage „%s“ von %s (%s).', $model->topic->label(), $model->name, $model->email),
|
||||
user: $model->user,
|
||||
subject: $model,
|
||||
actorLabel: (string) ($model->name ?: $model->email),
|
||||
meta: ['portal' => $this->stringable($model->portal)],
|
||||
),
|
||||
$model instanceof LegalRequest => $this->activityLog->record(
|
||||
ActivityType::LegalRequest,
|
||||
sprintf('Rechtsanfrage (%s) von %s.', $model->type->label(), $model->requester_email),
|
||||
subject: $model,
|
||||
actorLabel: (string) $model->requester_email,
|
||||
meta: ['type' => $model->type->value, 'press_release_id' => $model->press_release_id],
|
||||
),
|
||||
$model instanceof WithdrawalRequest => $this->activityLog->record(
|
||||
ActivityType::WithdrawalRequest,
|
||||
sprintf('Widerruf eingegangen von %s (%s).', $model->name, $model->email),
|
||||
subject: $model,
|
||||
actorLabel: (string) ($model->name ?: $model->email),
|
||||
),
|
||||
$model instanceof TerminationRequest => $this->activityLog->record(
|
||||
ActivityType::TerminationRequest,
|
||||
sprintf('Kündigung (%s) von %s (%s).', $model->kind, $model->name, $model->email),
|
||||
subject: $model,
|
||||
actorLabel: (string) ($model->name ?: $model->email),
|
||||
meta: ['kind' => $model->kind],
|
||||
),
|
||||
$model instanceof NewsletterSubscription => $this->activityLog->record(
|
||||
ActivityType::NewsletterSubscription,
|
||||
sprintf('Newsletter-Anmeldung von %s.', $model->email),
|
||||
subject: $model,
|
||||
actorLabel: (string) $model->email,
|
||||
meta: ['portal' => $this->stringable($model->portal)],
|
||||
),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
private function stringable(mixed $value): ?string
|
||||
{
|
||||
return $value instanceof \BackedEnum ? (string) $value->value : ($value !== null ? (string) $value : null);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue