Punkte/Umsatz korrekt aus per-Zeile points/total_net je status berechnen statt SUM der kumulativen month_*-Snapshots (zaehlte massiv mehrfach). Zentralisiert in SalesVolumePoints, genutzt von Dashboard- & Drilldown-Service. Detail-Tabelle neu gegliedert: Eigenpunkte | Shop | Abo | Sonstige | Kundenpunkte (=Summe) | Gesamt, mit gruener Hervorhebung der Basis-/Summenspalten. Neue VIP-Seite "Meine Statistik" (Untermenue neben Team-Statistik): KPI-Karten + nachvollziehbare Detailtabellen, gruppiert nach eigenen Bestellungen, Shop-Bestellungen und Abos (PersonalStatisticsService). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
121 lines
4.1 KiB
PHP
121 lines
4.1 KiB
PHP
<?php
|
||
|
||
namespace App\Services\Backoffice;
|
||
|
||
use App\Models\UserSalesVolume;
|
||
use App\User;
|
||
|
||
/**
|
||
* Persönliche Monatsstatistik eines Beraters: nachvollziehbare Auflistung der
|
||
* Punkte/Umsätze des Monats, gruppiert nach eigenen Bestellungen, Shop-Bestellungen
|
||
* und Abos.
|
||
*
|
||
* Im Gegensatz zur Team-Statistik ({@see BackofficeDashboardService}) werden hier die
|
||
* einzelnen `user_sales_volumes`-Zeilen (per-Zeile `points`/`total_net`) des Users selbst
|
||
* gruppiert – nicht die kumulativen month_*-Snapshots.
|
||
*/
|
||
class PersonalStatisticsService
|
||
{
|
||
/**
|
||
* @return array{
|
||
* month: int,
|
||
* year: int,
|
||
* totals: array{own_points: float, shop_points: float, abo_points: float, other_points: float, total_points: float, turnover_net: float},
|
||
* groups: array<string, array{key: string, label: string, points: float, net: float, rows: array<int, array<string, mixed>>}>
|
||
* }
|
||
*/
|
||
public function monthly(User $user, int $month, int $year): array
|
||
{
|
||
$volumes = UserSalesVolume::query()
|
||
->with(['shopping_order.shopping_user'])
|
||
->where('user_id', $user->id)
|
||
->where('month', $month)
|
||
->where('year', $year)
|
||
->orderBy('id')
|
||
->get();
|
||
|
||
$groups = [
|
||
'own' => $this->emptyGroup('own', 'Eigene Bestellungen'),
|
||
'shop' => $this->emptyGroup('shop', 'Shop-Bestellungen'),
|
||
'abo' => $this->emptyGroup('abo', 'Abos'),
|
||
'other' => $this->emptyGroup('other', 'Sonstige'),
|
||
];
|
||
|
||
foreach ($volumes as $volume) {
|
||
$key = $this->groupKey($volume);
|
||
$groups[$key]['rows'][] = $this->mapRow($volume);
|
||
$groups[$key]['points'] += (float) $volume->points;
|
||
$groups[$key]['net'] += (float) $volume->total_net;
|
||
}
|
||
|
||
$totals = [
|
||
'own_points' => $groups['own']['points'],
|
||
'shop_points' => $groups['shop']['points'],
|
||
'abo_points' => $groups['abo']['points'],
|
||
'other_points' => $groups['other']['points'],
|
||
'total_points' => $groups['own']['points'] + $groups['shop']['points'] + $groups['abo']['points'] + $groups['other']['points'],
|
||
'turnover_net' => $groups['own']['net'] + $groups['shop']['net'] + $groups['abo']['net'] + $groups['other']['net'],
|
||
];
|
||
|
||
return [
|
||
'month' => $month,
|
||
'year' => $year,
|
||
'totals' => $totals,
|
||
'groups' => $groups,
|
||
];
|
||
}
|
||
|
||
private function groupKey(UserSalesVolume $volume): string
|
||
{
|
||
$status = (int) $volume->status;
|
||
$order = $volume->shopping_order;
|
||
|
||
if ($status === 1) {
|
||
return 'own';
|
||
}
|
||
|
||
if ($status === 2 && $order) {
|
||
return $order->is_abo ? 'abo' : 'shop';
|
||
}
|
||
|
||
return 'other';
|
||
}
|
||
|
||
/**
|
||
* @return array{key: string, label: string, points: float, net: float, rows: array<int, array<string, mixed>>}
|
||
*/
|
||
private function emptyGroup(string $key, string $label): array
|
||
{
|
||
return [
|
||
'key' => $key,
|
||
'label' => $label,
|
||
'points' => 0.0,
|
||
'net' => 0.0,
|
||
'rows' => [],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @return array<string, mixed>
|
||
*/
|
||
private function mapRow(UserSalesVolume $volume): array
|
||
{
|
||
$order = $volume->shopping_order;
|
||
$customer = $order?->shopping_user;
|
||
|
||
return [
|
||
'date' => $volume->date,
|
||
'points' => (float) $volume->points,
|
||
'total_net' => (float) $volume->total_net,
|
||
'status' => (int) $volume->status,
|
||
'status_label' => $volume->getStatusType(),
|
||
'status_color' => $volume->getStatusColor(),
|
||
'order_id' => $order?->id,
|
||
'order_type' => $order ? ((int) $volume->status === 1 ? 'own' : 'shop') : null,
|
||
'customer_name' => $customer ? trim(($customer->billing_firstname ?? '').' '.($customer->billing_lastname ?? '')) : null,
|
||
'customer_email' => $customer?->billing_email,
|
||
'info' => $volume->info,
|
||
'message' => $volume->message,
|
||
];
|
||
}
|
||
}
|