Team-Abo-Uebersicht: nur aktive Berater zaehlen

Die Linientabelle zaehlte alle Downline-User statt nur aktive Berater
(m_level + gueltiges payment_account), wodurch zu viele Berater angezeigt
wurden. Filter-Logik aus BackofficeDashboardService (isActiveConsultant,
activeTeamUserIds) wiederverwendet und in TeamAboOverviewService
(buildLineRow, teamKpis, monthlyGrowth) angewendet.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin 2026-06-25 13:25:44 +00:00
parent 386851aee8
commit 9565814221
3 changed files with 74 additions and 11 deletions

View file

@ -57,7 +57,7 @@ class TeamAboOverviewService
*/
public function teamKpis(User $user, int $month, int $year): array
{
$teamUserIds = $this->dashboardService->teamUserIds($user->id);
$teamUserIds = $this->dashboardService->activeTeamUserIds($user->id);
if ($teamUserIds === []) {
return [
@ -116,7 +116,7 @@ class TeamAboOverviewService
*/
public function monthlyGrowth(User $user, int $year): array
{
$teamUserIds = $this->dashboardService->teamUserIds($user->id);
$teamUserIds = $this->dashboardService->activeTeamUserIds($user->id);
$monthNames = \App\Services\HTMLHelper::getTransMonths();
$ownByMonth = $teamUserIds === [] ? [] : $this->newAbosByMonth($teamUserIds, 'user_id', 'me', $year);
@ -250,6 +250,11 @@ class TeamAboOverviewService
*/
private function buildLineRow(int $line, Collection $users, int $month, int $year): array
{
// Nur aktive Berater zählen/anzeigen (konsistent mit der Backoffice-Statistik).
// Die Baum-Traversierung in lineBuckets() bleibt unberührt, damit auch die
// Downline inaktiver Zwischen-Berater weiterhin erreicht wird.
$users = $users->filter(fn (User $teamUser) => $this->dashboardService->isActiveConsultant($teamUser))->values();
$userIds = $users->pluck('id')->map(fn ($id) => (int) $id)->all();
if ($userIds === []) {

View file

@ -175,6 +175,31 @@ class BackofficeDashboardService
->all();
}
/**
* IDs der gesamten Downline, aber nur aktive Berater
* (gleiche Definition wie die "Berater"-Spalte der Backoffice-Statistik).
*
* @return int[]
*/
public function activeTeamUserIds(int $rootUserId): array
{
return collect($this->lineBuckets($rootUserId))
->flatMap(fn (Collection $users) => $users)
->filter(fn (User $user) => $this->isActiveConsultant($user))
->map(fn (User $user) => (int) $user->id)
->values()
->all();
}
/**
* Ein aktiver Berater hat eine Mitgliedsebene und ein gesetztes Abrechnungskonto.
* Maßgeblich für die "Berater"-Zählung in den Team-Übersichten.
*/
public function isActiveConsultant(User $user): bool
{
return $user->m_level !== null && $user->payment_account !== null;
}
/**
* @param \Illuminate\Support\Collection<int, \App\User> $users
* @return array<string, mixed>
@ -261,7 +286,7 @@ class BackofficeDashboardService
private function activeConsultants(Collection $users): int
{
return $users
->filter(fn (User $user) => $user->m_level !== null && $user->payment_account !== null)
->filter(fn (User $user) => $this->isActiveConsultant($user))
->count();
}
@ -324,12 +349,12 @@ class BackofficeDashboardService
->whereIn('user_id', $userIds)
->where('month', $month)
->where('year', $year)
->selectRaw('COALESCE(SUM(month_KP_points), 0) as own_points')
->selectRaw('COALESCE(SUM(month_shop_points), 0) as external_points')
->selectRaw('COALESCE(SUM(CASE WHEN shopping_orders.is_abo = 1 THEN month_shop_points ELSE 0 END), 0) as customer_abo_points')
->selectRaw('COALESCE(SUM(CASE WHEN shopping_orders.id IS NOT NULL AND (shopping_orders.is_abo = 0 OR shopping_orders.is_abo IS NULL) THEN month_shop_points ELSE 0 END), 0) as customer_single_order_points')
->selectRaw('COALESCE(SUM(CASE WHEN shopping_orders.id IS NULL THEN month_shop_points ELSE 0 END), 0) as customer_other_points')
->selectRaw('COALESCE(SUM(month_total_net), 0) + COALESCE(SUM(month_shop_total_net), 0) as turnover_net')
->selectRaw(SalesVolumePoints::ownPoints().' as own_points')
->selectRaw(SalesVolumePoints::externalPoints().' as external_points')
->selectRaw(SalesVolumePoints::customerAboPoints().' as customer_abo_points')
->selectRaw(SalesVolumePoints::customerSingleOrderPoints().' as customer_single_order_points')
->selectRaw(SalesVolumePoints::customerOtherPoints().' as customer_other_points')
->selectRaw(SalesVolumePoints::turnoverNet().' as turnover_net')
->first();
$ownPoints = (float) ($summary->own_points ?? 0);
@ -382,9 +407,9 @@ class BackofficeDashboardService
->where('month', $month)
->where('year', $year)
->select('user_id')
->selectRaw('COALESCE(SUM(month_KP_points), 0) + COALESCE(SUM(month_shop_points), 0) as total_points')
->selectRaw(SalesVolumePoints::totalPoints().' as total_points')
->groupBy('user_id')
->havingRaw('COALESCE(SUM(month_KP_points), 0) + COALESCE(SUM(month_shop_points), 0) >= 1000')
->havingRaw(SalesVolumePoints::totalPoints().' >= 1000')
->get()
->count();
}

View file

@ -123,6 +123,38 @@ it('gruppiert Team-Abos nach Ebenen mit Eigen- und Kundenabos', function (): voi
->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 pro Mitglied das Eigen-Abo-Flag und Punkte', function (): void {
$root = makeUser();
$anna = makeUser($root->id);
@ -229,6 +261,7 @@ it('liefert leichtgewichtige Dashboard-KPIs inkl. 1000-Punkte-Shop', function ()
'year' => 2026,
'month_KP_points' => 600,
'month_shop_points' => 500,
'points' => 1100,
'status' => 1,
]);