'Планово', 2 => 'Экстренно']; /** @var array */ private array $sources; public function __construct() { $this->sources = [ 'duty_metrics' => $this->dutyMetricsSource(), 'duty_patients' => $this->dutyPatientsSource(), 'nurse_patients' => $this->nursePatientsSource(), 'unwanted_events' => $this->unwantedEventsSource(), 'observable_patients' => $this->observablePatientsSource(), ]; } /** @return array */ public function all(): array { return $this->sources; } public function get(string $key): ReportSource { if (! isset($this->sources[$key])) { throw new InvalidArgumentException("Неизвестный источник отчёта: {$key}"); } return $this->sources[$key]; } /** * Сданные дежурные отчёты отделения, пересекающиеся с периодом. */ public function dutyReports(Department $department, DateRange $dateRange): Collection { return ReportDuty::where('rf_department_id', $department->department_id) ->withinPeriod($dateRange->startSql(), $dateRange->endSql()) ->onlySubmitted() ->orderBy('period_end') ->get(); } /** * Сданные отчёты (журналы) медсестры отделения, пересекающиеся с периодом. */ public function nurseReports(Department $department, DateRange $dateRange): Collection { return ReportNurse::where('rf_department_id', $department->department_id) ->where('status_id', 2) ->where('period_end', '>=', $dateRange->startSql()) ->where('period_start', '<=', $dateRange->endSql()) ->orderBy('period_end') ->get(); } private function dutyMetricsSource(): ReportSource { $columns = [ 'period' => 'Период', 'beds' => 'Коек', 'recipient_plan' => 'Поступило плановых', 'recipient_emergency' => 'Поступило экстренных', 'discharged' => 'Выписано', 'transferred' => 'Переведено', 'deceased' => 'Умерло', 'occupancy_percent' => 'Занятость, %', 'avg_bed_days' => 'Ср. койко-день', 'lethality_percent' => 'Летальность, %', 'surgery_plan' => 'Операции плановые', 'surgery_emergency' => 'Операции экстренные', 'staff_count' => 'Мед. персонал', ]; $metricMap = [ 'beds' => MetrikaConfig::BEDS, 'recipient_plan' => MetrikaConfig::PLAN, 'recipient_emergency' => MetrikaConfig::EMERGENCY, 'discharged' => MetrikaConfig::DISCHARGED, 'transferred' => MetrikaConfig::TRANSFERRED, 'deceased' => MetrikaConfig::DECEASED, 'occupancy_percent' => MetrikaConfig::DEPARTMENT_LOADED, 'avg_bed_days' => MetrikaConfig::AVERAGE_BED_DAYS, 'lethality_percent' => MetrikaConfig::LETHALITY, 'surgery_plan' => MetrikaConfig::PLAN_SURGERY, 'surgery_emergency' => MetrikaConfig::EMERGENCY_SURGERY, 'staff_count' => MetrikaConfig::STAFF_COUNT, ]; $resolver = function (Department $department, DateRange $dateRange, array $columns) use ($metricMap) { $reports = $this->dutyReports($department, $dateRange); if ($reports->isEmpty()) { return []; } $values = DutyReportMetricResult::whereIn('rf_report_id', $reports->pluck('id')) ->get(['rf_report_id', 'rf_metrika_item_id', 'value']) ->groupBy('rf_report_id'); $rows = []; foreach ($reports as $report) { $byMetric = ($values->get($report->id) ?? collect())->pluck('value', 'rf_metrika_item_id'); $row = []; foreach ($columns as $key) { if ($key === 'period') { $row[$key] = $report->period_start?->format('d.m.Y H:i').' — '.$report->period_end?->format('d.m.Y H:i'); continue; } $metricId = $metricMap[$key] ?? null; $row[$key] = $metricId !== null ? ($byMetric->get($metricId) ?? 0) : null; } $rows[] = $row; } return $rows; }; return new ReportSource('duty_metrics', 'Показатели смены (дежурный врач)', $columns, [], $resolver); } private function patientColumns(): array { return [ 'full_name' => 'ФИО', 'birth_date' => 'Дата рождения', 'medical_card_number' => '№ карты', 'recipient_date' => 'Дата поступления', 'extract_date' => 'Дата выбытия', 'diagnosis_code' => 'Код диагноза', 'diagnosis_name' => 'Диагноз', 'urgency' => 'Срочность', 'outcome' => 'Исход', ]; } private function patientFilterableFields(): array { return [ 'urgency_id' => ['label' => 'Срочность', 'options' => self::URGENCY_OPTIONS], 'visit_result_id' => ['label' => 'Код исхода', 'options' => null], ]; } private function dutyPatientsSource(): ReportSource { $columns = $this->patientColumns(); $filterable = $this->patientFilterableFields(); $resolver = function (Department $department, DateRange $dateRange, array $columns, array $filters) { $reportIds = $this->dutyReports($department, $dateRange)->pluck('id'); if ($reportIds->isEmpty()) { return []; } $query = ReportDutyPatient::whereIn('report_duty_id', $reportIds)->with('latestMigration'); $this->applyEqualityFilters($query, $filters, ['urgency_id', 'visit_result_id']); return $query->get()->map(fn ($patient) => $this->mapPatientRow($patient, $columns))->all(); }; return new ReportSource('duty_patients', 'Пациенты (дежурный врач)', $columns, $filterable, $resolver); } private function nursePatientsSource(): ReportSource { $columns = $this->patientColumns(); $filterable = $this->patientFilterableFields(); $resolver = function (Department $department, DateRange $dateRange, array $columns, array $filters) { $reportIds = $this->nurseReports($department, $dateRange)->pluck('id'); if ($reportIds->isEmpty()) { return []; } $query = ReportNursePatient::whereIn('report_nurse_id', $reportIds)->with('latestMigration'); $this->applyEqualityFilters($query, $filters, ['urgency_id', 'visit_result_id']); return $query->get()->map(fn ($patient) => $this->mapPatientRow($patient, $columns))->all(); }; return new ReportSource('nurse_patients', 'Журнал пациентов (старшая медсестра)', $columns, $filterable, $resolver); } private function unwantedEventsSource(): ReportSource { $columns = [ 'title' => 'Событие', 'comment' => 'Комментарий', 'created_at' => 'Дата', ]; $resolver = function (Department $department, DateRange $dateRange) { $reportIds = $this->dutyReports($department, $dateRange)->pluck('id'); if ($reportIds->isEmpty()) { return []; } return DutyUnwantedEvent::whereIn('report_duty_id', $reportIds)->get() ->map(fn ($event) => [ 'title' => $event->title, 'comment' => $event->comment, 'created_at' => $event->created_at?->format('d.m.Y H:i'), ])->all(); }; return new ReportSource('unwanted_events', 'Нежелательные события', $columns, [], $resolver); } private function observablePatientsSource(): ReportSource { $columns = [ 'full_name' => 'ФИО', 'birth_date' => 'Дата рождения', 'observable_in' => 'Начало наблюдения', 'observable_out' => 'Конец наблюдения', 'observable_reason' => 'Причина', ]; $resolver = function (Department $department, DateRange $dateRange) { $reportIds = $this->dutyReports($department, $dateRange)->pluck('id'); if ($reportIds->isEmpty()) { return []; } return DB::table('observable_medical_histories as omh') ->join('report_duty_patients as rdp', 'rdp.original_id', '=', 'omh.original_id') ->whereIn('rdp.report_duty_id', $reportIds) ->where('omh.observable_in', '>=', $dateRange->startSql()) ->where('omh.observable_in', '<=', $dateRange->endSql()) ->select('omh.full_name', 'omh.birth_date', 'omh.observable_in', 'omh.observable_out', 'omh.observable_reason') ->distinct() ->get() ->map(fn ($row) => [ 'full_name' => $row->full_name, 'birth_date' => $this->formatDate($row->birth_date, 'd.m.Y'), 'observable_in' => $this->formatDate($row->observable_in, 'd.m.Y H:i'), 'observable_out' => $this->formatDate($row->observable_out, 'd.m.Y H:i'), 'observable_reason' => $row->observable_reason, ])->all(); }; return new ReportSource('observable_patients', 'Пациенты на контроле', $columns, [], $resolver); } private function mapPatientRow(ReportDutyPatient|ReportNursePatient $patient, array $columns): array { $migration = $patient->latestMigration; $row = []; foreach ($columns as $key) { $row[$key] = match ($key) { 'full_name' => $patient->full_name, 'birth_date' => $patient->birth_date?->format('d.m.Y'), 'medical_card_number' => $patient->medical_card_number, 'recipient_date' => $patient->recipient_date?->format('d.m.Y H:i'), 'extract_date' => $patient->extract_date?->format('d.m.Y H:i'), 'diagnosis_code' => $migration?->diagnosis_code, 'diagnosis_name' => $migration?->diagnosis_name, 'urgency' => self::URGENCY_OPTIONS[(int) $patient->urgency_id] ?? '—', 'outcome' => $this->outcomeLabel($patient), default => null, }; } return $row; } private function outcomeLabel(ReportDutyPatient|ReportNursePatient $patient): string { if ($patient->death_date) { return 'Умер'; } if (in_array((int) $patient->visit_result_id, [4, 14], true)) { return 'Переведён'; } if ($patient->extract_date) { return 'Выписан'; } return 'В отделении'; } /** * @param array $filters * @param array $allowedFields allow-list полей источника — защита от произвольных имён колонок */ private function applyEqualityFilters(Builder $query, array $filters, array $allowedFields): void { foreach ($filters as $filter) { $field = $filter['field'] ?? null; $value = $filter['value'] ?? null; if ($field === null || $value === null || $value === '' || ! in_array($field, $allowedFields, true)) { continue; } $query->where($field, $value); } } private function formatDate(?string $value, string $format): ?string { return $value ? Carbon::parse($value)->format($format) : null; } }