Team-Statistik = Team-Struktur (Provisionsbaum, Umschalter) und Laufstand-Fix in user_sales_volumes

K11 aus dem September-Protokoll (Tickets C1 und C2 im Entwicklungsplan), live seit 14.09.2026.

C1 – Statistik rechnet wie die Provision
- Standard-Baum ist der Provisionsbaum (Mitglieder wie TreeCalcBot: Mitgliedsebene,
  Abrechnungskonto, active_date; ausgefilterte Knoten kappen ihren Unterbaum, max. 21 Linien).
  Gesamte Downline bleibt per Umschalter erreichbar, Wahl in der Session.
- Punkte-Definition: Provisionspunkte (TP + Shop), reine Karrierepunkte ausgewiesen statt
  eingerechnet, negative Monatssalden je User wie in der Provision auf 0 gekappt.
- Abgeschlossene Monate nehmen die Mitglieder je Linie aus dem gespeicherten Provisionslauf
  (user_businesses), Snapshots je Baum-Definition (Payload-Version 3, Migration).
- Struktur-Modal: Gesamt aus den Linien-Punkten mit Dezimalen statt der int-Spalte total_pp.
- Messlauf statistics:compare-trees (Downline / Provisionsbaum / Struktur je VIP, Laufstand-Drift).

C2 – Laufstand-Race (Provision zu niedrig bei zeitgleichen Buchungen)
- Neuberechnung liest die Zeilen des Beraters sperrend (FOR UPDATE), Index (user_id, year, month).
- Paid-Pfad der Payone- und Stripe-Webhooks als DB::transaction mit 3 Anlaeufen; Deadlock und
  Lock-Timeout aus der Rechnungserstellung werden nicht mehr geschluckt.
- Sicherheitsnetz sales:repair-running-totals taeglich 02:45 vor dem Provisionslauf;
  abgeschlossene Monate werden nicht angefasst (Entscheid 14.09.: keine Nachzahlung fuer 06/08).

Doku: Entwicklungsplan zum Protokoll, Deploy-Notiz 14.09. (inkl. Migrations-Stolperstein:
Migrationen tragen Werte als Literale, nicht als Konstanten aus dem Anwendungscode).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-09-14 15:31:32 +00:00
parent 8f4e105c0f
commit d57687b4e3
33 changed files with 2321 additions and 139 deletions

View file

@ -16,6 +16,8 @@ uses(TestCase::class);
beforeEach(function () {
Schema::dropIfExists('user_sales_volumes');
Schema::dropIfExists('backoffice_statistics_snapshots');
Schema::dropIfExists('user_businesses');
Schema::dropIfExists('user_business_structures');
Schema::dropIfExists('user_abo_orders');
Schema::dropIfExists('user_abo_items');
Schema::dropIfExists('products');
@ -160,11 +162,32 @@ beforeEach(function () {
$table->timestamps();
});
// Gespeicherter Provisionslauf: fuer abgeschlossene Monate liest die
// Statistik die Struktur von dort (hier leer → Live-Baum).
Schema::create('user_business_structures', function ($table) {
$table->increments('id');
$table->unsignedTinyInteger('month');
$table->unsignedSmallInteger('year');
$table->boolean('completed')->default(false);
$table->timestamps();
});
Schema::create('user_businesses', function ($table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->unsignedTinyInteger('month');
$table->unsignedSmallInteger('year');
$table->unsignedInteger('b_structure_id')->nullable();
$table->text('sponsor')->nullable();
$table->timestamps();
});
Schema::create('backoffice_statistics_snapshots', function ($table) {
$table->id();
$table->unsignedInteger('user_id');
$table->unsignedSmallInteger('year');
$table->unsignedTinyInteger('month');
$table->string('tree', 16)->default('commission');
$table->json('payload');
$table->timestamp('calculated_at')->nullable();
$table->timestamps();