comment(Inspiring::quote()); })->purpose('Display an inspiring quote'); /** * Betriebs-Alert bei fehlgeschlagenem Scheduler-Task (R5.3): meldet den Task * per Log + Best-Effort-Mail, damit ein stiller Ausfall (z. B. Rechnungslauf, * Kontingent-Reset) nicht unbemerkt bleibt. */ $alertOnFailure = fn (string $task) => function (Event $event) use ($task): void { OperationalAlert::send("Geplanter Task fehlgeschlagen: {$task}", [ 'exit_code' => $event->exitCode, ]); }; // ======================================== // Tägliche Bereinigungen // ======================================== // Bounce-Postfach: Rückläufer lesen und unzustellbare Adressen sperren // (Roadmap 1b.6). Alle zehn Minuten auf UNSEEN; ohne konfiguriertes Konto // beendet sich der Befehl sofort. Schedule::command(FetchMailBounces::class) ->everyTenMinutes() ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('mail:fetch-bounces')); // Kontakterreichbarkeit: abgelaufene DNS-Befunde der Mail-Domains erneuern. // Nur Auffrischung — neue Adressen prüft der Observer beim Anlegen. Ein // Befund darf nicht unbegrenzt als Domainsperre stehen bleiben. Schedule::command(CheckMailDomains::class, ['--stale', '--limit=500']) ->dailyAt('04:20') ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('mail:check-domains')); // Magic-Links: Abgelaufene / verbrauchte Tokens entfernen (täglich 03:00) Schedule::command(PurgeMagicLinks::class, ['--days=30']) ->dailyAt('03:00') ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('magic-links:purge')); // Meldeverfahren: Schwärzungs-Snapshots nach Löschfrist entfernen (täglich 03:10) Schedule::command(PurgeRedactionSnapshots::class) ->dailyAt('03:10') ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('snapshots:purge')); // PM-Entwürfe: Zombie-Drafts nach 180 Tagen Inaktivität archivieren (wöchentlich) Schedule::command(PurgeExpiredPressReleaseDrafts::class, ['--days=180']) ->weekly() ->sundays() ->at('04:00') ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('press-releases:purge-drafts')); // ======================================== // Geplante PM-Veröffentlichung // ======================================== // PM mit scheduled_at <= now & Status review automatisch veröffentlichen. // Läuft alle 5 Min — passt zum FormRule "scheduled_at min. 5 Min in Zukunft". Schedule::command(PublishScheduledPressReleases::class) ->everyFiveMinutes() ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('press-releases:publish-scheduled')); // ======================================== // Aufruf-Zähler // ======================================== // Gezählte Aufrufe aus dem Ereignis-Log in press_releases.hits verbuchen. // Bewusst außerhalb des Requests: der Schreibzugriff auf die große Tabelle // darf keine Web-Worker blockieren (siehe AggregatePressReleaseViews). Schedule::command(AggregatePressReleaseViews::class) ->everyMinute() ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('press-releases:aggregate-views')); // Ereignis-Log auf den Aufbewahrungszeitraum begrenzen (täglich 03:20). // Die Tabelle nimmt jeden Seitenaufruf auf und wuchs als einzige Log-Tabelle // des Systems unbegrenzt. Gelöscht wird nur unterhalb der Wasserstandsmarke — // noch nicht in `hits` verbuchte Aufrufe bleiben unangetastet. Schedule::command(PurgePressReleaseEvents::class) ->dailyAt('03:20') ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('press-releases:purge-events')); // Indexierungsstatus aus der Search Console (Roadmap 1c.4), täglich 05:10. // Ohne GSC_ENABLED/Schlüsseldatei beendet sich der Befehl sofort. Schedule::command(CheckIndexStatus::class) ->dailyAt('05:10') ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('press-releases:check-index-status')); // Konversions-Ereignisse auf den Aufbewahrungszeitraum begrenzen (täglich 03:25). // Frist zusammen mit der Tabelle festgelegt (395 Tage), siehe config/conversion.php. Schedule::command(PurgeConversionEvents::class) ->dailyAt('03:25') ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('conversion:purge-events')); // ======================================== // Manueller Rechnungskreis (MAN-) — Legacy-Zahlungen // ======================================== // Bestandsschutz mit überschrittenem Legacy-Vertragsende beenden (Audit // F06) — vor dem Rechnungslauf, damit kein beendeter Vertrag mehr // abgerechnet wird. Schedule::command(ExpireGrandfathering::class) ->dailyAt('04:15') ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('billing:expire-grandfathering')); // Fällige Rechnungen für laufende Legacy-Zahlungsvereinbarungen ausstellen // (Periodenende erreicht), wie im Altsystem. Neue Abschlüsse laufen über // Stripe (STR-Kreis) und werden hier nicht angefasst. Schedule::command(GenerateManualInvoices::class) ->dailyAt('04:30') ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('billing:generate-manual-invoices')); // ======================================== // PM-Kontingent (Kalendermonats-Reset des Tarif-Modells) // ======================================== // Monatlicher Reset des verbrauchten PM-Kontingents (am 1. um 00:05). Schedule::command(ResetMonthlyPressReleaseQuota::class) ->monthlyOn(1, '00:05') ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('press-releases:reset-monthly-quota')); // ======================================== // Börsendaten für den Web-Ticker // ======================================== // Kurse (DAX, EUR/USD, Brent) alle 15 Minuten aktualisieren; die Views lesen // nur aus dem Cache — ein API-Ausfall leert die Anzeige nicht sofort. Schedule::command(RefreshMarketData::class) ->everyFifteenMinutes() ->withoutOverlapping() ->runInBackground() ->onFailure($alertOnFailure('market:refresh'));