- 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>
165 lines
5.5 KiB
PHP
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
|
|
});
|