Entwicklungsplan September, B2 (Protokoll 08.09., Rueckmeldung 14.09., R3): - Ablauftag + 2 Kalendertage (Nachtlauf): Team, Kunden, Kundenabos und Portal-Logins gehen umkehrbar an den naechsten aktiven Sponsor, jede Umhaengung in consultant_suspension_transfers protokolliert. Kurzinfo an den Sponsor ohne Listen, keine Mail an das Firmenkonto. - Zahlung bis Tag 27: Berater aktiv, Team und Kundendaten zurueck (nur was noch beim Sponsor liegt); Umsaetze der Zwischenzeit bleiben dort. - Tag 28: was noch am Berater haengt geht endgueltig an den Sponsor, Berater wird dessen Kunde (ohne Duplikat) und geloescht; Listen-Mail mit Vorname, Nachname, Telefon, E-Mail an jeden haltenden Sponsor. - Cron, Zahlungseingang und Admin-Ablaufdatum laufen ueber ConsultantSuspensionService; user:cleanup --dry-run listet beide Stufen. - Mail 35 und Dashboard nennen Pausentag und Frist; Fristen in config/mivita.php (cleanup_suspend_days, cleanup_delete_days). - Nebenbefunde: Kernel referenziert UserCleanUp, user:restore aus der User-Bereinigung laeuft nur noch einmal. - Testschema: shopping_users.auth_user_id nullable wie auf Produktion (Migration aendert nur, wo die Spalte noch NOT NULL ist). - Uebersicht fuer die Kundin: dev/_tickets/2026-09-23-mitgliedschaft- ablauf-uebersicht.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
242 lines
8.4 KiB
PHP
242 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Mail\MailCustomMessage;
|
|
use App\Models\UserHistory;
|
|
use App\Models\UserMessage;
|
|
use App\Services\ConsultantSuspensionService;
|
|
use App\Services\Util;
|
|
use App\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Lang;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Mail;
|
|
|
|
class CheckPaymentsAccount extends Command
|
|
{
|
|
/**
|
|
* The name and signature of the console command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'payments:check-accounts';
|
|
|
|
/**
|
|
* The console command description.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'Überprüft Benutzer-Zahlungskonten und sendet Erinnerungen basierend auf Erneuerungsdaten.';
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*/
|
|
/** Zaehler fuer die Zusammenfassung am Ende des Laufs. */
|
|
private int $remindersSent = 0;
|
|
|
|
private int $remindersSkipped = 0;
|
|
|
|
public function handle()
|
|
{
|
|
Log::channel('cron')->info('COMMAND [payments:check-accounts] started.');
|
|
$this->info('COMMAND [payments:check-accounts] started.');
|
|
|
|
// Die Logik wurde 1:1 aus der checkPaymentsAccounts-Methode übernommen
|
|
$renewalDate = Carbon::now()->modify('+'.(config('mivita.remind_first_days') + 1).' days');
|
|
Log::channel('cron')->debug('Erneuerungsdatum für Zahlungen: '.$renewalDate->format('Y-m-d H:i:s'));
|
|
|
|
$users = User::where('payment_account', '!=', null)
|
|
->where('active', '=', 1)
|
|
->where('blocked', '!=', 1)
|
|
->where('payment_account', '<', $renewalDate)
|
|
->get();
|
|
|
|
Log::channel('cron')->info('Found '.$users->count().' users for payment reminders.');
|
|
$this->info('Found '.$users->count().' users for payment reminders.');
|
|
|
|
$this->remindersSent = 0;
|
|
$this->remindersSkipped = 0;
|
|
|
|
foreach ($users as $user) {
|
|
Log::channel('cron')->debug('Prüfe Zahlungserinnerungen für Benutzer: '.$user->email);
|
|
$this->checkReminderPayments($user);
|
|
}
|
|
|
|
$summary = sprintf(
|
|
'COMMAND [payments:check-accounts] finished. Geprüft: %d, versendet: %d, übersprungen: %d.',
|
|
$users->count(),
|
|
$this->remindersSent,
|
|
$this->remindersSkipped
|
|
);
|
|
Log::channel('cron')->info($summary);
|
|
$this->info($summary);
|
|
|
|
return 0; // Success
|
|
}
|
|
|
|
/**
|
|
* Überprüft und sendet Zahlungserinnerungen basierend auf Benutzerkontostand
|
|
*
|
|
* RULES:
|
|
* > 14 remind_sec_days = 31 reminder_first
|
|
* <= 14 remind_sec_days = 33 reminder_sec
|
|
* <= 2 remind_last_days = 34 reminder_last
|
|
* Mitgliedschaft abgelaufen = 35 reminder_deaktiv
|
|
*
|
|
* Die SEPA-Stati 32/36/37 sind entfallen, den Lastschrifteinzug gibt es nicht mehr.
|
|
*
|
|
* @param User $user Benutzer
|
|
* @return void
|
|
*/
|
|
private function checkReminderPayments(User $user)
|
|
{
|
|
// 35 reminder_deaktiv, 36 reminder_deaktiv_sepa
|
|
if (! $user->isActiveAccount()) {
|
|
Log::channel('cron')->debug('Inaktives Konto für Benutzer: '.$user->email);
|
|
$this->checkIsReminderSend($user, 35);
|
|
|
|
return;
|
|
}
|
|
|
|
// 34 reminder_last
|
|
if ($user->daysActiveAccount() <= config('mivita.remind_last_days')) {
|
|
Log::channel('cron')->debug('Letzte Erinnerung für Benutzer: '.$user->email.' (Tage aktiv: '.$user->daysActiveAccount().')');
|
|
$this->checkIsReminderSend($user, 34);
|
|
|
|
return;
|
|
}
|
|
|
|
// 33 reminder_sec
|
|
if ($user->daysActiveAccount() <= config('mivita.remind_sec_days')) {
|
|
Log::channel('cron')->debug('Zweite Erinnerung für Benutzer: '.$user->email.' (Tage aktiv: '.$user->daysActiveAccount().')');
|
|
$this->checkIsReminderSend($user, 33);
|
|
|
|
return;
|
|
}
|
|
|
|
// 31 reminder_first
|
|
if ($user->daysActiveAccount() > config('mivita.remind_sec_days')) {
|
|
Log::channel('cron')->debug('Erste Erinnerung für Benutzer: '.$user->email.' (Tage aktiv: '.$user->daysActiveAccount().')');
|
|
$this->checkIsReminderSend($user, 31);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Überprüft, ob eine Erinnerung bereits gesendet wurde
|
|
*
|
|
* @param User $user Benutzer
|
|
* @param int $status Status-Code der Erinnerung
|
|
* @return bool
|
|
*/
|
|
private function checkIsReminderSend(User $user, $status)
|
|
{
|
|
$isSend = UserHistory::whereUserId($user->id)
|
|
->whereAction('reminder_payments')
|
|
->whereIdentifier($user->payment_account)
|
|
->whereStatus($status)
|
|
->latest()
|
|
->first();
|
|
|
|
if ($isSend) {
|
|
$this->remindersSkipped++;
|
|
Log::channel('cron')->debug('Erinnerung bereits gesendet für Benutzer: '.$user->email.' (Status: '.$status.')');
|
|
|
|
return true;
|
|
}
|
|
|
|
$this->remindersSent++;
|
|
|
|
Log::channel('cron')->debug('Sende neue Erinnerung für Benutzer: '.$user->email.' (Status: '.$status.')');
|
|
$referenz = $this->sendReminderMail($user, $status);
|
|
|
|
UserHistory::create([
|
|
'user_id' => $user->id,
|
|
'action' => 'reminder_payments',
|
|
'referenz' => $referenz,
|
|
'identifier' => $user->payment_account,
|
|
'status' => $status,
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Sendet eine Erinnerungs-E-Mail an den Benutzer
|
|
*
|
|
* @param User $user Benutzer
|
|
* @param int $status Status-Code der Erinnerung
|
|
* @return int
|
|
*/
|
|
private function sendReminderMail(User $user, $status)
|
|
{
|
|
if (! Lang::has('reminder.copy_first_'.$status)) {
|
|
Log::channel('cron')->error('Kein Erinnerungstext für Status '.$status.' vorhanden, Mail an '.$user->email.' wird übersprungen.');
|
|
|
|
return 0;
|
|
}
|
|
|
|
$suspension = app(ConsultantSuspensionService::class);
|
|
$replace = [
|
|
'days' => abs($user->daysActiveAccount()),
|
|
'datetime' => $user->getPaymentAccountDateFormat(),
|
|
'suspend_date' => $suspension->suspensionDateFor($user)?->format('d.m.Y') ?? '',
|
|
'deadline' => $suspension->deadlineFor($user)?->format('d.m.Y') ?? '',
|
|
];
|
|
|
|
$message = __('reminder.copy_first_'.$status, $replace);
|
|
$message_last = __('reminder.copy_last_'.$status, $replace);
|
|
$button = __('reminder.button_'.$status);
|
|
|
|
$message = preg_replace("/[\n\r]/", '', $message);
|
|
$message_last = preg_replace("/[\n\r]/", '', $message_last);
|
|
|
|
$data = [
|
|
'subject' => __('reminder.subject').' | ID: '.$status,
|
|
'message' => $message,
|
|
'message_last' => $message_last,
|
|
'url' => rtrim(Util::getMyMivitaUrl(), '/').'/user/membership',
|
|
'button' => $button,
|
|
];
|
|
|
|
$sender = User::find(1);
|
|
$customer_mail = UserMessage::create(['user_id' => $user->id, 'send_user_id' => $sender->id, 'email' => $user->email, 'subject' => $data['subject'], 'message' => $data['message'].' '.$data['message_last']]);
|
|
|
|
try {
|
|
if (! Util::isTestSystem()) {
|
|
if ($status >= 34) {
|
|
Log::channel('cron')->info('Sende kritische Erinnerung mit BCC an: '.$user->email);
|
|
Mail::to($user->email)
|
|
->locale($user->getLocale())
|
|
->bcc(config('app.default_mail'))
|
|
->send(new MailCustomMessage($user, $data, $sender, false));
|
|
} else {
|
|
Log::channel('cron')->info('Sende normale Erinnerung an: '.$user->email);
|
|
Mail::to($user->email)
|
|
->locale($user->getLocale())
|
|
->send(new MailCustomMessage($user, $data, $sender, false));
|
|
}
|
|
} else {
|
|
Log::channel('cron')->info('Testsystem: E-Mail-Versand simuliert für: '.$user->email);
|
|
}
|
|
} catch (\Exception $e) {
|
|
Log::channel('cron')->error('Mail-Fehler für Benutzer '.$user->email.': '.$e->getMessage());
|
|
$customer_mail->fail = true;
|
|
$customer_mail->error = $e->getMessage();
|
|
$customer_mail->save();
|
|
|
|
return 0;
|
|
}
|
|
|
|
$customer_mail->send = true;
|
|
$customer_mail->sent_at = now();
|
|
$customer_mail->save();
|
|
|
|
Log::channel('cron')->info('Erinnerungsmail erfolgreich gesendet an: '.$user->email);
|
|
|
|
return 1;
|
|
}
|
|
}
|