Abo-Uebersichten: Team-Ansicht konsolidiert, Monatsfilter, Wachstum & Dashboard-KPIs

- /user/abos/ot: Filter nach Eingangsmonat (start_date) ueber neuen UserAbo-Scope
- Konsolidierte Team-Abo-Ansicht nach Ebenen/Linien (Bestand, neue Abos im Monat,
  Eigen-/Kundenabos, Punkte je Ebene + Summen) via TeamAboOverviewService
- team/customers leitet auf konsolidierte Ansicht um; Navigation bereinigt
- Mitglieder-Detail-Modal mit Abo-Zusammenfassung (Eigen-Abo, Kundenabos,
  Punkte, Beginn, naechste Ausfuehrung, Ausfuehrungen)
- Wachstums-Tabelle (Jahr x Monate) + Liniendiagramm (neue Eigen-/Kundenabos)
- Dashboard-KPI-Kachel (Gesamtabos, Kundenabos, 1000-Punkte-Shops)
- i18n de/en/es/fr, Tests (Filter, Overview, Wachstum, Modal, Redirect)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin 2026-06-23 14:07:29 +00:00
parent 65b867f8ee
commit 4135726772
15 changed files with 967 additions and 174 deletions

View file

@ -0,0 +1,66 @@
<?php
use App\Models\UserAbo;
use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
function createOtAbo(User $member, string $startDate): UserAbo
{
return UserAbo::create([
'user_id' => $member->id,
'member_id' => $member->id,
'shopping_user_id' => 0,
'email' => 'cust-'.uniqid('', true).'@example.com',
'is_for' => 'ot',
'payone_userid' => 0,
'clearingtype' => 'cc',
'amount' => 5000,
'status' => 2,
'abo_interval' => 5,
'start_date' => $startDate,
'last_date' => $startDate,
'next_date' => '2026-12-10',
]);
}
it('filtert Abos nach Eingangsmonat (start_date)', function (): void {
$member = User::forceCreate([
'email' => 'member-'.uniqid('', true).'@example.com',
'password' => bcrypt('secret'),
'lang' => 'de',
]);
$june = createOtAbo($member, '2026-06-15');
$july = createOtAbo($member, '2026-07-03');
$juneOtherYear = createOtAbo($member, '2025-06-20');
$filtered = UserAbo::where('member_id', $member->id)
->where('is_for', 'ot')
->addedInMonth(2026, 6)
->pluck('id');
expect($filtered)->toContain($june->id)
->not->toContain($july->id)
->not->toContain($juneOtherYear->id);
});
it('lässt die Abfrage unverändert, wenn kein Monat gewählt ist (month=0)', function (): void {
$member = User::forceCreate([
'email' => 'member-'.uniqid('', true).'@example.com',
'password' => bcrypt('secret'),
'lang' => 'de',
]);
createOtAbo($member, '2026-06-15');
createOtAbo($member, '2026-07-03');
$count = UserAbo::where('member_id', $member->id)
->where('is_for', 'ot')
->addedInMonth(2026, 0)
->count();
expect($count)->toBe(2);
});

View file

@ -160,6 +160,13 @@ it('rendert die Team-Abo-Linienansicht mit Ebenen und Summen', function (): void
'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();
@ -168,6 +175,7 @@ it('rendert die Team-Abo-Linienansicht mit Ebenen und Summen', function (): void
->toContain(__('abo.team_by_level'))
->toContain(__('abo.team_total_abos'))
->toContain(__('abo.team_growth'))
->toContain('aboGrowthChart')
->toContain($anna->email);
});

View file

@ -0,0 +1,18 @@
<?php
use App\Http\Controllers\User\TeamController;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\URL;
use Tests\TestCase;
uses(TestCase::class);
it('leitet die alte Team-Kundenabos-Route auf die konsolidierte Übersicht um', function (): void {
URL::resolveMissingNamedRoutesUsing(fn (string $name, array $parameters) => '/'.$name);
$controller = new TeamController;
$response = $controller->showTeamCustomerAbos();
expect($response)->toBeInstanceOf(RedirectResponse::class)
->and($response->getTargetUrl())->toContain('user_abos_team_show');
});