331 lines
14 KiB
PHP
331 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Backoffice;
|
|
|
|
use App\Models\UserAbo;
|
|
use App\Models\UserSalesVolume;
|
|
use App\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
class BackofficeDrilldownService
|
|
{
|
|
public function __construct(private BackofficeDashboardService $dashboardService) {}
|
|
|
|
/**
|
|
* @return array{metric: string, metric_label: string, line: int, line_label: string, month: int, year: int, rows: array<int, array<string, mixed>>, summary: array<string, mixed>}
|
|
*/
|
|
public function details(User $viewer, int $line, string $metric, int $month, int $year): array
|
|
{
|
|
$metricLabels = $this->dashboardService->metricLabels();
|
|
|
|
if (! array_key_exists($metric, $metricLabels)) {
|
|
abort(404);
|
|
}
|
|
|
|
$userIds = $this->dashboardService->lineUserIds($viewer->id, $line);
|
|
$rows = match ($metric) {
|
|
'consultants' => $this->consultantRows($userIds),
|
|
'new_partners' => $this->newPartnerRows($userIds, $month, $year),
|
|
'team_partner_abos' => $this->partnerAboRows($userIds, $month, $year),
|
|
'team_customer_abos' => $this->customerAboRows($userIds, $month, $year),
|
|
'own_points', 'external_points', 'customer_abo_points', 'customer_single_order_points', 'customer_other_points', 'total_points', 'shop_1000' => $this->pointsRows($userIds, $month, $year, $metric),
|
|
default => [],
|
|
};
|
|
|
|
return [
|
|
'metric' => $metric,
|
|
'metric_label' => $metricLabels[$metric],
|
|
'line' => $line,
|
|
'line_label' => $line > 0 ? 'Linie '.$line : 'Alle Linien',
|
|
'month' => $month,
|
|
'year' => $year,
|
|
'rows' => $rows,
|
|
'summary' => $this->summary($rows),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @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}
|
|
*/
|
|
private function summary(array $rows): array
|
|
{
|
|
return [
|
|
'count' => count($rows),
|
|
'points' => (float) collect($rows)->sum('points'),
|
|
'own_points' => (float) collect($rows)->sum('own_points'),
|
|
'external_points' => (float) collect($rows)->sum('external_points'),
|
|
'customer_abo_points' => (float) collect($rows)->sum('customer_abo_points'),
|
|
'customer_single_order_points' => (float) collect($rows)->sum('customer_single_order_points'),
|
|
'customer_other_points' => (float) collect($rows)->sum('customer_other_points'),
|
|
'total_points' => (float) collect($rows)->sum('total_points'),
|
|
'deliveries' => (int) collect($rows)->sum('deliveries'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param int[] $userIds
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function consultantRows(array $userIds): array
|
|
{
|
|
return User::query()
|
|
->with(['account', 'user_level'])
|
|
->whereIn('id', $userIds)
|
|
->whereNotNull('m_level')
|
|
->whereNotNull('payment_account')
|
|
->orderBy('id')
|
|
->get()
|
|
->map(fn (User $user) => [
|
|
'type' => 'user',
|
|
'user_id' => $user->id,
|
|
'name' => $this->userName($user),
|
|
'email' => $user->email,
|
|
'career_level' => $this->careerLevel($user),
|
|
'is_account_active' => Carbon::parse($user->payment_account)->isFuture(),
|
|
'account_status' => Carbon::parse($user->payment_account)->isFuture() ? 'Aktiv' : 'Abgelaufen',
|
|
'active_date' => $this->formatDate($user->active_date),
|
|
'payment_account' => $this->formatDate($user->payment_account),
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @param int[] $userIds
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function newPartnerRows(array $userIds, int $month, int $year): array
|
|
{
|
|
$startDate = Carbon::create($year, $month, 1)->startOfMonth();
|
|
$endDate = Carbon::create($year, $month, 1)->endOfMonth();
|
|
|
|
return User::query()
|
|
->with(['account', 'user_level'])
|
|
->whereIn('id', $userIds)
|
|
->whereNotNull('m_level')
|
|
->whereNotNull('payment_account')
|
|
->whereBetween('active_date', [$startDate, $endDate])
|
|
->orderBy('active_date')
|
|
->get()
|
|
->filter(fn (User $user) => Carbon::parse($user->payment_account)->isFuture())
|
|
->map(fn (User $user) => [
|
|
'type' => 'user',
|
|
'user_id' => $user->id,
|
|
'name' => $this->userName($user),
|
|
'email' => $user->email,
|
|
'career_level' => $this->careerLevel($user),
|
|
'active_date' => $this->formatDate($user->active_date),
|
|
'payment_account' => $this->formatDate($user->payment_account),
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @param int[] $userIds
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function partnerAboRows(array $userIds, int $month, int $year): array
|
|
{
|
|
return $this->activeAboQuery()
|
|
->with(['user.account', 'user.user_level', 'user_abo_items.product', 'user_abo_orders.shopping_order.shopping_payments.payment_transactions'])
|
|
->whereIn('user_id', $userIds)
|
|
->where('is_for', 'me')
|
|
->orderBy('next_date')
|
|
->get()
|
|
->map(fn (UserAbo $abo) => [
|
|
'type' => 'abo',
|
|
'abo_id' => $abo->id,
|
|
'user_id' => $abo->user_id,
|
|
'name' => $abo->user ? $this->userName($abo->user) : '#'.$abo->user_id,
|
|
'email' => $abo->user?->email,
|
|
'career_level' => $abo->user ? $this->careerLevel($abo->user) : '-',
|
|
'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),
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @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'])
|
|
->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),
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @param int[] $userIds
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function pointsRows(array $userIds, int $month, int $year, string $metric): array
|
|
{
|
|
$rows = UserSalesVolume::query()
|
|
->leftJoin('shopping_orders', 'shopping_orders.id', '=', 'user_sales_volumes.shopping_order_id')
|
|
->whereIn('user_id', $userIds)
|
|
->where('month', $month)
|
|
->where('year', $year)
|
|
->select('user_sales_volumes.user_id')
|
|
->selectRaw('COALESCE(SUM(month_KP_points), 0) as own_points')
|
|
->selectRaw('COALESCE(SUM(month_shop_points), 0) as external_points')
|
|
->selectRaw('COALESCE(SUM(CASE WHEN shopping_orders.is_abo = 1 THEN month_shop_points ELSE 0 END), 0) as customer_abo_points')
|
|
->selectRaw('COALESCE(SUM(CASE WHEN shopping_orders.id IS NOT NULL AND (shopping_orders.is_abo = 0 OR shopping_orders.is_abo IS NULL) THEN month_shop_points ELSE 0 END), 0) as customer_single_order_points')
|
|
->selectRaw('COALESCE(SUM(CASE WHEN shopping_orders.id IS NULL THEN month_shop_points ELSE 0 END), 0) as customer_other_points')
|
|
->selectRaw('COALESCE(SUM(month_KP_points), 0) + COALESCE(SUM(month_shop_points), 0) as total_points')
|
|
->groupBy('user_sales_volumes.user_id');
|
|
|
|
if ($metric === 'own_points') {
|
|
$rows->havingRaw('COALESCE(SUM(month_KP_points), 0) > 0');
|
|
}
|
|
|
|
if ($metric === 'external_points') {
|
|
$rows->havingRaw('COALESCE(SUM(month_shop_points), 0) > 0');
|
|
}
|
|
|
|
if ($metric === 'customer_abo_points') {
|
|
$rows->havingRaw('COALESCE(SUM(CASE WHEN shopping_orders.is_abo = 1 THEN month_shop_points ELSE 0 END), 0) > 0');
|
|
}
|
|
|
|
if ($metric === 'customer_single_order_points') {
|
|
$rows->havingRaw('COALESCE(SUM(CASE WHEN shopping_orders.id IS NOT NULL AND (shopping_orders.is_abo = 0 OR shopping_orders.is_abo IS NULL) THEN month_shop_points ELSE 0 END), 0) > 0');
|
|
}
|
|
|
|
if ($metric === 'customer_other_points') {
|
|
$rows->havingRaw('COALESCE(SUM(CASE WHEN shopping_orders.id IS NULL THEN month_shop_points ELSE 0 END), 0) > 0');
|
|
}
|
|
|
|
if ($metric === 'total_points') {
|
|
$rows->havingRaw('COALESCE(SUM(month_KP_points), 0) + COALESCE(SUM(month_shop_points), 0) > 0');
|
|
}
|
|
|
|
if ($metric === 'shop_1000') {
|
|
$rows->havingRaw('COALESCE(SUM(month_KP_points), 0) + COALESCE(SUM(month_shop_points), 0) >= 1000');
|
|
}
|
|
|
|
$salesRows = $rows->orderByDesc('total_points')->get();
|
|
$users = User::query()->with(['account', 'user_level'])->whereIn('id', $salesRows->pluck('user_id'))->get()->keyBy('id');
|
|
|
|
return $salesRows
|
|
->map(fn (UserSalesVolume $row) => [
|
|
'type' => 'points',
|
|
'user_id' => $row->user_id,
|
|
'name' => $users->has($row->user_id) ? $this->userName($users->get($row->user_id)) : '#'.$row->user_id,
|
|
'email' => $users->get($row->user_id)?->email,
|
|
'career_level' => $users->has($row->user_id) ? $this->careerLevel($users->get($row->user_id)) : '-',
|
|
'own_points' => (float) $row->own_points,
|
|
'external_points' => (float) $row->external_points,
|
|
'customer_abo_points' => (float) $row->customer_abo_points,
|
|
'customer_single_order_points' => (float) $row->customer_single_order_points,
|
|
'customer_other_points' => (float) $row->customer_other_points,
|
|
'total_points' => (float) $row->total_points,
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function activeAboQuery(): Builder
|
|
{
|
|
return UserAbo::query()
|
|
->where('active', true)
|
|
->whereNotIn('status', [4, 5, 6]);
|
|
}
|
|
|
|
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 : '-');
|
|
}
|
|
|
|
private function aboStatusReason(UserAbo $abo): ?string
|
|
{
|
|
if ((int) $abo->status === 2) {
|
|
return null;
|
|
}
|
|
|
|
$transaction = $abo->user_abo_orders
|
|
->sortByDesc('created_at')
|
|
->pluck('shopping_order')
|
|
->filter()
|
|
->map(fn ($order) => $order->getLastShoppingPaymentTransaction())
|
|
->filter()
|
|
->first();
|
|
|
|
if (! $transaction) {
|
|
return null;
|
|
}
|
|
|
|
$message = $transaction->errormessage ?: $transaction->customermessage;
|
|
|
|
if (! $message) {
|
|
return null;
|
|
}
|
|
|
|
return $transaction->errorcode ? '['.$transaction->errorcode.'] '.$message : $message;
|
|
}
|
|
|
|
private function isAboNewInMonth(UserAbo $abo, int $month, int $year): bool
|
|
{
|
|
$startDate = $abo->getRawOriginal('start_date');
|
|
|
|
if (! $startDate) {
|
|
return false;
|
|
}
|
|
|
|
$date = Carbon::parse($startDate);
|
|
|
|
return (int) $date->month === $month && (int) $date->year === $year;
|
|
}
|
|
|
|
private function formatDate(mixed $date): ?string
|
|
{
|
|
if ($date === null || $date === '') {
|
|
return null;
|
|
}
|
|
|
|
return Carbon::parse($date)->format('d.m.Y');
|
|
}
|
|
}
|