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>
This commit is contained in:
parent
0b8f8abeeb
commit
452d1ac17f
5 changed files with 288 additions and 0 deletions
104
app/Console/Commands/AboCheckOverdue.php
Normal file
104
app/Console/Commands/AboCheckOverdue.php
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue