@extends('layouts.app') @section('content')
{{-- Заголовок --}}

Кто рано встает - тому разбирать диспуты

@php use Carbon\Carbon; use Illuminate\Support\Facades\DB; $pendingCount = \App\Models\PayoutRequest::where('status', 'pending')->count(); $processingCount = \App\Models\PayoutRequest::where('status', 'processing')->count(); $workingCount = $pendingCount + $processingCount; $onlineTradersCount = DB::table('markets')->where('payout_status', 'online')->count(); $onlineTraders = DB::table('markets')->where('payout_status', 'online')->select('id', 'hash_id')->get(); $thirtyMinutesAgo = Carbon::now()->subMinutes(20); $oldPendings = \App\Models\PayoutRequest::where('status', 'pending') ->where('created_at', '<', $thirtyMinutesAgo) ->orderBy('created_at')->get(); // Возрастные корзины $payouts5min = \App\Models\PayoutRequest::whereIn('status', ['pending','processing']) ->where('created_at', '<', Carbon::now()->subMinutes(5))->count(); $payouts15min = \App\Models\PayoutRequest::whereIn('status', ['pending','processing']) ->where('created_at', '<', Carbon::now()->subMinutes(15))->count(); $payouts30min = \App\Models\PayoutRequest::whereIn('status', ['pending','processing']) ->where('created_at', '<', Carbon::now()->subMinutes(30))->count(); // Бар-чарт по текущему месяцу (кол-во done) $monthStart = now()->startOfMonth(); $monthEnd = now()->endOfMonth(); $labelsMonth = []; $dataMonth = []; $cur = $monthStart->copy(); while ($cur <= $monthEnd) { $labelsMonth[] = $cur->format('d.m'); $dataMonth[] = \App\Models\PayoutRequest::whereDate('created_at', $cur) ->where('status', 'done')->count(); $cur->addDay(); } @endphp {{-- Верхние карточки --}}
PENDING
{{ $pendingCount }}
PROCESSING
{{ $processingCount }}
В РАБОТЕ
{{ $workingCount }}
@php $errorCount = \App\Models\PayoutRequest::where('status', 'error')->count(); $errorCountToday = \App\Models\PayoutRequest::where('status', 'error') ->whereBetween('created_at', [Carbon::today()->startOfDay(), Carbon::today()->endOfDay()])->count(); $processingOldCount = \App\Models\PayoutRequest::where('status', 'processing') ->where('updated_at', '<', Carbon::now()->subMinutes(20))->count(); $priorityTrader = DB::table('markets')->where('prioritypayout', true)->first(); @endphp
ОШИБКА ОПЛАТЫ
{{ $errorCountToday }} Общее {{ $errorCount }}
ЗАЯВКИ 5+ мин
{{ $payouts5min }}
ЗАЯВКИ 15+ мин
{{ $payouts15min }}
ЗАЯВКИ > 20 мин+
{{ $processingOldCount }}
ЗАЯВКИ 30+ мин
{{ $payouts30min }}
{{-- Статистика за сегодня --}}
ВЫПЛАЧЕНО ЗА СЕГОДНЯ:
Фиат: {{ number_format(\App\Models\PayoutRequest::whereBetween('created_at', [Carbon::today()->startOfDay(), Carbon::today()->endOfDay()])->where('status', 'done')->sum('to_amount'), 2, '.', ' ') }}
USDT: {{ number_format(\App\Models\PayoutRequest::whereBetween('created_at', [Carbon::today()->startOfDay(), Carbon::today()->endOfDay()])->where('status', 'done')->sum('from_amount'), 2, '.', ' ') }}
Профит платформы (USDT): {{ number_format(\App\Models\PayoutRequest::whereBetween('created_at', [Carbon::today()->startOfDay(), Carbon::today()->endOfDay()])->where('status', 'done')->sum('platform_commission_amount'), 2, '.', ' ') }}
{{-- АНАЛИТИКА: дата-диапазон + расчёт метрик/рядов для графика --}} @php $from = request('from') ?: Carbon::now()->subDays(30)->format('Y-m-d'); $to = request('to') ?: Carbon::now()->format('Y-m-d'); $start = Carbon::parse($from)->startOfDay(); $end = Carbon::parse($to)->endOfDay(); $days = $start->diffInDays($end); $labels = []; $avg_done = []; $avg_cancelled = []; $avg_error = []; $count_done = []; // суммы по дням (done) $sum_fiat = []; $sum_usdt = []; // для метрик периода $periodDoneRows = \App\Models\PayoutRequest::whereBetween('created_at', [$start, $end]) ->where('status','done') ->whereColumn('updated_at', '>=', 'created_at') ->get(['id','created_at','updated_at','assigned_at','to_amount','from_amount','platform_commission_amount']); $periodTotalCount = \App\Models\PayoutRequest::whereBetween('created_at', [$start, $end])->count(); $periodDoneCount = $periodDoneRows->count(); $periodErrorCount = \App\Models\PayoutRequest::whereBetween('created_at', [$start, $end])->where('status','error')->count(); $periodCancCount = \App\Models\PayoutRequest::whereBetween('created_at', [$start, $end])->where('status','cancelled')->count(); // длительности (мин) $durations = $periodDoneRows->map(fn($r) => round(Carbon::parse($r->updated_at)->diffInSeconds(Carbon::parse($r->created_at)) / 60, 2) )->values()->all(); // время до назначения (если есть assigned_at) $pickup = $periodDoneRows->filter(fn($r)=>!empty($r->assigned_at))->map(fn($r) => round(Carbon::parse($r->assigned_at)->diffInSeconds(Carbon::parse($r->created_at)) / 60, 2) )->values()->all(); // агрегаты сумм за период $periodFiatSum = round($periodDoneRows->sum('to_amount'), 2); $periodUsdtSum = round($periodDoneRows->sum('from_amount'), 2); $periodPlatformSum = round($periodDoneRows->sum('platform_commission_amount'), 2); // метрики по размерам выплат $amountsFiat = $periodDoneRows->pluck('to_amount')->filter()->sort()->values()->all(); // helpers $median = function(array $a) { $n = count($a); if ($n===0) return 0; sort($a, SORT_NUMERIC); $m = intdiv($n,2); return $n%2 ? $a[$m] : round(($a[$m-1]+$a[$m])/2, 2); }; $pctl = function(array $a, float $p) { // p in (0..1] $n = count($a); if ($n===0) return 0; sort($a, SORT_NUMERIC); $idx = max(0, min($n-1, (int)ceil($p*$n)-1)); return $a[$idx]; }; $avgMinutes = $periodDoneCount ? round(array_sum($durations)/$periodDoneCount, 2) : 0; $medianMinutes = $median($durations); $p95Minutes = $pctl($durations, 0.95); $avgPickup = count($pickup) ? round(array_sum($pickup)/count($pickup), 2) : 0; $p95Pickup = $pctl($pickup, 0.95); $avgPayoutFiat = $periodDoneCount ? round(array_sum($amountsFiat)/$periodDoneCount, 2) : 0; $medianPayoutFiat= $median($amountsFiat); $p95PayoutFiat = $pctl($amountsFiat, 0.95); $sla = 15; // мин $slaWithin = $periodDoneCount ? round(100 * count(array_filter($durations, fn($x)=>$x <= $sla)) / $periodDoneCount, 2) : 0; $errRate = $periodTotalCount ? round(100 * $periodErrorCount / $periodTotalCount, 2) : 0; $cancRate = $periodTotalCount ? round(100 * $periodCancCount / $periodTotalCount, 2) : 0; $convRate = $periodTotalCount ? round(100 * $periodDoneCount / $periodTotalCount, 2) : 0; // ряд за дни for ($i = 0; $i <= $days; $i++) { $date = $start->copy()->addDays($i); $labels[] = $date->format('d.m'); $done = \App\Models\PayoutRequest::whereDate('created_at', $date) ->where('status', 'done') ->whereColumn('updated_at', '>=', 'created_at') ->get(['created_at','updated_at','to_amount','from_amount']); $count_done[] = $done->count(); $avg_done[] = $done->count() ? round($done->avg(fn($r)=>Carbon::parse($r->updated_at)->diffInSeconds(Carbon::parse($r->created_at)))/60, 2) : 0; $sum_fiat[] = (float) $done->sum('to_amount'); $sum_usdt[] = (float) $done->sum('from_amount'); $cancelled = \App\Models\PayoutRequest::whereDate('created_at', $date) ->where('status', 'cancelled') ->whereColumn('updated_at', '>=', 'created_at') ->get(['created_at','updated_at']); $avg_cancelled[] = $cancelled->count() ? round($cancelled->avg(fn($r)=>Carbon::parse($r->updated_at)->diffInSeconds(Carbon::parse($r->created_at)))/60, 2) : 0; $error = \App\Models\PayoutRequest::whereDate('created_at', $date) ->where('status', 'error') ->whereColumn('updated_at', '>=', 'created_at') ->get(['created_at','updated_at']); $avg_error[] = $error->count() ? round($error->avg(fn($r)=>Carbon::parse($r->updated_at)->diffInSeconds(Carbon::parse($r->created_at)))/60, 2) : 0; } // 7-дневные MA для сумм $ma7_fiat = []; $ma7_usdt = []; $win = 7; for ($i=0; $iselectRaw('trader_id, COUNT(*) as cnt, COALESCE(SUM(to_amount),0) as sum_fiat, COALESCE(SUM(from_amount),0) as sum_usdt') ->whereBetween('created_at', [$start, $end]) ->where('status','done') ->groupBy('trader_id') ->orderByDesc('sum_fiat') ->limit(5)->get(); $ids = $topRows->pluck('trader_id')->filter()->unique()->values()->all(); $marketMap = $ids ? DB::table('markets')->whereIn('id',$ids)->pluck('hash_id','id') : collect(); @endphp {{-- Панель сводных метрик за период --}}
Сводка за период {{ $start->format('d.m.Y') }} — {{ $end->format('d.m.Y') }}
Конверсия
done / все
{{ $periodDoneCount }} / {{ $periodTotalCount }} ({{ number_format($convRate,2,'.',' ') }}%)
Сумма пейаутов
Фиат: {{ number_format($periodFiatSum, 2, '.', ' ') }}
USDT: {{ number_format($periodUsdtSum, 2, '.', ' ') }}
Платформа: {{ number_format($periodPlatformSum, 2, '.', ' ') }} USDT
Время обработки (done)
avg: {{ number_format($avgMinutes,2,'.',' ') }} мин
p50: {{ number_format($medianMinutes,2,'.',' ') }} мин
p95: {{ number_format($p95Minutes,2,'.',' ') }} мин
до назначения avg: {{ number_format($avgPickup,2,'.',' ') }} / p95: {{ number_format($p95Pickup,2,'.',' ') }}
SLA ≤ 15 мин
{{ number_format($slaWithin,2,'.',' ') }}%
Ошибки: {{ number_format($errRate,2,'.',' ') }}% • Отмены: {{ number_format($cancRate,2,'.',' ') }}%
Размер выплаты (фиат)
avg: {{ number_format($avgPayoutFiat,2,'.',' ') }}
p50: {{ number_format($medianPayoutFiat,2,'.',' ') }}
p95: {{ number_format($p95PayoutFiat,2,'.',' ') }}
{{-- Top-5 трейдеров за период --}}
Top-5 трейдеров за период (по сумме фиата, status=done)
@forelse($topRows as $i => $r) @empty @endforelse
# Trader Σ Фиат Σ USDT Кол-во
{{ $i+1 }} @php $hid = $r->trader_id ? ($marketMap[$r->trader_id] ?? null) : null; @endphp @if($hid) {{ $hid }} (ID: {{ $r->trader_id }}) @else @endif {{ number_format($r->sum_fiat, 2, '.', ' ') }} {{ number_format($r->sum_usdt, 2, '.', ' ') }} {{ number_format($r->cnt, 0, '.', ' ') }}
Нет данных
{{-- ГРАФИКИ --}}
{{-- Просроченные заявки --}} @if($oldPendings->count())
⚠️ Есть заявки, которые более 30 минут ожидают обработки:
@foreach($oldPendings as $pending) @php $details = is_array($pending->details) ? $pending->details : json_decode($pending->details, true); $trader = $pending->trader_id ? DB::table('markets')->where('id', $pending->trader_id)->first() : null; @endphp
#{{ $pending->id }} {{ Carbon::parse($pending->created_at)->format('d.m.Y H:i') }}
Сумма к выплате: {{ number_format($pending->to_amount, 2, '.', ' ') }} {{ $pending->to_currency }}
Профит трейдера: {{ number_format($pending->trader_commission_amount, 2, '.', ' ') }} USDT
@if($trader && $trader->hash_id)
Трейдер: {{ $trader->hash_id }}
@endif
Реквизиты: @if($details) @foreach($details as $key => $val)
{{ ucfirst($key) }}: {{ $val }}
@endforeach @else - @endif
@endforeach
@endif {{-- Таблица заявок (фильтры + список) --}}
@php $query = \App\Models\PayoutRequest::query(); if (request('status')) $query->where('status', request('status')); if (request('client_id')) $query->where('client_id', request('client_id')); if (request('id')) $query->where('id', request('id')); if (request('from')) $query->where('created_at', '>=', request('from').' 00:00:00'); if (request('to')) $query->where('created_at', '<=', request('to').' 23:59:59'); $payouts = $query->orderByDesc('id')->paginate(30)->appends(request()->all()); @endphp @if($payouts->count())
@foreach($payouts as $payout) @php $details = is_array($payout->details) ? $payout->details : json_decode($payout->details, true); $client = \App\Models\Client::find($payout->client_id); $trader = $payout->trader_id ? DB::table('markets')->where('id', $payout->trader_id)->first() : null; $shown = [ 'id','status','created_at','updated_at','order_id','amount_to_trader','from_amount','to_amount', 'to_currency','rate','curse','client_id','trader_id','assigned_at','platform_commission_amount', 'platform_commission_percent','trader_commission_amount','trader_commission_percent', 'client_commission_amount','client_commission_percent','callback_url','details','comment','screenshot_path' ]; $extraFields = []; foreach ($payout->getAttributes() as $key => $value) { if (!in_array($key, $shown)) { $extraFields[$key] = $value; } } @endphp {{-- Модалка --}} @endforeach
ID Статус Создана Обновление статуса Сумма Валюта Профит трейдера Профит системы Комиссия клиента Участники
{{ $payout->id }} @if ($payout->status == 'done') ✅ done @elseif ($payout->status == 'processing') 🔄 processing @elseif ($payout->status == 'pending') ⏳ pending @elseif ($payout->status == 'error') ❌ error @elseif ($payout->status == 'cancelled') ⛔ cancelled @else {{ $payout->status }} @endif {{ Carbon::parse($payout->created_at)->format('d.m.Y H:i') }} {{ $payout->updated_at ? Carbon::parse($payout->updated_at)->format('d.m.Y H:i') : '-' }} {{ number_format($payout->to_amount, 2, '.', ' ') }} {{ $payout->to_currency }} {{ number_format($payout->trader_commission_amount, 2, '.', ' ') }} ({{ rtrim(rtrim(number_format($payout->trader_commission_percent, 2, '.', ''), '0'), '.') }}%) {{ number_format($payout->platform_commission_amount, 2, '.', ' ') }} ({{ rtrim(rtrim(number_format($payout->platform_commission_percent, 2, '.', ''), '0'), '.') }}%) {{ number_format($payout->client_commission_amount, 2, '.', ' ') }} ({{ rtrim(rtrim(number_format($payout->client_commission_percent, 2, '.', ''), '0'), '.') }}%) @if($trader && $trader->hash_id) {{ $trader->hash_id }} @endif @if($client && $client->id)
Клиент {{ $client->id }} @else @endif

@if(!in_array($payout->status, ['error','done','cancelled']))
@csrf
@endif
{{ $payouts->withQueryString()->links() }}
@else
Заявок не найдено.
@endif
{{-- Favicon с числом pending --}} {{-- Скриншот-модалка --}} {{-- Модалка для скриншота --}} {{-- Chart.js --}} @endsection Server Error
500
Server Error