Files
onboard/app/Services/StatisticsService.php
brusnitsyn 52a80ccd3b nothing
2026-02-20 17:28:16 +09:00

894 lines
35 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\Services;
use App\Models\Department;
use App\Models\Report;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Collection;
use Carbon\Carbon;
class StatisticsService
{
/**
* Метрики, которые нужно суммировать (целые числа)
*/
protected array $summableMetrics = [4, 12, 11, 10, 13, 7, 9, 8, 17, 14, 16];
/**
* Метрики, которые нужно усреднять (числа с плавающей точкой)
*/
protected array $averageMetrics = [18];
protected array $metricMapping = [
4 => 'plan', // Плановые поступления
12 => 'emergency', // Экстренные поступления
11 => 'plan_surgical', // Плановые операции
10 => 'emergency_surgical', // Экстренные операции
13 => 'transferred', // Переведенные
7 => 'outcome', // Выбыло
9 => 'deceased', // Умерло
8 => 'current', // Состоит
17 => 'count_staff', // Кол-во мед. персонала
14 => 'count_observable', // Кол-во пациентов на контроле
16 => 'count_unwanted', // Кол-во нежелательных событий
18 => 'average_bed_days' // Средний койко-день
];
public function __construct(
protected BedDayService $bedDayService
) { }
/**
* Получить статистические данные с оптимизацией
*/
public function getStatisticsData(User $user, string $startDate, string $endDate, bool $isRangeOneDay): array
{
// Очищаем кэш памяти перед началом
$this->bedDayService->clearMemoryCache();
// Определяем порог для использования оптимизированного метода
$daysDiff = Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate));
// Для диапазонов больше 30 дней используем агрегированные данные
if ($daysDiff > 30) {
return $this->getAggregatedStatistics($user, $startDate, $endDate, $isRangeOneDay);
}
// Для диапазонов 7-30 дней используем оптимизированный метод
if ($daysDiff > 7) {
return $this->getOptimizedStatistics($user, $startDate, $endDate, $isRangeOneDay);
}
// Для малых диапазонов используем детальный метод
return $this->getDetailedStatistics($user, $startDate, $endDate, $isRangeOneDay);
}
/**
* Получить средние койко-дни за период из сохраненных метрик отчетов
*/
protected function getAverageBedDaysFromReports(array $departmentIds, string $startDate, string $endDate): array
{
if (empty($departmentIds)) {
return [];
}
try {
$results = DB::table('reports as r')
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
->whereIn('r.rf_department_id', $departmentIds)
->where('mr.rf_metrika_item_id', 18)
->whereBetween('r.created_at', [$startDate, $endDate])
->select(
'r.rf_department_id',
DB::raw('AVG(CAST(mr.value AS DECIMAL)) as avg_value')
)
->groupBy('r.rf_department_id')
->get()
->keyBy('rf_department_id');
$averages = [];
foreach ($departmentIds as $departmentId) {
if (isset($results[$departmentId]) && $results[$departmentId]->avg_value !== null) {
$averages[$departmentId] = round((float)$results[$departmentId]->avg_value, 1);
} else {
$averages[$departmentId] = 0;
}
}
return $averages;
} catch (\Exception $e) {
\Log::error("Error in getAverageBedDaysFromReports: " . $e->getMessage());
return array_fill_keys($departmentIds, 0);
}
}
/**
* Получить общий средний койко-день
*/
protected function getOverallAverageBedDays(array $averages): float
{
$total = 0;
$count = 0;
foreach ($averages as $avg) {
if ($avg > 0) {
$total += $avg;
$count++;
}
}
return $count > 0 ? round($total / $count, 1) : 0;
}
/**
* Агрегированный метод для очень больших диапазонов (больше 30 дней)
*/
private function getAggregatedStatistics(User $user, string $startDate, string $endDate, bool $isRangeOneDay): array
{
// Устанавливаем дату отчета
if ($isRangeOneDay) {
$dateReport = $endDate;
} else {
$dateReport = [$startDate, $endDate];
}
// Загружаем все отделения
$departments = Department::select('department_id', 'rf_department_type', 'name_short')
->with(['departmentType'])
->orderBy('rf_department_type')
->get()
->keyBy('department_id');
// Загружаем метрики по умолчанию
$defaultMetrics = $this->getDefaultMetricsBatch($departments->pluck('department_id')->toArray());
// Получаем агрегированные данные по отчетам
$aggregatedData = $this->getAggregatedReportData(
$departments->pluck('department_id')->toArray(),
$dateReport,
$isRangeOneDay
);
// Получаем последние отчеты для текущих пациентов
$lastReportsData = $this->getLastReportsData(
$departments->pluck('department_id')->toArray(),
$isRangeOneDay ? $dateReport : $dateReport[1]
);
// Получаем средние койко-дни из метрик отчетов
$averageBedDays = $this->getAverageBedDaysFromReports(
$departments->pluck('department_id')->toArray(),
$startDate,
$endDate
);
// Общий средний койко-день
$overallAverageBedDays = $this->getOverallAverageBedDays($averageBedDays);
return $this->processAggregatedData(
$departments,
$defaultMetrics,
$aggregatedData,
$lastReportsData,
$isRangeOneDay,
$averageBedDays,
$overallAverageBedDays
);
}
/**
* Получить агрегированные данные по отчетам
*/
private function getAggregatedReportData(array $departmentIds, $dateReport, bool $isRangeOneDay): Collection
{
// Для суммируемых метрик - SUM
$summableQuery = DB::table('reports as r')
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
->select(
'r.rf_department_id',
'mr.rf_metrika_item_id',
DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total')
)
->whereIn('r.rf_department_id', $departmentIds)
->whereIn('mr.rf_metrika_item_id', $this->summableMetrics);
if ($isRangeOneDay) {
$summableQuery->whereDate('r.created_at', $dateReport);
} else {
$summableQuery->whereBetween('r.created_at', $dateReport);
}
$summableResults = $summableQuery
->groupBy('r.rf_department_id', 'mr.rf_metrika_item_id')
->get();
// Для усредняемых метрик - AVG (например, средний койко-день)
$averageQuery = DB::table('reports as r')
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
->select(
'r.rf_department_id',
'mr.rf_metrika_item_id',
DB::raw('AVG(CAST(mr.value AS DECIMAL)) as total')
)
->whereIn('r.rf_department_id', $departmentIds)
->whereIn('mr.rf_metrika_item_id', $this->averageMetrics);
if ($isRangeOneDay) {
$averageQuery->whereDate('r.created_at', $dateReport);
} else {
$averageQuery->whereBetween('r.created_at', $dateReport);
}
$averageResults = $averageQuery
->groupBy('r.rf_department_id', 'mr.rf_metrika_item_id')
->get();
// Объединяем результаты
$allResults = $summableResults->concat($averageResults);
return $allResults->groupBy('rf_department_id');
}
/**
* Получить данные последних отчетов
*/
private function getLastReportsData(array $departmentIds, string $date): Collection
{
// Находим ID последних отчетов для каждого отделения
$subQuery = DB::table('reports')
->select('rf_department_id', DB::raw('MAX(report_id) as last_report_id'))
->whereIn('rf_department_id', $departmentIds)
->whereDate('created_at', '<=', $date)
->groupBy('rf_department_id');
return DB::table('metrika_results as mr')
->joinSub($subQuery, 'last_reports', function ($join) {
$join->on('mr.rf_report_id', '=', 'last_reports.last_report_id');
})
->where('mr.rf_metrika_item_id', 8) // Только текущие пациенты
->select('last_reports.rf_department_id', 'mr.value')
->get()
->keyBy('rf_department_id');
}
/**
* Обработать агрегированные данные
*/
private function processAggregatedData(
Collection $departments,
Collection $defaultMetrics,
Collection $aggregatedData,
Collection $lastReportsData,
bool $isRangeOneDay,
array $averageBedDays,
float $overallAverageBedDays
): array {
$groupedData = [];
$totalsByType = [];
foreach ($departments as $department) {
$departmentId = $department->department_id;
$departmentType = $department->departmentType->name_full;
if (!isset($groupedData[$departmentType])) {
$groupedData[$departmentType] = [];
$totalsByType[$departmentType] = $this->initTypeTotals();
}
// Получаем агрегированные метрики
$metrics = $aggregatedData->get($departmentId, collect());
$counters = array_fill_keys(array_values($this->metricMapping), 0);
foreach ($metrics as $metric) {
$key = $this->metricMapping[$metric->rf_metrika_item_id] ?? null;
if ($key) {
// Для агрегированных данных всегда берем сумму
$counters[$key] = (int)$metric->total;
}
}
// Текущие пациенты из последнего отчета
$currentCount = (int)($lastReportsData->get($departmentId)?->value ?? 0);
$counters['current'] = $currentCount;
// Количество коек
$bedsCount = (int)($defaultMetrics->get($departmentId)?->value ?? 0);
// Рассчитываем значения
$allCount = $counters['plan'] + $counters['emergency'];
$percentLoadedBeds = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
// Получаем средний койко-день для отделения
$departmentAverageBedDays = $averageBedDays[$departmentId] ?? 0;
// Формируем данные
$departmentData = $this->createDepartmentData(
$department->name_short,
$departmentId,
$bedsCount,
$allCount,
$counters,
$percentLoadedBeds,
$departmentType,
null,
$departmentAverageBedDays,
$overallAverageBedDays
);
$groupedData[$departmentType][] = $departmentData;
$this->updateTypeTotals($totalsByType[$departmentType], $departmentData);
}
return $this->buildFinalData($groupedData, $totalsByType);
}
/**
* Оптимизированный метод для средних диапазонов (7-30 дней)
*/
private function getOptimizedStatistics(User $user, string $startDate, string $endDate, bool $isRangeOneDay): array
{
// Устанавливаем дату отчета
if ($isRangeOneDay) {
$dateReport = $endDate;
} else {
$dateReport = [$startDate, $endDate];
}
// Загружаем все отделения
$departments = Department::select('department_id', 'rf_department_type', 'name_short')
->with(['departmentType'])
->orderBy('rf_department_type')
->get()
->keyBy('department_id');
// Загружаем метрики по умолчанию
$defaultMetrics = $this->getDefaultMetricsBatch($departments->pluck('department_id')->toArray());
// Загружаем отчеты
$reports = $this->getReportsBatch($departments->pluck('department_id')->toArray(), $dateReport, $isRangeOneDay);
// Загружаем метрики отчетов
$reportIds = $reports->flatMap(fn($items) => $items->pluck('report_id'))->toArray();
$reportMetrics = $this->getReportMetricsBatch($reportIds);
// Получаем средние койко-дни из метрик отчетов
$averageBedDays = $this->getAverageBedDaysFromReports(
$departments->pluck('department_id')->toArray(),
$startDate,
$endDate
);
// Общий средний койко-день
$overallAverageBedDays = $this->getOverallAverageBedDays($averageBedDays);
return $this->processOptimizedData(
$departments,
$defaultMetrics,
$reports,
$reportMetrics,
$dateReport,
$isRangeOneDay,
$averageBedDays,
$overallAverageBedDays
);
}
/**
* Получить метрики по умолчанию для всех отделений пачкой
*/
private function getDefaultMetricsBatch(array $departmentIds): Collection
{
return DB::table('department_metrika_defaults')
->whereIn('rf_department_id', $departmentIds)
->where('rf_metrika_item_id', 1) // только койки
->select('rf_department_id', 'value')
->get()
->keyBy('rf_department_id');
}
/**
* Получить отчеты для всех отделений пачкой
*/
private function getReportsBatch(array $departmentIds, $dateReport, bool $isRangeOneDay): Collection
{
$query = Report::whereIn('rf_department_id', $departmentIds);
if ($isRangeOneDay) {
$query->whereDate('created_at', $dateReport);
} else {
$query->whereBetween('created_at', $dateReport);
}
return $query->select('report_id', 'rf_department_id', 'created_at')
->orderBy('created_at')
->get()
->groupBy('rf_department_id');
}
/**
* Получить метрики отчетов пачкой
*/
private function getReportMetricsBatch(array $reportIds): Collection
{
if (empty($reportIds)) {
return collect();
}
// Получаем все метрики, но для 18 не преобразуем в integer
$results = DB::table('metrika_results')
->whereIn('rf_report_id', $reportIds)
->whereIn('rf_metrika_item_id', array_keys($this->metricMapping))
->select('rf_report_id', 'rf_metrika_item_id', 'value')
->get()
->groupBy('rf_report_id');
// Преобразуем значения в зависимости от типа метрики
foreach ($results as $reportId => $metrics) {
foreach ($metrics as $metric) {
if (in_array($metric->rf_metrika_item_id, $this->summableMetrics)) {
$metric->value = (int)$metric->value;
} else {
$metric->value = (float)$metric->value;
}
}
}
return $results;
}
/**
* Обработать оптимизированные данные
*/
private function processOptimizedData(
Collection $departments,
Collection $defaultMetrics,
Collection $reports,
Collection $reportMetrics,
$dateReport,
bool $isRangeOneDay,
array $averageBedDays,
float $overallAverageBedDays
): array {
$groupedData = [];
$totalsByType = [];
foreach ($departments as $department) {
$departmentId = $department->department_id;
$departmentType = $department->departmentType->name_full;
if (!isset($groupedData[$departmentType])) {
$groupedData[$departmentType] = [];
$totalsByType[$departmentType] = $this->initTypeTotals();
}
// Получаем отчеты отделения
$departmentReports = $reports->get($departmentId, collect());
$lastReport = $departmentReports->last();
// Инициализируем счетчики
$counters = array_fill_keys(array_values($this->metricMapping), 0);
// Обрабатываем каждый отчет
foreach ($departmentReports as $report) {
$metrics = $reportMetrics->get($report->report_id, collect())
->keyBy('rf_metrika_item_id');
foreach ($this->metricMapping as $metricId => $key) {
if ($metrics->has($metricId)) {
$value = (int)$metrics[$metricId]->value;
// Разная логика для одного дня и диапазона
if ($isRangeOneDay) {
// Для одного дня: суммируем
$counters[$key] += $value;
} else {
// Для диапазона:
if ($metricId === 8) {
// Для текущих пациентов берем ПОСЛЕДНЕЕ значение
if ($report === $lastReport) {
$counters[$key] = $value;
}
} else {
// Для остальных суммируем
$counters[$key] += $value;
}
}
}
}
}
// Если нет отчетов за день, но есть последний отчет ранее
if ($counters['current'] === 0 && $lastReport) {
$metrics = $reportMetrics->get($lastReport->report_id, collect());
$currentMetric = $metrics->firstWhere('rf_metrika_item_id', 8);
if ($currentMetric) {
$counters['current'] = (int)$currentMetric->value;
}
}
// Получаем количество коек
$bedsCount = (int)($defaultMetrics->get($departmentId)?->value ?? 0);
// Рассчитываем значения
$allCount = $counters['plan'] + $counters['emergency'];
$percentLoadedBeds = $bedsCount > 0 ? round($counters['current'] * 100 / $bedsCount) : 0;
// Получаем средний койко-день для отделения
$departmentAverageBedDays = $averageBedDays[$departmentId] ?? 0;
// Формируем данные отделения
$departmentData = $this->createDepartmentData(
$department->name_short,
$departmentId,
$bedsCount,
$allCount,
$counters,
$percentLoadedBeds,
$departmentType,
null,
$departmentAverageBedDays,
$overallAverageBedDays
);
$groupedData[$departmentType][] = $departmentData;
$this->updateTypeTotals($totalsByType[$departmentType], $departmentData);
}
return $this->buildFinalData($groupedData, $totalsByType);
}
/**
* Детальный метод для небольших диапазонов (до 7 дней)
*/
private function getDetailedStatistics(User $user, string $startDate, string $endDate, bool $isRangeOneDay): array
{
// Устанавливаем дату отчета
if ($isRangeOneDay) {
$dateReport = $endDate;
} else {
$dateReport = [$startDate, $endDate];
}
$groupedData = [];
$totalsByType = [];
$departments = Department::select('department_id', 'rf_department_type', 'name_short')
->with(['departmentType', 'reports' => function ($query) use ($dateReport, $isRangeOneDay) {
if ($isRangeOneDay) {
$query->whereDate('created_at', $dateReport);
} else {
$query->whereBetween('created_at', $dateReport);
}
$query->with('metrikaResults');
}])
->orderBy('rf_department_type')
->get();
// Получаем средние койко-дни из метрик отчетов
$averageBedDays = $this->getAverageBedDaysFromReports(
$departments->pluck('department_id')->toArray(),
$startDate,
$endDate
);
// Общий средний койко-день
$overallAverageBedDays = $this->getOverallAverageBedDays($averageBedDays);
foreach ($departments as $department) {
$departmentType = $department->departmentType->name_full;
if (!isset($groupedData[$departmentType])) {
$groupedData[$departmentType] = [];
$totalsByType[$departmentType] = $this->initTypeTotals();
}
// Получаем отчеты
$reports = $department->reports;
$lastReport = $reports->last();
// Инициализируем счетчики
$counters = array_fill_keys(array_values($this->metricMapping), 0);
// Суммируем метрики
foreach ($reports as $report) {
foreach ($report->metrikaResults as $metric) {
$key = $this->metricMapping[$metric->rf_metrika_item_id] ?? null;
if ($key) {
$value = (int)$metric->value;
// ВАЖНО: разная логика для одного дня и диапазона
if ($isRangeOneDay) {
// Для одного дня: суммируем все значения
$counters[$key] += $value;
} else {
// Для диапазона:
if ($metric->rf_metrika_item_id === 8) {
// Для текущих пациентов берем ПОСЛЕДНЕЕ значение
// из последнего отчета за день
if ($report === $lastReport) {
$counters[$key] = $value;
}
} else {
// Для остальных метрик СУММИРУЕМ за весь период
$counters[$key] += $value;
}
}
}
}
}
// Получаем количество коек
$bedsCount = (int)$department->metrikaDefault()
->where('rf_metrika_item_id', 1)
->value('value') ?? 0;
// Рассчитываем итоговые значения
$allCount = $counters['plan'] + $counters['emergency'];
$percentLoadedBeds = $bedsCount > 0 ? round($counters['current'] * 100 / $bedsCount) : 0;
if ($isRangeOneDay)
$isReportToday = !empty($lastReport);
else $isReportToday = null;
$departmentAverageBedDays = $averageBedDays[$department->department_id] ?? 0;
// Формируем данные отделения
$departmentData = $this->createDepartmentData(
$department->name_short,
$department->department_id,
$bedsCount,
$allCount,
$counters,
$percentLoadedBeds,
$departmentType,
$isReportToday,
$departmentAverageBedDays,
$overallAverageBedDays
);
$groupedData[$departmentType][] = $departmentData;
$this->updateTypeTotals($totalsByType[$departmentType], $departmentData);
}
return $this->buildFinalData($groupedData, $totalsByType);
}
/**
* Создать данные отделения
*/
private function createDepartmentData(
string $name,
int $departmentId,
int $beds,
int $allCount,
array $counters,
int $percentLoadedBeds,
string $type,
?bool $isReportToday = null,
float $departmentAverageBedDays = 0,
float $overallAverageBedDays = 0
): array {
return [
'department' => $name,
'department_id' => $departmentId,
'beds' => $beds,
'recipients' => [
'all' => $allCount,
'plan' => $counters['plan'],
'emergency' => $counters['emergency'],
'transferred' => $counters['transferred'],
],
'outcome' => $counters['outcome'],
'consist' => $counters['current'],
'percentLoadedBeds' => $percentLoadedBeds,
'surgical' => [
'plan' => $counters['plan_surgical'],
'emergency' => $counters['emergency_surgical']
],
'deceased' => $counters['deceased'],
'countStaff' => $counters['count_staff'],
'countObservable' => $counters['count_observable'],
'countUnwanted' => $counters['count_unwanted'],
'averageBedDays' => $departmentAverageBedDays,
'overallAverageBedDays' => $overallAverageBedDays,
'type' => $type,
'isDepartment' => true,
'isReportToday' => $isReportToday,
];
}
/**
* Инициализировать итоги по типу
*/
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,
'average_bed_days_total' => 0,
'average_bed_days_count' => 0,
];
}
/**
* Обновить итоги по типу
*/
private function updateTypeTotals(array &$totals, array $departmentData): void
{
$totals['departments_count']++;
$totals['beds_sum'] += $departmentData['beds'];
$totals['recipients_all_sum'] += $departmentData['recipients']['all'];
$totals['recipients_plan_sum'] += $departmentData['recipients']['plan'];
$totals['recipients_emergency_sum'] += $departmentData['recipients']['emergency'];
$totals['recipients_transferred_sum'] += $departmentData['recipients']['transferred'];
$totals['outcome_sum'] += $departmentData['outcome'];
$totals['consist_sum'] += $departmentData['consist'];
$totals['plan_surgical_sum'] += $departmentData['surgical']['plan'];
$totals['emergency_surgical_sum'] += $departmentData['surgical']['emergency'];
$totals['deceased_sum'] += $departmentData['deceased'];
$totals['percentLoadedBeds_total'] += $departmentData['percentLoadedBeds'];
$totals['percentLoadedBeds_count']++;
$totals['staff_sum'] += $departmentData['countStaff'];
$totals['observable_sum'] += $departmentData['countObservable'];
$totals['unwanted_sum'] += $departmentData['countUnwanted'];
$totals['average_bed_days_total'] += $departmentData['averageBedDays'];
$totals['average_bed_days_count']++;
}
/**
* Построить финальные данные с итогами
*/
private function buildFinalData(array $groupedData, array $totalsByType): array
{
$finalData = [];
$grandTotals = $this->initTypeTotals();
foreach ($groupedData as $type => $departmentsInType) {
// Добавляем заголовок группы
$finalData[] = [
'isGroupHeader' => true,
'groupName' => $type,
'colspan' => 14,
'type' => $type
];
// Добавляем отделения
foreach ($departmentsInType as $department) {
$finalData[] = $department;
}
// Добавляем итоги по группе
if (!empty($departmentsInType) && isset($totalsByType[$type])) {
$total = $totalsByType[$type];
$avgPercent = $total['percentLoadedBeds_count'] > 0
? round($total['percentLoadedBeds_total'] / $total['percentLoadedBeds_count'])
: 0;
$finalData[] = $this->createTotalRow($type, $total, $avgPercent, false);
// Обновляем общие итоги
$this->updateGrandTotals($grandTotals, $total);
}
}
// Добавляем общие итоги
// if ($grandTotals['departments_count'] > 0) {
// $avgPercent = $grandTotals['percentLoadedBeds_count'] > 0
// ? round($grandTotals['percentLoadedBeds_total'] / $grandTotals['percentLoadedBeds_count'])
// : 0;
//
// $grandAvgBedDays = $grandTotals['averageBedDays_count'] > 0
// ? round($grandTotals['averageBedDays_total'] / $grandTotals['averageBedDays_count'], 1)
// : $overallAverageBedDays;
//
// $finalData[] = $this->createTotalRow('all', $grandTotals, $avgPercent, true, $grandAvgBedDays);
// }
return [
'data' => $finalData,
'totalsByType' => $totalsByType,
'grandTotals' => $grandTotals
];
}
/**
* Создать строку итогов
*/
private function createTotalRow(string $type, array $total, int $avgPercent, bool $isGrandTotal, float $avgBedDays = 0): array
{
$row = [
'isTotalRow' => !$isGrandTotal,
'isGrandTotal' => $isGrandTotal,
'department' => $isGrandTotal
? 'ОБЩИЕ ИТОГИ:'
: 'ИТОГО:',
'beds' => '—',//$total['beds_sum'],
'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' => '—',//$avgPercent,
'surgical' => [
'plan' => $total['plan_surgical_sum'],
'emergency' => $total['emergency_surgical_sum']
],
'deceased' => $total['deceased_sum'],
'averageBedDays' => '—',
'type' => $type,
'departments_count' => $total['departments_count'],
'countStaff' => $total['staff_sum'],
'countObservable' => $total['observable_sum'],
'countUnwanted' => $total['unwanted_sum'],
'isBold' => true
];
if ($isGrandTotal) {
$row['backgroundColor'] = '#f0f8ff';
}
return $row;
}
/**
* Обновить общие итоги
*/
private function updateGrandTotals(array &$grandTotals, array $typeTotal): void
{
foreach ($grandTotals as $key => &$value) {
if (isset($typeTotal[$key])) {
$value += $typeTotal[$key];
}
}
}
/**
* Очистить кэш статистики при создании отчета
*/
public function clearStatisticsCache(User $user, ?string $date = null): void
{
// Очищаем кэш по тегам
Cache::tags([
'statistics',
'reports',
'department_' . $user->rf_department_id
])->flush();
\Log::info("Statistics cache cleared for department: " . $user->rf_department_id);
}
/**
* Очистить кэш статистики для всех пользователей отдела
*/
public function clearDepartmentStatisticsCache(int $departmentId): void
{
Cache::tags([
'statistics',
'reports',
'department_' . $departmentId
])->flush();
\Log::info("Statistics cache cleared for entire department: " . $departmentId);
}
}