Files
onboard/app/Http/Controllers/Web/StatisticController.php
brusnitsyn 9ee33bc517 * блокировка изменения отчета для врача
* вывод данных из отчетов для ролей адм и зав
* поправил ширину стобцов ввода
* добавил календарь на страницу статистики
* переделал календарь у заведующего на странице отчета
* добавил и привязал метрики в статистику
2026-02-03 17:03:37 +09:00

294 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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) {
// Статистика выводится с нарастающим числом
$reports = $department->reports()
->whereDate('created_at', $dateReport)
->get();
} else {
$reports = $department->reports()
->whereBetween('created_at', $dateReport)->get();
}
// Метрики зависищие от отчетов
$allCount = 0; $outcomeCount = 0; $currentCount = 0; $occupiedBeds = 0; $planCount = 0;
$emergencyCount = 0; $planSurgical = 0; $emergencySurgical = 0; $transferredCount = 0;
$deceasedCount = 0;
foreach ($reports as $report) {
$allCount += $this->getMetrikaResultFromReport($report, 3, $isRangeOneDay);
$currentCount += $this->getMetrikaResultFromReport($report, 8, false);
$occupiedBeds += $this->getMetrikaResultFromReport($report, 8, $isRangeOneDay);
$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);
}
// Независимые метрики (установки по умолчанию и т.п.)
$bedsCount = $department->metrikaDefault()
->where('rf_metrika_item_id', 1)->value('value');
$percentLoadedBeds = $bedsCount > 0 ? round($occupiedBeds * 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);
}
}