mivita/tests/Feature/BackofficeStatisticsDrilldownTest.php
Kevin 386851aee8 Backoffice-Statistik vereinfacht & Dashboard-Monatsstatistik konsolidiert
- Statistik-Uebersicht: Tabelle auf Linie/Berater/Umsatz(Punkte)/Teamabos/
  Teamkundenabos reduziert; Neupartner als grünes "+N"-Badge in der
  Berater-Spalte; 1000-Punkte-Shop & Neupartner als klickbare KPI-Cards
  (Augen-Icon/Liste anzeigen)
- Teamkundenabos-Drilldown pro Teampartner gruppiert (Anzahl + Volumenpunkte
  gesamt, aufklappbar je Abo), CSV-Export angepasst
- Home: fehlerhafte Monatsstatistik-Card (inkonsistente aktiv-Definition,
  kein Monatsbezug, schwere Live-Logik im Blade) mit der Abo-KPI-Card
  zusammengefuehrt; Logik nach HomeController::buildMonthlyStats() ausgelagert,
  konsistent ueber TeamAboOverviewService/BackofficeDashboardService
- Team-Payline fuer laufenden Monat live berechnet und 6h gecacht (Snapshot)
- Kacheln in Cluster (Umsatz/Neupartner/Abos) gruppiert
- Services: teamKpis() um *_new ergaenzt, personalAboKpis() & newPartnersCount() neu
- i18n (de/en/es/fr), Tests ergaenzt/aktualisiert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 13:00:38 +00:00

165 lines
5.5 KiB
PHP

<?php
use App\Models\Product;
use App\Models\UserAbo;
use App\Models\UserAboItem;
use App\Services\Backoffice\BackofficeDashboardService;
use App\Services\Backoffice\BackofficeDrilldownService;
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 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
});