>, * totals: array * } */ public function linesOverview(User $user, int $month, int $year): array { $lineBuckets = $this->dashboardService->lineBuckets($user->id); $lines = []; $totals = $this->emptyLine(0); $totals['label'] = __('abo.team_total'); foreach ($lineBuckets as $line => $users) { $row = $this->buildLineRow((int) $line, $users, $month, $year); $lines[$line] = $row; $totals = $this->addToTotals($totals, $row); } return [ 'month' => $month, 'year' => $year, 'lines' => $lines, 'totals' => $totals, ]; } /** * Leichtgewichtige Kennzahlen für das Dashboard (ohne Punkte-/Mitglieder-Details). * * @return array{team_total_abos: int, team_partner_abos: int, team_customer_abos: int, shop_1000: int} */ public function teamKpis(User $user, int $month, int $year): array { $teamUserIds = $this->dashboardService->teamUserIds($user->id); if ($teamUserIds === []) { return [ 'team_total_abos' => 0, 'team_partner_abos' => 0, 'team_customer_abos' => 0, 'shop_1000' => 0, ]; } $partnerAbos = UserAbo::query()->activeAbo()->where('is_for', 'me')->whereIn('user_id', $teamUserIds)->count(); $customerAbos = UserAbo::query()->activeAbo()->where('is_for', 'ot')->whereIn('member_id', $teamUserIds)->count(); return [ 'team_total_abos' => $partnerAbos + $customerAbos, 'team_partner_abos' => $partnerAbos, 'team_customer_abos' => $customerAbos, 'shop_1000' => $this->dashboardService->shop1000Count($teamUserIds, $month, $year), ]; } /** * Monatliches Abo-Wachstum des Teams für ein Jahr: je Monat die Anzahl neu * hinzugekommener (aktiver) Eigen- und Kunden-Abos anhand des Eingangsmonats * (start_date). Dient der separaten Wachstums-Tabelle unter der Bestandsübersicht. * * @return array{ * year: int, * months: array, * totals: array{own_new: int, customer_new: int, total_new: int} * } */ public function monthlyGrowth(User $user, int $year): array { $teamUserIds = $this->dashboardService->teamUserIds($user->id); $monthNames = \App\Services\HTMLHelper::getTransMonths(); $ownByMonth = $teamUserIds === [] ? [] : $this->newAbosByMonth($teamUserIds, 'user_id', 'me', $year); $customerByMonth = $teamUserIds === [] ? [] : $this->newAbosByMonth($teamUserIds, 'member_id', 'ot', $year); $months = []; $totals = ['own_new' => 0, 'customer_new' => 0, 'total_new' => 0]; for ($month = 1; $month <= 12; $month++) { $own = $ownByMonth[$month] ?? 0; $customer = $customerByMonth[$month] ?? 0; $months[] = [ 'month' => $month, 'label' => $monthNames[$month] ?? (string) $month, 'own_new' => $own, 'customer_new' => $customer, 'total_new' => $own + $customer, ]; $totals['own_new'] += $own; $totals['customer_new'] += $customer; $totals['total_new'] += $own + $customer; } return [ 'year' => $year, 'months' => $months, 'totals' => $totals, ]; } /** * Zählt neue (aktive) Abos je Monat eines Jahres anhand start_date. * DB-agnostisch (kein MONTH()/strftime): rohe Datumswerte werden in PHP gruppiert. * * @param int[] $userIds * @return array */ private function newAbosByMonth(array $userIds, string $userColumn, string $isFor, int $year): array { $dates = UserAbo::query() ->activeAbo() ->where('is_for', $isFor) ->whereIn($userColumn, $userIds) ->whereYear('start_date', $year) ->toBase() ->pluck('start_date'); $byMonth = []; foreach ($dates as $date) { if (! $date) { continue; } $month = (int) Carbon::parse($date)->month; $byMonth[$month] = ($byMonth[$month] ?? 0) + 1; } return $byMonth; } /** * Abo-Zusammenfassung eines einzelnen Team-Mitglieds für das Detail-Modal. * Liefert null, wenn das Mitglied nicht in der Downline des Owners liegt. * * @return array{ * name: string, * email: string, * own_abo: array|null, * customer_abos: array{count: int, total_points: float, items: array>} * }|null */ public function memberAboSummary(User $owner, int $memberId): ?array { if (! \App\Services\AboHelper::isUserInTeam($owner->id, $memberId)) { return null; } $member = User::with('account')->find($memberId); if (! $member) { return null; } $ownAbo = UserAbo::query() ->activeAbo() ->where('is_for', 'me') ->where('user_id', $memberId) ->with(['user_abo_items.product', 'user_abo_orders']) ->orderByDesc('id') ->first(); $customerAbos = UserAbo::query() ->activeAbo() ->where('is_for', 'ot') ->where('member_id', $memberId) ->with(['user_abo_items.product', 'user_abo_orders']) ->orderBy('start_date') ->get(); return [ 'name' => $this->memberName($member), 'email' => $member->email ?? '', 'own_abo' => $ownAbo ? $this->aboSummary($ownAbo) : null, 'customer_abos' => [ 'count' => $customerAbos->count(), 'total_points' => (float) $customerAbos->sum(fn (UserAbo $abo) => $abo->getTotalPoints()), 'items' => $customerAbos->map(fn (UserAbo $abo) => $this->aboSummary($abo))->all(), ], ]; } /** * @return array{points: float, start_date: string, next_date: string, executions: int, amount: string, interval: int} */ private function aboSummary(UserAbo $abo): array { return [ 'points' => (float) $abo->getTotalPoints(), 'start_date' => $abo->start_date, 'next_date' => $abo->next_date, 'executions' => $abo->getCountPaidOrders(), 'amount' => $abo->getFormattedAmount(), 'interval' => (int) $abo->abo_interval, ]; } /** * @param \Illuminate\Support\Collection $users * @return array */ private function buildLineRow(int $line, Collection $users, int $month, int $year): array { $userIds = $users->pluck('id')->map(fn ($id) => (int) $id)->all(); if ($userIds === []) { return $this->emptyLine($line); } $users->loadMissing('user_level'); $ownAbos = $this->ownAbosByUser($userIds); $customerAbos = $this->customerAbosByMember($userIds); $members = []; $membersWithOwnAbo = 0; $ownAbosCount = 0; $ownAboPoints = 0.0; $customerAbosCount = 0; $customerAboPoints = 0.0; foreach ($users as $teamUser) { $uid = (int) $teamUser->id; $ownAbo = $ownAbos->get($uid); $custList = $customerAbos->get($uid, collect()); $hasOwnAbo = $ownAbo !== null; $memberOwnPoints = $hasOwnAbo ? (float) $ownAbo->getTotalPoints() : 0.0; $memberCustomerCount = $custList->count(); $memberCustomerPoints = (float) $custList->sum(fn (UserAbo $abo) => $abo->getTotalPoints()); if ($hasOwnAbo) { $membersWithOwnAbo++; $ownAbosCount++; $ownAboPoints += $memberOwnPoints; } $customerAbosCount += $memberCustomerCount; $customerAboPoints += $memberCustomerPoints; $members[] = [ 'user_id' => $uid, 'name' => $this->memberName($teamUser), 'career_level' => $teamUser->user_level?->getLang('name') ?? '', 'has_own_abo' => $hasOwnAbo, 'own_abo_points' => $memberOwnPoints, 'customer_abo_count' => $memberCustomerCount, 'customer_abo_points' => $memberCustomerPoints, 'total_abos' => ($hasOwnAbo ? 1 : 0) + $memberCustomerCount, ]; } return [ 'line' => $line, 'label' => __('abo.team_line', ['line' => $line]), 'consultants' => $users->count(), 'members_total' => $users->count(), 'members_with_own_abo' => $membersWithOwnAbo, 'own_abos' => $ownAbosCount, 'own_abos_new' => $this->newAboCount($userIds, 'user_id', 'me', $month, $year), 'own_abo_points' => $ownAboPoints, 'customer_abos' => $customerAbosCount, 'customer_abos_new' => $this->newAboCount($userIds, 'member_id', 'ot', $month, $year), 'customer_abo_points' => $customerAboPoints, 'total_abos' => $ownAbosCount + $customerAbosCount, 'members' => $members, ]; } /** * Eigene (me) aktive Abos je Berater, indiziert nach user_id (ein Abo pro Berater). * * @param int[] $userIds * @return \Illuminate\Support\Collection */ private function ownAbosByUser(array $userIds): Collection { return UserAbo::query() ->activeAbo() ->where('is_for', 'me') ->whereIn('user_id', $userIds) ->with('user_abo_items.product') ->get() ->keyBy(fn (UserAbo $abo) => (int) $abo->user_id); } /** * Kundenabos (ot) je Berater, gruppiert nach member_id. * * @param int[] $userIds * @return \Illuminate\Support\Collection> */ private function customerAbosByMember(array $userIds): Collection { return UserAbo::query() ->activeAbo() ->where('is_for', 'ot') ->whereIn('member_id', $userIds) ->with('user_abo_items.product') ->get() ->groupBy(fn (UserAbo $abo) => (int) $abo->member_id); } /** * @param int[] $userIds */ private function newAboCount(array $userIds, string $userColumn, string $isFor, int $month, int $year): int { $startDate = Carbon::create($year, $month, 1)->startOfMonth(); $endDate = Carbon::create($year, $month, 1)->endOfMonth(); return UserAbo::query() ->activeAbo() ->whereIn($userColumn, $userIds) ->where('is_for', $isFor) ->whereBetween('start_date', [$startDate, $endDate]) ->count(); } private function memberName(User $user): string { if ($user->account) { $name = trim($user->account->first_name.' '.$user->account->last_name); if ($name !== '') { return $name; } } return $user->email ?? ('#'.$user->id); } /** * @return array */ private function emptyLine(int $line): array { return [ 'line' => $line, 'label' => $line > 0 ? __('abo.team_line', ['line' => $line]) : __('abo.team_total'), 'consultants' => 0, 'members_total' => 0, 'members_with_own_abo' => 0, 'own_abos' => 0, 'own_abos_new' => 0, 'own_abo_points' => 0.0, 'customer_abos' => 0, 'customer_abos_new' => 0, 'customer_abo_points' => 0.0, 'total_abos' => 0, 'members' => [], ]; } /** * @param array $totals * @param array $row * @return array */ private function addToTotals(array $totals, array $row): array { foreach (['consultants', 'members_total', 'members_with_own_abo', 'own_abos', 'own_abos_new', 'own_abo_points', 'customer_abos', 'customer_abos_new', 'customer_abo_points', 'total_abos'] as $key) { $totals[$key] += $row[$key]; } return $totals; } }