'Vor'.uniqid(), 'last_name' => 'Nach'.uniqid(), 'phone' => '0123456789', ], $accountData)); return User::forceCreate([ 'email' => 'tfl-'.uniqid('', true).'@example.com', 'password' => bcrypt('secret'), 'lang' => 'de', 'm_level' => 1, 'm_sponsor' => $sponsorId, 'account_id' => $account->id, 'active_date' => '2026-01-15', 'partner_paid_at' => '2026-01-15', 'payment_account' => now()->addYear(), ]); } it('listet die Firstlines mit Monats-Gesamtpunkten sortiert und laesst Tiefere aus', function (): void { URL::resolveMissingNamedRoutesUsing(fn (string $name, $parameters = []) => '/'.$name); $root = tflMakeUser(); $anna = tflMakeUser($root->id, ['first_name' => 'Anna', 'last_name' => 'Alpha']); $ben = tflMakeUser($root->id, ['first_name' => 'Ben', 'last_name' => 'Beta']); tflMakeUser($anna->id); // Ebene 2: keine Firstline // Abgelaufenes, aber gesetztes Abrechnungskonto: bleibt in der Liste (Nachfassen) // UND zaehlt als Berater - dieselbe Regel wie die Dashboard-Kachel // (isActiveConsultant), nicht isActiveAccount. $abgelaufen = tflMakeUser($root->id, ['first_name' => 'Ina', 'last_name' => 'Inaktiv']); $abgelaufen->forceFill(['payment_account' => now()->subMonth()])->save(); // Ohne Abrechnungskonto: kein Berater $ohneKonto = tflMakeUser($root->id, ['first_name' => 'Ohne', 'last_name' => 'Konto']); $ohneKonto->forceFill(['payment_account' => null])->save(); // Eigene (Status 1) + Kundenpunkte (Status 2) zaehlen zusammen UserSalesVolume::forceCreate(['user_id' => $anna->id, 'month' => 6, 'year' => 2026, 'points' => 100, 'status' => 1]); UserSalesVolume::forceCreate(['user_id' => $anna->id, 'month' => 6, 'year' => 2026, 'points' => 50, 'status' => 2]); UserSalesVolume::forceCreate(['user_id' => $ben->id, 'month' => 6, 'year' => 2026, 'points' => 400, 'status' => 2]); UserSalesVolume::forceCreate(['user_id' => $ben->id, 'month' => 5, 'year' => 2026, 'points' => 900, 'status' => 2]); // anderer Monat $this->actingAs($root); Request::merge(['month' => 6, 'year' => 2026]); Request::instance()->setLaravelSession(app('session')->driver()); $view = app(TeamController::class)->firstlines(app(BackofficeDashboardService::class)); $rows = $view->getData()['rows']; expect($rows)->toHaveCount(4) ->and($view->getData()['activeCount'])->toBe(3) // Kachel-Zahl: nur aktive Berater // Dritte Zahl im Kopf: die Zeilenmarkierung beantwortet eine andere Frage // (abgelaufene Jahresgebuehr) als "Berater" — ohne sie liesse sich die // Kopfzahl nicht mit den unmarkierten Zeilen abgleichen. ->and($view->getData()['expiredCount'])->toBe(2) ->and($rows[0]['user_id'])->toBe($ben->id) // 400 Punkte zuerst ->and($rows[0]['total_points'])->toBe(400.0) ->and($rows[1]['user_id'])->toBe($anna->id) ->and($rows[1]['total_points'])->toBe(150.0) ->and($rows[1]['name'])->toBe('Anna Alpha') ->and($rows[1]['phone'])->toBe('0123456789') ->and($rows[1]['email'])->toBe($anna->email); // View rendert ohne Fehler (Route-Fallback aktiv) expect($view->render()) ->toContain('Anna Alpha') ->toContain('Ben Beta'); }); it('zeigt eine leere Firstlines-Liste ohne direkte Teampartner', function (): void { URL::resolveMissingNamedRoutesUsing(fn (string $name, $parameters = []) => '/'.$name); $root = tflMakeUser(); $this->actingAs($root); Request::instance()->setLaravelSession(app('session')->driver()); $view = app(TeamController::class)->firstlines(app(BackofficeDashboardService::class)); expect($view->getData()['rows'])->toBe([]) ->and($view->render())->toContain(e(__('team.firstlines_none'))); });