Backoffice-Statistik: Punkte-Fix (kumulative Snapshots) & Meine Statistik

Punkte/Umsatz korrekt aus per-Zeile points/total_net je status berechnen
statt SUM der kumulativen month_*-Snapshots (zaehlte massiv mehrfach).
Zentralisiert in SalesVolumePoints, genutzt von Dashboard- & Drilldown-Service.

Detail-Tabelle neu gegliedert: Eigenpunkte | Shop | Abo | Sonstige |
Kundenpunkte (=Summe) | Gesamt, mit gruener Hervorhebung der Basis-/Summenspalten.

Neue VIP-Seite "Meine Statistik" (Untermenue neben Team-Statistik):
KPI-Karten + nachvollziehbare Detailtabellen, gruppiert nach eigenen
Bestellungen, Shop-Bestellungen und Abos (PersonalStatisticsService).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-06-25 13:31:14 +00:00
parent db5dd7e1aa
commit 54bf2e5595
14 changed files with 666 additions and 70 deletions

View file

@ -4,6 +4,7 @@ use App\Http\Controllers\User\BackofficeStatisticsController;
use App\Models\ShoppingOrder;
use App\Services\Backoffice\BackofficeDashboardService;
use App\Services\Backoffice\BackofficeDrilldownService;
use App\Services\Backoffice\PersonalStatisticsService;
use App\User;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;
@ -31,12 +32,13 @@ function makeBackofficeStatisticsRequest(User $user): Request
return $request;
}
function makeBackofficeStatisticsController(?BackofficeDashboardService $dashboardService = null, ?BackofficeDrilldownService $drilldownService = null): BackofficeStatisticsController
function makeBackofficeStatisticsController(?BackofficeDashboardService $dashboardService = null, ?BackofficeDrilldownService $drilldownService = null, ?PersonalStatisticsService $personalService = null): BackofficeStatisticsController
{
$dashboardService ??= Mockery::mock(BackofficeDashboardService::class);
$drilldownService ??= Mockery::mock(BackofficeDrilldownService::class);
$personalService ??= Mockery::mock(PersonalStatisticsService::class);
return new BackofficeStatisticsController($dashboardService, $drilldownService);
return new BackofficeStatisticsController($dashboardService, $drilldownService, $personalService);
}
function backofficeStatisticsStreamedContent(StreamedResponse $response): string
@ -113,6 +115,34 @@ it('blockiert die Backoffice-Statistik fuer normale aktive User', function () {
->toThrow(NotFoundHttpException::class);
});
it('blockiert die persoenliche Statistik fuer normale aktive User', function () {
$user = makeBackofficeStatisticsUser(0);
$controller = makeBackofficeStatisticsController();
expect(fn () => $controller->my(makeBackofficeStatisticsRequest($user)))
->toThrow(NotFoundHttpException::class);
});
it('liefert die persoenliche Statistik fuer VIP-User', function () {
$vip = makeBackofficeStatisticsUser(1);
$personalService = Mockery::mock(PersonalStatisticsService::class);
$personalService
->shouldReceive('monthly')
->once()
->andReturn(['month' => 5, 'year' => 2026, 'totals' => [], 'groups' => []]);
$controller = makeBackofficeStatisticsController(null, null, $personalService);
$request = makeBackofficeStatisticsRequest($vip);
$request->query->set('month', 5);
$request->query->set('year', 2026);
$response = $controller->my($request);
expect($response->name())->toBe('user.backoffice.statistics.my');
expect($response->getData()['selectedMonth'])->toBe(5);
expect($response->getData()['statistics']['year'])->toBe(2026);
});
it('erstellt einen CSV-Export fuer die Statistik-Uebersicht', function () {
$vip = makeBackofficeStatisticsUser(1);
$dashboardService = Mockery::mock(BackofficeDashboardService::class);
@ -229,9 +259,9 @@ it('erstellt einen CSV-Export fuer Detaildaten mit Karriere-Level und Summenzeil
expect($response)->toBeInstanceOf(StreamedResponse::class);
expect($response->headers->get('content-disposition'))->toContain('backoffice-statistik-shop_1000-linie-alle-05-2026.csv');
expect($content)->toContain('Name;E-Mail;Karriere-Level;Eigenpunkte;"Externe Punkte"');
expect($content)->toContain('"Max Mustermann";max@example.test;Partner;400;700;700;0;0;1100');
expect($content)->toContain('Summe;"1 Eintraege";;400;700;700;0;0;1100');
expect($content)->toContain('Name;E-Mail;Karriere-Level;Eigenpunkte;Shop-Punkte;Abo-Punkte;"Sonstige Punkte";Kundenpunkte;Gesamtpunkte');
expect($content)->toContain('"Max Mustermann";max@example.test;Partner;400;0;700;0;700;1100');
expect($content)->toContain('Summe;"1 Eintraege";;400;0;700;0;700;1100');
});
it('nutzt den gespeicherten Zeitraum fuer Detailansicht und Export', function () {