Backoffice-Statistik: Punkte-Fix (kumulative Snapshots) & Meine Statistik
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>
This commit is contained in:
parent
9565814221
commit
7a620d66ae
14 changed files with 666 additions and 70 deletions
|
|
@ -89,11 +89,11 @@
|
|||
<th data-sortable="true" data-sort-type="date"><span class="sortable-label">Nächste Ausführung <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number"><span class="sortable-label justify-content-end">Lieferungen <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
@elseif (in_array($details['metric'], ['own_points', 'external_points', 'customer_abo_points', 'customer_single_order_points', 'customer_other_points', 'total_points', 'shop_1000'], true))
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number"><span class="sortable-label justify-content-end">Eigenpunkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number"><span class="sortable-label justify-content-end">Externe Punkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number"><span class="sortable-label justify-content-end">Kundenabo-Punkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number"><span class="sortable-label justify-content-end">Einzelbestellungs-Punkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number"><span class="sortable-label justify-content-end">Sonstige Kundenpunkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number" style="background-color:#d4edda;"><span class="sortable-label justify-content-end">Eigenpunkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number"><span class="sortable-label justify-content-end">Shop-Punkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number"><span class="sortable-label justify-content-end">Abo-Punkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number"><span class="sortable-label justify-content-end">Sonstige Punkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number" style="background-color:#d4edda;"><span class="sortable-label justify-content-end">Kundenpunkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
<th class="text-right" data-sortable="true" data-sort-type="number"><span class="sortable-label justify-content-end">Gesamtpunkte <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
@else
|
||||
<th data-sortable="true" data-sort-type="date"><span class="sortable-label">Aktiv seit <i class="ion ion-ios-swap sort-icon"></i></span></th>
|
||||
|
|
@ -151,11 +151,11 @@
|
|||
<td>{{ $row['next_date'] ?? '-' }}</td>
|
||||
<td class="text-right">{{ number_format($row['deliveries'], 0, ',', '.') }}</td>
|
||||
@elseif (in_array($details['metric'], ['own_points', 'external_points', 'customer_abo_points', 'customer_single_order_points', 'customer_other_points', 'total_points', 'shop_1000'], true))
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($row['own_points']) }}</td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($row['external_points']) }}</td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($row['customer_abo_points']) }}</td>
|
||||
<td class="text-right"><span class="badge" style="background-color:#d4edda;color:#155724;font-size:90%;">{{ \App\Services\Util::formatNumber($row['own_points']) }}</span></td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($row['customer_single_order_points']) }}</td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($row['customer_abo_points']) }}</td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($row['customer_other_points']) }}</td>
|
||||
<td class="text-right font-weight-bold"><span class="badge" style="background-color:#d4edda;color:#155724;font-size:90%;">{{ \App\Services\Util::formatNumber($row['external_points']) }}</span></td>
|
||||
<td class="text-right font-weight-bold">{{ \App\Services\Util::formatNumber($row['total_points']) }}</td>
|
||||
@else
|
||||
<td>{{ $row['active_date'] ?? '-' }}</td>
|
||||
|
|
@ -188,11 +188,11 @@
|
|||
<td></td>
|
||||
<td class="text-right">{{ number_format($details['summary']['deliveries'], 0, ',', '.') }}</td>
|
||||
@elseif (in_array($details['metric'], ['own_points', 'external_points', 'customer_abo_points', 'customer_single_order_points', 'customer_other_points', 'total_points', 'shop_1000'], true))
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($details['summary']['own_points']) }}</td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($details['summary']['external_points']) }}</td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($details['summary']['customer_abo_points']) }}</td>
|
||||
<td class="text-right" style="background-color:#d4edda;">{{ \App\Services\Util::formatNumber($details['summary']['own_points']) }}</td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($details['summary']['customer_single_order_points']) }}</td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($details['summary']['customer_abo_points']) }}</td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($details['summary']['customer_other_points']) }}</td>
|
||||
<td class="text-right" style="background-color:#d4edda;">{{ \App\Services\Util::formatNumber($details['summary']['external_points']) }}</td>
|
||||
<td class="text-right">{{ \App\Services\Util::formatNumber($details['summary']['total_points']) }}</td>
|
||||
@else
|
||||
<td></td>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
@section('content')
|
||||
<h4 class="font-weight-bold py-2 mb-2 d-flex justify-content-between align-items-center w-100">
|
||||
<div>
|
||||
{{ __('navigation.statistics') }}
|
||||
{{ __('navigation.team_statistics') }}
|
||||
<span class="badge badge-secondary ml-2">VIP</span>
|
||||
</div>
|
||||
</h4>
|
||||
|
|
@ -17,12 +17,13 @@
|
|||
Linienbasierte Übersicht für alle vorhandenen Team-Linien. Klickbare Zahlen führen direkt in die
|
||||
passenden Namen- und Detaillisten.
|
||||
</p>
|
||||
@if (! empty($performance))
|
||||
@if (!empty($performance))
|
||||
<div class="small text-muted mt-2">
|
||||
Datenquelle:
|
||||
<span class="badge badge-outline-default">{{ $performance['source_label'] }}</span>
|
||||
<span class="ml-2">Berechnet in {{ number_format($performance['duration_ms'], 2, ',', '.') }} ms</span>
|
||||
@if (! empty($performance['calculated_at']))
|
||||
<span class="ml-2">Berechnet in {{ number_format($performance['duration_ms'], 2, ',', '.') }}
|
||||
ms</span>
|
||||
@if (!empty($performance['calculated_at']))
|
||||
<span class="ml-2">Snapshot vom {{ $performance['calculated_at'] }}</span>
|
||||
@endif
|
||||
</div>
|
||||
|
|
@ -59,12 +60,7 @@
|
|||
</div>
|
||||
|
||||
@php
|
||||
$clickableMetrics = [
|
||||
'consultants',
|
||||
'total_points',
|
||||
'team_partner_abos',
|
||||
'team_customer_abos',
|
||||
];
|
||||
$clickableMetrics = ['consultants', 'total_points', 'team_partner_abos', 'team_customer_abos'];
|
||||
|
||||
$metricHeadings = [
|
||||
'consultants' => 'Berater',
|
||||
|
|
@ -100,7 +96,8 @@
|
|||
'metric' => 'new_partners',
|
||||
'month' => $selectedMonth,
|
||||
'year' => $selectedYear,
|
||||
]) }}" class="card h-100 text-decoration-none backoffice-kpi-card">
|
||||
]) }}"
|
||||
class="card h-100 text-decoration-none backoffice-kpi-card">
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="text-muted small">Neupartner ({{ $selectedMonthName }} {{ $selectedYear }})</div>
|
||||
|
|
@ -121,10 +118,12 @@
|
|||
'metric' => 'shop_1000',
|
||||
'month' => $selectedMonth,
|
||||
'year' => $selectedYear,
|
||||
]) }}" class="card h-100 text-decoration-none backoffice-kpi-card">
|
||||
]) }}"
|
||||
class="card h-100 text-decoration-none backoffice-kpi-card">
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="text-muted small">1000-Punkte-Shops ({{ $selectedMonthName }} {{ $selectedYear }})</div>
|
||||
<div class="text-muted small">1000-Punkte-Shops ({{ $selectedMonthName }} {{ $selectedYear }})
|
||||
</div>
|
||||
<div class="display-4 font-weight-bold text-success">
|
||||
{{ number_format((float) ($statistics['totals']['shop_1000'] ?? 0), 0, ',', '.') }}
|
||||
</div>
|
||||
|
|
|
|||
204
resources/views/user/backoffice/statistics/my.blade.php
Normal file
204
resources/views/user/backoffice/statistics/my.blade.php
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$totals = $statistics['totals'];
|
||||
$selectedMonthName = $filterMonths[(int) $selectedMonth] ?? $selectedMonth;
|
||||
$fmt = fn($value) => \App\Services\Util::formatNumber($value);
|
||||
|
||||
$kpiCards = [
|
||||
[
|
||||
'label' => 'Eigenpunkte',
|
||||
'value' => $totals['own_points'],
|
||||
'icon' => 'ion-md-person',
|
||||
'color' => 'success',
|
||||
'highlight' => true,
|
||||
],
|
||||
[
|
||||
'label' => 'Shop-Punkte',
|
||||
'value' => $totals['shop_points'],
|
||||
'icon' => 'ion-md-cart',
|
||||
'color' => 'primary',
|
||||
'highlight' => false,
|
||||
],
|
||||
[
|
||||
'label' => 'Abo-Punkte',
|
||||
'value' => $totals['abo_points'],
|
||||
'icon' => 'ion-md-repeat',
|
||||
'color' => 'info',
|
||||
'highlight' => false,
|
||||
],
|
||||
[
|
||||
'label' => 'Gesamtpunkte',
|
||||
'value' => $totals['total_points'],
|
||||
'icon' => 'ion-md-stats',
|
||||
'color' => 'success',
|
||||
'highlight' => true,
|
||||
],
|
||||
];
|
||||
@endphp
|
||||
|
||||
<h4 class="font-weight-bold py-2 mb-2 d-flex justify-content-between align-items-center w-100">
|
||||
<div>
|
||||
{{ __('navigation.my_statistics') }}
|
||||
<span class="badge badge-secondary ml-2">VIP</span>
|
||||
</div>
|
||||
</h4>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<h5 class="mb-2">{{ __('navigation.my_statistics') }} — {{ $selectedMonthName }} {{ $selectedYear }}
|
||||
</h5>
|
||||
<p class="text-muted mb-md-0">
|
||||
Nachvollziehbare Auflistung deiner Punkte und Umsätze des Monats, getrennt nach eigenen
|
||||
Bestellungen, Shop-Bestellungen und Abos.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4 mt-3 mt-md-0">
|
||||
<form method="GET" action="{{ route('user_my_statistics') }}"
|
||||
class="form-inline justify-content-md-end">
|
||||
<select name="month" class="form-control custom-select form-control-sm mr-2">
|
||||
@foreach ($filterMonths as $monthNumber => $monthName)
|
||||
<option value="{{ $monthNumber }}"
|
||||
{{ (int) $selectedMonth === (int) $monthNumber ? 'selected' : '' }}>
|
||||
{{ $monthName }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="year" class="form-control custom-select form-control-sm mr-2">
|
||||
@foreach ($filterYears as $year)
|
||||
<option value="{{ $year }}"
|
||||
{{ (int) $selectedYear === (int) $year ? 'selected' : '' }}>
|
||||
{{ $year }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button type="submit" class="btn btn-sm btn-primary">Anzeigen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
@foreach ($kpiCards as $card)
|
||||
<div class="col-md-6 col-lg-3 mb-4">
|
||||
<div class="card h-100"
|
||||
@if ($card['highlight']) style="background-color:#eafaf0;border:1px solid #c3e6cb;" @endif>
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="text-muted small">{{ $card['label'] }}</div>
|
||||
<div class="display-4 font-weight-bold text-{{ $card['color'] }}">{{ $fmt($card['value']) }}
|
||||
</div>
|
||||
</div>
|
||||
<span class="ion {{ $card['icon'] }} display-4 text-muted"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body py-3 d-flex flex-wrap align-items-center">
|
||||
<span class="mr-4 mb-1">
|
||||
<span class="text-muted small">Gesamtpunkte:</span>
|
||||
<strong>{{ $fmt($totals['total_points']) }}</strong>
|
||||
</span>
|
||||
<span class="mb-1">
|
||||
<span class="text-muted small">Umsatz (netto):</span>
|
||||
<strong>{{ $fmt($totals['turnover_net']) }} €</strong>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@foreach ($statistics['groups'] as $group)
|
||||
@if ($group['rows'] !== [])
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header with-elements d-flex justify-content-between align-items-center flex-wrap">
|
||||
<div class="card-header-title">{{ $group['label'] }}</div>
|
||||
<div class="text-muted small">
|
||||
{{ count($group['rows']) }} Einträge ·
|
||||
<strong class="text-body">{{ $fmt($group['points']) }}</strong> Punkte ·
|
||||
<strong class="text-body">{{ $fmt($group['net']) }} €</strong>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datum</th>
|
||||
<th class="text-right">Punkte</th>
|
||||
<th class="text-right">Netto</th>
|
||||
<th>Typ</th>
|
||||
<th>Bestellung</th>
|
||||
@if (in_array($group['key'], ['shop', 'abo'], true))
|
||||
<th>Kunde</th>
|
||||
@endif
|
||||
<th>Info</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($group['rows'] as $row)
|
||||
<tr>
|
||||
<td class="font-weight-semibold">{{ $row['date'] }}</td>
|
||||
<td class="text-right font-weight-semibold">{{ $fmt($row['points']) }}</td>
|
||||
<td class="text-right">{{ $fmt($row['total_net']) }} €</td>
|
||||
<td>
|
||||
<span
|
||||
class="badge badge-pill badge-{{ $row['status_color'] }}">{{ $row['status_label'] }}</span>
|
||||
</td>
|
||||
<td>
|
||||
@if ($row['order_id'] && $row['order_type'] === 'own')
|
||||
<a href="{{ route('user_order_detail', [$row['order_id']]) }}"
|
||||
class="btn btn-xs btn-primary">{{ $row['order_id'] }}</a>
|
||||
@elseif ($row['order_id'] && $row['order_type'] === 'shop')
|
||||
<a href="{{ route('user_shop_order_detail', [$row['order_id']]) }}"
|
||||
class="btn btn-xs btn-secondary">{{ $row['order_id'] }}</a>
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
@if (in_array($group['key'], ['shop', 'abo'], true))
|
||||
<td>
|
||||
@if ($row['customer_name'])
|
||||
<span class="no-line-break">{{ $row['customer_name'] }}<br>
|
||||
<small
|
||||
class="text-muted">{{ $row['customer_email'] }}</small></span>
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
@endif
|
||||
<td><span class="no-line-break">{{ $row['info'] ?: $row['message'] }}</span></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="font-weight-bold bg-light">
|
||||
<td>Summe</td>
|
||||
<td class="text-right">{{ $fmt($group['points']) }}</td>
|
||||
<td class="text-right">{{ $fmt($group['net']) }} €</td>
|
||||
<td colspan="{{ in_array($group['key'], ['shop', 'abo'], true) ? 4 : 3 }}"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
@php
|
||||
$hasRows = collect($statistics['groups'])->contains(fn($group) => $group['rows'] !== []);
|
||||
@endphp
|
||||
@unless ($hasRows)
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<p class="text-muted mb-0">{{ __('tables.no_data_available') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endunless
|
||||
@endsection
|
||||
Loading…
Add table
Add a link
Reference in a new issue