mivita/app/Console/Commands/AboCheckOverdue.php
Kevin 452d1ac17f Abos: taegliche Warnung fuer ueberfaellige Abos, ohne Nachbuchung
user:make_abo_order nimmt nur Abos mit next_date = heute; faellt ein Lauf
aus, bleibt ein Abo stillschweigend liegen (Abo 39: Termin 25.03.2026).
abo:check-overdue meldet taeglich um 05:30 alle aktiven Abos mit Status
okay und verstrichenem Termin per Mail an mivita (Link ins Admin-Abo) und
ins abo_order-Log. Es wird bewusst nichts nachgebucht; --no-mail nur fuer
die Ausgabe.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:44:45 +00:00

104 lines
3.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Console\Commands;
use App\Mail\AboOverdueAlertMail;
use App\Models\UserAbo;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
/**
* Warnt vor laufenden Abos, deren nächste Ausführung in der Vergangenheit liegt.
*
* `user:make_abo_order` nimmt nur Abos mit `next_date = heute`. Fällt ein Lauf aus
* oder bricht ein Abo ab, ohne dass sein Termin weitergesetzt wird, bleibt es
* stillschweigend liegen (Befund Abo 39: Termin 25.03.2026, bis September nicht
* ausgeführt). Dieser Command bucht bewusst nichts nach — er meldet nur, damit
* jemand entscheidet, ob nachgeliefert, der Termin korrigiert oder das Abo beendet wird.
*/
class AboCheckOverdue extends Command
{
/**
* @var string
*/
protected $signature = 'abo:check-overdue
{--no-mail : Nur ausgeben und loggen, keine Mail}';
/**
* @var string
*/
protected $description = 'Laufende Abos mit einem Ausführungstermin in der Vergangenheit melden (ohne Nachbuchung)';
public function handle(): int
{
$overdue = $this->overdueAbos();
if ($overdue->isEmpty()) {
$this->info('Keine überfälligen Abos.');
Log::channel('abo_order')->info('abo:check-overdue: keine ueberfaelligen Abos');
return self::SUCCESS;
}
$this->warn('Überfällige Abos: '.$overdue->count());
$this->table(
['Abo', 'Art', 'Kunde/Berater', 'Termin', 'Tage überfällig', 'Letzte Ausführung'],
$overdue->map(fn (UserAbo $abo) => [
$abo->id,
$abo->is_for,
$this->holderName($abo),
Carbon::parse($abo->next_date)->format('d.m.Y'),
$this->daysOverdue($abo),
$abo->last_date ? Carbon::parse($abo->last_date)->format('d.m.Y') : '–',
])->all(),
);
Log::channel('abo_order')->warning('abo:check-overdue: '.$overdue->count().' ueberfaellige Abos', [
'abo_ids' => $overdue->pluck('id')->all(),
]);
if (! $this->option('no-mail')) {
try {
Mail::to(config('app.default_mail'))->send(new AboOverdueAlertMail($overdue->all()));
} catch (\Throwable $e) {
Log::channel('abo_order')->error('abo:check-overdue: Mail fehlgeschlagen: '.$e->getMessage());
}
}
return self::SUCCESS;
}
/**
* Aktiv, Status „okay“, Termin vor heute — genau die Abos, die der Ausführungslauf
* nie mehr anfassen würde.
*
* @return Collection<int, UserAbo>
*/
private function overdueAbos(): Collection
{
return UserAbo::query()
->with(['shopping_user', 'user.account', 'member.account'])
->where('active', true)
->where('status', 2)
->whereDate('next_date', '<', now()->toDateString())
->orderBy('next_date')
->get();
}
private function daysOverdue(UserAbo $abo): int
{
return (int) Carbon::parse($abo->next_date)->startOfDay()->diffInDays(now()->startOfDay());
}
private function holderName(UserAbo $abo): string
{
if ($abo->is_for === 'me') {
return trim($abo->user?->account?->first_name.' '.$abo->user?->account?->last_name) ?: (string) $abo->email;
}
return trim($abo->shopping_user?->billing_firstname.' '.$abo->shopping_user?->billing_lastname) ?: (string) $abo->email;
}
}