hasColumn('products', 'slug')) { Schema::connection('sqlite')->table('products', function ($table) { $table->string('slug')->nullable(); }); } }); function boMakeUser(?int $sponsorId = null, ?string $activeDate = null, ?string $paymentAccount = null): User { return User::forceCreate([ 'email' => 'bo-'.uniqid('', true).'@example.com', 'password' => bcrypt('secret'), 'lang' => 'de', 'm_level' => 1, 'm_sponsor' => $sponsorId, 'active_date' => $activeDate, 'payment_account' => $paymentAccount ?? now()->addYear(), ]); } function boMakeAbo(int $memberId, string $isFor, string $startDate, int $points = 0): UserAbo { $abo = UserAbo::create([ 'user_id' => $memberId, 'member_id' => $memberId, 'shopping_user_id' => 0, 'email' => 'a-'.uniqid('', true).'@example.com', 'is_for' => $isFor, 'payone_userid' => 0, 'clearingtype' => 'cc', 'amount' => 5000, 'active' => true, 'status' => 2, 'abo_interval' => 5, 'start_date' => $startDate, 'last_date' => $startDate, 'next_date' => '2026-12-10', ]); if ($points > 0) { $product = Product::forceCreate([ 'name' => 'P-'.uniqid('', true), 'title' => 'Test', 'price' => 10.0, 'tax' => 19.0, 'active' => true, 'pos' => 1, 'weight' => 100, 'points' => $points, 'slug' => 'p-'.uniqid('', true), ]); UserAboItem::create([ 'user_abo_id' => $abo->id, 'product_id' => $product->id, 'comp' => 0, 'qty' => 1, 'status' => 1, ]); } return $abo->fresh(); } it('gruppiert Teamkundenabos pro Teampartner mit Punkten je Abo und gesamt', function (): void { $root = boMakeUser(); $anna = boMakeUser($root->id); $lena = boMakeUser($root->id); // Anna: 2 Kundenabos (20 + 30 Punkte), Lena: 1 Kundenabo (15 Punkte) boMakeAbo($anna->id, 'ot', '2026-06-10', 20); boMakeAbo($anna->id, 'ot', '2026-05-10', 30); boMakeAbo($lena->id, 'ot', '2026-06-12', 15); $details = app(BackofficeDrilldownService::class)->details($root, 0, 'team_customer_abos', 6, 2026); expect($details['rows'])->toHaveCount(2); // Nach Gesamtpunkten absteigend: Anna (50) vor Lena (15) $first = $details['rows'][0]; expect($first['type'])->toBe('customer_abo_group') ->and($first['consultant_id'])->toBe($anna->id) ->and($first['abo_count'])->toBe(2) ->and($first['points'])->toBe(50.0) ->and($first['abos'])->toHaveCount(2); expect($details['summary']['abo_count'])->toBe(3) ->and($details['summary']['points'])->toBe(65.0); }); it('markiert im Teamkundenabo-Drilldown die im Monat neuen Abos', function (): void { $root = boMakeUser(); $anna = boMakeUser($root->id); boMakeAbo($anna->id, 'ot', '2026-06-10', 20); // neu im Juni boMakeAbo($anna->id, 'ot', '2026-05-10', 30); // nicht neu $details = app(BackofficeDrilldownService::class)->details($root, 0, 'team_customer_abos', 6, 2026); $abos = collect($details['rows'][0]['abos']); expect($abos->where('is_new_this_month', true))->toHaveCount(1) ->and($abos->where('is_new_this_month', false))->toHaveCount(1); }); it('zaehlt Neupartner nur fuer den gewaehlten Monat mit aktivem Account', function (): void { $root = boMakeUser(); $newInJune = boMakeUser($root->id, '2026-06-15'); $newInMay = boMakeUser($root->id, '2026-05-15'); $expiredAccount = boMakeUser($root->id, '2026-06-20', now()->subDay()); $service = app(BackofficeDashboardService::class); $teamIds = $service->teamUserIds($root->id); expect($service->newPartnersCount($teamIds, 6, 2026))->toBe(1) ->and($service->newPartnersCount($teamIds, 5, 2026))->toBe(1); }); it('rendert die kombinierte Monatsstatistik-Card mit Abo- und Neupartner-Kennzahlen', function (): void { URL::resolveMissingNamedRoutesUsing(fn (string $name, $parameters = []) => '/'.$name); $html = view('dashboard._statistics', [ 'monthlyStats' => [ 'month' => 6, 'year' => 2026, 'customer_points' => 1200.0, 'team_payline_points' => 3400.0, 'payline_is_live' => true, 'direct_new_partners' => 2, 'team_total_abos' => 5, 'team_partner_abos' => 3, 'team_partner_abos_new' => 1, 'team_customer_abos' => 2, 'team_customer_abos_new' => 1, 'shop_1000' => 1, 'new_partners' => 4, 'own_abos' => 1, 'own_abos_new' => 0, 'own_customer_abos' => 2, 'own_customer_abos_new' => 1, ], ])->render(); expect($html) ->toContain(__('home.monthly_statistics')) ->toContain(__('home.team_customer_subscriptions')) ->toContain(__('abo.shop_1000')) ->toContain(__('home.team_new_partners')) ->toContain('user_abos_team_show') ->toContain('+1'); // "+neu"-Badge im gewählten Monat });