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>
101 lines
3.5 KiB
PHP
101 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Services\ConsultantSuspensionService;
|
|
use App\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Console\Command;
|
|
|
|
/**
|
|
* Nächtlicher Ablauf für Berater mit abgelaufener Mitgliedschaft (Entwicklungsplan B2).
|
|
*
|
|
* Reihenfolge wie bisher: erst der harte Cut (Ablauf + `mivita.cleanup_delete_days`),
|
|
* dann das Pausieren (Ablauf + `mivita.cleanup_suspend_days`). Die Arbeit macht
|
|
* {@see ConsultantSuspensionService}; dieser Command wählt nur die Kandidaten aus.
|
|
*/
|
|
class UserCleanUp extends Command
|
|
{
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $signature = 'user:cleanup
|
|
{--dry-run : Nur auflisten, was passieren würde, nichts ändern und keine Mails senden}';
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $description = 'Berater mit abgelaufener Mitgliedschaft pausieren (Team und Kunden an den Sponsor) bzw. nach der Frist endgültig entfernen';
|
|
|
|
public function handle(ConsultantSuspensionService $suspension): int
|
|
{
|
|
$dryRun = (bool) $this->option('dry-run');
|
|
|
|
$this->info('RUN Command user:cleanup'.($dryRun ? ' (dry-run)' : ''));
|
|
\Log::channel('cleanup')->info('COMMAND [user:cleanup] started.'.($dryRun ? ' dry-run' : ''));
|
|
|
|
$this->runStage(
|
|
'Harter Cut (Ablauf vor mind. '.$suspension->deleteDays().' Tagen)',
|
|
$suspension->terminationCandidates(),
|
|
$suspension,
|
|
$dryRun,
|
|
fn (User $user) => $suspension->terminate($user),
|
|
);
|
|
|
|
$this->runStage(
|
|
'Pausieren (Ablauf vor mind. '.$suspension->suspendDays().' Tagen)',
|
|
$suspension->suspensionCandidates(),
|
|
$suspension,
|
|
$dryRun,
|
|
fn (User $user) => $suspension->suspend($user, 'user:cleanup'),
|
|
);
|
|
|
|
\Log::channel('cleanup')->info('COMMAND [user:cleanup] finished.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @param iterable<User> $candidates
|
|
* @param callable(User): (array<string, int>|null) $action
|
|
*/
|
|
private function runStage(string $label, iterable $candidates, ConsultantSuspensionService $suspension, bool $dryRun, callable $action): void
|
|
{
|
|
$rows = [];
|
|
$done = 0;
|
|
$failed = 0;
|
|
|
|
foreach ($candidates as $user) {
|
|
$counts = $dryRun ? $suspension->preview($user) : null;
|
|
|
|
if (! $dryRun) {
|
|
try {
|
|
$counts = $action($user);
|
|
} catch (\Throwable $e) {
|
|
\Log::channel('cleanup')->error('user:cleanup '.$label.' fehlgeschlagen fuer user_id '.$user->id.': '.$e->getMessage());
|
|
$counts = null;
|
|
}
|
|
$counts === null ? $failed++ : $done++;
|
|
}
|
|
|
|
$rows[] = [
|
|
$user->id,
|
|
trim($user->account?->first_name.' '.$user->account?->last_name),
|
|
$user->email,
|
|
$user->payment_account ? Carbon::parse($user->payment_account)->format('d.m.Y') : '',
|
|
$counts['team'] ?? '-',
|
|
$counts['customers'] ?? '-',
|
|
$counts['abos'] ?? '-',
|
|
$counts['portal_customers'] ?? '-',
|
|
$counts['parked'] ?? '-',
|
|
];
|
|
}
|
|
|
|
$this->line('');
|
|
$this->info($label.': '.count($rows).' Berater'.($dryRun ? '' : " (erledigt {$done}, übersprungen {$failed})"));
|
|
|
|
if ($rows !== []) {
|
|
$this->table(['ID', 'Name', 'E-Mail', 'Ablauf', 'Team', 'Kunden', 'Kundenabos', 'Portal-Logins', 'beim Sponsor geparkt'], $rows);
|
|
}
|
|
}
|
|
}
|