Berater faellt aus (B2): pausieren nach 2 Tagen, Rueckgabe bei Zahlung, harter Cut am 28. Tag

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>
This commit is contained in:
Kevin 2026-09-23 15:44:45 +00:00
parent 03ad249eeb
commit bb57522b70
29 changed files with 1528 additions and 193 deletions

View file

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Protokoll der Kundendaten, die beim Pausieren eines Beraters (Ablauf der
* Mitgliedschaft, Entwicklungsplan B2) vorübergehend beim Sponsor liegen:
* Kunden (shopping_users), Kundenabos (user_abos) und Portal-Logins (customers).
*
* Zahlt der Berater innerhalb der Frist, gibt das Protokoll genau diese
* Datensätze zurück; beim harten Cut ist es die Quelle der Sponsor-Mail.
*/
return new class extends Migration
{
public function up(): void
{
Schema::create('consultant_suspension_transfers', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('consultant_id')->comment('pausierter Berater, bisheriger member_id');
$table->unsignedInteger('sponsor_id')->comment('Sponsor, der die Datensaetze voruebergehend erhaelt');
$table->string('record_type', 20)->comment('shopping_user, user_abo oder customer');
$table->unsignedBigInteger('record_id');
$table->timestamp('returned_at')->nullable()->comment('Rueckgabe an den Berater nach Zahlung');
$table->timestamps();
$table->index(['consultant_id', 'returned_at']);
$table->index(['record_type', 'record_id']);
});
}
public function down(): void
{
Schema::dropIfExists('consultant_suspension_transfers');
}
};

View file

@ -0,0 +1,38 @@
<?php
use App\Services\UserUtil;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* `shopping_users.auth_user_id` ist auf Produktion längst nullable — Kunden ohne
* Login und der beim harten Cut angelegte Kundendatensatz eines ausgeschiedenen
* Beraters ({@see UserUtil::setUserToClient()}) haben keinen.
* Die Ursprungsmigration legt die Spalte aber NOT NULL an, sodass Testdatenbank und
* Neuinstallationen anders reagieren als Produktion (wie `status_turnover`,
* Migration 2026_09_09_140000).
*
* Ändert nur, wo die Spalte noch NOT NULL ist; auf Produktion also nichts.
*/
return new class extends Migration
{
public function up(): void
{
$column = collect(Schema::getColumns('shopping_users'))->firstWhere('name', 'auth_user_id');
if ($column === null || $column['nullable']) {
return;
}
Schema::table('shopping_users', function (Blueprint $table) {
$table->unsignedInteger('auth_user_id')->nullable()->change();
});
}
public function down(): void
{
// Bewusst leer: Produktion war schon vorher nullable, ein Zurückdrehen würde
// bestehende Kundendatensätze ohne Login ungültig machen.
}
};