billing: Vertragsablauf bedingt beenden, Kundenmail nachholbar (Block 7, U11/U12)

- Beenden je Kunde in einer Transaktion mit erneuter Prüfung aller
  Auswahlbedingungen unter Zeilensperre; Ereignis nur für getroffene Zeilen
- Marker grandfathering_expired_at/-notified_at; Mail in zweiter Phase,
  bei Fehler beim Einplanen FAILURE und Nachholen im nächsten Lauf

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-18 12:03:04 +00:00
parent 05cf53e17d
commit eb95b3b830
4 changed files with 363 additions and 46 deletions

View file

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Nachholbare Ablaufbenachrichtigung beim Bestandsschutz (Audit U12).
*
* `billing:expire-grandfathering` beendete den Posten zuerst und plante die
* Kundenmail danach ein. Scheiterte das Einplanen, war der Vertrag bereits
* `cancelled` und kein späterer Lauf hätte die Mail nachgeholt.
* `grandfathering_expired_at` wird im selben Schreibzugriff wie
* `cancelled_at` gesetzt und markiert die offene Benachrichtigung;
* `grandfathering_notified_at` schließt sie, sobald die Mail eingeplant
* (oder wegen gesperrter Adresse bewusst ausgelassen) wurde. Kleine Tabelle.
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('user_payment_options', function (Blueprint $table) {
if (! Schema::hasColumn('user_payment_options', 'grandfathering_expired_at')) {
$table->timestamp('grandfathering_expired_at')->nullable()->after('cancelled_at');
}
if (! Schema::hasColumn('user_payment_options', 'grandfathering_notified_at')) {
$table->timestamp('grandfathering_notified_at')->nullable()->after('grandfathering_expired_at');
}
});
}
public function down(): void
{
Schema::table('user_payment_options', function (Blueprint $table) {
$table->dropColumn(array_filter([
Schema::hasColumn('user_payment_options', 'grandfathering_expired_at') ? 'grandfathering_expired_at' : null,
Schema::hasColumn('user_payment_options', 'grandfathering_notified_at') ? 'grandfathering_notified_at' : null,
]));
});
}
};