Ergebnis des Senior-Reviews über dev/2026-07-22 (Konzept 22.07., Feedback 24.07.,
Plan 19.08.). Rechnerisch war nichts zu korrigieren: Payline-Punkte und Provision
des Prognose-Blocks (N4) stimmen exakt mit der Provisions-Engine überein
(54.718,5 P. bzw. 3.772,88 EUR gegengerechnet an User 454). Behoben sind
Darstellungs- und Konsistenzfehler, die der Beraterin als falsche Zahlen
aufgefallen wären:
- "+N neu" schrumpfte rückwirkend, der Bestand daneben nicht: newAboCount() nutzte
weiter activeAbo(), während aboStockCount() auf das Monatsende zurückrechnet.
Beide teilen sich jetzt aboStockQuery(); die Jahres-Wachstumstabelle ebenso.
- Level-Kachel widersprach sich ("Zum nächsten Level Diamant fehlen X" über
"Karriere-Level: Diamant"): Text auf die Monats-Qualifikation umformuliert
(de/en/es/fr), Überschrift "Qualifikation in diesem Monat", Karriere-Level nur
noch bei Abweichung.
- Firstlines-Kachel zählte alle direkten Partner, "Teampartner gesamt" daneben nur
aktive (F1): Kachel nutzt jetzt isActiveConsultant(), die Detailseite listet
weiter alle und weist beide Zahlen aus.
- Neupartnerzahlen vergangener Monate sanken auf dem Home-Dashboard, sobald
Jahresgebühren ausliefen: neue User::newPartnerReferenceDate() (laufender Monat =
heute, abgeschlossener Monat = Monatsende). Dabei die drei Inline-Kopien der
Neupartner-Regel auf scopeNewPartnerIn()/isNewPartnerIn() zusammengeführt.
- Top-3-Karte ließ den Betrachter aus, obwohl "ich gehört in die Liste" in der
Juli-Runde für alle anderen Ranglisten umgesetzt wurde: zählt jetzt mit, inkl.
is_self und "ich"-Badge.
- Neues Profilfoto zeigte bis zu 6 h ein totes Bild: die URL steckt nicht mehr im
Cache, sondern entsteht beim Lesen (withProfileImages()).
- Test-Regression seit der Juli-Runde: Unit/Services/BackofficeDashboardServiceTest
fiel über die fehlende Spalte status_points (kam mit nonPaylinePoints() dazu).
- Kleinigkeiten: ProfileImageController gab die komplette Exception als
Flash-Message aus (jetzt Log + Sprachkey), BackofficeDrilldownService formatierte
Datumsangaben hart auf d.m.Y statt über Util::formatDateDB().
Tests: neuer HomeMonthlyStatsTest (buildMonthlyStats war ungetestet) plus Tests für
Stichtags-Zählung und Foto-Cache. Fünf Tests kodierten das alte Verhalten
(Betrachter zählt nicht mit, abgelaufener Account zählt nie) und wurden auf die
neue Semantik umgestellt, jeweils mit Gegenprobe. Suite: 861 grün (vorher 855),
89 rot — ausschließlich die in docs/2026-06-11-test-suite-vorbestehende-fehler.md
dokumentierten Altlasten.
Offen und bewusst nicht enthalten:
- Deploy-Schritt: backoffice:store-statistics-snapshots --force (vorher dumpen).
Kein vorhandener Snapshot enthält recruiter_ranking, Mai/Juni zusätzlich weder
non_payline_points noch die Führungskräfte-"gesamt"-Spalten; der Cron überspringt
vorhandene Snapshots ohne --force. Achtung: die Neuberechnung ist nicht
verlustfrei.
- TreeHtmlRendererTest/TreeCalcBotIntegrationTest: die Abo-Badges machten den
Renderer datenbankabhängig; die Dateien waren schon vorher rot und gehören in die
separate Test-Aufräumaufgabe.
- deleteImage bleibt ein GET-Route (gleiches Muster wie das Shop-Bild).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
433 lines
16 KiB
PHP
433 lines
16 KiB
PHP
<?php
|
|
|
|
use App\Models\Product;
|
|
use App\Models\ShoppingOrder;
|
|
use App\Models\UserAbo;
|
|
use App\Models\UserAboItem;
|
|
use App\Models\UserAboOrder;
|
|
use App\Models\UserAboStatusHistory;
|
|
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 je Ebene abgerechnete Abo-Punkte und die Prognose bis zum letzten Liefertag', function (): void {
|
|
Carbon\Carbon::setTestNow('2026-06-10');
|
|
|
|
$root = makeUser();
|
|
$anna = makeUser($root->id);
|
|
|
|
// Offene Ausfuehrung bis zum 25.: zaehlt in der Prognose
|
|
makeAbo($anna->id, $anna->id, 'me', '2026-05-05', 50)->update(['next_date' => '2026-06-20']);
|
|
// Naechste Ausfuehrung erst im Folgemonat: zaehlt nicht
|
|
makeAbo($anna->id, $anna->id, 'ot', '2026-05-10', 30)->update(['next_date' => '2026-07-05']);
|
|
|
|
// Bereits abgerechnete Abo-Punkte im Juni
|
|
$order = ShoppingOrder::forceCreate([
|
|
'shopping_user_id' => 0,
|
|
'auth_user_id' => $anna->id,
|
|
'country_id' => 0,
|
|
'user_shop_id' => 0,
|
|
'is_abo' => true,
|
|
'paid' => true,
|
|
]);
|
|
UserSalesVolume::forceCreate([
|
|
'user_id' => $anna->id,
|
|
'shopping_order_id' => $order->id,
|
|
'month' => 6,
|
|
'year' => 2026,
|
|
'points' => 30,
|
|
'status' => 2,
|
|
]);
|
|
|
|
$overview = app(TeamAboOverviewService::class)->linesOverview($root, 6, 2026);
|
|
$line1 = $overview['lines'][1];
|
|
|
|
expect($line1['abo_points_billed'])->toBe(30.0)
|
|
->and($line1['abo_points_expected'])->toBe(80.0) // 30 abgerechnet + 50 offen bis 20.06.
|
|
->and($overview['totals']['abo_points_billed'])->toBe(30.0)
|
|
->and($overview['totals']['abo_points_expected'])->toBe(80.0);
|
|
|
|
Carbon\Carbon::setTestNow();
|
|
});
|
|
|
|
it('liefert Abo-Bestaende monatsbezogen: vergangene Monate zeigen den Bestand zum Monatsende', function (): void {
|
|
Carbon\Carbon::setTestNow('2026-08-15');
|
|
|
|
$root = makeUser();
|
|
$anna = makeUser($root->id);
|
|
|
|
// Läuft seit Mai bis heute: zählt in Mai, Juli und im Live-Monat August
|
|
makeAbo($anna->id, $anna->id, 'me', '2026-05-10');
|
|
// Im Juli gekündigt: zählt im Juni, nicht mehr im Juli-Bestand (Stichtag 31.07.)
|
|
makeAbo($anna->id, $anna->id, 'ot', '2026-06-20')
|
|
->update(['status' => 4, 'active' => false, 'cancel_date' => '2026-07-15']);
|
|
// Im Juli soft-geloescht: zählt im Juni, nicht mehr im Juli
|
|
$deleted = makeAbo($anna->id, $anna->id, 'ot', '2026-05-01');
|
|
$deleted->delete();
|
|
UserAbo::withTrashed()->whereKey($deleted->id)->update(['deleted_at' => '2026-07-10']);
|
|
// Heute inaktiv, aber laut Status-Historie erst im August deaktiviert: zählt im Juli noch
|
|
$paused = makeAbo($anna->id, $anna->id, 'ot', '2026-06-01');
|
|
$paused->update(['active' => false]);
|
|
UserAboStatusHistory::forceCreate([
|
|
'user_abo_id' => $paused->id,
|
|
'action' => UserAboStatusHistory::ACTION_DEACTIVATED,
|
|
'created_at' => '2026-08-02',
|
|
]);
|
|
// Laut Historie schon im Juni deaktiviert, erst im August geloescht: darf
|
|
// im Juni/Juli NICHT wiederauferstehen (spaeteres Ereignis heilt nichts)
|
|
$deactivatedEarly = makeAbo($anna->id, $anna->id, 'ot', '2026-05-20');
|
|
$deactivatedEarly->update(['active' => false]);
|
|
UserAboStatusHistory::forceCreate([
|
|
'user_abo_id' => $deactivatedEarly->id,
|
|
'action' => UserAboStatusHistory::ACTION_DEACTIVATED,
|
|
'created_at' => '2026-06-10',
|
|
]);
|
|
$deactivatedEarly->delete();
|
|
UserAbo::withTrashed()->whereKey($deactivatedEarly->id)->update(['deleted_at' => '2026-08-05']);
|
|
|
|
$svc = app(TeamAboOverviewService::class);
|
|
|
|
$august = $svc->teamKpis($root, 8, 2026);
|
|
expect($august['team_partner_abos'])->toBe(1)
|
|
->and($august['team_customer_abos'])->toBe(0); // live: gekündigt/gelöscht/deaktiviert zählen nicht
|
|
|
|
$juli = $svc->teamKpis($root, 7, 2026);
|
|
expect($juli['team_partner_abos'])->toBe(1)
|
|
->and($juli['team_customer_abos'])->toBe(1); // nur das erst im August deaktivierte
|
|
|
|
$juni = $svc->teamKpis($root, 6, 2026);
|
|
expect($juni['team_customer_abos'])->toBe(3); // gekündigtes + gelöschtes + später deaktiviertes
|
|
|
|
$mai = $svc->teamKpis($root, 5, 2026);
|
|
expect($mai['team_partner_abos'])->toBe(1)
|
|
->and($mai['team_customer_abos'])->toBe(2); // Mai-Kundenabo (gelöscht 07/26) + das erst im Juni deaktivierte
|
|
|
|
Carbon\Carbon::setTestNow();
|
|
});
|
|
|
|
it('zaehlt "+N neu" mit derselben Stichtags-Logik wie den Bestand', function (): void {
|
|
Carbon\Carbon::setTestNow('2026-08-15');
|
|
|
|
$root = makeUser();
|
|
$anna = makeUser($root->id);
|
|
|
|
// Im Juni gestartet, erst im Juli gekuendigt: gehoert in den Juni-Bestand
|
|
// UND in die Juni-Zugaenge - frueher fiel es aus "+N neu" heraus, weil es
|
|
// heute nicht mehr aktiv ist.
|
|
makeAbo($anna->id, $anna->id, 'ot', '2026-06-20')
|
|
->update(['status' => 4, 'active' => false, 'cancel_date' => '2026-07-15']);
|
|
|
|
// Im Juni gestartet und noch im Juni gekuendigt: zaehlt in keiner Juni-Zahl.
|
|
makeAbo($anna->id, $anna->id, 'ot', '2026-06-02')
|
|
->update(['status' => 4, 'active' => false, 'cancel_date' => '2026-06-20']);
|
|
|
|
$svc = app(TeamAboOverviewService::class);
|
|
$juni = $svc->teamKpis($root, 6, 2026);
|
|
|
|
expect($juni['team_customer_abos'])->toBe(1)
|
|
->and($juni['team_customer_abos_new'])->toBe(1);
|
|
|
|
// Wachstumstabelle nutzt dieselbe Zaehlung
|
|
$growth = collect($svc->monthlyGrowth($root, 2026)['months'])->firstWhere('month', 6);
|
|
expect($growth['customer_new'])->toBe(1);
|
|
|
|
Carbon\Carbon::setTestNow();
|
|
});
|
|
|
|
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();
|
|
});
|