bedDayService->clearMemoryCache(); // Годовой план $recipientPlanOfYear = 0; $progressPlanOfYear = 0; // 1. Получаем отделения $departments = Department::select('department_id', 'name_short', 'rf_department_type', 'user_name', 'order') ->with('departmentType') ->join((new UserDepartment)->getTable(), (new Department)->getTable() . '.department_id', (new UserDepartment)->getTable() . '.rf_department_id') ->where((new UserDepartment)->getTable() . '.rf_user_id', $user->id) ->orderBy('rf_department_type') ->orderBy((new UserDepartment)->getTable() . '.order', 'asc') ->get() ->groupBy('departmentType.name_full'); if ($departments->isEmpty()) { return $this->emptyResponse(); } // Рассчитываем коэффициент периода (дни периода / 365) $start = Carbon::parse($startDate); $end = Carbon::parse($endDate); $monthsInPeriod = $start->diffInMonths($end); // +1 чтобы включить оба дня $periodCoefficient = $monthsInPeriod / 12; $monthsInPeriod = ceil($start->diffInMonths($end)); // foreach ($departments as $departmentType) { // foreach ($departmentType as $department) { // if ($department->recipientPlanOfYear() === null) continue; // $recipientPlanOfYear += (int)$department->recipientPlanOfYear()->value; // } // } $allDeptIds = $departments->flatten()->pluck('department_id')->toArray(); // 2. Получаем ВСЕ метрики за период ОДНИМ запросом $metrics = DB::table('reports as r') ->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id') ->whereIn('r.rf_department_id', $allDeptIds) ->whereDate('r.sent_at', '>=', $startDate) ->whereDate('r.sent_at', '<=', $endDate) ->select( 'r.rf_department_id', 'mr.rf_metrika_item_id', DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total'), DB::raw('COUNT(*) as records_count') ) ->whereIn('mr.rf_metrika_item_id', [1, 4, 12, 11, 10, 13, 7, 9, 17, 14, 16, 18, 19, 22]) ->groupBy('r.rf_department_id', 'mr.rf_metrika_item_id') ->get() ->groupBy('rf_department_id'); // 3. Получаем текущих пациентов $currentPatients = DB::table('reports as r') ->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id') ->whereIn('r.rf_department_id', $allDeptIds) ->where('mr.rf_metrika_item_id', 8) ->where('r.sent_at', '<=', $endDate) ->select('r.rf_department_id', 'mr.value', 'r.created_at') ->orderBy('r.rf_department_id') // Сначала поле из DISTINCT ON ->orderBy('r.sent_at', 'desc') // Потом остальные ->distinct('r.rf_department_id') ->get() ->keyBy('rf_department_id'); // 4. Получаем количество коек $beds = DB::table('department_metrika_defaults') ->whereIn('rf_department_id', $allDeptIds) ->where('rf_metrika_item_id', 1) ->select('rf_department_id', 'value') ->get() ->keyBy('rf_department_id'); // 5. Собираем данные $groupedData = []; $totalsByType = []; $grandRecipientPlan = 0; $grandProgressPlan = 0; foreach ($departments as $typeName => $deptList) { $groupedData[$typeName] = []; $totalsByType[$typeName] = $this->initTypeTotals(); foreach ($deptList as $dept) { $deptId = $dept->department_id; $lastReport = Report::where('rf_department_id', $deptId) ->whereDate('sent_at', '>=', Carbon::parse($startDate)->format('Y-m-d')) ->whereDate('sent_at', '<=', Carbon::parse($endDate)->format('Y-m-d')) ->orderBy('sent_at', 'desc') ->first(); // Базовые показатели $bedsCount = (int)($beds[$deptId]->value ?? 0); $currentCount = (int)($currentPatients[$deptId]->value ?? 0); // Получаем годовой план $annualPlanModel = $dept->recipientPlanOfYear(); // $annualPlan = $annualPlanModel ? (int)$annualPlanModel->value : 0; $annualPlan = $annualPlanModel ? (int)$annualPlanModel->value : 0; $oneMonthPlan = ceil($annualPlan / 12); // Рассчитываем план на период $periodPlan = round($oneMonthPlan * $monthsInPeriod); // $periodPlan = round($annualPlan * $periodCoefficient); // Счетчики $plan = 0; $emergency = 0; $planSurgical = 0; $emergencySurgical = 0; $transferred = 0; $outcome = 0; $deceased = 0; $staff = 0; $observable = 0; $unwanted = 0; $bedDaysSum = 0; if (isset($metrics[$deptId])) { foreach ($metrics[$deptId] as $item) { $value = (float)$item->total; match ($item->rf_metrika_item_id) { 4 => $plan = (int)$value, 12 => $emergency = (int)$value, 11 => $planSurgical = (int)$value, 10 => $emergencySurgical = (int)$value, 13 => $transferred = (int)$value, 7 => $outcome = (int)$value, 9 => $deceased = (int)$value, 17 => $staff = (int)$value, 14 => $observable = (int)$value, 16 => $unwanted = (int)$value, 18 => $bedDaysSum += $value, 19 => $lethalitySum = $value, // 24 => $completePlanProgress = (int)$value, default => null }; } } $grandRecipientPlan += $periodPlan; $grandProgressPlan += $outcome; $percentPlanOfYear = $periodPlan > 0 ? round($outcome * 100 / $periodPlan) : 0; // Расчеты $allCount = $plan + $emergency; $percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0; // Средний койко-день $avgBedDays = $outcome > 0 ? round($bedDaysSum / $outcome, 2) : 0; // Предоперационный койко-день $preoperativeValue = $lastReport ? (float)MetrikaResult::where('rf_report_id', $lastReport->report_id) ->where('rf_metrika_item_id', 21) ->value('value') : 0; // Летальность $lethality = $outcome > 0 ? round(($deceased / $outcome) * 100, 2) : 0; $departmentName = $dept->user_name ?? $dept->name_short; $progressPlanOfYear += $outcome; $data = [ 'department' => $departmentName, 'department_id' => $deptId, 'beds' => $bedsCount, 'recipients' => [ 'all' => $allCount, 'plan' => $plan, 'emergency' => $emergency, 'transferred' => $transferred, ], 'outcome' => $outcome, 'consist' => $currentCount, 'percentLoadedBeds' => $percentLoaded, 'surgical' => [ 'plan' => $planSurgical, 'emergency' => $emergencySurgical ], 'deceased' => $deceased, 'countStaff' => $staff, 'countObservable' => $observable, 'countUnwanted' => $unwanted, 'averageBedDays' => $avgBedDays, 'preoperativeDays' => $preoperativeValue, 'progressPlanOfYear' => $periodPlan, 'percentPlanOfYear' => $percentPlanOfYear, 'lethality' => $lethality, 'type' => $typeName, 'isDepartment' => true, 'isReportToday' => $lastReport ? Carbon::parse($lastReport->sent_at)->isSameDay($endDate) : null, ]; $groupedData[$typeName][] = $data; $this->updateTypeTotals($totalsByType[$typeName], $data); } } return [ 'data' => $this->buildFinalData($groupedData, $totalsByType), 'totalsByType' => $totalsByType, 'grandTotals' => $this->calculateGrandTotals($totalsByType), 'recipientPlanOfYear' => [ 'plan' => $grandRecipientPlan, // Сумма планов по периоду 'progress' => $grandProgressPlan, // Сумма фактов по периоду ] ]; } /** * Инициализация итогов по типу */ private function initTypeTotals(): array { return [ 'departments_count' => 0, 'beds_sum' => 0, 'recipients_all_sum' => 0, 'recipients_plan_sum' => 0, 'recipients_emergency_sum' => 0, 'recipients_transferred_sum' => 0, 'outcome_sum' => 0, 'consist_sum' => 0, 'plan_surgical_sum' => 0, 'emergency_surgical_sum' => 0, 'deceased_sum' => 0, 'percentLoadedBeds_total' => 0, 'percentLoadedBeds_count' => 0, 'staff_sum' => 0, 'observable_sum' => 0, 'unwanted_sum' => 0, ]; } /** * Обновление итогов по типу */ private function updateTypeTotals(array &$totals, array $data): void { $totals['departments_count']++; $totals['beds_sum'] += $data['beds']; $totals['recipients_all_sum'] += $data['recipients']['all']; $totals['recipients_plan_sum'] += $data['recipients']['plan']; $totals['recipients_emergency_sum'] += $data['recipients']['emergency']; $totals['recipients_transferred_sum'] += $data['recipients']['transferred']; $totals['outcome_sum'] += $data['outcome']; $totals['consist_sum'] += $data['consist']; $totals['plan_surgical_sum'] += $data['surgical']['plan']; $totals['emergency_surgical_sum'] += $data['surgical']['emergency']; $totals['deceased_sum'] += $data['deceased']; $totals['percentLoadedBeds_total'] += $data['percentLoadedBeds']; $totals['percentLoadedBeds_count']++; $totals['staff_sum'] += $data['countStaff']; $totals['observable_sum'] += $data['countObservable']; $totals['unwanted_sum'] += $data['countUnwanted']; } /** * Расчет общих итогов */ private function calculateGrandTotals(array $totalsByType): array { $grand = $this->initTypeTotals(); foreach ($totalsByType as $totals) { foreach ($grand as $key => &$value) { if (isset($totals[$key])) { $value += $totals[$key]; } } } return $grand; } /** * Построение финальных данных */ private function buildFinalData(array $groupedData, array $totalsByType): array { $final = []; foreach ($groupedData as $type => $items) { $final[] = [ 'isGroupHeader' => true, 'groupName' => $type, 'colspan' => 16 ]; foreach ($items as $item) { $final[] = $item; } if (!empty($items) && isset($totalsByType[$type])) { $final[] = $this->createTotalRow($type, $totalsByType[$type], false); } } return $final; } /** * Создание строки итогов */ private function createTotalRow(string $type, array $total, bool $isGrandTotal): array { return [ 'isTotalRow' => !$isGrandTotal, 'isGrandTotal' => $isGrandTotal, 'department' => $isGrandTotal ? 'ОБЩИЕ ИТОГИ:' : 'ИТОГО:', 'beds' => '—', 'recipients' => [ 'all' => $total['recipients_all_sum'], 'plan' => $total['recipients_plan_sum'], 'emergency' => $total['recipients_emergency_sum'], 'transferred' => $total['recipients_transferred_sum'], ], 'outcome' => $total['outcome_sum'], 'consist' => $total['consist_sum'], 'percentLoadedBeds' => '—', 'surgical' => [ 'plan' => $total['plan_surgical_sum'], 'emergency' => $total['emergency_surgical_sum'] ], 'deceased' => $total['deceased_sum'], 'averageBedDays' => '—', 'preoperativeDays' => '—', 'lethality' => '—', 'type' => $type, 'departments_count' => $total['departments_count'], 'countStaff' => $total['staff_sum'], 'countObservable' => $total['observable_sum'], 'countUnwanted' => $total['unwanted_sum'], 'isBold' => true ]; } /** * Пустой ответ */ private function emptyResponse(): array { return [ 'data' => [], 'totalsByType' => [], 'grandTotals' => $this->initTypeTotals() ]; } }