Backoffice-Statistik vereinfacht & Dashboard-Monatsstatistik konsolidiert

- Statistik-Uebersicht: Tabelle auf Linie/Berater/Umsatz(Punkte)/Teamabos/
  Teamkundenabos reduziert; Neupartner als grünes "+N"-Badge in der
  Berater-Spalte; 1000-Punkte-Shop & Neupartner als klickbare KPI-Cards
  (Augen-Icon/Liste anzeigen)
- Teamkundenabos-Drilldown pro Teampartner gruppiert (Anzahl + Volumenpunkte
  gesamt, aufklappbar je Abo), CSV-Export angepasst
- Home: fehlerhafte Monatsstatistik-Card (inkonsistente aktiv-Definition,
  kein Monatsbezug, schwere Live-Logik im Blade) mit der Abo-KPI-Card
  zusammengefuehrt; Logik nach HomeController::buildMonthlyStats() ausgelagert,
  konsistent ueber TeamAboOverviewService/BackofficeDashboardService
- Team-Payline fuer laufenden Monat live berechnet und 6h gecacht (Snapshot)
- Kacheln in Cluster (Umsatz/Neupartner/Abos) gruppiert
- Services: teamKpis() um *_new ergaenzt, personalAboKpis() & newPartnersCount() neu
- i18n (de/en/es/fr), Tests ergaenzt/aktualisiert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-06-24 13:00:38 +00:00
parent 0d67b02021
commit 27b4ad3eb5
20 changed files with 631 additions and 323 deletions

View file

@ -47,12 +47,13 @@ class BackofficeDrilldownService
/**
* @param array<int, array<string, mixed>> $rows
* @return array{count: int, points: float, own_points: float, external_points: float, customer_abo_points: float, customer_single_order_points: float, customer_other_points: float, total_points: float, deliveries: int}
* @return array{count: int, abo_count: int, points: float, own_points: float, external_points: float, customer_abo_points: float, customer_single_order_points: float, customer_other_points: float, total_points: float, deliveries: int}
*/
private function summary(array $rows): array
{
return [
'count' => count($rows),
'abo_count' => (int) collect($rows)->sum('abo_count'),
'points' => (float) collect($rows)->sum('points'),
'own_points' => (float) collect($rows)->sum('own_points'),
'external_points' => (float) collect($rows)->sum('external_points'),
@ -157,37 +158,47 @@ class BackofficeDrilldownService
}
/**
* Teamkundenabos pro Teampartner (member_id) gruppiert: je Teampartner die
* Anzahl der Kundenabos, die Gesamt-Volumenpunkte sowie die einzelnen Abos
* mit Punkten je Abo. Kundennamen bleiben aus Datenschutzgründen außen vor.
*
* @param int[] $userIds
* @return array<int, array<string, mixed>>
*/
private function customerAboRows(array $userIds, int $month, int $year): array
{
return $this->activeAboQuery()
->with(['member.account', 'member.user_level', 'user.account', 'user_abo_items.product', 'user_abo_orders.shopping_order.shopping_payments.payment_transactions'])
->with(['member.account', 'member.user_level', 'user_abo_items.product'])
->whereIn('member_id', $userIds)
->where('is_for', 'ot')
->orderBy('member_id')
->orderBy('next_date')
->get()
->map(fn (UserAbo $abo) => [
'type' => 'customer_abo',
'abo_id' => $abo->id,
'user_id' => $abo->user_id,
'member_id' => $abo->member_id,
'name' => $abo->user ? $this->userName($abo->user) : ($abo->email ?: '#'.$abo->user_id),
'email' => $abo->email ?: $abo->user?->email,
'consultant_name' => $abo->member ? $this->userName($abo->member) : '#'.$abo->member_id,
'career_level' => $abo->member ? $this->careerLevel($abo->member) : '-',
'points' => $abo->getTotalPoints(),
'start_date' => $this->formatDate($abo->getRawOriginal('start_date')),
'is_new_this_month' => $this->isAboNewInMonth($abo, $month, $year),
'next_date' => $this->formatDate($abo->next_date),
'deliveries' => $abo->getCountOrders(),
'status' => $abo->status,
'status_label' => $abo->getStatusType(),
'status_badge' => $abo->getStatusFormated(),
'status_reason' => $this->aboStatusReason($abo),
])
->groupBy(fn (UserAbo $abo) => (int) $abo->member_id)
->map(function ($abos) use ($month, $year) {
$consultant = $abos->first()->member;
$memberId = (int) $abos->first()->member_id;
$aboItems = $abos->map(fn (UserAbo $abo) => [
'abo_id' => $abo->id,
'points' => (float) $abo->getTotalPoints(),
'start_date' => $this->formatDate($abo->getRawOriginal('start_date')),
'next_date' => $this->formatDate($abo->next_date),
'is_new_this_month' => $this->isAboNewInMonth($abo, $month, $year),
])->values()->all();
return [
'type' => 'customer_abo_group',
'consultant_id' => $memberId,
'name' => $consultant ? $this->userName($consultant) : '#'.$memberId,
'email' => $consultant?->email,
'career_level' => $consultant ? $this->careerLevel($consultant) : '-',
'abo_count' => $abos->count(),
'points' => (float) $abos->sum(fn (UserAbo $abo) => $abo->getTotalPoints()),
'abos' => $aboItems,
];
})
->sortByDesc('points')
->values()
->all();
}