298 lines
12 KiB
PHP
298 lines
12 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Web;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\Department;
|
||
use App\Models\MetrikaForm;
|
||
use App\Models\MetrikaGroup;
|
||
use App\Models\MetrikaItem;
|
||
use App\Models\MetrikaResult;
|
||
use App\Models\Report;
|
||
use App\Services\DateRangeService;
|
||
use Illuminate\Database\Eloquent\Builder;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Auth;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Validator;
|
||
use Inertia\Inertia;
|
||
|
||
class StatisticController extends Controller
|
||
{
|
||
protected DateRangeService $dateService;
|
||
|
||
public function __construct(DateRangeService $dateRangeService)
|
||
{
|
||
$this->dateService = $dateRangeService;
|
||
}
|
||
|
||
public function index(Request $request)
|
||
{
|
||
$user = $request->user();
|
||
|
||
$queryStartDate = $request->query('startAt');
|
||
$queryEndDate = $request->query('endAt');
|
||
[$startDate, $endDate] = $this->dateService->getDateRangeForUser($user, $queryStartDate, $queryEndDate);
|
||
$isRangeOneDay = $this->dateService->isRangeOneDay($startDate, $endDate);
|
||
|
||
// Если диапазон содержит сутки
|
||
if ($isRangeOneDay) {
|
||
// Устанавливаем дату отчета, как последний день из выборки
|
||
$dateReport = $endDate;
|
||
} else {
|
||
// Устанавливаем дату отчета, как выборку
|
||
$dateReport = [$startDate, $endDate];
|
||
}
|
||
|
||
$groupedData = [];
|
||
|
||
$departments = Department::select('department_id', 'rf_department_type', 'name_short')
|
||
->with(['reports'])
|
||
->orderBy('name_short')->get();
|
||
|
||
foreach ($departments as $department) {
|
||
$departmentType = $department->departmentType->name_full;
|
||
|
||
if (!isset($groupedData[$departmentType])) {
|
||
$groupedData[$departmentType] = [];
|
||
}
|
||
|
||
if ($isRangeOneDay) {
|
||
// Статистика выводится с нарастающим числом
|
||
$query = $department->reports();
|
||
$reports = $query->whereDate('created_at', $dateReport)
|
||
->get();
|
||
$lastReport = $query->whereDate('created_at', $dateReport)->first();
|
||
} else {
|
||
$query = $department->reports();
|
||
$reports = $query->clone()->whereBetween('created_at', $dateReport)
|
||
->get();
|
||
$lastReport = $query->clone()->whereDate('created_at', $dateReport[1])->first();
|
||
}
|
||
|
||
// Метрики зависищие от отчетов
|
||
$allCount = 0; $outcomeCount = 0; $currentCount = 0; $planCount = 0;
|
||
$emergencyCount = 0; $planSurgical = 0; $emergencySurgical = 0; $transferredCount = 0;
|
||
$deceasedCount = 0;
|
||
$currentCount = $lastReport ? $this->getMetrikaResultFromReport($lastReport, 8, false) : 0; // Состоит
|
||
foreach ($reports as $report) {
|
||
$planCount += $this->getMetrikaResultFromReport($report, 4, $isRangeOneDay); // Поступление - Планово
|
||
$emergencyCount += $this->getMetrikaResultFromReport($report, 12, $isRangeOneDay); // Поступление - Экстренно
|
||
$planSurgical += $this->getMetrikaResultFromReport($report, 11, $isRangeOneDay); // Операции - Планово
|
||
$emergencySurgical += $this->getMetrikaResultFromReport($report, 10, $isRangeOneDay); // Операции - Экстренно
|
||
$transferredCount += $this->getMetrikaResultFromReport($report, 13, $isRangeOneDay); // Поступление - Перевод
|
||
$outcomeCount += $this->getMetrikaResultFromReport($report, 7, $isRangeOneDay); // Выбыло
|
||
$deceasedCount += $this->getMetrikaResultFromReport($report, 9, $isRangeOneDay); // Умерло
|
||
}
|
||
|
||
$allCount = $planCount + $emergencyCount; // Поступило
|
||
|
||
// Независимые метрики (установки по умолчанию и т.п.)
|
||
$bedsCount = $department->metrikaDefault()
|
||
->where('rf_metrika_item_id', 1)->value('value');
|
||
|
||
$percentLoadedBeds = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
|
||
|
||
$groupedData[$departmentType][] = [
|
||
'department' => $department->name_short,
|
||
'beds' => $bedsCount,
|
||
'recipients' => [
|
||
'all' => $allCount,
|
||
'plan' => $planCount,
|
||
'emergency' => $emergencyCount,
|
||
'transferred' => $transferredCount,
|
||
],
|
||
'outcome' => $outcomeCount,
|
||
'consist' => $currentCount,
|
||
'percentLoadedBeds' => $percentLoadedBeds,
|
||
'surgical' => [
|
||
'plan' => $planSurgical,
|
||
'emergency' => $emergencySurgical
|
||
],
|
||
'deceased' => $deceasedCount,
|
||
'type' => $departmentType
|
||
];
|
||
}
|
||
|
||
// Преобразуем группированные данные в плоский массив с заголовками групп
|
||
$finalData = [];
|
||
foreach ($groupedData as $type => $departmentsInType) {
|
||
// Добавляем строку-заголовок группы
|
||
$finalData[] = [
|
||
'isGroupHeader' => true,
|
||
'groupName' => $type,
|
||
'colspan' => 12, // Количество колонок в таблице
|
||
'type' => $type
|
||
];
|
||
|
||
// Добавляем отделения этой группы
|
||
foreach ($departmentsInType as $department) {
|
||
$finalData[] = $department;
|
||
}
|
||
}
|
||
|
||
$isHeadOrAdmin = $user->isAdmin() || $user->isHeadOfDepartment();
|
||
$date = $isHeadOrAdmin ? [
|
||
$this->dateService->parseDate($isRangeOneDay ? $endDate : $startDate)->getTimestampMs(),
|
||
$this->dateService->parseDate($endDate)->getTimestampMs()
|
||
] : $this->dateService->parseDate($endDate)->getTimestampMs();
|
||
|
||
return Inertia::render('Statistic/Index', [
|
||
'data' => $finalData,
|
||
'isHeadOrAdmin' => $isHeadOrAdmin,
|
||
'date' => $date,
|
||
'isOneDay' => $isRangeOneDay
|
||
]);
|
||
}
|
||
|
||
private function getMetrikaResultFromReport(Report $report, int $metrikaItem, bool $sum = true)
|
||
{
|
||
if ($sum) {
|
||
return (int) ($report->metrikaResults()
|
||
->where('rf_metrika_item_id', $metrikaItem)
|
||
->sum(DB::raw('CAST(value AS INTEGER)')) ?: 0);
|
||
}
|
||
|
||
return (int) ($report->metrikaResults()
|
||
->where('rf_metrika_item_id', $metrikaItem)
|
||
->value('value') ?: 0);
|
||
}
|
||
|
||
public function indexOld(Request $request)
|
||
{
|
||
$user = Auth::user();
|
||
|
||
$validator = Validator::make($request->all(), [
|
||
'sent_at' => 'required|string'
|
||
]);
|
||
|
||
$groupId = (int)$request->query('groupId');
|
||
|
||
if ($validator->fails()) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'errors' => $validator->errors()
|
||
], 422);
|
||
}
|
||
|
||
$timestamps = explode(',', $request->sent_at);
|
||
$startAt = intval($timestamps[0] / 1000);
|
||
$endAt = intval($timestamps[1] / 1000);
|
||
|
||
// Проверяем период (максимум 1 год)
|
||
$daysDiff = ($endAt - $startAt) / (60 * 60 * 24);
|
||
if ($daysDiff > 365) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'Период не может превышать 1 год'
|
||
], 400);
|
||
}
|
||
|
||
$dateStart = date('Y-m-d', $startAt);
|
||
$dateEnd = date('Y-m-d', $endAt);
|
||
|
||
$group = MetrikaGroup::findOrFail($groupId);
|
||
|
||
// Оптимизированный агрегированный запрос
|
||
$aggregatedData = DB::table('metrika_results as mr')
|
||
->join('metrika_result_values as mv', 'mr.metrika_result_id', '=', 'mv.rf_metrika_result_id')
|
||
->join('reports as r', 'mr.rf_report_id', '=', 'r.report_id')
|
||
->where('mr.rf_metrika_group_id', $groupId)
|
||
->whereBetween('r.sent_at', [$dateStart, $dateEnd])
|
||
->when(!$user->isAdmin() && !$user->isHeadOfDepartment(), function ($query) use ($user) {
|
||
return $query->where('r.rf_user_id', $user->id);
|
||
})
|
||
->select([
|
||
'mv.rf_metrika_item_id',
|
||
DB::raw('SUM(CAST(mv.value AS DECIMAL(10,2))) as total_sum'),
|
||
DB::raw('COUNT(DISTINCT r.report_id) as reports_count'),
|
||
DB::raw('AVG(CAST(mv.value AS DECIMAL(10,2))) as avg_value')
|
||
])
|
||
->groupBy('mv.rf_metrika_item_id')
|
||
->get()
|
||
->keyBy('rf_metrika_item_id');
|
||
|
||
if ($aggregatedData->isEmpty()) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'Данные за указанный период не найдены'
|
||
], 404);
|
||
}
|
||
|
||
// Получаем названия метрик одним запросом
|
||
$itemIds = $aggregatedData->pluck('rf_metrika_item_id')->toArray();
|
||
$items = MetrikaItem::whereIn('metrika_item_id', $itemIds)
|
||
->pluck('name', 'metrika_item_id');
|
||
|
||
// Формируем ответ
|
||
$formValues = [];
|
||
foreach ($aggregatedData as $itemId => $data) {
|
||
$formValues["metrika_item_{$itemId}"] = [
|
||
'sum' => (float) $data->total_sum,
|
||
'average' => (float) $data->avg_value,
|
||
'reports_count' => $data->reports_count,
|
||
'item_name' => $items[$itemId] ?? 'Неизвестный показатель'
|
||
];
|
||
}
|
||
|
||
// Получаем структуру формы
|
||
$formData = MetrikaForm::getFormData($groupId);
|
||
|
||
return Inertia::render('Statistic/Index', [
|
||
'is_view_only' => true,
|
||
'period' => [
|
||
'start' => $dateStart,
|
||
'end' => $dateEnd,
|
||
'days' => $daysDiff + 1
|
||
],
|
||
'group' => [
|
||
'id' => $group->metrika_group_id,
|
||
'name' => $group->name,
|
||
'description' => $group->description,
|
||
],
|
||
'metrics' => [
|
||
'total_items' => count($formValues),
|
||
'total_reports' => $aggregatedData->first()->reports_count ?? 0,
|
||
'values' => $formValues,
|
||
'aggregation' => 'sum_and_average'
|
||
],
|
||
'form' => [
|
||
'fields' => $formData,
|
||
'sections' => $this->groupFieldsBySection($formData)
|
||
]
|
||
]);
|
||
}
|
||
|
||
private function groupFieldsBySection($fields)
|
||
{
|
||
$sections = [];
|
||
|
||
foreach ($fields as $field) {
|
||
$section = $field['section'] ?? 'general';
|
||
|
||
if (!isset($sections[$section])) {
|
||
$sections[$section] = [
|
||
'name' => $this->getSectionName($section),
|
||
'fields' => []
|
||
];
|
||
}
|
||
|
||
$sections[$section]['fields'][] = $field;
|
||
}
|
||
|
||
return array_values($sections);
|
||
}
|
||
|
||
private function getSectionName($section)
|
||
{
|
||
$names = [
|
||
'general' => 'Основные показатели',
|
||
'admissions' => 'Поступления',
|
||
'discharges' => 'Выписки',
|
||
'additional' => 'Дополнительная информация'
|
||
];
|
||
|
||
return $names[$section] ?? ucfirst($section);
|
||
}
|
||
}
|