mivita/tests/Feature/TeamAboOverviewTest.php
Kevin 9565814221 Team-Abo-Uebersicht: nur aktive Berater zaehlen
Die Linientabelle zaehlte alle Downline-User statt nur aktive Berater
(m_level + gueltiges payment_account), wodurch zu viele Berater angezeigt
wurden. Filter-Logik aus BackofficeDashboardService (isActiveConsultant,
activeTeamUserIds) wiederverwendet und in TeamAboOverviewService
(buildLineRow, teamKpis, monthlyGrowth) angewendet.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 13:25:44 +00:00

307 lines
11 KiB
PHP

<?php
use App\Models\Product;
use App\Models\UserAbo;
use App\Models\UserAboItem;
use App\Models\UserAboOrder;
use App\Models\UserSalesVolume;
use App\Services\Abo\TeamAboOverviewService;
use App\Services\HTMLHelper;
use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\URL;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
beforeEach(function () {
if (! Schema::connection('sqlite')->hasColumn('products', 'slug')) {
Schema::connection('sqlite')->table('products', function ($table) {
$table->string('slug')->nullable();
});
}
});
function makeUser(?int $sponsorId = null): User
{
$user = User::forceCreate([
'email' => 'u-'.uniqid('', true).'@example.com',
'password' => bcrypt('secret'),
'lang' => 'de',
'm_level' => 1,
'm_sponsor' => $sponsorId,
'payment_account' => now()->addYear(),
]);
return $user;
}
function makeAbo(int $userId, int $memberId, string $isFor, string $startDate, int $points = 0): UserAbo
{
$abo = UserAbo::create([
'user_id' => $userId,
'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 Team-Abos nach Ebenen mit Eigen- und Kundenabos', function (): void {
$root = makeUser();
// 1. Ebene: zwei direkte Partner
$anna = makeUser($root->id);
$lena = makeUser($root->id);
// 2. Ebene: ein Partner unter Anna
$lisa = makeUser($anna->id);
// Anna hat ein Eigen-Abo (50 Punkte) und 2 Kundenabos
makeAbo($anna->id, $anna->id, 'me', '2026-06-05', 50);
makeAbo($anna->id, $anna->id, 'ot', '2026-06-10', 20);
makeAbo($anna->id, $anna->id, 'ot', '2026-05-10', 30);
// Lena hat kein Eigen-Abo, aber 1 Kundenabo
makeAbo($lena->id, $lena->id, 'ot', '2026-06-12', 15);
// Lisa (Ebene 2) hat ein Eigen-Abo
makeAbo($lisa->id, $lisa->id, 'me', '2026-06-01', 40);
$service = app(TeamAboOverviewService::class);
$overview = $service->linesOverview($root, 6, 2026);
expect($overview['lines'])->toHaveKey(1)
->and($overview['lines'])->toHaveKey(2);
$line1 = $overview['lines'][1];
expect($line1['consultants'])->toBe(2)
->and($line1['members_with_own_abo'])->toBe(1) // nur Anna
->and($line1['own_abos'])->toBe(1)
->and($line1['customer_abos'])->toBe(3) // Anna 2 + Lena 1
->and($line1['customer_abos_new'])->toBe(2) // im Juni: Anna 1 + Lena 1 (Mai zählt nicht)
->and($line1['own_abos_new'])->toBe(1);
$line2 = $overview['lines'][2];
expect($line2['consultants'])->toBe(1)
->and($line2['own_abos'])->toBe(1)
->and($line2['customer_abos'])->toBe(0);
$totals = $overview['totals'];
expect($totals['own_abos'])->toBe(2)
->and($totals['customer_abos'])->toBe(3)
->and($totals['total_abos'])->toBe(5);
});
it('zählt nur aktive Berater und blendet inaktive/fehlerhafte aus', function (): void {
$root = makeUser();
// Aktiver Berater (m_level + gültiges payment_account)
$anna = makeUser($root->id);
// Inaktiv: kein payment_account
$noPayment = makeUser($root->id);
$noPayment->forceFill(['payment_account' => null])->save();
// Inaktiv: keine Mitgliedsebene
$noLevel = makeUser($root->id);
$noLevel->forceFill(['m_level' => null])->save();
// Abos der inaktiven Berater dürfen nicht in die Übersicht einfließen
makeAbo($anna->id, $anna->id, 'me', '2026-06-05', 50);
makeAbo($noPayment->id, $noPayment->id, 'me', '2026-06-05', 10);
makeAbo($noLevel->id, $noLevel->id, 'ot', '2026-06-05', 10);
$service = app(TeamAboOverviewService::class);
$overview = $service->linesOverview($root, 6, 2026);
$line1 = $overview['lines'][1];
expect($line1['consultants'])->toBe(1)
->and($line1['members'])->toHaveCount(1)
->and($line1['members'][0]['user_id'])->toBe($anna->id)
->and($line1['own_abos'])->toBe(1)
->and($line1['customer_abos'])->toBe(0)
->and($overview['totals']['consultants'])->toBe(1);
});
it('liefert pro Mitglied das Eigen-Abo-Flag und Punkte', function (): void {
$root = makeUser();
$anna = makeUser($root->id);
makeAbo($anna->id, $anna->id, 'me', '2026-06-05', 50);
makeAbo($anna->id, $anna->id, 'ot', '2026-06-10', 20);
$service = app(TeamAboOverviewService::class);
$overview = $service->linesOverview($root, 6, 2026);
$member = collect($overview['lines'][1]['members'])->firstWhere('user_id', $anna->id);
expect($member['has_own_abo'])->toBeTrue()
->and($member['own_abo_points'])->toBe(50.0)
->and($member['customer_abo_count'])->toBe(1)
->and($member['customer_abo_points'])->toBe(20.0)
->and($member['total_abos'])->toBe(2);
});
it('rendert die Team-Abo-Linienansicht mit Ebenen und Summen', function (): void {
URL::resolveMissingNamedRoutesUsing(fn (string $name, array $parameters) => '/'.$name.'/'.implode('/', $parameters));
$root = makeUser();
$anna = makeUser($root->id);
makeAbo($anna->id, $anna->id, 'me', '2026-06-05', 50);
makeAbo($anna->id, $anna->id, 'ot', '2026-06-10', 20);
$this->actingAs($root);
$service = app(TeamAboOverviewService::class);
$overview = $service->linesOverview($root, 6, 2026);
$growth = $service->monthlyGrowth($root, 2026);
$html = view('user.team.abos', [
'currentMonthName' => HTMLHelper::getTransMonths()[6],
'overview' => $overview,
'growth' => $growth,
'growthChart' => [
'labels' => array_map(fn ($r) => $r['label'], $growth['months']),
'own' => array_map(fn ($r) => $r['own_new'], $growth['months']),
'customer' => array_map(fn ($r) => $r['customer_new'], $growth['months']),
'step' => 1,
'suggested_max' => 5,
],
'growthYear' => 2026,
'filterYears' => HTMLHelper::getYearRange(2022),
])->render();
expect($html)
->toContain(__('abo.team_by_level'))
->toContain(__('abo.team_total_abos'))
->toContain(__('abo.team_growth'))
->toContain('aboGrowthChart')
->toContain($anna->email);
});
it('berechnet das monatliche Abo-Wachstum je Jahr', function (): void {
$root = makeUser();
$anna = makeUser($root->id);
$lena = makeUser($root->id);
// Juni 2026: 1 Eigen-Abo + 2 Kundenabos
makeAbo($anna->id, $anna->id, 'me', '2026-06-05');
makeAbo($anna->id, $anna->id, 'ot', '2026-06-10');
makeAbo($lena->id, $lena->id, 'ot', '2026-06-20');
// März 2026: 1 Kundenabo
makeAbo($anna->id, $anna->id, 'ot', '2026-03-15');
// Anderes Jahr: zählt nicht
makeAbo($anna->id, $anna->id, 'ot', '2025-06-01');
$growth = app(TeamAboOverviewService::class)->monthlyGrowth($root, 2026);
expect($growth['year'])->toBe(2026)
->and($growth['months'])->toHaveCount(12);
$june = collect($growth['months'])->firstWhere('month', 6);
$march = collect($growth['months'])->firstWhere('month', 3);
expect($june['own_new'])->toBe(1)
->and($june['customer_new'])->toBe(2)
->and($june['total_new'])->toBe(3)
->and($march['customer_new'])->toBe(1)
->and($march['own_new'])->toBe(0);
expect($growth['totals']['own_new'])->toBe(1)
->and($growth['totals']['customer_new'])->toBe(3)
->and($growth['totals']['total_new'])->toBe(4);
});
it('liefert leichtgewichtige Dashboard-KPIs inkl. 1000-Punkte-Shop', function (): void {
$root = makeUser();
$anna = makeUser($root->id);
$lena = makeUser($root->id);
// Abos
makeAbo($anna->id, $anna->id, 'me', '2026-06-05'); // 1 Eigen-Abo
makeAbo($anna->id, $anna->id, 'ot', '2026-06-10'); // 1 Kundenabo
makeAbo($lena->id, $lena->id, 'ot', '2026-06-12'); // 1 Kundenabo
// Anna erreicht 1000 Punkte Kundenumsatz im Juni 2026
UserSalesVolume::create([
'user_id' => $anna->id,
'month' => 6,
'year' => 2026,
'month_KP_points' => 600,
'month_shop_points' => 500,
'points' => 1100,
'status' => 1,
]);
$kpis = app(TeamAboOverviewService::class)->teamKpis($root, 6, 2026);
expect($kpis['team_partner_abos'])->toBe(1)
->and($kpis['team_customer_abos'])->toBe(2)
->and($kpis['team_total_abos'])->toBe(3)
->and($kpis['shop_1000'])->toBe(1);
});
it('liefert die Abo-Zusammenfassung eines Team-Mitglieds inkl. Ausführungen', function (): void {
$root = makeUser();
$anna = makeUser($root->id);
$ownAbo = makeAbo($anna->id, $anna->id, 'me', '2026-06-05', 50);
makeAbo($anna->id, $anna->id, 'ot', '2026-06-10', 20);
makeAbo($anna->id, $anna->id, 'ot', '2026-05-01', 30);
// Zwei ausgeführte (bezahlte) Lieferungen für das Eigen-Abo
UserAboOrder::create(['user_abo_id' => $ownAbo->id, 'shopping_order_id' => 0, 'status' => 2, 'paid' => true]);
UserAboOrder::create(['user_abo_id' => $ownAbo->id, 'shopping_order_id' => 0, 'status' => 2, 'paid' => true]);
$summary = app(TeamAboOverviewService::class)->memberAboSummary($root, $anna->id);
expect($summary)->not->toBeNull()
->and($summary['email'])->toBe($anna->email)
->and($summary['own_abo'])->not->toBeNull()
->and($summary['own_abo']['points'])->toBe(50.0)
->and($summary['own_abo']['executions'])->toBe(2)
->and($summary['customer_abos']['count'])->toBe(2)
->and($summary['customer_abos']['total_points'])->toBe(50.0)
->and($summary['customer_abos']['items'])->toHaveCount(2);
});
it('liefert null, wenn das Mitglied nicht im Team liegt', function (): void {
$root = makeUser();
$stranger = makeUser(); // kein Sponsor → nicht in der Downline
$summary = app(TeamAboOverviewService::class)->memberAboSummary($root, $stranger->id);
expect($summary)->toBeNull();
});