Backoffice-Statistik: Neue-Kundenabos-Ranking & Führungskräfte-Abos
Zwei neue Listen unter der Team-Übersicht: Ranking der Berater nach neuen Kundenabos im Monat (inkl. Gesamtbestand und Linie) sowie eigene und Kundenabos aller Führungskräfte ab Stufe Vertriebsleiter mit Gesamtsumme. Beides fließt mit in die Monats-Snapshots ein. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0660f4e0a0
commit
d50be7135a
3 changed files with 417 additions and 1 deletions
|
|
@ -4,6 +4,7 @@ namespace App\Services\Backoffice;
|
|||
|
||||
use App\Models\BackofficeStatisticsSnapshot;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserLevel;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
|
|
@ -14,6 +15,8 @@ class BackofficeDashboardService
|
|||
{
|
||||
private const MAX_DEPTH_SAFETY_LIMIT = 100;
|
||||
|
||||
private const LEADER_LEVEL_NAME = 'Vertriebsleiter';
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
|
|
@ -94,6 +97,8 @@ class BackofficeDashboardService
|
|||
'metric_labels' => $this->metricLabels(),
|
||||
'lines' => $lines,
|
||||
'totals' => $totals,
|
||||
'new_customer_abos' => $this->newCustomerAboRanking($lineBuckets, $month, $year),
|
||||
'leader_abos' => $this->leaderAboOverview($lineBuckets),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +133,7 @@ class BackofficeDashboardService
|
|||
|
||||
for ($line = 1; $line <= self::MAX_DEPTH_SAFETY_LIMIT && $currentSponsorIds !== []; $line++) {
|
||||
$users = User::query()
|
||||
->with('account')
|
||||
->with(['account', 'user_level'])
|
||||
->whereIn('m_sponsor', $currentSponsorIds)
|
||||
->whereNotIn('id', $visitedUserIds)
|
||||
->whereColumn('id', '!=', 'm_sponsor')
|
||||
|
|
@ -395,6 +400,156 @@ class BackofficeDashboardService
|
|||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ranking: Wer im Team hat im Monat wie viele neue Kundenabos abgeschlossen?
|
||||
* Sortiert nach Anzahl absteigend (nicht nach Linie), Linie wird je Berater mitgeliefert,
|
||||
* dazu der aktuelle Gesamtbestand an Kundenabos des Beraters.
|
||||
* Zählweise identisch zu team_customer_abos_new {@see newAboCount()}.
|
||||
*
|
||||
* @param array<int, \Illuminate\Support\Collection<int, \App\User>> $lineBuckets
|
||||
* @return array<int, array{user_id: int, name: string, career_level: string, line: int, abo_count: int, total_abo_count: int}>
|
||||
*/
|
||||
private function newCustomerAboRanking(array $lineBuckets, int $month, int $year): array
|
||||
{
|
||||
$usersByLine = $this->usersWithLine($lineBuckets);
|
||||
|
||||
if ($usersByLine === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$startDate = Carbon::create($year, $month, 1)->startOfMonth();
|
||||
$endDate = Carbon::create($year, $month, 1)->endOfMonth();
|
||||
|
||||
$counts = $this->activeAboQuery()
|
||||
->whereIn('member_id', array_keys($usersByLine))
|
||||
->where('is_for', 'ot')
|
||||
->whereBetween('start_date', [$startDate, $endDate])
|
||||
->selectRaw('member_id, COUNT(*) as abo_count')
|
||||
->groupBy('member_id')
|
||||
->pluck('abo_count', 'member_id');
|
||||
|
||||
$totalCounts = $this->customerAboCounts($counts->keys()->all());
|
||||
|
||||
return collect($usersByLine)
|
||||
->filter(fn (array $entry, int $userId) => $counts->has($userId))
|
||||
->map(fn (array $entry, int $userId) => [
|
||||
'user_id' => $userId,
|
||||
'name' => $this->userName($entry['user']),
|
||||
'career_level' => $this->careerLevel($entry['user']),
|
||||
'line' => $entry['line'],
|
||||
'abo_count' => (int) $counts->get($userId),
|
||||
'total_abo_count' => (int) $totalCounts->get($userId, 0),
|
||||
])
|
||||
->sortByDesc('abo_count')
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Abos der Führungskräfte (ab Stufe Vertriebsleiter) im Team: Gesamtzahlen plus
|
||||
* Liste aller Führungskräfte inklusive Linie, eigener Abos und Kundenabos.
|
||||
*
|
||||
* @param array<int, \Illuminate\Support\Collection<int, \App\User>> $lineBuckets
|
||||
* @return array{level_name: string, total: int, total_own: int, total_customer: int, rows: array<int, array{user_id: int, name: string, career_level: string, line: int, own_abo_count: int, customer_abo_count: int}>}
|
||||
*/
|
||||
private function leaderAboOverview(array $lineBuckets): array
|
||||
{
|
||||
$leaderMinPos = UserLevel::query()
|
||||
->where('name', self::LEADER_LEVEL_NAME)
|
||||
->value('pos');
|
||||
|
||||
$leadersByLine = $leaderMinPos === null ? [] : collect($this->usersWithLine($lineBuckets))
|
||||
->filter(fn (array $entry) => ($entry['user']->user_level?->pos ?? 0) >= $leaderMinPos)
|
||||
->all();
|
||||
|
||||
if ($leadersByLine === []) {
|
||||
return ['level_name' => self::LEADER_LEVEL_NAME, 'total' => 0, 'total_own' => 0, 'total_customer' => 0, 'rows' => []];
|
||||
}
|
||||
|
||||
$leaderIds = array_keys($leadersByLine);
|
||||
|
||||
$ownCounts = $this->activeAboQuery()
|
||||
->whereIn('user_id', $leaderIds)
|
||||
->where('is_for', 'me')
|
||||
->selectRaw('user_id, COUNT(*) as abo_count')
|
||||
->groupBy('user_id')
|
||||
->pluck('abo_count', 'user_id');
|
||||
|
||||
$customerCounts = $this->customerAboCounts($leaderIds);
|
||||
|
||||
$rows = collect($leadersByLine)
|
||||
->map(fn (array $entry, int $userId) => [
|
||||
'user_id' => $userId,
|
||||
'name' => $this->userName($entry['user']),
|
||||
'career_level' => $this->careerLevel($entry['user']),
|
||||
'line' => $entry['line'],
|
||||
'own_abo_count' => (int) $ownCounts->get($userId, 0),
|
||||
'customer_abo_count' => (int) $customerCounts->get($userId, 0),
|
||||
])
|
||||
->sortByDesc(fn (array $row) => $row['own_abo_count'] + $row['customer_abo_count'])
|
||||
->values();
|
||||
|
||||
$totalOwn = (int) $rows->sum('own_abo_count');
|
||||
$totalCustomer = (int) $rows->sum('customer_abo_count');
|
||||
|
||||
return [
|
||||
'level_name' => self::LEADER_LEVEL_NAME,
|
||||
'total' => $totalOwn + $totalCustomer,
|
||||
'total_own' => $totalOwn,
|
||||
'total_customer' => $totalCustomer,
|
||||
'rows' => $rows->all(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktueller Bestand aktiver Kundenabos je Berater (member_id).
|
||||
*
|
||||
* @param int[] $memberIds
|
||||
* @return \Illuminate\Support\Collection<int, int>
|
||||
*/
|
||||
private function customerAboCounts(array $memberIds): Collection
|
||||
{
|
||||
if ($memberIds === []) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $this->activeAboQuery()
|
||||
->whereIn('member_id', $memberIds)
|
||||
->where('is_for', 'ot')
|
||||
->selectRaw('member_id, COUNT(*) as abo_count')
|
||||
->groupBy('member_id')
|
||||
->pluck('abo_count', 'member_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, \Illuminate\Support\Collection<int, \App\User>> $lineBuckets
|
||||
* @return array<int, array{user: \App\User, line: int}>
|
||||
*/
|
||||
private function usersWithLine(array $lineBuckets): array
|
||||
{
|
||||
$usersByLine = [];
|
||||
|
||||
foreach ($lineBuckets as $line => $users) {
|
||||
foreach ($users as $user) {
|
||||
$usersByLine[(int) $user->id] = ['user' => $user, 'line' => $line];
|
||||
}
|
||||
}
|
||||
|
||||
return $usersByLine;
|
||||
}
|
||||
|
||||
private function userName(User $user): string
|
||||
{
|
||||
$name = trim(($user->account?->first_name ?? '').' '.($user->account?->last_name ?? ''));
|
||||
|
||||
return $name !== '' ? $name : ($user->email ?: '#'.$user->id);
|
||||
}
|
||||
|
||||
private function careerLevel(User $user): string
|
||||
{
|
||||
return $user->user_level?->name ?: ($user->m_level ? 'Level '.$user->m_level : '-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Anzahl der Berater mit >= 1000 Punkten Kundenumsatz (KP + Shop) im Monat.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue