From 63daa6288850ee799b06d8f0d83ca7352b4d1d6b Mon Sep 17 00:00:00 2001 From: brusnitsyn Date: Fri, 24 Apr 2026 16:46:10 +0900 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ClearStatisticsCache.php | 28 +- .../Commands/FillAverageBedDaysMetric.php | 18 +- app/Console/Commands/FillReportsFromDate.php | 6 +- .../RecalculatePreoperativeMetric.php | 20 +- app/Contracts/MetricCalculatorInterface.php | 1 + app/Contracts/PatientProviderInterface.php | 1 + app/Data/UnifiedPatientData.php | 19 +- app/Exports/StatisticsExport.php | 70 ++-- app/Factories/MetricCalculatorFactory.php | 9 +- app/Http/Controllers/Api/AuthController.php | 58 ++-- .../Controllers/Api/MetrikaFormController.php | 111 ++++--- .../Controllers/Api/OperationController.php | 2 +- app/Http/Controllers/Api/ReportController.php | 304 ++++++++++++------ app/Http/Controllers/Api/RoleController.php | 2 +- .../Controllers/Api/StatisticController.php | 2 +- app/Http/Controllers/AuthController.php | 29 +- app/Http/Controllers/TestController.php | 8 +- .../Controllers/Web/Admin/AdminController.php | 1 - app/Http/Controllers/Web/IndexController.php | 5 +- app/Http/Controllers/Web/ReportController.php | 150 +++++++++ .../Controllers/Web/StatisticController.php | 19 +- app/Http/Middleware/Authenticate.php | 2 - app/Http/Middleware/HandleInertiaRequests.php | 7 +- .../DepartmentPatientOperationResource.php | 2 +- .../Mis/FormattedPatientResource.php | 8 +- app/Models/Department.php | 2 +- app/Models/DepartmentMetrikaDefault.php | 3 +- app/Models/DepartmentType.php | 3 +- app/Models/LifeMisMigrationPatient.php | 24 +- app/Models/MedicalHistorySnapshot.php | 3 +- app/Models/MetrikaForm.php | 10 +- app/Models/MetrikaGroup.php | 1 + app/Models/MetrikaGroupItem.php | 1 + app/Models/MetrikaItem.php | 1 + app/Models/MetrikaResult.php | 19 +- app/Models/MetrikaResultValue.php | 1 + app/Models/MisBed.php | 6 +- app/Models/MisBedAction.php | 9 +- app/Models/MisDiagnos.php | 1 + app/Models/MisDocPrvd.php | 1 + app/Models/MisLpuDoctor.php | 1 + app/Models/MisMKB.php | 3 +- app/Models/MisMedicalHistory.php | 20 +- app/Models/MisMigrationPatient.php | 26 +- app/Models/MisOperationPurpose.php | 2 +- app/Models/MisServiceMedical.php | 1 + app/Models/MisStationarBranch.php | 1 + app/Models/MisSurgicalOperation.php | 12 +- app/Models/ObservationPatient.php | 3 +- app/Models/Report.php | 7 +- app/Models/User.php | 15 +- app/Models/UserDepartment.php | 2 +- app/Services/AutoReportService.php | 4 +- app/Services/Base/BaseMetricService.php | 11 +- app/Services/BedDayService.php | 45 +-- app/Services/Cache/CacheKeyBuilder.php | 2 +- app/Services/CurrentPatientService.php | 5 +- app/Services/DateRange.php | 2 +- app/Services/DateRangeService.php | 10 +- .../AverageBedDaysCalculator.php | 5 +- .../MetricCalculators/LethalityCalculator.php | 10 +- .../PreoperativeDaysCalculator.php | 7 +- app/Services/MetrikaService.php | 10 +- app/Services/MisPatientService.php | 23 +- app/Services/PatientMigrationService.php | 42 +-- app/Services/PatientService.php | 100 ++++-- app/Services/RecipientPatientService.php | 8 +- app/Services/ReportService.php | 270 ++++++++++------ app/Services/SnapshotService.php | 14 +- app/Services/StationarBranchService.php | 1 - app/Services/StatisticsService.php | 84 ++--- app/Services/UnifiedPatientService.php | 15 +- config/app.php | 2 +- config/excel.php | 126 ++++---- config/time.php | 2 +- ...55140_create_metrika_group_items_table.php | 2 +- database/seeders/DatabaseSeeder.php | 1 - database/seeders/TestDepartmentDataSeeder.php | 91 +++--- database/seeders/TestMetrikaSeeder.php | 21 +- database/seeders/TestUserSeeder.php | 5 +- .../Pages/Report/Components/ReportHeader.vue | 16 + .../Report/Components/ReportSectionItem.vue | 121 ++++++- resources/js/Stores/report.js | 12 + routes/api.php | 2 + routes/web.php | 4 +- tests/Feature/AutoFillReportsTest.php | 17 +- tests/Feature/ReportPatientsServicesTest.php | 21 +- 87 files changed, 1380 insertions(+), 791 deletions(-) diff --git a/app/Console/Commands/ClearStatisticsCache.php b/app/Console/Commands/ClearStatisticsCache.php index 19545df..4c6cb13 100644 --- a/app/Console/Commands/ClearStatisticsCache.php +++ b/app/Console/Commands/ClearStatisticsCache.php @@ -33,6 +33,7 @@ class ClearStatisticsCache extends Command // Очищаем весь кэш статистики $this->clearAllStatisticsCache(); $this->info('✅ Весь кэш статистики очищен'); + return 0; } @@ -40,6 +41,7 @@ class ClearStatisticsCache extends Command // Очищаем кэш для конкретного отделения $this->clearDepartmentCache($departmentId); $this->info("✅ Кэш статистики для отдела {$departmentId} очищен"); + return 0; } @@ -47,11 +49,13 @@ class ClearStatisticsCache extends Command // Очищаем кэш за конкретную дату $this->clearDateCache($date); $this->info("✅ Кэш статистики за {$date} очищен"); + return 0; } // Если опции не указаны, показываем помощь $this->showHelp(); + return 1; } @@ -69,7 +73,7 @@ class ClearStatisticsCache extends Command $this->clearStatisticsKeys(); } - $this->info("Очищено: весь кэш статистики"); + $this->info('Очищено: весь кэш статистики'); } /** @@ -78,14 +82,14 @@ class ClearStatisticsCache extends Command private function clearDepartmentCache(int $departmentId): void { if (method_exists(Cache::store(), 'tags')) { - Cache::tags(['statistics', 'department_' . $departmentId])->flush(); + Cache::tags(['statistics', 'department_'.$departmentId])->flush(); } else { // Ищем и удаляем ключи для отдела - $keys = Cache::get('statistics_keys_' . $departmentId, []); + $keys = Cache::get('statistics_keys_'.$departmentId, []); foreach ($keys as $key) { Cache::forget($key); } - Cache::forget('statistics_keys_' . $departmentId); + Cache::forget('statistics_keys_'.$departmentId); } $this->info("Очищено: кэш для отдела {$departmentId}"); @@ -97,7 +101,7 @@ class ClearStatisticsCache extends Command private function clearDateCache(string $date): void { if (method_exists(Cache::store(), 'tags')) { - Cache::tags(['statistics', 'date_' . $date])->flush(); + Cache::tags(['statistics', 'date_'.$date])->flush(); } else { // Ищем ключи с этой датой $prefix = config('cache.prefix', 'laravel'); @@ -119,9 +123,9 @@ class ClearStatisticsCache extends Command $redis = $store->getRedis(); $keys = $redis->keys($pattern); - if (!empty($keys)) { + if (! empty($keys)) { $redis->del($keys); - $this->info("Удалено ключей: " . count($keys)); + $this->info('Удалено ключей: '.count($keys)); } } } @@ -139,9 +143,9 @@ class ClearStatisticsCache extends Command $redis = $store->getRedis(); $keys = $redis->keys("{$prefix}:statistics:*"); - if (!empty($keys)) { + if (! empty($keys)) { $redis->del($keys); - $this->info("Удалено ключей статистики: " . count($keys)); + $this->info('Удалено ключей статистики: '.count($keys)); } } // Для файлового кэша @@ -156,11 +160,11 @@ class ClearStatisticsCache extends Command */ private function clearFileCache(string $directory, string $pattern): void { - if (!is_dir($directory)) { + if (! is_dir($directory)) { return; } - $files = glob($directory . '/*'); + $files = glob($directory.'/*'); $deleted = 0; foreach ($files as $file) { @@ -173,7 +177,7 @@ class ClearStatisticsCache extends Command } } - $this->info("Удалено файлов кэша: " . $deleted); + $this->info('Удалено файлов кэша: '.$deleted); } /** diff --git a/app/Console/Commands/FillAverageBedDaysMetric.php b/app/Console/Commands/FillAverageBedDaysMetric.php index 2396c4b..65e05cb 100644 --- a/app/Console/Commands/FillAverageBedDaysMetric.php +++ b/app/Console/Commands/FillAverageBedDaysMetric.php @@ -1,4 +1,5 @@ warn('No reports found matching criteria.'); + return 0; } $this->info("Found {$totalReports} reports to process"); // Confirm if large number - if ($totalReports > 1000 && !$this->confirm("Processing {$totalReports} reports may take a while. Continue?")) { + if ($totalReports > 1000 && ! $this->confirm("Processing {$totalReports} reports may take a while. Continue?")) { $this->info('Command cancelled.'); + return 0; } $force = $this->option('force'); - $chunkSize = (int)$this->option('chunk'); + $chunkSize = (int) $this->option('chunk'); // Progress bar $bar = $this->output->createProgressBar($totalReports); @@ -110,7 +112,7 @@ class FillAverageBedDaysMetric extends Command } catch (\Exception $e) { $errors++; - $this->warn("\nError processing report {$report->report_id}: " . $e->getMessage()); + $this->warn("\nError processing report {$report->report_id}: ".$e->getMessage()); } $bar->advance(); @@ -139,7 +141,7 @@ class FillAverageBedDaysMetric extends Command $this->info('Sample of updated reports:'); // Get a sample of recently updated reports - $sampleQuery = Report::whereHas('metrikaResults', function($q) { + $sampleQuery = Report::whereHas('metrikaResults', function ($q) { $q->where('rf_metrika_item_id', 18); }) ->orderBy('report_id', 'desc') @@ -156,10 +158,11 @@ class FillAverageBedDaysMetric extends Command $sampleQuery->where('rf_department_id', $departmentId); } - $sample = $sampleQuery->get()->map(function($report) { + $sample = $sampleQuery->get()->map(function ($report) { $metric = $report->metrikaResults ->where('rf_metrika_item_id', 18) ->first(); + return [ 'report_id' => $report->report_id, 'department' => $report->rf_department_id, @@ -189,7 +192,7 @@ class FillAverageBedDaysMetric extends Command ->where('rf_metrika_item_id', 18) ->first(); - if ($existingMetric && !$force) { + if ($existingMetric && ! $force) { return 'skipped'; } @@ -207,6 +210,7 @@ class FillAverageBedDaysMetric extends Command ], ['value' => 0] ); + return 'no_snapshots'; } diff --git a/app/Console/Commands/FillReportsFromDate.php b/app/Console/Commands/FillReportsFromDate.php index ad27b0e..fae9119 100644 --- a/app/Console/Commands/FillReportsFromDate.php +++ b/app/Console/Commands/FillReportsFromDate.php @@ -38,11 +38,13 @@ class FillReportsFromDate extends Command $end = Carbon::createFromFormat('Y-m-d', $endDate, 'Asia/Yakutsk')->startOfDay(); } catch (\Throwable) { $this->error('Неверный формат даты. Используйте YYYY-MM-DD.'); + return 1; } if ($start->gt($end)) { $this->error('Дата начала больше даты окончания.'); + return 1; } @@ -55,6 +57,7 @@ class FillReportsFromDate extends Command if ($departments->isEmpty()) { $this->error('Отделения не найдены'); + return 1; } @@ -66,8 +69,9 @@ class FillReportsFromDate extends Command $user = $this->resolveResponsibleUser($department, $userId); - if (!$user) { + if (! $user) { $this->warn("В отделении {$department->name_short} нет подходящего пользователя для автозаполнения"); + continue; } diff --git a/app/Console/Commands/RecalculatePreoperativeMetric.php b/app/Console/Commands/RecalculatePreoperativeMetric.php index 08df419..49396d8 100644 --- a/app/Console/Commands/RecalculatePreoperativeMetric.php +++ b/app/Console/Commands/RecalculatePreoperativeMetric.php @@ -1,11 +1,12 @@ option('force'); - $chunkSize = (int)$this->option('chunk'); + $chunkSize = (int) $this->option('chunk'); $totalReports = $query->count(); if ($totalReports === 0) { $this->warn('❌ Отчеты не найдены'); + return 0; } $this->info("📊 Найдено отчетов: {$totalReports}"); - if ($totalReports > 1000 && !$this->confirm("⚠️ Обработка {$totalReports} отчетов может занять время. Продолжить?")) { + if ($totalReports > 1000 && ! $this->confirm("⚠️ Обработка {$totalReports} отчетов может занять время. Продолжить?")) { $this->info('❌ Операция отменена'); + return 0; } @@ -104,7 +107,7 @@ class RecalculatePreoperativeMetric extends Command } catch (\Exception $e) { $errors++; - Log::error("Ошибка обработки отчета {$report->report_id}: " . $e->getMessage()); + Log::error("Ошибка обработки отчета {$report->report_id}: ".$e->getMessage()); } $bar->advance(); @@ -131,16 +134,17 @@ class RecalculatePreoperativeMetric extends Command $this->newLine(); $this->info('📋 Примеры обновленных отчетов:'); - $samples = Report::whereHas('metrikaResults', function($q) { + $samples = Report::whereHas('metrikaResults', function ($q) { $q->where('rf_metrika_item_id', 21); }) ->orderBy('report_id', 'desc') ->limit(5) ->get() - ->map(function($report) { + ->map(function ($report) { $metric = $report->metrikaResults ->where('rf_metrika_item_id', 21) ->first(); + return [ 'report_id' => $report->report_id, 'department' => $report->rf_department_id, @@ -168,7 +172,7 @@ class RecalculatePreoperativeMetric extends Command ->where('rf_metrika_item_id', 21) ->first(); - if ($existing && !$force) { + if ($existing && ! $force) { return 'skipped'; } @@ -186,6 +190,7 @@ class RecalculatePreoperativeMetric extends Command ], ['value' => 0] ); + return 'no_patients'; } @@ -212,6 +217,7 @@ class RecalculatePreoperativeMetric extends Command ], ['value' => 0] ); + return 'no_patients'; } diff --git a/app/Contracts/MetricCalculatorInterface.php b/app/Contracts/MetricCalculatorInterface.php index fa0dd3e..3bf3662 100644 --- a/app/Contracts/MetricCalculatorInterface.php +++ b/app/Contracts/MetricCalculatorInterface.php @@ -5,5 +5,6 @@ namespace App\Contracts; interface MetricCalculatorInterface { public function calculate(array $departmentIds, string $startDate, string $endDate): array; + public function getMetricId(): int; } diff --git a/app/Contracts/PatientProviderInterface.php b/app/Contracts/PatientProviderInterface.php index e57590c..414d0de 100644 --- a/app/Contracts/PatientProviderInterface.php +++ b/app/Contracts/PatientProviderInterface.php @@ -8,5 +8,6 @@ use Illuminate\Support\Collection; interface PatientProviderInterface { public function getPatients(string $type, int $branchId, DateRange $dateRange, array $options = []): Collection; + public function getCount(string $type, int $branchId, DateRange $dateRange, array $options = []): int; } diff --git a/app/Data/UnifiedPatientData.php b/app/Data/UnifiedPatientData.php index d5e9f49..7104eba 100644 --- a/app/Data/UnifiedPatientData.php +++ b/app/Data/UnifiedPatientData.php @@ -28,6 +28,7 @@ class UnifiedPatientData public ?string $outcomeType = null, public ?string $outcomeDate = null, public ?string $comment = null, + public ?bool $reanimationIsComplete = null, public bool $isRecipientToday = false, public bool $isManual = false, public bool $canManageManual = false, @@ -49,7 +50,7 @@ class UnifiedPatientData $migration = $patient->relationLoaded('migrations') ? $patient->migrations->first() : null; - if (!$migration && $patient->relationLoaded('latestMigration')) { + if (! $migration && $patient->relationLoaded('latestMigration')) { $migration = $patient->latestMigration; } $diagnosisMkb = $outcomeMigration?->mainDiagnosis?->mkb ?? $migration?->mainDiagnosis?->mkb; @@ -79,11 +80,13 @@ class UnifiedPatientData outcomeType: $patient->outcome_type ?? $linkedManualPatient?->outcome_type, outcomeDate: $patient->outcome_date ?? $linkedManualPatient?->outcome_at?->toIso8601String(), comment: $comment, + reanimationIsComplete: isset($patient->reanimation_is_complete) ? (bool) $patient->reanimation_is_complete : null, isRecipientToday: $isRecipientToday, isManual: (bool) $linkedManualPatient, canManageManual: (bool) $linkedManualPatient, ); } + public static function fromMisMigrationPatient( MisMigrationPatient $migration, bool $isRecipientToday = false, @@ -123,6 +126,7 @@ class UnifiedPatientData outcomeType: $migration->outcome_type ?? $linkedManualPatient?->outcome_type, outcomeDate: $migration->outcome_date ?? $linkedManualPatient?->outcome_at?->toIso8601String(), comment: $comment, + reanimationIsComplete: isset($medicalHistory->reanimation_is_complete) ? (bool) $medicalHistory->reanimation_is_complete : null, isRecipientToday: $isRecipientToday, isManual: (bool) $linkedManualPatient, canManageManual: (bool) $linkedManualPatient, @@ -154,6 +158,7 @@ class UnifiedPatientData outcomeType: $patient->outcome_type, outcomeDate: $patient->outcome_at?->toIso8601String(), comment: $comment, + reanimationIsComplete: null, isRecipientToday: $isRecipientToday, isManual: true, canManageManual: true, @@ -164,12 +169,11 @@ class UnifiedPatientData MedicalHistorySnapshot $snapshot, bool $isRecipientToday = false, ?array $operations = null - ): self - { + ): self { $birthDate = self::normalizeDate($snapshot->birth_date); -// if ($snapshot->rf_medicalhistory_id === 334148) -// dd($snapshot); + // if ($snapshot->rf_medicalhistory_id === 334148) + // dd($snapshot); return new self( id: $snapshot->rf_department_patient_id ? "manual:{$snapshot->rf_department_patient_id}" : ($snapshot->patient_uid ?: "mis:{$snapshot->rf_medicalhistory_id}"), @@ -190,6 +194,7 @@ class UnifiedPatientData outcomeType: $snapshot->outcome_type, outcomeDate: self::normalizeDateTime($snapshot->outcome_at), comment: null, + reanimationIsComplete: null, isRecipientToday: $isRecipientToday, isManual: (bool) $snapshot->is_manual, canManageManual: false, @@ -232,7 +237,7 @@ class UnifiedPatientData private static function normalizeDateTime($value): ?string { - if (!$value) { + if (! $value) { return null; } @@ -245,7 +250,7 @@ class UnifiedPatientData private static function normalizeDate($value): ?string { - if (!$value) { + if (! $value) { return null; } diff --git a/app/Exports/StatisticsExport.php b/app/Exports/StatisticsExport.php index 5a88b8a..8da7033 100644 --- a/app/Exports/StatisticsExport.php +++ b/app/Exports/StatisticsExport.php @@ -7,16 +7,18 @@ use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\WithHeadings; use Maatwebsite\Excel\Concerns\WithMapping; use Maatwebsite\Excel\Concerns\WithStyles; -use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; -use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Fill; +use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class StatisticsExport implements FromCollection, WithHeadings, WithMapping, WithStyles { protected array $data; + protected array $dateRange; + protected string $reportName; public function __construct(array $data, array $dateRange, string $reportName = 'Статистика по отделениям') @@ -42,7 +44,7 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit return [ // Шапка отчета (первые 3 строки) [$this->reportName], - ['Дата создания: ' . now()->format('d.m.Y H:i:s')], + ['Дата создания: '.now()->format('d.m.Y H:i:s')], [$this->formatDateRange()], [], // Пустая строка для отступа @@ -61,7 +63,7 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit 'Операции', // Будет объединено с 2 колонками '', // Пустые для заполнения 'Умерло', - 'Мед. персонал' + 'Мед. персонал', ], // Второй уровень заголовков (детализация) @@ -81,8 +83,8 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit 'Э', 'П', '', - '' - ] + '', + ], ]; } @@ -94,8 +96,10 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit if (isset($this->dateRange[0]) && isset($this->dateRange[1])) { $startAt = Carbon::create($this->dateRange[0])->format('d.m.Y H:i'); $endAt = Carbon::create($this->dateRange[1])->format('d.m.Y H:i'); - return 'Период: ' . $startAt . ' - ' . $endAt; + + return 'Период: '.$startAt.' - '.$endAt; } + return 'Период: За весь период'; } @@ -108,7 +112,7 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit if (isset($row['isGroupHeader']) && $row['isGroupHeader']) { return [ $row['groupName'], // Название группы - '', '', '', '', '', '', '', '', '', '', '', '', '' + '', '', '', '', '', '', '', '', '', '', '', '', '', ]; } @@ -130,7 +134,7 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit $this->formatZero($row['surgical']['emergency'] ?? 0), $this->formatZero($row['surgical']['plan'] ?? 0), $this->formatZero($row['deceased'] ?? 0), - $this->formatZero($row['countStaff'] ?? 0) + $this->formatZero($row['countStaff'] ?? 0), ]; } @@ -151,7 +155,7 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit $this->formatZero($row['surgical']['emergency'] ?? 0), $this->formatZero($row['surgical']['plan'] ?? 0), $this->formatZero($row['deceased'] ?? 0), - $this->formatZero($row['countStaff'] ?? 0) + $this->formatZero($row['countStaff'] ?? 0), ]; } @@ -173,15 +177,15 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit $sheet->getPageSetup()->setFitToWidth(1); // 1 страница в ширину $sheet->getPageSetup()->setFitToHeight(1); // 1 страница в высоту (опционально) -// $sheet->getPageSetup()->setScale(90); + // $sheet->getPageSetup()->setScale(90); $highestRow = $sheet->getHighestRow(); $highestColumn = $sheet->getHighestColumn(); // ОБЪЕДИНЕНИЕ ЯЧЕЕК ДЛЯ ШАПКИ (строки 1-3) - $sheet->mergeCells('A1:' . $highestColumn . '1'); // Наименование - $sheet->mergeCells('A2:' . $highestColumn . '2'); // Дата создания - $sheet->mergeCells('A3:' . $highestColumn . '3'); // Временной интервал + $sheet->mergeCells('A1:'.$highestColumn.'1'); // Наименование + $sheet->mergeCells('A2:'.$highestColumn.'2'); // Дата создания + $sheet->mergeCells('A3:'.$highestColumn.'3'); // Временной интервал // ОБЪЕДИНЕНИЕ ДЛЯ ОБЫЧНЫХ ЗАГОЛОВКОВ $sheet->mergeCells('A5:A6'); @@ -217,7 +221,7 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit ]); // СТИЛИ ДЛЯ ЗАГОЛОВКОВ (строки 5 и 6) - $sheet->getStyle('A5:' . $highestColumn . '6')->applyFromArray([ + $sheet->getStyle('A5:'.$highestColumn.'6')->applyFromArray([ 'font' => [ 'bold' => true, 'color' => ['argb' => '000000'], @@ -250,13 +254,12 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit // СТИЛИ ДЛЯ ВСЕХ СТРОК С ДАННЫМИ $indexData = 0; for ($row = 7; $row <= $highestRow; $row++) { - $cellValue = $sheet->getCell('A' . $row)->getValue(); + $cellValue = $sheet->getCell('A'.$row)->getValue(); $currentRowInData = $this->data[$indexData]; // Заголовки групп (Хирургические отделения, Терапевтические отделения) - if (array_key_exists('isGroupHeader', $currentRowInData)) - { - $sheet->getStyle('A' . $row . ':' . $highestColumn . $row)->applyFromArray([ + if (array_key_exists('isGroupHeader', $currentRowInData)) { + $sheet->getStyle('A'.$row.':'.$highestColumn.$row)->applyFromArray([ 'font' => [ 'bold' => true, 'size' => 11, @@ -268,12 +271,12 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit ]); // Объединяем ячейки для группы - $sheet->mergeCells('A' . $row . ':' . $highestColumn . $row); + $sheet->mergeCells('A'.$row.':'.$highestColumn.$row); } // Итоговые строки (ИТОГО:) if (array_key_exists('isTotalRow', $currentRowInData)) { - $sheet->getStyle('A' . $row . ':' . $highestColumn . $row)->applyFromArray([ + $sheet->getStyle('A'.$row.':'.$highestColumn.$row)->applyFromArray([ 'font' => [ 'bold' => true, ], @@ -290,12 +293,13 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit ]); } - if ($indexData < count($this->data)) + if ($indexData < count($this->data)) { $indexData++; + } } // ГРАНИЦЫ ДЛЯ ВСЕЙ ТАБЛИЦЫ - $sheet->getStyle('A5:' . $highestColumn . $highestRow)->applyFromArray([ + $sheet->getStyle('A5:'.$highestColumn.$highestRow)->applyFromArray([ 'borders' => [ 'allBorders' => [ 'borderStyle' => Border::BORDER_THIN, @@ -305,14 +309,14 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit ]); // ВЫРАВНИВАНИЕ ДЛЯ ЧИСЛОВЫХ КОЛОНОК - $sheet->getStyle('B7:' . $highestColumn . $highestRow)->applyFromArray([ + $sheet->getStyle('B7:'.$highestColumn.$highestRow)->applyFromArray([ 'alignment' => [ 'horizontal' => Alignment::HORIZONTAL_CENTER, ], ]); // ВЫРАВНИВАНИЕ ДЛЯ НАЗВАНИЙ ОТДЕЛЕНИЙ - $sheet->getStyle('A7:A' . $highestRow)->applyFromArray([ + $sheet->getStyle('A7:A'.$highestRow)->applyFromArray([ 'alignment' => [ 'horizontal' => Alignment::HORIZONTAL_LEFT, 'vertical' => Alignment::VERTICAL_CENTER, @@ -342,7 +346,7 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit $sheet->getRowDimension(6)->setRowHeight(25); // Добавляем примечание внизу -// $this->addFootnote($sheet, $highestRow, $highestColumn); + // $this->addFootnote($sheet, $highestRow, $highestColumn); return []; } @@ -378,16 +382,16 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit { $footnoteRow = $startRow + 2; // Отступаем 2 строки от таблицы - $sheet->setCellValue('A' . $footnoteRow, 'ПРИМЕЧАНИЕ:'); + $sheet->setCellValue('A'.$footnoteRow, 'ПРИМЕЧАНИЕ:'); - $sheet->setCellValue('B' . $footnoteRow, '• % загруженности актуален на дату формирования отчёта (' . now()->format('d.m.Y') . ')'); - $sheet->mergeCells('B' . $footnoteRow . ':' . $highestColumn . $footnoteRow); + $sheet->setCellValue('B'.$footnoteRow, '• % загруженности актуален на дату формирования отчёта ('.now()->format('d.m.Y').')'); + $sheet->mergeCells('B'.$footnoteRow.':'.$highestColumn.$footnoteRow); - $sheet->setCellValue('B' . ($footnoteRow + 1), '• Поступление, выбытие, операции — за указанный в шапке период'); - $sheet->mergeCells('B' . ($footnoteRow + 1) . ':' . $highestColumn . ($footnoteRow + 1)); + $sheet->setCellValue('B'.($footnoteRow + 1), '• Поступление, выбытие, операции — за указанный в шапке период'); + $sheet->mergeCells('B'.($footnoteRow + 1).':'.$highestColumn.($footnoteRow + 1)); // Стили для примечания - $sheet->getStyle('A' . $footnoteRow . ':' . $highestColumn . ($footnoteRow + 1))->applyFromArray([ + $sheet->getStyle('A'.$footnoteRow.':'.$highestColumn.($footnoteRow + 1))->applyFromArray([ 'font' => [ 'italic' => true, 'size' => 9, @@ -399,7 +403,7 @@ class StatisticsExport implements FromCollection, WithHeadings, WithMapping, Wit ]); // Жирный шрифт для слова "ПРИМЕЧАНИЕ" - $sheet->getStyle('A' . $footnoteRow)->applyFromArray([ + $sheet->getStyle('A'.$footnoteRow)->applyFromArray([ 'font' => [ 'bold' => true, 'color' => ['argb' => '000000'], diff --git a/app/Factories/MetricCalculatorFactory.php b/app/Factories/MetricCalculatorFactory.php index 7324915..c40a0d9 100644 --- a/app/Factories/MetricCalculatorFactory.php +++ b/app/Factories/MetricCalculatorFactory.php @@ -1,12 +1,13 @@ calculators[$metricId])) { + if (! isset($this->calculators[$metricId])) { throw new RuntimeException("No calculator for metric ID: {$metricId}"); } $class = $this->calculators[$metricId]; + return app($class); } @@ -38,6 +40,7 @@ class MetricCalculatorFactory foreach (array_keys($this->calculators) as $metricId) { $calculators[$metricId] = $this->getCalculator($metricId); } + return $calculators; } } diff --git a/app/Http/Controllers/Api/AuthController.php b/app/Http/Controllers/Api/AuthController.php index 326e3ea..43c5b94 100644 --- a/app/Http/Controllers/Api/AuthController.php +++ b/app/Http/Controllers/Api/AuthController.php @@ -23,21 +23,21 @@ class AuthController extends Controller 'department' => 'required|string', 'position' => 'required|string', 'phone' => 'nullable|string', - 'role' => 'required|in:doctor,nurse,head_of_department,statistician,admin' + 'role' => 'required|in:doctor,nurse,head_of_department,statistician,admin', ]); if ($validator->fails()) { return response()->json([ 'success' => false, - 'errors' => $validator->errors() + 'errors' => $validator->errors(), ], 422); } // Проверка прав на создание пользователя - if (Auth::check() && !Auth::user()->isAdmin()) { + if (Auth::check() && ! Auth::user()->isAdmin()) { return response()->json([ 'success' => false, - 'message' => 'Недостаточно прав для создания пользователя' + 'message' => 'Недостаточно прав для создания пользователя', ], 403); } @@ -49,7 +49,7 @@ class AuthController extends Controller 'position' => $request->position, 'phone' => $request->phone, 'role' => $request->role, - 'is_active' => true + 'is_active' => true, ]); $token = $user->createToken('auth_token')->plainTextToken; @@ -59,7 +59,7 @@ class AuthController extends Controller 'message' => 'Пользователь успешно зарегистрирован', 'user' => $user->only(['id', 'name', 'login', 'department', 'position', 'role']), 'token' => $token, - 'permissions' => $user->permissions() + 'permissions' => $user->permissions(), ], 201); } @@ -67,9 +67,9 @@ class AuthController extends Controller public function login(Request $request) { // Проверяем, что пользователь уже авторизован через сессию - if (!Auth::check()) { + if (! Auth::check()) { return response()->json([ - 'error' => 'Not authenticated' + 'error' => 'Not authenticated', ], 401); } @@ -87,7 +87,7 @@ class AuthController extends Controller 'user' => $user->only(['id', 'name', 'login']), 'token' => $token, 'permissions' => $user->permissions(), - 'available_departments' => $user->availableDepartments() + 'available_departments' => $user->availableDepartments(), ]); } @@ -102,7 +102,7 @@ class AuthController extends Controller return response()->json([ 'success' => true, - 'message' => 'Успешный выход' + 'message' => 'Успешный выход', ]); } @@ -111,10 +111,10 @@ class AuthController extends Controller { $user = $request->user(); - if (!$user) { + if (! $user) { return response()->json([ 'success' => false, - 'message' => 'Пользователь не авторизован' + 'message' => 'Пользователь не авторизован', ], 401); } @@ -122,7 +122,7 @@ class AuthController extends Controller 'success' => true, 'user' => $user->only(['id', 'name', 'login', 'department', 'position', 'role', 'phone']), 'permissions' => $user->permissions(), - 'available_departments' => $user->availableDepartments() + 'available_departments' => $user->availableDepartments(), ]); } @@ -133,26 +133,26 @@ class AuthController extends Controller $validator = Validator::make($request->all(), [ 'name' => 'sometimes|string|max:255', - 'login' => 'sometimes|unique:users,login,' . $user->id, + 'login' => 'sometimes|unique:users,login,'.$user->id, 'current_password' => 'required_with:password', 'password' => 'sometimes|min:8|confirmed', 'department' => 'sometimes|string', - 'phone' => 'sometimes|string' + 'phone' => 'sometimes|string', ]); if ($validator->fails()) { return response()->json([ 'success' => false, - 'errors' => $validator->errors() + 'errors' => $validator->errors(), ], 422); } // Проверка текущего пароля при смене пароля if ($request->has('password')) { - if (!Hash::check($request->current_password, $user->password)) { + if (! Hash::check($request->current_password, $user->password)) { return response()->json([ 'success' => false, - 'message' => 'Текущий пароль неверен' + 'message' => 'Текущий пароль неверен', ], 422); } $user->password = Hash::make($request->password); @@ -179,7 +179,7 @@ class AuthController extends Controller return response()->json([ 'success' => true, 'message' => 'Профиль обновлен', - 'user' => $user->only(['id', 'name', 'login', 'department', 'position', 'role', 'phone']) + 'user' => $user->only(['id', 'name', 'login', 'department', 'position', 'role', 'phone']), ]); } @@ -188,13 +188,13 @@ class AuthController extends Controller { $token = $request->bearerToken(); - if (!$token) { + if (! $token) { return response()->json(['valid' => false], 401); } $accessToken = PersonalAccessToken::findToken($token); - if (!$accessToken || !$accessToken->tokenable) { + if (! $accessToken || ! $accessToken->tokenable) { return response()->json(['valid' => false], 401); } @@ -206,10 +206,10 @@ class AuthController extends Controller { $user = $request->user(); - if (!$user->isAdmin()) { + if (! $user->isAdmin()) { return response()->json([ 'success' => false, - 'message' => 'Недостаточно прав' + 'message' => 'Недостаточно прав', ], 403); } @@ -219,7 +219,7 @@ class AuthController extends Controller return response()->json([ 'success' => true, - 'users' => $users + 'users' => $users, ]); } @@ -228,10 +228,10 @@ class AuthController extends Controller { $currentUser = $request->user(); - if (!$currentUser->isAdmin()) { + if (! $currentUser->isAdmin()) { return response()->json([ 'success' => false, - 'message' => 'Недостаточно прав' + 'message' => 'Недостаточно прав', ], 403); } @@ -240,13 +240,13 @@ class AuthController extends Controller $validator = Validator::make($request->all(), [ 'is_active' => 'boolean', 'role' => 'in:doctor,nurse,head_of_department,statistician,admin', - 'department' => 'string' + 'department' => 'string', ]); if ($validator->fails()) { return response()->json([ 'success' => false, - 'errors' => $validator->errors() + 'errors' => $validator->errors(), ], 422); } @@ -267,7 +267,7 @@ class AuthController extends Controller return response()->json([ 'success' => true, 'message' => 'Пользователь обновлен', - 'user' => $user->only(['id', 'name', 'login', 'department', 'position', 'role', 'is_active']) + 'user' => $user->only(['id', 'name', 'login', 'department', 'position', 'role', 'is_active']), ]); } } diff --git a/app/Http/Controllers/Api/MetrikaFormController.php b/app/Http/Controllers/Api/MetrikaFormController.php index 87e216c..0fe11d4 100644 --- a/app/Http/Controllers/Api/MetrikaFormController.php +++ b/app/Http/Controllers/Api/MetrikaFormController.php @@ -40,10 +40,10 @@ class MetrikaFormController extends Controller 'fields' => $formData, 'validation' => $validationSchema['schema'], 'defaults' => $validationSchema['defaults'], - 'sections' => $this->groupFieldsBySection($formData) + 'sections' => $this->groupFieldsBySection($formData), ], 'existing_data' => $existingReport, - 'created_at' => now()->format('Y-m-d') + 'created_at' => now()->format('Y-m-d'), ]); } @@ -62,7 +62,7 @@ class MetrikaFormController extends Controller return response()->json([ 'success' => false, 'errors' => $validator->errors(), - 'message' => 'Пожалуйста, исправьте ошибки в форме' + 'message' => 'Пожалуйста, исправьте ошибки в форме', ], 422); } @@ -75,7 +75,7 @@ class MetrikaFormController extends Controller 'sent_at' => $periodEnd, 'period_start' => $periodStart, 'period_end' => $periodEnd, - 'rf_department_id' => $user->department->departmentid + 'rf_department_id' => $user->department->departmentid, ] ); @@ -107,7 +107,7 @@ class MetrikaFormController extends Controller 'success' => true, 'message' => 'Данные успешно сохранены', 'report_id' => $report->report_id, - 'result_id' => $metricResult->metrika_result_id + 'result_id' => $metricResult->metrika_result_id, ]); } @@ -133,8 +133,8 @@ class MetrikaFormController extends Controller 'form' => [ 'fields' => $formData, 'values' => $formValues, - 'comment' => $metricResult->comment - ] + 'comment' => $metricResult->comment, + ], ]); } @@ -145,10 +145,10 @@ class MetrikaFormController extends Controller { $user = Auth::user(); - if (!$user) { + if (! $user) { return response()->json([ 'success' => false, - 'message' => 'Пользователь не авторизован' + 'message' => 'Пользователь не авторизован', ], 401); } @@ -158,11 +158,11 @@ class MetrikaFormController extends Controller ->orderBy('created_at', 'desc') ->first(); - if (!$report) { + if (! $report) { return response()->json([ 'success' => true, 'existing_data' => null, - 'message' => 'Нет сохраненных данных за сегодня' + 'message' => 'Нет сохраненных данных за сегодня', ]); } @@ -172,11 +172,11 @@ class MetrikaFormController extends Controller ->with(['values.item']) ->first(); - if (!$metricResult) { + if (! $metricResult) { return response()->json([ 'success' => true, 'existing_data' => null, - 'message' => 'Нет данных для этой группы метрик' + 'message' => 'Нет данных для этой группы метрик', ]); } @@ -199,7 +199,7 @@ class MetrikaFormController extends Controller $comments[$fieldName] = [ 'item_id' => $value->rf_metrika_item_id, 'item_name' => $value->item->name ?? 'Неизвестно', - 'updated_at' => $value->updated_at ? $value->updated_at->format('H:i') : null + 'updated_at' => $value->updated_at ? $value->updated_at->format('H:i') : null, ]; } @@ -214,12 +214,12 @@ class MetrikaFormController extends Controller 'comments' => $comments, 'submitted_at' => $metricResult->created_at ? $metricResult->created_at->format('Y-m-d H:i:s') : null, 'last_updated' => $metricResult->updated_at ? $metricResult->updated_at->format('Y-m-d H:i:s') : null, - 'status' => $metricResult->status ?? 'submitted' + 'status' => $metricResult->status ?? 'submitted', ], 'report_info' => [ 'created_at' => $report->created_at, 'report_status' => $report->status, - ] + ], ]); } @@ -231,13 +231,13 @@ class MetrikaFormController extends Controller $user = Auth::user(); $validator = Validator::make($request->all(), [ - 'sent_at' => 'required|string' + 'sent_at' => 'required|string', ]); if ($validator->fails()) { return response()->json([ 'success' => false, - 'errors' => $validator->errors() + 'errors' => $validator->errors(), ], 422); } @@ -250,12 +250,12 @@ class MetrikaFormController extends Controller if ($daysDiff > 365) { return response()->json([ 'success' => false, - 'message' => 'Период не может превышать 1 год' + 'message' => 'Период не может превышать 1 год', ], 400); } - $dateStart = date('Y-m-d', $startAt) . ' 00:00:00'; - $dateEnd = date('Y-m-d', $endAt) . ' 23:59:59'; + $dateStart = date('Y-m-d', $startAt).' 00:00:00'; + $dateEnd = date('Y-m-d', $endAt).' 23:59:59'; $dateEndExclusive = Carbon::parse($dateEnd, 'Asia/Yakutsk')->addSecond()->format('Y-m-d H:i:s'); $group = MetrikaGroup::findOrFail($groupId); @@ -267,14 +267,14 @@ class MetrikaFormController extends Controller ->where('mr.rf_metrika_group_id', $groupId) ->where('r.period_start', '>=', $dateStart) ->where('r.period_end', '<', $dateEndExclusive) - ->when(!$user->isAdmin() && !$user->isHeadOfDepartment(), function ($query) use ($user) { + ->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') + DB::raw('AVG(CAST(mv.value AS DECIMAL(10,2))) as avg_value'), ]) ->groupBy('mv.rf_metrika_item_id') ->get() @@ -283,7 +283,7 @@ class MetrikaFormController extends Controller if ($aggregatedData->isEmpty()) { return response()->json([ 'success' => false, - 'message' => 'Данные за указанный период не найдены' + 'message' => 'Данные за указанный период не найдены', ], 404); } @@ -299,7 +299,7 @@ class MetrikaFormController extends Controller 'sum' => (float) $data->total_sum, 'average' => (float) $data->avg_value, 'reports_count' => $data->reports_count, - 'item_name' => $items[$itemId] ?? 'Неизвестный показатель' + 'item_name' => $items[$itemId] ?? 'Неизвестный показатель', ]; } @@ -312,7 +312,7 @@ class MetrikaFormController extends Controller 'period' => [ 'start' => $dateStart, 'end' => $dateEnd, - 'days' => $daysDiff + 1 + 'days' => $daysDiff + 1, ], 'group' => [ 'id' => $group->metrika_group_id, @@ -323,12 +323,12 @@ class MetrikaFormController extends Controller 'total_items' => count($formValues), 'total_reports' => $aggregatedData->first()->reports_count ?? 0, 'values' => $formValues, - 'aggregation' => 'sum_and_average' + 'aggregation' => 'sum_and_average', ], 'form' => [ 'fields' => $formData, - 'sections' => $this->groupFieldsBySection($formData) - ] + 'sections' => $this->groupFieldsBySection($formData), + ], ]); } @@ -341,13 +341,13 @@ class MetrikaFormController extends Controller $validator = Validator::make($request->all(), [ 'year' => 'nullable|integer|min:2023|max:2030', - 'month' => 'nullable|integer|min:1|max:12' + 'month' => 'nullable|integer|min:1|max:12', ]); if ($validator->fails()) { return response()->json([ 'success' => false, - 'errors' => $validator->errors() + 'errors' => $validator->errors(), ], 422); } @@ -374,11 +374,11 @@ class MetrikaFormController extends Controller 'month' => $month, 'month_name' => $this->getMonthName($month), 'start_date' => $startDate, - 'end_date' => $endDate + 'end_date' => $endDate, ], 'statistics' => [ - 'total_reports' => $reports->count() - ] + 'total_reports' => $reports->count(), + ], ]); } @@ -395,20 +395,20 @@ class MetrikaFormController extends Controller 'year' => $year, 'month' => $month, 'month_name' => $this->getMonthName($month), - 'days' => [] + 'days' => [], ]; // Пустые дни в начале месяца for ($i = 1; $i < $firstWeekday; $i++) { $calendar['days'][] = [ 'day' => null, - 'empty' => true + 'empty' => true, ]; } // Дни месяца for ($day = 1; $day <= $daysInMonth; $day++) { - $date = date("{$year}-{$month}-" . sprintf('%02d', $day)); + $date = date("{$year}-{$month}-".sprintf('%02d', $day)); $report = $reports->first(fn (Report $item) => $item->period_end && $item->period_end->toDateString() === $date); $timestamp = strtotime($date) * 1000; // В миллисекундах @@ -421,9 +421,9 @@ class MetrikaFormController extends Controller 'day_name' => $this->getDayName(date('N', strtotime($date))), 'is_today' => $date === now()->toDateString(), 'is_weekend' => date('N', strtotime($date)) >= 6, - 'has_report' => !is_null($report), + 'has_report' => ! is_null($report), 'report_status' => $report ? $report->status : null, - 'sent_at' => $report && $report->period_end ? $report->period_end->getTimestamp() * 1000 : $timestamp + 'sent_at' => $report && $report->period_end ? $report->period_end->getTimestamp() * 1000 : $timestamp, ]; $calendar['days'][] = $dayData; @@ -434,7 +434,7 @@ class MetrikaFormController extends Controller while (count($calendar['days']) < $totalCells) { $calendar['days'][] = [ 'day' => null, - 'empty' => true + 'empty' => true, ]; } @@ -449,7 +449,7 @@ class MetrikaFormController extends Controller $months = [ 1 => 'Январь', 2 => 'Февраль', 3 => 'Март', 4 => 'Апрель', 5 => 'Май', 6 => 'Июнь', 7 => 'Июль', 8 => 'Август', - 9 => 'Сентябрь', 10 => 'Октябрь', 11 => 'Ноябрь', 12 => 'Декабрь' + 9 => 'Сентябрь', 10 => 'Октябрь', 11 => 'Ноябрь', 12 => 'Декабрь', ]; return $months[$month] ?? ''; @@ -462,7 +462,7 @@ class MetrikaFormController extends Controller { $days = [ 1 => 'Пн', 2 => 'Вт', 3 => 'Ср', 4 => 'Чт', - 5 => 'Пт', 6 => 'Сб', 7 => 'Вс' + 5 => 'Пт', 6 => 'Сб', 7 => 'Вс', ]; return $days[$dayOfWeek] ?? ''; @@ -477,12 +477,12 @@ class MetrikaFormController extends Controller $months = [ 1 => 'января', 2 => 'февраля', 3 => 'марта', 4 => 'апреля', 5 => 'мая', 6 => 'июня', 7 => 'июля', 8 => 'августа', - 9 => 'сентября', 10 => 'октября', 11 => 'ноября', 12 => 'декабря' + 9 => 'сентября', 10 => 'октября', 11 => 'ноября', 12 => 'декабря', ]; $days = [ 1 => 'понедельник', 2 => 'вторник', 3 => 'среда', - 4 => 'четверг', 5 => 'пятница', 6 => 'суббота', 7 => 'воскресенье' + 4 => 'четверг', 5 => 'пятница', 6 => 'суббота', 7 => 'воскресенье', ]; $dayOfWeek = date('N', $timestamp); @@ -491,10 +491,10 @@ class MetrikaFormController extends Controller $year = date('Y', $timestamp); return [ - 'formatted' => $days[$dayOfWeek] . ', ' . $day . ' ' . $months[$month] . ' ' . $year . ' г.', + 'formatted' => $days[$dayOfWeek].', '.$day.' '.$months[$month].' '.$year.' г.', 'day_of_week' => $days[$dayOfWeek], 'is_weekend' => $dayOfWeek >= 6, - 'is_today' => $date === now()->toDateString() + 'is_today' => $date === now()->toDateString(), ]; } @@ -509,10 +509,10 @@ class MetrikaFormController extends Controller switch ($dataType) { case 'integer': - return (int)$value; + return (int) $value; case 'float': case 'decimal': - return (float)$value; + return (float) $value; case 'boolean': return filter_var($value, FILTER_VALIDATE_BOOLEAN); case 'array': @@ -523,7 +523,7 @@ class MetrikaFormController extends Controller case 'datetime': return date('Y-m-d\TH:i', strtotime($value)); default: - return (string)$value; + return (string) $value; } } @@ -537,10 +537,10 @@ class MetrikaFormController extends Controller foreach ($fields as $field) { $section = $field['section'] ?? 'general'; - if (!isset($sections[$section])) { + if (! isset($sections[$section])) { $sections[$section] = [ 'name' => $this->getSectionName($section), - 'fields' => [] + 'fields' => [], ]; } @@ -556,7 +556,7 @@ class MetrikaFormController extends Controller 'general' => 'Основные показатели', 'admissions' => 'Поступления', 'discharges' => 'Выписки', - 'additional' => 'Дополнительная информация' + 'additional' => 'Дополнительная информация', ]; return $names[$section] ?? ucfirst($section); @@ -570,7 +570,7 @@ class MetrikaFormController extends Controller ->exactPeriod(...$this->getTodayPeriodBounds()) ->first(); - if (!$report) { + if (! $report) { return null; } @@ -579,7 +579,7 @@ class MetrikaFormController extends Controller ->with('values') ->first(); - if (!$result) { + if (! $result) { return null; } @@ -592,7 +592,7 @@ class MetrikaFormController extends Controller 'result_id' => $result->metrika_result_id, 'comment' => $result->comment, 'data' => $data, - 'submitted_at' => $result->created_at + 'submitted_at' => $result->created_at, ]; } @@ -605,5 +605,4 @@ class MetrikaFormController extends Controller $now->copy()->setTime(7, 0)->format('Y-m-d H:i:s'), ]; } - } diff --git a/app/Http/Controllers/Api/OperationController.php b/app/Http/Controllers/Api/OperationController.php index 269c754..2a45d69 100644 --- a/app/Http/Controllers/Api/OperationController.php +++ b/app/Http/Controllers/Api/OperationController.php @@ -12,7 +12,7 @@ class OperationController extends Controller public function operations(Request $request) { $validated = $request->validate([ - 'historyId' => 'required|integer' + 'historyId' => 'required|integer', ]); $operations = MisSurgicalOperation::where('rf_MedicalHistoryID', $request->historyId) diff --git a/app/Http/Controllers/Api/ReportController.php b/app/Http/Controllers/Api/ReportController.php index cc7e12b..bd77241 100644 --- a/app/Http/Controllers/Api/ReportController.php +++ b/app/Http/Controllers/Api/ReportController.php @@ -10,34 +10,30 @@ use App\Models\MedicalHistorySnapshot; use App\Models\MetrikaGroup; use App\Models\MetrikaResult; use App\Models\MisLpuDoctor; -use App\Models\MisMKB; use App\Models\MisMedicalHistory; use App\Models\MisMigrationPatient; +use App\Models\MisMKB; use App\Models\MisStationarBranch; use App\Models\MisSurgicalOperation; use App\Models\ObservationPatient; use App\Models\Report; use App\Models\UnwantedEvent; -use App\Models\User; use App\Services\DateRangeService; use App\Services\MisPatientService; use App\Services\ReportService; use Illuminate\Http\Request; use Illuminate\Support\Carbon; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Session; use Illuminate\Support\Str; -use Inertia\Inertia; class ReportController extends Controller { public function __construct( protected MisPatientService $misPatientService, protected ReportService $reportService, - protected DateRangeService $dateRangeService) - { } + protected DateRangeService $dateRangeService) {} public function index(Request $request) { @@ -122,8 +118,9 @@ class ReportController extends Controller $count = 0; foreach ($reports as $report) { foreach ($report->metrikaResults as $metrikaResult) { - if ($metrikaResult->rf_metrika_item_id === $metrikaItemId) + if ($metrikaResult->rf_metrika_item_id === $metrikaItemId) { $count += intval($metrikaResult->value) ?? 0; + } } } @@ -163,7 +160,7 @@ class ReportController extends Controller 'startAt' => 'required|integer', 'endAt' => 'required|integer', 'userId' => 'required|integer', - 'reportId' => 'nullable' + 'reportId' => 'nullable', ]); $metrics = $data['metrics']; $observationPatients = $data['observationPatients']; @@ -175,7 +172,7 @@ class ReportController extends Controller $metriks = []; foreach ($metrics as $key => $value) { $metrika = new MetrikaResult; - $metrikaId = (int)Str::replace('metrika_item_', '', $key); + $metrikaId = (int) Str::replace('metrika_item_', '', $key); $metrika->rf_metrika_item_id = $metrikaId; $metrika->value = $value; @@ -238,7 +235,7 @@ class ReportController extends Controller if (isset($data['reportId']) && $data['reportId']) { $report = Report::updateOrCreate( [ - 'report_id' => $data['reportId'] + 'report_id' => $data['reportId'], ], [ 'rf_department_id' => $data['departmentId'], @@ -294,7 +291,7 @@ class ReportController extends Controller MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, - 'rf_metrika_item_id' => 16 + 'rf_metrika_item_id' => 16, ], [ 'rf_report_id' => $report->report_id, @@ -307,7 +304,7 @@ class ReportController extends Controller MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, - 'rf_metrika_item_id' => $metrika->rf_metrika_item_id + 'rf_metrika_item_id' => $metrika->rf_metrika_item_id, ], [ 'rf_report_id' => $report->report_id, @@ -322,14 +319,14 @@ class ReportController extends Controller ObservationPatient::updateOrCreate( [ 'rf_medicalhistory_id' => $observationPatient['id'], - 'rf_department_id' => $data['departmentId'] + 'rf_department_id' => $data['departmentId'], ], [ 'rf_department_id' => $data['departmentId'], 'rf_report_id' => $report->report_id, 'rf_medicalhistory_id' => $observationPatient['id'], 'rf_mkab_id' => null, - 'comment' => $observationPatient['comment'] ?? null + 'comment' => $observationPatient['comment'] ?? null, ] ); } @@ -341,7 +338,7 @@ class ReportController extends Controller MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, - 'rf_metrika_item_id' => 14 + 'rf_metrika_item_id' => 14, ], [ 'rf_report_id' => $report->report_id, @@ -352,18 +349,18 @@ class ReportController extends Controller // Сохраняем снимок для каждого типа пациентов // Планово - //$this->getPlanOrEmergencyPatients('plan', false, $branchId, $dateRange->startSql(), $dateRange->endSql(), false, false, true); + // $this->getPlanOrEmergencyPatients('plan', false, $branchId, $dateRange->startSql(), $dateRange->endSql(), false, false, true); foreach ($planIds as $id) { MedicalHistorySnapshot::create([ 'rf_report_id' => $report->report_id, 'rf_medicalhistory_id' => $id, - 'patient_type' => 'plan' + 'patient_type' => 'plan', ]); } MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, - 'rf_metrika_item_id' => 4 + 'rf_metrika_item_id' => 4, ], [ 'rf_report_id' => $report->report_id, @@ -372,19 +369,19 @@ class ReportController extends Controller ] ); - //$this->getPlanOrEmergencyPatients('emergency', false, $branchId, $startDate, $endDate, false, false, true); + // $this->getPlanOrEmergencyPatients('emergency', false, $branchId, $startDate, $endDate, false, false, true); // Экстренно foreach ($emergencyIds as $id) { MedicalHistorySnapshot::create([ 'rf_report_id' => $report->report_id, 'rf_medicalhistory_id' => $id, - 'patient_type' => 'emergency' + 'patient_type' => 'emergency', ]); } MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, - 'rf_metrika_item_id' => 12 + 'rf_metrika_item_id' => 12, ], [ 'rf_report_id' => $report->report_id, @@ -393,12 +390,12 @@ class ReportController extends Controller ] ); - //$this->getDischargedPatients($branchId, $startDate, $endDate, true); + // $this->getDischargedPatients($branchId, $startDate, $endDate, true); foreach ($dischargedIds as $id) { MedicalHistorySnapshot::create([ 'rf_report_id' => $report->report_id, 'rf_medicalhistory_id' => $id, - 'patient_type' => 'discharged' + 'patient_type' => 'discharged', ]); } MetrikaResult::updateOrCreate( @@ -413,12 +410,12 @@ class ReportController extends Controller ] ); - //$this->getTransferredPatients($branchId, $startDate, $endDate, true); + // $this->getTransferredPatients($branchId, $startDate, $endDate, true); foreach ($transferredIds as $id) { MedicalHistorySnapshot::create([ 'rf_report_id' => $report->report_id, 'rf_medicalhistory_id' => $id, - 'patient_type' => 'transferred' + 'patient_type' => 'transferred', ]); } MetrikaResult::updateOrCreate( @@ -433,12 +430,12 @@ class ReportController extends Controller ] ); - //$this->getDeceasedOutcomePatients($branchId, $startDate, $endDate, false, true); + // $this->getDeceasedOutcomePatients($branchId, $startDate, $endDate, false, true); foreach ($deceasedIds as $id) { MedicalHistorySnapshot::create([ 'rf_report_id' => $report->report_id, 'rf_medicalhistory_id' => $id, - 'patient_type' => 'deceased' + 'patient_type' => 'deceased', ]); } MetrikaResult::updateOrCreate( @@ -453,22 +450,22 @@ class ReportController extends Controller ] ); -// $recipientIds = $this->getPlanOrEmergencyPatients( -// null, -// $isHeadOrAdmin, -// $branchId, -// $startDate, -// $endDate, -// false, -// true, -// true, -// today: true -// ); + // $recipientIds = $this->getPlanOrEmergencyPatients( + // null, + // $isHeadOrAdmin, + // $branchId, + // $startDate, + // $endDate, + // false, + // true, + // true, + // today: true + // ); foreach ($recipientIds as $id) { MedicalHistorySnapshot::create([ 'rf_report_id' => $report->report_id, 'rf_medicalhistory_id' => $id, - 'patient_type' => 'recipient' + 'patient_type' => 'recipient', ]); } MetrikaResult::updateOrCreate( @@ -484,19 +481,19 @@ class ReportController extends Controller ); // 7. Находящиеся на лечении -// $currentIds = $this->getCurrentPatients($branchId, false, true); -// foreach ($currentIds as $id) { -// MedicalHistorySnapshot::create([ -// 'rf_report_id' => $report->report_id, -// 'rf_medicalhistory_id' => $id, -// 'patient_type' => 'current' -// ]); -// } + // $currentIds = $this->getCurrentPatients($branchId, false, true); + // foreach ($currentIds as $id) { + // MedicalHistorySnapshot::create([ + // 'rf_report_id' => $report->report_id, + // 'rf_medicalhistory_id' => $id, + // 'patient_type' => 'current' + // ]); + // } \DB::commit(); return response()->json([ - 'message' => 'success' + 'message' => 'success', ]); } @@ -529,6 +526,10 @@ class ReportController extends Controller $dateRange )); + if ($this->resolveBaseStatus($validated['status']) === 'reanimation') { + $this->attachReanimationIndicators($patients, (int) $department->department_id); + } + if ($search !== '') { $needle = mb_strtolower($search); $patients = $patients->filter(function ($patient) use ($needle) { @@ -557,6 +558,58 @@ class ReportController extends Controller ]); } + public function saveReanimationIndicator(Request $request) + { + $user = Auth::user(); + $data = $request->validate([ + 'departmentId' => 'nullable|integer', + 'medical_history_id' => 'required|integer', + 'indicator' => 'required|string|max:100', + 'comment' => 'nullable|string|max:1000', + 'startAt' => 'nullable', + 'endAt' => 'nullable', + ]); + + $departmentId = (int) ($data['departmentId'] ?? $user->department->department_id); + $department = Department::where('department_id', $departmentId)->firstOrFail(); + $dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user); + $reportId = $this->getReportsForDateRange( + $department->department_id, + $dateRange->startSql(), + $dateRange->endSql() + )->pluck('report_id')->last(); + + $indicator = $this->reportService->saveReanimationIndicator( + $user, + $department->department_id, + (int) $data['medical_history_id'], + trim($data['indicator']), + isset($data['comment']) ? trim((string) $data['comment']) : null, + $reportId ? (int) $reportId : null + ); + + return response()->json($indicator, 201); + } + + public function getReanimationIndicatorHistory(Request $request) + { + $user = Auth::user(); + $data = $request->validate([ + 'departmentId' => 'nullable|integer', + 'medical_history_id' => 'required|integer', + 'limit' => 'nullable|integer|min:1|max:200', + ]); + + $departmentId = (int) ($data['departmentId'] ?? $user->department->department_id); + $history = $this->reportService->getReanimationIndicatorsHistory( + $departmentId, + (int) $data['medical_history_id'], + (int) ($data['limit'] ?? 50) + ); + + return response()->json($history); + } + public function getPatientsCount(Request $request) { $user = Auth::user(); @@ -565,7 +618,7 @@ class ReportController extends Controller 'status' => 'required|string', 'startAt' => 'nullable', 'endAt' => 'nullable', - 'departmentId' => 'nullable' + 'departmentId' => 'nullable', ]); $dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user); @@ -680,7 +733,7 @@ class ReportController extends Controller // Врач: используем currentlyInTreatment + фильтр по дате $query = MisMigrationPatient::currentlyInTreatment($branchId) ->when($today, function ($query) use ($startDate, $endDate) { -// return $query->whereBetween('DateIngoing', [$startDate, $endDate]); + // return $query->whereBetween('DateIngoing', [$startDate, $endDate]); return $query->where('DateIngoing', '>=', $startDate) ->where('DateIngoing', '<=', $endDate); }); @@ -690,30 +743,33 @@ class ReportController extends Controller $medicalHistoryIds = $query->pluck('rf_MedicalHistoryID')->toArray(); if (empty($medicalHistoryIds)) { - if ($returnedCount) return 0; + if ($returnedCount) { + return 0; + } + return collect(); } // Получаем истории $query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) ->with(['surgicalOperations' => function ($query) use ($startDate, $endDate) { -// $query->whereBetween('Date', [$startDate, $endDate]); + // $query->whereBetween('Date', [$startDate, $endDate]); $query->where('Date', '>=', $startDate) ->where('Date', '<=', $endDate); }]) ->orderBy('DateRecipient', 'DESC'); // Выбираем план или экстренность - if (!$all && !$isOutcomeStatus) { + if (! $all && ! $isOutcomeStatus) { if ($status === 'plan') { $query->plan(); - } else if ($status === 'emergency') { + } elseif ($status === 'emergency') { $query->emergency(); } } // Для врача добавляем условие "в отделении" - if (!$isHeadOrAdmin && !$isOutcomeStatus) { + if (! $isHeadOrAdmin && ! $isOutcomeStatus) { $query->currentlyHospitalized(); } @@ -721,8 +777,11 @@ class ReportController extends Controller return $query->select('MedicalHistoryID') ->pluck('MedicalHistoryID')->values(); } else { - if ($returnedCount) return $query->count(); - else return $query->get(); + if ($returnedCount) { + return $query->count(); + } else { + return $query->get(); + } } } @@ -748,12 +807,12 @@ class ReportController extends Controller */ private function getOutcomeTypeName($visitResultId): string { - return match($visitResultId) { + return match ($visitResultId) { 1, 7, 8, 9, 10, 11, 48, 49, 124 => 'Выписка', 2, 3, 4, 12, 13, 14 => 'Перевод', 5, 6, 15, 16 => 'Умер', // Добавьте другие коды по мере необходимости - default => 'Другое (' . $visitResultId . ')' + default => 'Другое ('.$visitResultId.')' }; } @@ -773,11 +832,14 @@ class ReportController extends Controller return $query->pluck('MedicalHistoryID')->values(); } - if ($returnedCount) return $query->count(); - else return $query - ->with(['surgicalOperations']) - ->orderBy('DateRecipient', 'DESC') - ->get(); + if ($returnedCount) { + return $query->count(); + } else { + return $query + ->with(['surgicalOperations']) + ->orderBy('DateRecipient', 'DESC') + ->get(); + } } private function buildOutcomeMedicalHistoryQuery( @@ -785,8 +847,7 @@ class ReportController extends Controller string $startDate, string $endDate, ?array $visitResultIds = null - ) - { + ) { $startDateOnly = Carbon::parse($startDate)->toDateString(); $endDateOnly = Carbon::parse($endDate)->toDateString(); @@ -797,7 +858,7 @@ class ReportController extends Controller ->whereHas('migrations', function ($migrationQuery) use ($branchId, $visitResultIds) { $migrationQuery->where('rf_StationarBranchID', $branchId); - if ($visitResultIds !== null && !empty($visitResultIds)) { + if ($visitResultIds !== null && ! empty($visitResultIds)) { $migrationQuery->whereIn('rf_kl_VisitResultID', $visitResultIds); } }); @@ -809,10 +870,10 @@ class ReportController extends Controller private function getSurgicalPatients(string $status, bool $isHeadOrAdmin, $branchId, $startDate, $endDate, bool $returnedCount = false) { $query = MisSurgicalOperation::where('rf_StationarBranchID', $branchId) - ->completed() + ->completed() // ->whereBetween('Date', [$startDate, $endDate]) - ->where('Date', '>=', $startDate) - ->where('Date', '<=', $endDate) + ->where('Date', '>=', $startDate) + ->where('Date', '<=', $endDate) ->orderBy('Date', 'DESC'); if ($status === 'plan') { @@ -821,9 +882,11 @@ class ReportController extends Controller $query->whereIn('rf_TypeSurgOperationInTimeID', [4, 5]); } - - if ($returnedCount) return $query->count(); - else return $query->get(); + if ($returnedCount) { + return $query->count(); + } else { + return $query->get(); + } } /** @@ -831,36 +894,44 @@ class ReportController extends Controller */ private function getCurrentPatients($branchId, bool $returnedCount = false, bool $onlyIds = false) { -// $currentCount = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) -// ->currentlyHospitalized() -// ->orderBy('DateRecipient', 'DESC') -// ->count(); + // $currentCount = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) + // ->currentlyHospitalized() + // ->orderBy('DateRecipient', 'DESC') + // ->count(); $medicalHistoryIds = MisMigrationPatient::currentlyInTreatment($branchId) ->pluck('rf_MedicalHistoryID') ->unique() ->toArray(); if (empty($medicalHistoryIds)) { - if ($returnedCount) return 0; + if ($returnedCount) { + return 0; + } + return collect(); } - if ($onlyIds) return $medicalHistoryIds; + if ($onlyIds) { + return $medicalHistoryIds; + } $patients = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) ->currentlyHospitalized() ->with(['surgicalOperations']) ->orderBy('DateRecipient', 'DESC'); - if ($returnedCount) return $patients->count(); - else return $patients->get(); + if ($returnedCount) { + return $patients->count(); + } else { + return $patients->get(); + } } public function removeObservation( Request $request, ) { $data = $request->validate([ - 'id' => 'required|string' + 'id' => 'required|string', ]); $this->reportService->removeObservationPatient($data['id']); @@ -1028,7 +1099,7 @@ class ReportController extends Controller 'id' => $item->MKBID, 'code' => $item->DS, 'name' => $item->NAME, - 'label' => trim(($item->DS ? "{$item->DS} " : '') . ($item->NAME ?? '')), + 'label' => trim(($item->DS ? "{$item->DS} " : '').($item->NAME ?? '')), ]) ->values(); @@ -1056,15 +1127,13 @@ class ReportController extends Controller 'id' => $item->ServiceMedicalID, 'code' => $item->ServiceMedicalCode, 'name' => $item->ServiceMedicalName, - 'label' => trim(($item->ServiceMedicalCode ? "{$item->ServiceMedicalCode} " : '') . ($item->ServiceMedicalName ?? '')), + 'label' => trim(($item->ServiceMedicalCode ? "{$item->ServiceMedicalCode} " : '').($item->ServiceMedicalName ?? '')), ]) ->values(); return response()->json($items); } - - // api/report/unwanted-event public function removeUnwantedEvent(UnwantedEvent $unwantedEvent, Request $request) { @@ -1090,7 +1159,7 @@ class ReportController extends Controller ->get(); return response()->json([ - ...$users + ...$users, ])->setStatusCode(200); } @@ -1099,22 +1168,67 @@ class ReportController extends Controller */ private function getReportsForDateRange($departmentId, $startDate, $endDate) { - if (Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate)) > 1.0) + if (Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate)) > 1.0) { return Report::where('rf_department_id', $departmentId) ->withinPeriod($startDate, $endDate) ->orderBy('period_end', 'ASC') ->get(); - else + } else { return Report::where('rf_department_id', $departmentId) - ->exactPeriod($startDate, $endDate) - ->orderBy('period_end', 'ASC') - ->get(); + ->exactPeriod($startDate, $endDate) + ->orderBy('period_end', 'ASC') + ->get(); + } + } + + private function resolveBaseStatus(string $status): string + { + if (str_starts_with($status, 'mis-')) { + return substr($status, 4); + } + if (str_starts_with($status, 'special-')) { + return substr($status, 8); + } + + return $status; + } + + private function attachReanimationIndicators(Collection $patients, int $departmentId): void + { + if ($patients->isEmpty()) { + return; + } + + $medicalHistoryIds = $patients + ->map(function ($patient) { + return (int) ($patient->medicalHistoryId ?? $patient->MedicalHistoryID ?? 0); + }) + ->filter() + ->unique() + ->values() + ->all(); + + if (empty($medicalHistoryIds)) { + return; + } + + $latestIndicators = $this->reportService->getLatestReanimationIndicators($departmentId, $medicalHistoryIds); + + $patients->transform(function ($patient) use ($latestIndicators) { + $medicalHistoryId = (int) ($patient->medicalHistoryId ?? $patient->MedicalHistoryID ?? 0); + $indicator = $medicalHistoryId ? $latestIndicators->get($medicalHistoryId) : null; + + $patient->reanimation_indicator = $indicator?->indicator; + $patient->reanimation_comment = $indicator?->comment; + + return $patient; + }); } public function checkReport(Request $request) { $request->validate([ - 'department_id' => 'required|integer|exists:departments,department_id' + 'department_id' => 'required|integer|exists:departments,department_id', ]); $report = Report::where('rf_department_id', $request->department_id) @@ -1123,7 +1237,7 @@ class ReportController extends Controller return response()->json([ 'report_id' => $report?->report_id, - 'exists' => (bool) $report + 'exists' => (bool) $report, ]); } } diff --git a/app/Http/Controllers/Api/RoleController.php b/app/Http/Controllers/Api/RoleController.php index 769c305..58328f6 100644 --- a/app/Http/Controllers/Api/RoleController.php +++ b/app/Http/Controllers/Api/RoleController.php @@ -20,7 +20,7 @@ class RoleController extends Controller public function setUserRole(Request $request) { $data = $request->validate([ - 'role_id' => 'required|integer|exists:roles,id' + 'role_id' => 'required|integer|exists:roles,id', ]); } diff --git a/app/Http/Controllers/Api/StatisticController.php b/app/Http/Controllers/Api/StatisticController.php index c6b2174..9e7d4a9 100644 --- a/app/Http/Controllers/Api/StatisticController.php +++ b/app/Http/Controllers/Api/StatisticController.php @@ -39,7 +39,7 @@ class StatisticController extends Controller $validated = $request->validate([ 'startAt' => 'required', 'endAt' => 'required', - 'departmentId' => 'required' + 'departmentId' => 'required', ]); $dateRange = $this->dateRangeService->getNormalizedDateRange($user, $validated['startAt'], $validated['endAt']); diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 1024e75..72ac252 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -6,7 +6,6 @@ use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Inertia\Inertia; @@ -25,7 +24,7 @@ class AuthController extends Controller if ($validator->fails()) { return back()->withErrors([ - $validator->errors() + $validator->errors(), ]); } @@ -34,18 +33,19 @@ class AuthController extends Controller 'password' => $password, ]; - if (!Auth::attempt($credentials)) { + if (! Auth::attempt($credentials)) { return back()->withErrors([ - 'Неверный логин или пароль' + 'Неверный логин или пароль', ]); } $user = User::where('login', $login)->first(); - if (!$user->is_active) { + if (! $user->is_active) { Auth::logout(); + return back()->withErrors([ - 'Учетная запись отключена' + 'Учетная запись отключена', ]); } @@ -63,17 +63,19 @@ class AuthController extends Controller { $user = Auth::user(); - if (!$user) return null; + if (! $user) { + return null; + } $data = $request->validate([ - 'role_id' => 'required|integer|exists:roles,role_id' + 'role_id' => 'required|integer|exists:roles,role_id', ]); $sessionId = session()->getId(); $token = $user->tokens()->where('name', $sessionId)->first(); if ($token) { - $token->abilities = ['role:' . $request->role_id]; + $token->abilities = ['role:'.$request->role_id]; $token->save(); } @@ -81,10 +83,10 @@ class AuthController extends Controller ->where('id', $sessionId) ->update(['role_id' => $request->role_id]); -// $sessionKey = 'user_' . $user->id . '_current_role'; -// -// $user->current_role_id = $data['role_id']; -// $user->save(); + // $sessionKey = 'user_' . $user->id . '_current_role'; + // + // $user->current_role_id = $data['role_id']; + // $user->save(); return redirect()->route('start')->setStatusCode(302); } @@ -106,5 +108,4 @@ class AuthController extends Controller return redirect()->route('login'); } - } diff --git a/app/Http/Controllers/TestController.php b/app/Http/Controllers/TestController.php index 76d0ae7..795a128 100644 --- a/app/Http/Controllers/TestController.php +++ b/app/Http/Controllers/TestController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers; use App\Services\PatientMigrationService; -use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Inertia\Inertia; @@ -14,12 +13,13 @@ class TestController extends Controller $startAt = Carbon::parse('2026-01-01T00:00:00')->format('Y-m-d H:i:s'); $endAt = Carbon::parse('2026-03-31T23:59:00')->format('Y-m-d H:i:s'); - $cacheKey = "branch_current_2"; - \Cache::tags(["migrations_in_branch_outcome"])->flush(); + $cacheKey = 'branch_current_2'; + \Cache::tags(['migrations_in_branch_outcome'])->flush(); $data = $migrationService->getMigrationsInBranchOutcome(2, $startAt, $endAt); + return Inertia::render('TestQuery', [ - 'data' => $data + 'data' => $data, ]); } diff --git a/app/Http/Controllers/Web/Admin/AdminController.php b/app/Http/Controllers/Web/Admin/AdminController.php index ad2f289..7ed0e30 100644 --- a/app/Http/Controllers/Web/Admin/AdminController.php +++ b/app/Http/Controllers/Web/Admin/AdminController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers\Web\Admin; use App\Http\Controllers\Controller; -use Illuminate\Http\Request; use Inertia\Inertia; class AdminController extends Controller diff --git a/app/Http/Controllers/Web/IndexController.php b/app/Http/Controllers/Web/IndexController.php index 74cabd4..9f6a248 100644 --- a/app/Http/Controllers/Web/IndexController.php +++ b/app/Http/Controllers/Web/IndexController.php @@ -41,8 +41,5 @@ class IndexController extends Controller ]); } - public function store(Request $request) - { - - } + public function store(Request $request) {} } diff --git a/app/Http/Controllers/Web/ReportController.php b/app/Http/Controllers/Web/ReportController.php index 70621b4..d0afdc4 100644 --- a/app/Http/Controllers/Web/ReportController.php +++ b/app/Http/Controllers/Web/ReportController.php @@ -2,14 +2,18 @@ namespace App\Http\Controllers\Web; +use App\Exports\ReportPageExport; use App\Http\Controllers\Controller; +use App\Http\Resources\Mis\FormattedPatientResource; use App\Models\Department; use App\Services\DateRangeService; use App\Services\ReportPageService; use App\Services\ReportService; use Illuminate\Http\Request; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Inertia\Inertia; +use Maatwebsite\Excel\Facades\Excel; class ReportController extends Controller { @@ -46,4 +50,150 @@ class ReportController extends Controller return redirect()->route('start'); } + + public function export(Request $request) + { + $user = Auth::user(); + $departmentId = $request->query('departmentId', $user->department->department_id); + $department = Department::where('department_id', $departmentId)->firstOrFail(); + $dateRange = $this->dateRangeService->getDateRangeFromRequest($request, $user); + + $statistics = $this->reportService->getReportStatistics($department, $user, $dateRange); + $counts = $this->reportService->getPatientsCountsMap($department, $user, $dateRange); + + $statusGroups = [ + 'МИС' => [ + 'mis-plan' => 'Планово', + 'mis-emergency' => 'Экстренно', + 'mis-observation' => 'На контроле', + 'mis-reanimation' => 'Реанимация', + 'mis-outcome-discharged' => 'Выписанные', + 'mis-outcome-deceased' => 'Умершие', + 'mis-outcome-transferred' => 'Переведенные', + ], + 'Спец. контингент' => [ + 'special-plan' => 'Планово', + 'special-emergency' => 'Экстренно', + 'special-observation' => 'На контроле', + 'special-outcome-discharged' => 'Выписанные', + 'special-outcome-deceased' => 'Умершие', + 'special-outcome-transferred' => 'Переведенные', + ], + ]; + + $summaryRows = [ + ['Показатель', 'Значение'], + ['Отделение', $department->name_full], + ['Период', $dateRange->startSql().' - '.$dateRange->endSql()], + ['Сформирован', now('Asia/Yakutsk')->format('Y-m-d H:i:s')], + ['Коек', $statistics['beds'] ?? 0], + ['Поступило', $statistics['recipientCount'] ?? 0], + ['Выбыло', $statistics['extractCount'] ?? 0], + ['Состоит', $statistics['currentCount'] ?? 0], + ['Умерло', $statistics['deadCount'] ?? 0], + ['% смертности', $statistics['percentDead'] ?? 0], + ['% загруженности', $statistics['percentLoadedBeds'] ?? 0], + [''], + ['Раздел / Статус', 'Количество'], + ]; + + foreach ($statusGroups as $groupLabel => $groupStatuses) { + $summaryRows[] = [$groupLabel, '']; + foreach ($groupStatuses as $status => $label) { + $summaryRows[] = [$label, $counts[$status] ?? 0]; + } + } + + $patientRows = [[ + 'Раздел', + 'Статус', + 'ФИО', + 'Возраст', + 'Дата рождения', + 'Дата поступления', + 'Код диагноза', + 'Диагноз', + 'Операции', + 'Состояние (реанимация)', + 'Период закрыт (реанимация)', + ]]; + + foreach ($statusGroups as $groupLabel => $groupStatuses) { + $patientRows[] = [$groupLabel, '', '', '', '', '', '', '', '', '', '']; + + foreach ($groupStatuses as $status => $label) { + $patients = collect($this->reportService->getPatientsByStatus( + $department, + $user, + $status, + $dateRange + )); + + if ($status === 'mis-reanimation') { + $this->attachReanimationIndicators($patients, (int) $department->department_id); + } + + $resolvedPatients = FormattedPatientResource::collection($patients)->resolve(); + + foreach ($resolvedPatients as $patient) { + $operations = collect($patient['operations'] ?? []) + ->map(fn ($operation) => $operation['code'] ?? $operation['name'] ?? null) + ->filter() + ->implode(', '); + + $patientRows[] = [ + $groupLabel, + $label, + $patient['fullname'] ?? '', + $patient['age'] ?? '', + $patient['birth_date'] ?? '', + $patient['admitted_at'] ?? '', + $patient['mkb']['ds'] ?? '', + $patient['mkb']['name'] ?? '', + $operations, + $patient['reanimation_indicator'] ?? '', + isset($patient['reanimation_is_complete']) && $patient['reanimation_is_complete'] ? 'Да' : 'Нет', + ]; + } + } + } + + $fileName = sprintf( + 'report_%d_%s.xlsx', + $department->department_id, + now('Asia/Yakutsk')->format('Ymd_His') + ); + + return Excel::download(new ReportPageExport($summaryRows, $patientRows), $fileName); + } + + private function attachReanimationIndicators(Collection $patients, int $departmentId): void + { + if ($patients->isEmpty()) { + return; + } + + $medicalHistoryIds = $patients + ->map(fn ($patient) => (int) ($patient->medicalHistoryId ?? $patient->MedicalHistoryID ?? 0)) + ->filter() + ->unique() + ->values() + ->all(); + + if (empty($medicalHistoryIds)) { + return; + } + + $latestIndicators = $this->reportService->getLatestReanimationIndicators($departmentId, $medicalHistoryIds); + + $patients->transform(function ($patient) use ($latestIndicators) { + $medicalHistoryId = (int) ($patient->medicalHistoryId ?? $patient->MedicalHistoryID ?? 0); + $indicator = $medicalHistoryId ? $latestIndicators->get($medicalHistoryId) : null; + + $patient->reanimation_indicator = $indicator?->indicator; + $patient->reanimation_comment = $indicator?->comment; + + return $patient; + }); + } } diff --git a/app/Http/Controllers/Web/StatisticController.php b/app/Http/Controllers/Web/StatisticController.php index 1b646fb..243d40b 100644 --- a/app/Http/Controllers/Web/StatisticController.php +++ b/app/Http/Controllers/Web/StatisticController.php @@ -4,20 +4,9 @@ namespace App\Http\Controllers\Web; use App\Exports\StatisticsExport; 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 App\Services\StatisticsService; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Validator; use Inertia\Inertia; use Maatwebsite\Excel\Facades\Excel; @@ -26,7 +15,7 @@ class StatisticController extends Controller public function __construct( protected DateRangeService $dateService, protected StatisticsService $statisticsService - ) { } + ) {} public function index(Request $request) { @@ -38,7 +27,7 @@ class StatisticController extends Controller $isRangeOneDay = $this->dateService->isRangeOneDay($startDate, $endDate); // Генерируем ключ кэша на основе параметров запроса -// $cacheKey = $this->generateCacheKey($user, $startDate, $endDate, $isRangeOneDay); + // $cacheKey = $this->generateCacheKey($user, $startDate, $endDate, $isRangeOneDay); // Получаем данные из кэша или вычисляем $finalData = $this->statisticsService->getStatisticsData($user, $startDate, $endDate, $isRangeOneDay); @@ -46,7 +35,7 @@ class StatisticController extends Controller $isHeadOrAdmin = $user->isAdmin() || $user->isHeadOfDepartment(); $date = $isHeadOrAdmin ? [ $this->dateService->parseDate($isRangeOneDay ? $endDate : $startDate)->getTimestampMs(), - $this->dateService->parseDate($endDate)->getTimestampMs() + $this->dateService->parseDate($endDate)->getTimestampMs(), ] : $this->dateService->parseDate($endDate)->getTimestampMs(); return Inertia::render('Statistic/Index', [ @@ -56,7 +45,7 @@ class StatisticController extends Controller 'isHeadOrAdmin' => $isHeadOrAdmin, 'date' => $date, 'isOneDay' => $isRangeOneDay, - 'recipientPlanOfYear' => $finalData['recipientPlanOfYear'] + 'recipientPlanOfYear' => $finalData['recipientPlanOfYear'], ]); } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 642cf6d..78ff5cd 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -4,8 +4,6 @@ namespace App\Http\Middleware; use Closure; use Illuminate\Auth\AuthenticationException; -use Illuminate\Http\Request; -use Symfony\Component\HttpFoundation\Response; use Illuminate\Auth\Middleware\Authenticate as Middleware; class Authenticate extends Middleware diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index f65c0a9..75025c9 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -38,14 +38,15 @@ class HandleInertiaRequests extends Middleware public function share(Request $request): array { $user = $request->user() ?? Auth::guard('sanctum')->user(); + return [ ...parent::share($request), 'app' => [ 'version' => config('app.version'), - 'tag' => config('app.tag') + 'tag' => config('app.tag'), ], 'config' => [ - 'timeEventSourceUrl' => config('time.eventSourceUrl') + 'timeEventSourceUrl' => config('time.eventSourceUrl'), ], 'user' => $user ? [ 'name' => $user->name, @@ -54,7 +55,7 @@ class HandleInertiaRequests extends Middleware 'role' => $user->currentRole(), 'available_roles' => $user->roles, 'available_departments' => $user->availableDepartments(), - 'current_department' => $user->department->load('departmentType') + 'current_department' => $user->department->load('departmentType'), ] : null, ]; } diff --git a/app/Http/Resources/Api/DepartmentPatientOperationResource.php b/app/Http/Resources/Api/DepartmentPatientOperationResource.php index 05f4b3d..142e230 100644 --- a/app/Http/Resources/Api/DepartmentPatientOperationResource.php +++ b/app/Http/Resources/Api/DepartmentPatientOperationResource.php @@ -24,7 +24,7 @@ class DepartmentPatientOperationResource extends JsonResource 'id' => $operation->rf_kl_service_medical_id, 'code' => $serviceCode, 'name' => $serviceName, - 'label' => trim(($serviceCode ? "{$serviceCode} " : '') . ($serviceName ?? '')), + 'label' => trim(($serviceCode ? "{$serviceCode} " : '').($serviceName ?? '')), ], 'startAt' => $operation->started_at?->toIso8601String(), 'endAt' => $operation->ended_at?->toIso8601String(), diff --git a/app/Http/Resources/Mis/FormattedPatientResource.php b/app/Http/Resources/Mis/FormattedPatientResource.php index b7fccaf..6683cfc 100644 --- a/app/Http/Resources/Mis/FormattedPatientResource.php +++ b/app/Http/Resources/Mis/FormattedPatientResource.php @@ -36,6 +36,9 @@ class FormattedPatientResource extends JsonResource 'outcome_type' => $this->outcomeType, 'outcome_date' => $this->outcomeDate, 'comment' => $this->comment, + 'reanimation_indicator' => $this->reanimation_indicator ?? null, + 'reanimation_comment' => $this->reanimation_comment ?? null, + 'reanimation_is_complete' => $this->reanimationIsComplete, 'is_recipient_today' => $this->isRecipientToday, 'is_manual' => $this->isManual, 'can_manage_manual' => $this->canManageManual, @@ -46,7 +49,7 @@ class FormattedPatientResource extends JsonResource 'id' => $this->MedicalHistoryID, 'mkb' => [ 'ds' => $this->outcomeMigration->first()->mainDiagnosis?->mkb?->DS, - 'name' => $this->outcomeMigration->first()->mainDiagnosis?->mkb?->NAME + 'name' => $this->outcomeMigration->first()->mainDiagnosis?->mkb?->NAME, ], 'operations' => $this->surgicalOperations->map(function ($operation) { return [ @@ -61,6 +64,9 @@ class FormattedPatientResource extends JsonResource 'outcome_type' => $this->when($this->outcome_type, $this->outcome_type), 'outcome_date' => $this->when($this->outcome_date, $this->outcome_date), 'comment' => $this->when($this->comment, $this->comment), + 'reanimation_indicator' => $this->when($this->reanimation_indicator, $this->reanimation_indicator), + 'reanimation_comment' => $this->when($this->reanimation_comment, $this->reanimation_comment), + 'reanimation_is_complete' => (bool) ($this->reanimation_is_complete ?? false), 'is_recipient_today' => (bool) ($this->is_recipient_today ?? false), 'is_manual' => false, 'can_manage_manual' => false, diff --git a/app/Models/Department.php b/app/Models/Department.php index ca0ef6b..8019ce0 100644 --- a/app/Models/Department.php +++ b/app/Models/Department.php @@ -15,7 +15,7 @@ class Department extends Model 'name_full', 'name_short', 'rf_mis_department_id', - 'rf_department_type' + 'rf_department_type', ]; public function metrikaDefault() diff --git a/app/Models/DepartmentMetrikaDefault.php b/app/Models/DepartmentMetrikaDefault.php index 1a66fe7..17534e6 100644 --- a/app/Models/DepartmentMetrikaDefault.php +++ b/app/Models/DepartmentMetrikaDefault.php @@ -7,12 +7,13 @@ use Illuminate\Database\Eloquent\Model; class DepartmentMetrikaDefault extends Model { protected $primaryKey = 'department_metrika_default_id'; + public $timestamps = false; protected $fillable = [ 'rf_department_id', 'rf_metrika_item_id', - 'value' + 'value', ]; public function departments() diff --git a/app/Models/DepartmentType.php b/app/Models/DepartmentType.php index 337b8bc..b3b7a1b 100644 --- a/app/Models/DepartmentType.php +++ b/app/Models/DepartmentType.php @@ -7,10 +7,11 @@ use Illuminate\Database\Eloquent\Model; class DepartmentType extends Model { protected $primaryKey = 'department_type_id'; + public $timestamps = false; protected $fillable = [ 'name_full', - 'name_short' + 'name_short', ]; } diff --git a/app/Models/LifeMisMigrationPatient.php b/app/Models/LifeMisMigrationPatient.php index 5f9360f..91eba25 100644 --- a/app/Models/LifeMisMigrationPatient.php +++ b/app/Models/LifeMisMigrationPatient.php @@ -9,6 +9,7 @@ use Illuminate\Support\Carbon; class LifeMisMigrationPatient extends Model { protected $table = 'life_stt_migrationpatient'; + protected $primaryKey = 'MigrationPatientID'; public function branch() @@ -34,7 +35,7 @@ class LifeMisMigrationPatient extends Model /** * Находятся на лечении */ - public function scopeCurrentlyInTreatment($query, $branchId = null, DateRange $dateRange = null) + public function scopeCurrentlyInTreatment($query, $branchId = null, ?DateRange $dateRange = null) { $query->where('rf_kl_VisitResultID', 0) ->where('rf_kl_StatCureResultID', 0) @@ -49,7 +50,7 @@ class LifeMisMigrationPatient extends Model } if ($dateRange) { -// $query->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]); + // $query->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]); $query->where('DateIngoing', '>=', $dateRange->startSql()) ->where('DateIngoing', '<=', $dateRange->endSql()); } @@ -71,7 +72,7 @@ class LifeMisMigrationPatient extends Model /** * Выбывшие пациенты (все исходы) */ - public function scopeOutcomePatients($query, $branchId = null, DateRange $dateRange = null) + public function scopeOutcomePatients($query, $branchId = null, ?DateRange $dateRange = null) { $query->where('rf_MedicalHistoryID', '<>', 0); @@ -94,7 +95,7 @@ class LifeMisMigrationPatient extends Model /** * Выписанные пациенты */ - public function scopeOutcomeDischarged($query, $branchId = null, DateRange $dateRange = null) + public function scopeOutcomeDischarged($query, $branchId = null, ?DateRange $dateRange = null) { // По уточненному SQL: Выписано за период $dischargeCodes = [1, 11, 2, 12, 7, 18, 48]; @@ -121,7 +122,7 @@ class LifeMisMigrationPatient extends Model /** * Перевод в другое отделение */ - public function scopeOutcomeTransferred($query, $branchId = null, DateRange $dateRange = null) + public function scopeOutcomeTransferred($query, $branchId = null, ?DateRange $dateRange = null) { // По заданному SQL: только эти коды перевода $transferCodes = [4, 14]; @@ -148,7 +149,7 @@ class LifeMisMigrationPatient extends Model /** * Умершие пациенты */ - public function scopeDeceasedOutcome($query, $branchId = null, DateRange $dateRange = null) + public function scopeDeceasedOutcome($query, $branchId = null, ?DateRange $dateRange = null) { // ID умершего $deceasedCodes = [5, 6, 15, 16]; @@ -172,7 +173,7 @@ class LifeMisMigrationPatient extends Model return $query; } - public function scopeOutcomeWithoutTransferred($query, $branchId = null, DateRange $dateRange = null) + public function scopeOutcomeWithoutTransferred($query, $branchId = null, ?DateRange $dateRange = null) { $query->whereNotIn('rf_kl_VisitResultID', [4, 14]) ->where('rf_kl_VisitResultID', '<>', 0) @@ -194,16 +195,17 @@ class LifeMisMigrationPatient extends Model return $query; } - public function scopeExtractedToday($query, $branchId = null, DateRange $dateRange = null) + public function scopeExtractedToday($query, $branchId = null, ?DateRange $dateRange = null) { -// if (is_null($startDate)) $startDate = Carbon::now()->addDays(-1)->format('Y-m-d'); -// if (is_null($endDate)) $endDate = Carbon::now()->format('Y-m-d'); + // if (is_null($startDate)) $startDate = Carbon::now()->addDays(-1)->format('Y-m-d'); + // if (is_null($endDate)) $endDate = Carbon::now()->format('Y-m-d'); $query->where('rf_kl_VisitResultID', '<>', 0) ->where('rf_MedicalHistoryID', '<>', 0) - ->when($dateRange, function($query) use ($dateRange) { + ->when($dateRange, function ($query) use ($dateRange) { $startDate = Carbon::parse($dateRange->startSql())->toDateString(); $endDate = Carbon::parse($dateRange->endSql())->toDateString(); + return $query->whereHas('medicalHistory', function ($mhQuery) use ($startDate, $endDate) { $mhQuery->whereDate('DateExtract', '>', $startDate) ->whereDate('DateExtract', '<=', $endDate); diff --git a/app/Models/MedicalHistorySnapshot.php b/app/Models/MedicalHistorySnapshot.php index 03c7667..6226ad8 100644 --- a/app/Models/MedicalHistorySnapshot.php +++ b/app/Models/MedicalHistorySnapshot.php @@ -37,6 +37,7 @@ class MedicalHistorySnapshot extends Model * Типы пациентов */ const PATIENT_TYPE_DISCHARGED = 'discharged'; // Выписанные + const PATIENT_TYPE_CURRENT = 'current'; // Текущие public function report() @@ -67,7 +68,7 @@ class MedicalHistorySnapshot extends Model public function scopeByDepartment($query, $departmentId) { - return $query->whereHas('medicalHistory.migrations.branch', function($q) use ($departmentId) { + return $query->whereHas('medicalHistory.migrations.branch', function ($q) use ($departmentId) { $q->where('rf_DepartmentID', $departmentId); }); } diff --git a/app/Models/MetrikaForm.php b/app/Models/MetrikaForm.php index 1ead582..6229153 100644 --- a/app/Models/MetrikaForm.php +++ b/app/Models/MetrikaForm.php @@ -7,12 +7,14 @@ use Illuminate\Database\Eloquent\Model; class MetrikaForm extends Model { protected $table = 'metrika_group_items'; + protected $primaryKey = 'metrika_group_item_id'; + public $timestamps = false; protected $fillable = [ 'rf_metrika_group_id', - 'rf_metrika_item_id' + 'rf_metrika_item_id', ]; // Метод для получения данных формы @@ -35,7 +37,7 @@ class MetrikaForm extends Model 'default_value' => $formItem->item->default_value, 'is_required' => $formItem->item->is_required ?? false, 'order' => $formItem->order ?? 0, - 'section' => $formItem->section ?? 'general' + 'section' => $formItem->section ?? 'general', ]; }) ->sortBy('order') @@ -105,13 +107,13 @@ class MetrikaForm extends Model return [ 'schema' => $schema, 'defaults' => $defaults, - 'fields' => $formItems + 'fields' => $formItems, ]; } private static function getDefaultByType($dataType) { - return match($dataType) { + return match ($dataType) { 'integer', 'float' => 0, 'string', 'text' => '', 'boolean' => false, diff --git a/app/Models/MetrikaGroup.php b/app/Models/MetrikaGroup.php index 7b3ca30..195302d 100644 --- a/app/Models/MetrikaGroup.php +++ b/app/Models/MetrikaGroup.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; class MetrikaGroup extends Model { protected $primaryKey = 'metrika_group_id'; + public $timestamps = false; protected $fillable = [ diff --git a/app/Models/MetrikaGroupItem.php b/app/Models/MetrikaGroupItem.php index 39f7e20..83d00b8 100644 --- a/app/Models/MetrikaGroupItem.php +++ b/app/Models/MetrikaGroupItem.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; class MetrikaGroupItem extends Model { protected $primaryKey = 'metrika_group_item_id'; + public $timestamps = false; protected $fillable = [ diff --git a/app/Models/MetrikaItem.php b/app/Models/MetrikaItem.php index 9b411a5..a98a77e 100644 --- a/app/Models/MetrikaItem.php +++ b/app/Models/MetrikaItem.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; class MetrikaItem extends Model { protected $primaryKey = 'metrika_item_id'; + public $timestamps = false; protected $fillable = [ diff --git a/app/Models/MetrikaResult.php b/app/Models/MetrikaResult.php index 768248e..2d59ab0 100644 --- a/app/Models/MetrikaResult.php +++ b/app/Models/MetrikaResult.php @@ -8,12 +8,13 @@ use Illuminate\Database\Eloquent\Relations\HasMany; class MetrikaResult extends Model { protected $primaryKey = 'metrika_result_id'; + public $timestamps = false; protected $fillable = [ 'rf_metrika_item_id', 'rf_report_id', - 'value' + 'value', ]; public function report(): \Illuminate\Database\Eloquent\Relations\BelongsTo @@ -21,13 +22,13 @@ class MetrikaResult extends Model return $this->belongsTo(Report::class, 'rf_report_id', 'report_id'); } -// public function group(): \Illuminate\Database\Eloquent\Relations\BelongsTo -// { -// return $this->belongsTo(MetrikaGroup::class, 'rf_metrika_group_id', 'metrika_group_id'); -// } + // public function group(): \Illuminate\Database\Eloquent\Relations\BelongsTo + // { + // return $this->belongsTo(MetrikaGroup::class, 'rf_metrika_group_id', 'metrika_group_id'); + // } -// public function values(): HasMany -// { -// return $this->hasMany(MetrikaResultValue::class, 'rf_metrika_result_id', 'metrika_result_id'); -// } + // public function values(): HasMany + // { + // return $this->hasMany(MetrikaResultValue::class, 'rf_metrika_result_id', 'metrika_result_id'); + // } } diff --git a/app/Models/MetrikaResultValue.php b/app/Models/MetrikaResultValue.php index 3709bcf..e8b44d1 100644 --- a/app/Models/MetrikaResultValue.php +++ b/app/Models/MetrikaResultValue.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; class MetrikaResultValue extends Model { protected $primaryKey = 'metrika_result_value_id'; + public $timestamps = false; protected $fillable = [ diff --git a/app/Models/MisBed.php b/app/Models/MisBed.php index aabfa4c..6455455 100644 --- a/app/Models/MisBed.php +++ b/app/Models/MisBed.php @@ -7,7 +7,9 @@ use Illuminate\Database\Eloquent\Model; class MisBed extends Model { protected $table = 'stt_Bed'; + protected $primaryKey = 'BedID'; + public $timestamps = false; protected $fillable = [ @@ -24,14 +26,14 @@ class MisBed extends Model 'x_Edition', 'x_Status', 'ShiftNum', - 'rf_PreviousBedID' + 'rf_PreviousBedID', ]; protected $casts = [ 'DateWorkB' => 'datetime', 'DateWorkE' => 'datetime', 'UGUID' => 'string', - 'x_Status' => 'integer' + 'x_Status' => 'integer', ]; public function bedActions() diff --git a/app/Models/MisBedAction.php b/app/Models/MisBedAction.php index 404fcf9..6c0c92b 100644 --- a/app/Models/MisBedAction.php +++ b/app/Models/MisBedAction.php @@ -7,7 +7,9 @@ use Illuminate\Database\Eloquent\Model; class MisBedAction extends Model { protected $table = 'stt_BedAction'; + protected $primaryKey = 'BedActionID'; + public $timestamps = false; protected $fillable = [ @@ -24,20 +26,21 @@ class MisBedAction extends Model 'rf_DocPRVDID', 'rf_MedStageID', 'rf_BedProfileID', - 'SystemDate' + 'SystemDate', ]; protected $casts = [ 'Date' => 'datetime', 'SystemDate' => 'datetime', 'UGUID' => 'string', - 'x_Status' => 'integer' + 'x_Status' => 'integer', ]; /** * Типы действий с койками */ const ACTION_TYPE_OCCUPIED = 1; // Поступление + const ACTION_TYPE_FREE = 2; // Выписка public function bed() @@ -53,7 +56,7 @@ class MisBedAction extends Model $query = self::where('rf_MigrationPatientID', $migrationPatientId) ->whereIn('rf_ActionTypeID', [ self::ACTION_TYPE_OCCUPIED, - self::ACTION_TYPE_FREE + self::ACTION_TYPE_FREE, ]) ->orderBy('Date'); diff --git a/app/Models/MisDiagnos.php b/app/Models/MisDiagnos.php index 2c307fb..a6527c8 100644 --- a/app/Models/MisDiagnos.php +++ b/app/Models/MisDiagnos.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; class MisDiagnos extends Model { protected $table = 'stt_diagnos'; + protected $primaryKey = 'DiagnosID'; protected $fillable = [ diff --git a/app/Models/MisDocPrvd.php b/app/Models/MisDocPrvd.php index 5c90ff7..e9c3551 100644 --- a/app/Models/MisDocPrvd.php +++ b/app/Models/MisDocPrvd.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; class MisDocPrvd extends Model { protected $primaryKey = 'DocPRVDID'; + protected $table = 'hlt_docprvd'; public function lpuDoctor() diff --git a/app/Models/MisLpuDoctor.php b/app/Models/MisLpuDoctor.php index d4e9b09..f799d8d 100644 --- a/app/Models/MisLpuDoctor.php +++ b/app/Models/MisLpuDoctor.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Auth; class MisLpuDoctor extends Model { public $table = 'hlt_lpudoctor'; + protected $primaryKey = 'LPUDoctorID'; public function scopeActive($query) diff --git a/app/Models/MisMKB.php b/app/Models/MisMKB.php index 36e2bef..703d1b2 100644 --- a/app/Models/MisMKB.php +++ b/app/Models/MisMKB.php @@ -7,10 +7,11 @@ use Illuminate\Database\Eloquent\Model; class MisMKB extends Model { protected $table = 'oms_mkb'; + protected $primaryKey = 'MKBID'; protected $fillable = [ 'DS', - 'NAME' + 'NAME', ]; } diff --git a/app/Models/MisMedicalHistory.php b/app/Models/MisMedicalHistory.php index 42b86ea..63a75f5 100644 --- a/app/Models/MisMedicalHistory.php +++ b/app/Models/MisMedicalHistory.php @@ -9,7 +9,9 @@ use Illuminate\Support\Facades\DB; class MisMedicalHistory extends Model { protected $table = 'stt_medicalhistory'; + protected $primaryKey = 'MedicalHistoryID'; + public $timestamps = false; protected $fillable = [ @@ -18,12 +20,12 @@ class MisMedicalHistory extends Model 'Name', 'OT', 'BD', - 'Address' + 'Address', ]; protected $casts = [ 'DateRecipient' => 'datetime', - 'DateExtract' => 'datetime' + 'DateExtract' => 'datetime', ]; /** @@ -45,7 +47,7 @@ class MisMedicalHistory extends Model */ public function getActualDischargeDate(): ?Carbon { - if (!$this->DateExtract) { + if (! $this->DateExtract) { return null; } @@ -62,7 +64,7 @@ class MisMedicalHistory extends Model */ public function getBedDays(): int { - if (!$this->DateRecipient) { + if (! $this->DateRecipient) { return 0; } @@ -106,15 +108,15 @@ class MisMedicalHistory extends Model return $this->surgicalOperations()->where('rf_StationarBranchID', $branchId) ->where('Date', '>=', $startDate) ->where('Date', '<=', $endDate); -// ->whereBetween('Date', [$startDate, $endDate]); + // ->whereBetween('Date', [$startDate, $endDate]); } public function scopeCurrentlyHospitalized($query) { return $query->where(function ($builder) { - $builder->whereDate('DateExtract', '1900-01-01') - ->orWhereDate('DateExtract', '2222-01-01'); - }) + $builder->whereDate('DateExtract', '1900-01-01') + ->orWhereDate('DateExtract', '2222-01-01'); + }) ->where('MedicalHistoryID', '<>', 0); } @@ -188,7 +190,7 @@ class MisMedicalHistory extends Model if ($startDate && $endDate) { $q->where('mp.DateIngoing', '>=', $startDate) ->where('mp.DateIngoing', '<=', $endDate); -// $q->whereBetween('mp.DateIngoing', [$startDate, $endDate]); + // $q->whereBetween('mp.DateIngoing', [$startDate, $endDate]); } }); } diff --git a/app/Models/MisMigrationPatient.php b/app/Models/MisMigrationPatient.php index 9262f5e..af5c67a 100644 --- a/app/Models/MisMigrationPatient.php +++ b/app/Models/MisMigrationPatient.php @@ -9,6 +9,7 @@ use Illuminate\Support\Carbon; class MisMigrationPatient extends Model { protected $table = 'stt_migrationpatient'; + protected $primaryKey = 'MigrationPatientID'; protected $casts = [ @@ -43,10 +44,10 @@ class MisMigrationPatient extends Model /** * Находятся на лечении */ - public function scopeCurrentlyInTreatment($query, $branchId = null, DateRange $dateRange = null) + public function scopeCurrentlyInTreatment($query, $branchId = null, ?DateRange $dateRange = null) { $query->whereNotIn('rf_kl_VisitResultID', [4]) - ->whereHas('medicalHistory', function ($query) use ($branchId, $dateRange) { + ->whereHas('medicalHistory', function ($query) { $query->whereDate('DateExtract', '1900-01-01'); }) ->where('rf_MedicalHistoryID', '<>', 0); @@ -56,7 +57,7 @@ class MisMigrationPatient extends Model } if ($dateRange) { -// $query->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]); + // $query->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]); $query->where('DateIngoing', '>=', $dateRange->startSql()) ->where('DateIngoing', '<=', $dateRange->endSql()); } @@ -78,7 +79,7 @@ class MisMigrationPatient extends Model /** * Выбывшие пациенты (все исходы) */ - public function scopeOutcomePatients($query, $branchId = null, DateRange $dateRange = null) + public function scopeOutcomePatients($query, $branchId = null, ?DateRange $dateRange = null) { $query->where('rf_MedicalHistoryID', '<>', 0); @@ -101,7 +102,7 @@ class MisMigrationPatient extends Model /** * Выписанные пациенты */ - public function scopeOutcomeDischarged($query, $branchId = null, DateRange $dateRange = null) + public function scopeOutcomeDischarged($query, $branchId = null, ?DateRange $dateRange = null) { // По уточненному SQL: Выписано за период $dischargeCodes = [1, 11, 2, 12, 7, 18, 48]; @@ -128,7 +129,7 @@ class MisMigrationPatient extends Model /** * Перевод в другое отделение */ - public function scopeOutcomeTransferred($query, $branchId = null, DateRange $dateRange = null) + public function scopeOutcomeTransferred($query, $branchId = null, ?DateRange $dateRange = null) { // По заданному SQL: только эти коды перевода $transferCodes = [4, 14]; @@ -155,7 +156,7 @@ class MisMigrationPatient extends Model /** * Умершие пациенты */ - public function scopeDeceasedOutcome($query, $branchId = null, DateRange $dateRange = null) + public function scopeDeceasedOutcome($query, $branchId = null, ?DateRange $dateRange = null) { // ID умершего $deceasedCodes = [5, 6, 15, 16]; @@ -179,7 +180,7 @@ class MisMigrationPatient extends Model return $query; } - public function scopeOutcomeWithoutTransferred($query, $branchId = null, DateRange $dateRange = null) + public function scopeOutcomeWithoutTransferred($query, $branchId = null, ?DateRange $dateRange = null) { // По заданной логике переводы только 4 и 14, исключаем их $query->whereNotIn('rf_kl_VisitResultID', [4, 14]) @@ -202,16 +203,17 @@ class MisMigrationPatient extends Model return $query; } - public function scopeExtractedToday($query, $branchId = null, DateRange $dateRange = null) + public function scopeExtractedToday($query, $branchId = null, ?DateRange $dateRange = null) { -// if (is_null($startDate)) $startDate = Carbon::now()->addDays(-1)->format('Y-m-d'); -// if (is_null($endDate)) $endDate = Carbon::now()->format('Y-m-d'); + // if (is_null($startDate)) $startDate = Carbon::now()->addDays(-1)->format('Y-m-d'); + // if (is_null($endDate)) $endDate = Carbon::now()->format('Y-m-d'); $query->where('rf_kl_VisitResultID', '<>', 0) ->where('rf_MedicalHistoryID', '<>', 0) - ->when($dateRange, function($query) use ($dateRange) { + ->when($dateRange, function ($query) use ($dateRange) { $startDate = Carbon::parse($dateRange->startSql())->toDateString(); $endDate = Carbon::parse($dateRange->endSql())->toDateString(); + return $query->whereHas('medicalHistory', function ($mhQuery) use ($startDate, $endDate) { $mhQuery->whereDate('DateExtract', '>', $startDate) ->whereDate('DateExtract', '<=', $endDate); diff --git a/app/Models/MisOperationPurpose.php b/app/Models/MisOperationPurpose.php index f176941..01f660b 100644 --- a/app/Models/MisOperationPurpose.php +++ b/app/Models/MisOperationPurpose.php @@ -14,4 +14,4 @@ class MisOperationPurpose extends Model { return $this->belongsTo(MisSurgicalOperation::class, 'rf_SurgicalOperationID', 'SurgicalOperationID'); } -} \ No newline at end of file +} diff --git a/app/Models/MisServiceMedical.php b/app/Models/MisServiceMedical.php index e8b30dd..93216ea 100644 --- a/app/Models/MisServiceMedical.php +++ b/app/Models/MisServiceMedical.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; class MisServiceMedical extends Model { protected $table = 'oms_servicemedical'; + protected $primaryKey = 'ServiceMedicalID'; public function surgicalOperations() diff --git a/app/Models/MisStationarBranch.php b/app/Models/MisStationarBranch.php index 42cd632..ffb27af 100644 --- a/app/Models/MisStationarBranch.php +++ b/app/Models/MisStationarBranch.php @@ -7,5 +7,6 @@ use Illuminate\Database\Eloquent\Model; class MisStationarBranch extends Model { protected $table = 'stt_stationarbranch'; + protected $primaryKey = 'StationarBranchID'; } diff --git a/app/Models/MisSurgicalOperation.php b/app/Models/MisSurgicalOperation.php index a68fc36..9ce8668 100644 --- a/app/Models/MisSurgicalOperation.php +++ b/app/Models/MisSurgicalOperation.php @@ -8,9 +8,11 @@ use LaravelIdea\Helper\App\Models\_IH_MisSurgicalOperation_QB; class MisSurgicalOperation extends Model { private const COMPLETED_OPERATION_STATUS_ID = 3; + private const CANCELLED_OPERATION_STATUS_ID = 4; protected $table = 'stt_surgicaloperation'; + protected $primaryKey = 'SurgicalOperationID'; public function serviceMedical() @@ -28,10 +30,9 @@ class MisSurgicalOperation extends Model return $this->hasOne(MisOperationPurpose::class, 'rf_SurgicalOperationID', 'SurgicalOperationID'); } - /** * Получить операции у которых исход <> 0 - * @param $query + * * @return _IH_MisSurgicalOperation_QB|_IH_MisSurgicalOperation_QB */ public function scopeCompleted($query) @@ -41,7 +42,7 @@ class MisSurgicalOperation extends Model /** * Получить операции с благоприятным исходом - * @param $query + * * @return _IH_MisSurgicalOperation_QB|_IH_MisSurgicalOperation_QB */ public function scopePositiveResult($query) @@ -51,7 +52,7 @@ class MisSurgicalOperation extends Model /** * Получить операции с неблагоприятным исходом - * @param $query + * * @return _IH_MisSurgicalOperation_QB|_IH_MisSurgicalOperation_QB */ public function scopeNotPositiveResult($query) @@ -59,10 +60,9 @@ class MisSurgicalOperation extends Model return $query->where('rf_OperationResultID', 2); } - /** * Получить отмененные операции через назначение - * @param $query + * * @return _IH_MisSurgicalOperation_QB */ public function scopeCanceled($query) diff --git a/app/Models/ObservationPatient.php b/app/Models/ObservationPatient.php index b0014c6..1bda160 100644 --- a/app/Models/ObservationPatient.php +++ b/app/Models/ObservationPatient.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; class ObservationPatient extends Model { protected $primaryKey = 'observation_patient_id'; + public $timestamps = false; protected $fillable = [ @@ -15,7 +16,7 @@ class ObservationPatient extends Model 'rf_mkab_id', 'rf_department_id', 'rf_report_id', - 'comment' + 'comment', ]; public function history() diff --git a/app/Models/Report.php b/app/Models/Report.php index 797bd8e..cdf6494 100644 --- a/app/Models/Report.php +++ b/app/Models/Report.php @@ -14,6 +14,7 @@ class Report extends Model const METRIC_BED_DAYS_ID = 18; protected $primaryKey = 'report_id'; + public $timestamps = false; protected $fillable = [ @@ -137,9 +138,9 @@ class Report extends Model // Сохраняем результат как метрику MetrikaResult::updateOrCreate([ - 'rf_report_id' => $this->report_id, - 'rf_metrika_item_id' => self::METRIC_BED_DAYS_ID, - ], + 'rf_report_id' => $this->report_id, + 'rf_metrika_item_id' => self::METRIC_BED_DAYS_ID, + ], [ 'value' => $avgBedDays, ]); diff --git a/app/Models/User.php b/app/Models/User.php index c854290..af71637 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,7 +5,6 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Foundation\Auth\User as Authenticatable; @@ -17,7 +16,7 @@ use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable, HasApiTokens; + use HasApiTokens, HasFactory, Notifiable; /** * The attributes that are mass assignable. @@ -30,7 +29,7 @@ class User extends Authenticatable 'password', 'rf_lpudoctor_id', 'rf_department_id', - 'current_role_id' + 'current_role_id', ]; /** @@ -99,10 +98,10 @@ class User extends Authenticatable $sessionId = session()->getId(); $token = Auth::user()->currentAccessToken(); -// $sessionKey = 'user_' . $this->id . '_current_role'; -// $roleId = $this->current_role_id ?? $defaultRoleId; -// -// $role = Role::where('role_id', $roleId)->first(); + // $sessionKey = 'user_' . $this->id . '_current_role'; + // $roleId = $this->current_role_id ?? $defaultRoleId; + // + // $role = Role::where('role_id', $roleId)->first(); if ($token) { foreach ($token->abilities as $ability) { @@ -121,7 +120,7 @@ class User extends Authenticatable $sessionRoleId = DB::table('sessions')->where('id', $sessionId)->value('role_id'); $roleId = $sessionRoleId ?? $defaultRoleId; -// dd($sessionId); + // dd($sessionId); $role = Role::where('role_id', $roleId)->first(); return $role; diff --git a/app/Models/UserDepartment.php b/app/Models/UserDepartment.php index a184055..1ac09ce 100644 --- a/app/Models/UserDepartment.php +++ b/app/Models/UserDepartment.php @@ -13,7 +13,7 @@ class UserDepartment extends Model 'rf_department_id', 'is_favorite', 'order', - 'user_name' + 'user_name', ]; protected $casts = [ diff --git a/app/Services/AutoReportService.php b/app/Services/AutoReportService.php index 0ef475a..699faae 100644 --- a/app/Services/AutoReportService.php +++ b/app/Services/AutoReportService.php @@ -3,7 +3,6 @@ namespace App\Services; use App\Models\Department; -use App\Models\DepartmentPatient; use App\Models\MedicalHistorySnapshot; use App\Models\MetrikaResult; use App\Models\ObservationPatient; @@ -73,7 +72,7 @@ class AutoReportService ->exactPeriod($dateRange->startSql(), $dateRange->endSql()) ->first(); - if ($existingReport && !$force) { + if ($existingReport && ! $force) { return false; // Отчет уже существует } @@ -120,5 +119,4 @@ class AutoReportService DB::table('reports')->where('report_id', $report->report_id)->delete(); }); } - } diff --git a/app/Services/Base/BaseMetricService.php b/app/Services/Base/BaseMetricService.php index af348c9..d827f33 100644 --- a/app/Services/Base/BaseMetricService.php +++ b/app/Services/Base/BaseMetricService.php @@ -1,18 +1,19 @@ calculate($departmentIds, $startDate, $endDate); $this->cache[$cacheKey] = $result; + return $result; } catch (\Exception $e) { - Log::error("Error in " . static::class . ": " . $e->getMessage()); + Log::error('Error in '.static::class.': '.$e->getMessage()); + return array_fill_keys($departmentIds, 0); } } protected function getCacheKey(array $departmentIds, string $startDate, string $endDate): string { - return static::class . '_' . md5(implode(',', $departmentIds) . $startDate . $endDate); + return static::class.'_'.md5(implode(',', $departmentIds).$startDate.$endDate); } public function clearCache(): void diff --git a/app/Services/BedDayService.php b/app/Services/BedDayService.php index ed44364..1379c12 100644 --- a/app/Services/BedDayService.php +++ b/app/Services/BedDayService.php @@ -1,15 +1,13 @@ cache[$cacheKey])) { return $this->cache[$cacheKey]; @@ -47,6 +45,7 @@ class BedDayService if ($reports->isEmpty()) { $this->cache[$cacheKey] = 0; + return 0; } @@ -59,6 +58,7 @@ class BedDayService if ($snapshots->isEmpty()) { $this->cache[$cacheKey] = 0; + return 0; } @@ -96,7 +96,7 @@ class BedDayService return []; } - $cacheKey = 'all_snapshots_' . md5(implode(',', $departmentIds) . $startDate . $endDate . ($isRangeOneDay ? '1day' : 'range')); + $cacheKey = 'all_snapshots_'.md5(implode(',', $departmentIds).$startDate.$endDate.($isRangeOneDay ? '1day' : 'range')); if (isset($this->cache[$cacheKey])) { return $this->cache[$cacheKey]; @@ -121,6 +121,7 @@ class BedDayService if ($departmentReports->isEmpty()) { $averages[$departmentId] = 0; + continue; } @@ -135,6 +136,7 @@ class BedDayService if ($snapshots->isEmpty()) { $averages[$departmentId] = 0; + continue; } @@ -199,7 +201,7 @@ class BedDayService 'total_patients' => 0, 'average_bed_days' => 0, 'distribution' => [], - 'by_month' => [] + 'by_month' => [], ]; } @@ -216,7 +218,7 @@ class BedDayService 'total_patients' => 0, 'average_bed_days' => 0, 'distribution' => [], - 'by_month' => [] + 'by_month' => [], ]; } @@ -226,7 +228,7 @@ class BedDayService '8-14' => 0, '15-21' => 0, '22-30' => 0, - '30+' => 0 + '30+' => 0, ]; $byMonth = []; @@ -245,16 +247,23 @@ class BedDayService $validCount++; // Распределение - if ($days <= 3) $distribution['1-3']++; - elseif ($days <= 7) $distribution['4-7']++; - elseif ($days <= 14) $distribution['8-14']++; - elseif ($days <= 21) $distribution['15-21']++; - elseif ($days <= 30) $distribution['22-30']++; - else $distribution['30+']++; + if ($days <= 3) { + $distribution['1-3']++; + } elseif ($days <= 7) { + $distribution['4-7']++; + } elseif ($days <= 14) { + $distribution['8-14']++; + } elseif ($days <= 21) { + $distribution['15-21']++; + } elseif ($days <= 30) { + $distribution['22-30']++; + } else { + $distribution['30+']++; + } // По месяцам $month = $end->format('Y-m'); - if (!isset($byMonth[$month])) { + if (! isset($byMonth[$month])) { $byMonth[$month] = ['total' => 0, 'count' => 0]; } $byMonth[$month]['total'] += $days; @@ -269,7 +278,7 @@ class BedDayService $monthlyStats[] = [ 'month' => $month, 'avg_days' => round($data['total'] / $data['count'], 1), - 'count' => $data['count'] + 'count' => $data['count'], ]; } @@ -279,7 +288,7 @@ class BedDayService 'total_patients' => $validCount, 'average_bed_days' => $validCount > 0 ? round($totalDays / $validCount, 1) : 0, 'distribution' => $distribution, - 'by_month' => $monthlyStats + 'by_month' => $monthlyStats, ]; } diff --git a/app/Services/Cache/CacheKeyBuilder.php b/app/Services/Cache/CacheKeyBuilder.php index 49f52ad..88ce5c1 100644 --- a/app/Services/Cache/CacheKeyBuilder.php +++ b/app/Services/Cache/CacheKeyBuilder.php @@ -10,7 +10,7 @@ class CacheKeyBuilder public function make(string $key, array $parts = []): string { - $suffix = empty($parts) ? '' : ':' . implode(':', array_map(static fn ($part) => (string) $part, $parts)); + $suffix = empty($parts) ? '' : ':'.implode(':', array_map(static fn ($part) => (string) $part, $parts)); return "{$this->version}:{$key}{$suffix}"; } diff --git a/app/Services/CurrentPatientService.php b/app/Services/CurrentPatientService.php index 86cfdbf..3d621d0 100644 --- a/app/Services/CurrentPatientService.php +++ b/app/Services/CurrentPatientService.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\DB; class CurrentPatientService { const PLAN_STATUSES = [1]; + const EMERGENCY_STATUSES = [2, 3]; public function getCurrentMedicalHistoryIds(string $type, int $branchId, DateRange $dateRange, bool $fillableAuto = false) @@ -16,7 +17,7 @@ class CurrentPatientService return $this->getHistoricalCurrentMedicalHistoryIds($type, $branchId, $dateRange); } - $typeIds = match($type) { + $typeIds = match ($type) { 'plan' => self::PLAN_STATUSES, 'emergency' => self::EMERGENCY_STATUSES, default => null @@ -34,7 +35,7 @@ class CurrentPatientService { $endAt = $dateRange->endSql(); - $typeIds = match($type) { + $typeIds = match ($type) { 'plan' => self::PLAN_STATUSES, 'emergency' => self::EMERGENCY_STATUSES, default => null diff --git a/app/Services/DateRange.php b/app/Services/DateRange.php index bcd3f76..c5b4576 100644 --- a/app/Services/DateRange.php +++ b/app/Services/DateRange.php @@ -12,7 +12,7 @@ readonly class DateRange public Carbon $endDate, public string $startDateRaw, public string $endDateRaw, - public bool $isOneDay + public bool $isOneDay ) {} /** diff --git a/app/Services/DateRangeService.php b/app/Services/DateRangeService.php index 66369e6..83c43d4 100644 --- a/app/Services/DateRangeService.php +++ b/app/Services/DateRangeService.php @@ -92,7 +92,7 @@ class DateRangeService return [ $startDate->format('Y-m-d H:i:s'), - $endDate->format('Y-m-d H:i:s') + $endDate->format('Y-m-d H:i:s'), ]; } @@ -107,7 +107,9 @@ class DateRangeService public function isRangeOneDay($startAt = null, $endAt = null): bool { - if (!$startAt || !$endAt) return false; + if (! $startAt || ! $endAt) { + return false; + } $startDate = $this->parseDate($startAt); $endDate = $this->parseDate($endAt); @@ -130,7 +132,7 @@ class DateRangeService return [ $startDate->format('Y-m-d H:i:s'), - $endDate->format('Y-m-d H:i:s') + $endDate->format('Y-m-d H:i:s'), ]; } @@ -145,7 +147,7 @@ class DateRangeService return [ $startDate->format('Y-m-d H:i:s'), - $endDate->format('Y-m-d H:i:s') + $endDate->format('Y-m-d H:i:s'), ]; } diff --git a/app/Services/MetricCalculators/AverageBedDaysCalculator.php b/app/Services/MetricCalculators/AverageBedDaysCalculator.php index 5066243..3726640 100644 --- a/app/Services/MetricCalculators/AverageBedDaysCalculator.php +++ b/app/Services/MetricCalculators/AverageBedDaysCalculator.php @@ -1,10 +1,11 @@ avg_value, 1) + ? round((float) $results[$deptId]->avg_value, 1) : 0; } diff --git a/app/Services/MetricCalculators/LethalityCalculator.php b/app/Services/MetricCalculators/LethalityCalculator.php index 146e546..47dc16d 100644 --- a/app/Services/MetricCalculators/LethalityCalculator.php +++ b/app/Services/MetricCalculators/LethalityCalculator.php @@ -1,10 +1,11 @@ rf_metrika_item_id == 9) $deceased = (int)$item->total; - else $discharged = (int)$item->total; + if ($item->rf_metrika_item_id == 9) { + $deceased = (int) $item->total; + } else { + $discharged = (int) $item->total; + } } } diff --git a/app/Services/MetricCalculators/PreoperativeDaysCalculator.php b/app/Services/MetricCalculators/PreoperativeDaysCalculator.php index 014c20c..1c30192 100644 --- a/app/Services/MetricCalculators/PreoperativeDaysCalculator.php +++ b/app/Services/MetricCalculators/PreoperativeDaysCalculator.php @@ -1,12 +1,13 @@ rf_report_id]->rf_department_id; $historyId = $snapshot->rf_medicalhistory_id; - if (!isset($operations[$historyId])) { + if (! isset($operations[$historyId])) { continue; } @@ -77,7 +78,7 @@ class PreoperativeDaysCalculator extends BaseMetricService implements MetricCalc ->diffInDays(Carbon::parse($op->first_operation)); if ($days >= 0) { - if (!isset($results[$deptId])) { + if (! isset($results[$deptId])) { $results[$deptId] = ['total' => 0, 'count' => 0]; } $results[$deptId]['total'] += $days; diff --git a/app/Services/MetrikaService.php b/app/Services/MetrikaService.php index fd07623..fe7b741 100644 --- a/app/Services/MetrikaService.php +++ b/app/Services/MetrikaService.php @@ -2,7 +2,6 @@ namespace App\Services; -use App\Models\Report; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; @@ -15,8 +14,7 @@ class MetrikaService array $departmentIds, string $startDate, string $endDate - ): array - { + ): array { if (empty($departmentIds)) { return []; } @@ -44,8 +42,9 @@ class MetrikaService $preoperativeDays = []; foreach ($departmentIds as $deptId) { - if (!isset($results[$deptId]) || $results[$deptId]->isEmpty()) { + if (! isset($results[$deptId]) || $results[$deptId]->isEmpty()) { $preoperativeDays[$deptId] = 0; + continue; } @@ -69,7 +68,8 @@ class MetrikaService return $preoperativeDays; } catch (\Exception $e) { - \Log::error("Error in calculatePreoperativeDaysFromSnapshots: " . $e->getMessage()); + \Log::error('Error in calculatePreoperativeDaysFromSnapshots: '.$e->getMessage()); + return array_fill_keys($departmentIds, 0); } } diff --git a/app/Services/MisPatientService.php b/app/Services/MisPatientService.php index 6fbce98..7bf98b1 100644 --- a/app/Services/MisPatientService.php +++ b/app/Services/MisPatientService.php @@ -3,7 +3,6 @@ namespace App\Services; use App\Models\MisMedicalHistory; -use App\Models\MisMigrationPatient; class MisPatientService { @@ -24,7 +23,7 @@ class MisPatientService */ public function getInStationarPatients(string $status, $branchId, string|array $dateRange) { - $emerSign = $status === 'plan' ? 1 : [2,4]; + $emerSign = $status === 'plan' ? 1 : [2, 4]; $query = MisMedicalHistory::query(); $query->when(isset($branchId), function ($query) use ($branchId, $dateRange) { $query->with(['migrations', 'surgicalOperations']) @@ -50,11 +49,11 @@ class MisPatientService $query = MisMedicalHistory::query(); $query->when(isset($branchId), function ($query) use ($branchId, $dateRange) { $query->with('migrations') - ->whereHas('migrations', function ($q) use ($branchId, $dateRange) { - $q->where('rf_StationarBranchID', $branchId) - ->whereDate('DateRecipient', '<', $dateRange[1]); - }); - }) + ->whereHas('migrations', function ($q) use ($branchId, $dateRange) { + $q->where('rf_StationarBranchID', $branchId) + ->whereDate('DateRecipient', '<', $dateRange[1]); + }); + }) ->where('rf_EmerSignID', 1) ->where('MedicalHistoryID', '<>', 0); @@ -73,7 +72,7 @@ class MisPatientService $q->where('rf_StationarBranchID', $branchId) ->where('DateIngoing', '>', $dateRange[0]) ->where('DateIngoing', '<=', $dateRange[1]); -// ->whereBetween('DateIngoing', $dateRange); + // ->whereBetween('DateIngoing', $dateRange); }); }) ->whereIn('rf_EmerSignID', [2, 4]) @@ -84,9 +83,9 @@ class MisPatientService /** * Получение запроса пациентов которые попали в отделение в определенную дату - * @param int $branchId Индентификатор ветки стационара - * @param array $dateRange Массив дат (0 - начальная дата, 1 - конечная дата) - * @return \Illuminate\Database\Eloquent\Builder + * + * @param int $branchId Индентификатор ветки стационара + * @param array $dateRange Массив дат (0 - начальная дата, 1 - конечная дата) */ public function getRecipientPatientsQuery(int $branchId, array $dateRange): \Illuminate\Database\Eloquent\Builder { @@ -96,7 +95,7 @@ class MisPatientService $q->where('rf_StationarBranchID', $branchId) ->where('DateIngoing', '>=', $dateRange[0]) ->where('DateIngoing', '<=', $dateRange[1]); -// ->whereBetween('DateIngoing', $dateRange); + // ->whereBetween('DateIngoing', $dateRange); })->where('MedicalHistoryID', '<>', 0); return $query; diff --git a/app/Services/PatientMigrationService.php b/app/Services/PatientMigrationService.php index 0c00fed..d3ff024 100644 --- a/app/Services/PatientMigrationService.php +++ b/app/Services/PatientMigrationService.php @@ -11,14 +11,16 @@ use Illuminate\Support\Facades\Cache; class PatientMigrationService { private const ACTIVE_DATE_OUT = '2222-01-01 00:00:00'; + const DEAD_VISIT_RESULT_IDS = [5, 6, 15, 16]; + const TRANSFER_VISIT_RESULT_IDS = [2, 3, 4, 12, 13, 14, 35]; + const OUTCOME_VISIT_RESULT_IDS = [1, 11]; public function __construct( protected StationarBranchService $branchService - ) { } - + ) {} /** * Получить всех пациентов в отделении за период @@ -30,7 +32,7 @@ class PatientMigrationService startAt: $startAt, endAt: $endAt, cacheTag: 'migrations_in_branch', - dateFilter: fn($q) => $q->where('DateOut', '>', $startAt)->where('DateOut', '<=', $endAt), + dateFilter: fn ($q) => $q->where('DateOut', '>', $startAt)->where('DateOut', '<=', $endAt), ); } @@ -44,7 +46,7 @@ class PatientMigrationService startAt: $startAt, endAt: $endAt, cacheTag: 'migrations_in_branch_current', - dateFilter: fn($q) => $q->where('DateOut', self::ACTIVE_DATE_OUT), + dateFilter: fn ($q) => $q->where('DateOut', self::ACTIVE_DATE_OUT), ); } @@ -58,12 +60,12 @@ class PatientMigrationService startAt: $startAt, endAt: $endAt, cacheTag: 'migrations_in_branch_dead', - dateFilter: fn($q) => $q->where('DateOut', '>', $startAt)->where('DateOut', '<=', $endAt), + dateFilter: fn ($q) => $q->where('DateOut', '>', $startAt)->where('DateOut', '<=', $endAt), additionalFilters: [ [ 'key' => 'visit_result:dead', - 'apply' => fn($q) => $q->whereIn('rf_kl_VisitResultID', self::DEAD_VISIT_RESULT_IDS) - ] + 'apply' => fn ($q) => $q->whereIn('rf_kl_VisitResultID', self::DEAD_VISIT_RESULT_IDS), + ], ] ); } @@ -75,12 +77,12 @@ class PatientMigrationService startAt: $startAt, endAt: $endAt, cacheTag: 'migrations_in_branch_transfer', - dateFilter: fn($q) => $q->where('DateOut', '>', $startAt)->where('DateOut', '<=', $endAt), + dateFilter: fn ($q) => $q->where('DateOut', '>', $startAt)->where('DateOut', '<=', $endAt), additionalFilters: [ [ 'key' => 'visit_result:transfer', - 'apply' => fn($q) => $q->whereIn('rf_kl_VisitResultID', self::TRANSFER_VISIT_RESULT_IDS) - ] + 'apply' => fn ($q) => $q->whereIn('rf_kl_VisitResultID', self::TRANSFER_VISIT_RESULT_IDS), + ], ] ); } @@ -92,12 +94,12 @@ class PatientMigrationService startAt: $startAt, endAt: $endAt, cacheTag: 'migrations_in_branch_outcome', - dateFilter: fn($q) => $q->where('DateOut', '>', $startAt)->where('DateOut', '<=', $endAt), + dateFilter: fn ($q) => $q->where('DateOut', '>', $startAt)->where('DateOut', '<=', $endAt), additionalFilters: [ [ 'key' => 'visit_result:outcome', - 'apply' => fn($q) => $q->whereIn('rf_kl_VisitResultID', self::OUTCOME_VISIT_RESULT_IDS) - ] + 'apply' => fn ($q) => $q->whereIn('rf_kl_VisitResultID', self::OUTCOME_VISIT_RESULT_IDS), + ], ] ); } @@ -121,7 +123,7 @@ class PatientMigrationService // Нормализованный ключ кеша $filterKeys = array_column($additionalFilters, 'key'); $filterHash = substr(md5(implode('|', $filterKeys)), 0, 6); - $dateHash = substr(md5($startAt . '_' . $endAt), 0, 8); + $dateHash = substr(md5($startAt.'_'.$endAt), 0, 8); $cacheKey = "{$cacheTag}_{$branchId}_{$dateHash}_f{$filterHash}"; return Cache::tags([$cacheTag, "migrations_branch_{$branchId}"]) @@ -151,28 +153,28 @@ class PatientMigrationService ->select([ 'MigrationPatientID', 'DateIngoing', 'DateOut', 'BedDays', 'rf_MedicalHistoryID', 'rf_StationarBranchID', 'rf_DiagnosID', 'rf_kl_ProfitTypeID', 'rf_kl_StatCureResultID', - 'rf_kl_VisitResultID', 'rf_BedProfileID', 'rf_kl_BedProfileID' + 'rf_kl_VisitResultID', 'rf_BedProfileID', 'rf_kl_BedProfileID', ]) ->where('rf_StationarBranchID', $branchId) // Жадная загрузка с ограничениями ->with([ - 'medicalHistory' => fn($q) => $q->select('MedicalHistoryID', 'FAMILY', 'Name', 'OT', 'BD'), - 'medicalHistory.operationPurpose' => fn($q) => $q + 'medicalHistory' => fn ($q) => $q->select('MedicalHistoryID', 'FAMILY', 'Name', 'OT', 'BD'), + 'medicalHistory.operationPurpose' => fn ($q) => $q ->where('rf_StationarBranchID', $branchId) ->select('OperationPurposeID', 'rf_MedicalHistoryID', 'Date', 'rf_OperationStatusID', 'CancelDate', 'rf_StationarBranchID', 'PhysicalExam', 'Description', 'Indications', 'EpicrisDate', 'rf_SurgicalOperationID' ), - 'medicalHistory.operationPurpose.surgicalOperation' => fn($q) => $q + 'medicalHistory.operationPurpose.surgicalOperation' => fn ($q) => $q ->select('SurgicalOperationID', 'DataEnd', 'Date', 'Num', 'rf_kl_ServiceMedicalID', 'rf_MedicalHistoryID', 'rf_StationarBranchID', 'Description', 'rf_OperationResultID' ), - 'diagnosis' => fn($q) => $q + 'diagnosis' => fn ($q) => $q ->where('rf_DiagnosTypeID', 3) ->select('DiagnosID', 'Date', 'rf_DiagnosTypeID', 'rf_MedicalHistoryID', 'rf_MKBID', 'rf_MigrationPatientID', 'Description' ), - 'diagnosis.mkb' => fn($q) => $q->select(['MKBID', 'DS', 'NAME']), + 'diagnosis.mkb' => fn ($q) => $q->select(['MKBID', 'DS', 'NAME']), ]); } } diff --git a/app/Services/PatientService.php b/app/Services/PatientService.php index d4c246d..10ab8da 100644 --- a/app/Services/PatientService.php +++ b/app/Services/PatientService.php @@ -6,7 +6,6 @@ use App\Models\MisMedicalHistory; use App\Models\MisMigrationPatient; use App\Models\MisReanimation; use App\Models\MisSurgicalOperation; -use App\Models\ObservationPatient; class PatientService { @@ -14,8 +13,7 @@ class PatientService protected OutcomePatientService $outcomePatientService, protected RecipientPatientService $recipientPatientService, protected CurrentPatientService $currentPatientService - ) - { } + ) {} /** * Получить плановых или экстренных пациентов @@ -63,6 +61,7 @@ class PatientService return $res->get() ->map(function ($patient) use ($recipientIds) { $patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds, true); + return $patient; }); @@ -94,7 +93,10 @@ class PatientService $allIds = array_unique(array_merge($recipientIds, $currentIds)); if (empty($allIds)) { - if ($countOnly) return 0; + if ($countOnly) { + return 0; + } + return collect(); } @@ -179,10 +181,11 @@ class PatientService ]) ->orderBy('DateRecipient', 'DESC'); - return $res->get() - ->map(function ($patient) use ($recipientIds) { - $patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds); - return $patient; + return $res->get() + ->map(function ($patient) use ($recipientIds) { + $patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds); + + return $patient; }); } @@ -195,7 +198,7 @@ class PatientService $q->where('rf_department_id', $departmentId); }); - if (!$onlyIds) { + if (! $onlyIds) { $query->with(['observationPatient' => function ($query) use ($departmentId) { $query->where('rf_department_id', $departmentId) ->select(['rf_medicalhistory_id', 'observation_patient_id', 'comment']); @@ -203,8 +206,9 @@ class PatientService ->orderBy('DateRecipient', 'DESC'); } - if ($onlyIds) $patients = $query->pluck('MedicalHistoryID'); - else { + if ($onlyIds) { + $patients = $query->pluck('MedicalHistoryID'); + } else { $query->with([ 'outcomeMigration.mainDiagnosis.mkb', ]); @@ -216,6 +220,7 @@ class PatientService ->pluck('comment') ->filter() ->implode('; '); + return $patient; }); } @@ -233,28 +238,58 @@ class PatientService } /** - * Получить пациентов, находящихся в реанимации на конец периода + * Получить пациентов с записями в реанимации по отделению */ public function getReanimationPatients( int $branchId, DateRange $dateRange, bool $onlyIds = false ) { - $medicalHistoryIds = MisReanimation::query() + $currentIds = $this->getAllPatientsInDepartment( + true, + $branchId, + $dateRange, + false, + true, + false + )->all(); + + $outcomeIds = $this->getOutcomePatients( + $branchId, + $dateRange, + 'all', + true + )->all(); + + $reportCohortIds = array_values(array_unique(array_merge($currentIds, $outcomeIds))); + + if (empty($reportCohortIds)) { + return collect(); + } + + $reanimationByMedicalHistory = MisReanimation::query() ->join('stt_migrationpatient as mp', 'mp.MigrationPatientID', '=', 'stt_reanimation.rf_MigrationPatientID') - ->where('stt_reanimation.rf_StationarBranchID', $branchId) ->where('mp.rf_StationarBranchID', $branchId) ->where('mp.rf_MedicalHistoryID', '<>', 0) - ->where('stt_reanimation.DateIn', '<=', $dateRange->endSql()) - ->where(function ($query) use ($dateRange) { - $query->where('stt_reanimation.DateOut', '>=', $dateRange->endSql()) - ->orWhereNull('stt_reanimation.DateOut') - ->orWhereDate('stt_reanimation.DateOut', '1900-01-01') - ->orWhereDate('stt_reanimation.DateOut', '2222-01-01'); - }) - ->distinct() - ->pluck('mp.rf_MedicalHistoryID') - ->toArray(); + ->whereIn('mp.rf_MedicalHistoryID', $reportCohortIds) + ->selectRaw(' + mp."rf_MedicalHistoryID" as medical_history_id, + MAX(stt_reanimation."DateIn") as reanimation_date_in, + BOOL_OR(COALESCE(stt_reanimation."isComplete", false)) as reanimation_is_complete + ') + ->groupBy('mp.rf_MedicalHistoryID') + ->get(); + + $medicalHistoryIds = $reanimationByMedicalHistory + ->pluck('medical_history_id') + ->map(fn ($id) => (int) $id) + ->values() + ->all(); + + $reanimationDateByMedicalHistory = $reanimationByMedicalHistory + ->pluck('reanimation_date_in', 'medical_history_id'); + $reanimationCompleteByMedicalHistory = $reanimationByMedicalHistory + ->pluck('reanimation_is_complete', 'medical_history_id'); if (empty($medicalHistoryIds)) { return collect(); @@ -312,7 +347,16 @@ class PatientService }, ]) ->orderBy('DateRecipient', 'DESC') - ->get(); + ->get() + ->map(function ($patient) use ($reanimationDateByMedicalHistory, $reanimationCompleteByMedicalHistory) { + $reanimationDateIn = $reanimationDateByMedicalHistory->get($patient->MedicalHistoryID); + if ($reanimationDateIn) { + $patient->DateRecipient = $reanimationDateIn; + } + $patient->reanimation_is_complete = (bool) $reanimationCompleteByMedicalHistory->get($patient->MedicalHistoryID, false); + + return $patient; + }); } /** @@ -325,9 +369,9 @@ class PatientService bool $countOnly = false ) { $query = MisSurgicalOperation::where('rf_StationarBranchID', $branchId) - ->completed() - ->where('Date', '>=', $dateRange->startSql()) - ->where('Date', '<=', $dateRange->endSql()); + ->completed() + ->where('Date', '>=', $dateRange->startSql()) + ->where('Date', '<=', $dateRange->endSql()); if ($type === 'plan') { $query->where('rf_TypeSurgOperationInTimeID', 6); diff --git a/app/Services/RecipientPatientService.php b/app/Services/RecipientPatientService.php index 12cc653..f4653d4 100644 --- a/app/Services/RecipientPatientService.php +++ b/app/Services/RecipientPatientService.php @@ -2,15 +2,13 @@ namespace App\Services; -use App\Models\MisMigrationPatient; use Illuminate\Support\Facades\DB; class RecipientPatientService { public function __construct( - protected CurrentPatientService $currentPatientService - ) - { } + protected CurrentPatientService $currentPatientService + ) {} // Получить IDs поступивших public function getRecipientMedicalHistoryIds( @@ -90,7 +88,7 @@ class RecipientPatientService $dateRange ); - if (!$includeCurrent) { + if (! $includeCurrent) { return array_values(array_unique($recipientIds)); } diff --git a/app/Services/ReportService.php b/app/Services/ReportService.php index 5dfe7fd..d9d77cd 100644 --- a/app/Services/ReportService.php +++ b/app/Services/ReportService.php @@ -5,19 +5,19 @@ namespace App\Services; use App\Models\Department; use App\Models\DepartmentPatient; use App\Models\DepartmentPatientOperation; -use App\Models\MisServiceMedical; use App\Models\MedicalHistorySnapshot; use App\Models\MetrikaResult; use App\Models\MisLpuDoctor; -use App\Models\MisMigrationPatient; use App\Models\MisMedicalHistory; +use App\Models\MisMigrationPatient; +use App\Models\MisServiceMedical; use App\Models\MisStationarBranch; use App\Models\ObservationPatient; use App\Models\Report; +use App\Models\ReanimationPatientIndicator; use App\Models\UnwantedEvent; use App\Models\User; use Carbon\Carbon; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; @@ -88,7 +88,7 @@ class ReportService $branchId, $dateRange, true - ) + ), ]; $manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange); $surgicalCount = [ @@ -117,7 +117,7 @@ class ReportService 'planCount' => $planCount, 'emergencyCount' => $emergencyCount, 'percentDead' => $percentDead, - 'beds' => $beds->value + 'beds' => $beds->value, ]; } @@ -139,33 +139,23 @@ class ReportService $this->saveObservationPatients($report, $data['observationPatients'] ?? [], $user->rf_department_id); - $shouldBuildSnapshots = (bool) $fillableAuto; - if ($shouldBuildSnapshots) { - $this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto); + $this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto); - $this->syncCalculatedMetrics($report, $user, $data); - } else { - MedicalHistorySnapshot::query() - ->where('rf_report_id', $report->report_id) - ->delete(); - } + $this->syncCalculatedMetrics($report, $user, $data); return $report; }); - $shouldBuildDerivedMetrics = (bool) $fillableAuto; - if ($shouldBuildDerivedMetrics) { - DB::transaction(function () use ($report) { - // Сохраняем метрику койко-дня + среднего койко-дня из снапшотов - $this->saveBedDaysMetric($report); + DB::transaction(function () use ($report) { + // Сохраняем метрику койко-дня + среднего койко-дня из снапшотов + $this->saveBedDaysMetric($report); - $this->saveLethalMetricFromSnapshots($report); + $this->saveLethalMetricFromSnapshots($report); - $this->savePreoperativeMetric($report); + $this->savePreoperativeMetric($report); - $this->saveDepartmentLoadedMetric($report); - }); - } + $this->saveDepartmentLoadedMetric($report); + }); } catch (\Throwable $e) { throw $e; } @@ -367,7 +357,7 @@ class ReportService ['value' => $result['avg_days']] ); } catch (\Throwable $e) { - \Log::error('Failed to save bed days metric: ' . $e->getMessage()); + \Log::error('Failed to save bed days metric: '.$e->getMessage()); } } @@ -384,31 +374,31 @@ class ReportService foreach ($snapshots as $snapshot) { $history = $snapshot->medicalHistory; - if (!$history) { + if (! $history) { continue; } $start = $history->DateRecipientHS ?? $history->DateRecipient ?? null; - if (!$start) { + if (! $start) { continue; } $end = null; if ($snapshot->patient_type === 'deceased') { - if ($history->DateDeath && !in_array($history->DateDeath->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) { + if ($history->DateDeath && ! in_array($history->DateDeath->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) { $end = $history->DateDeath; - } elseif ($history->DateExtract && !in_array($history->DateExtract->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) { + } elseif ($history->DateExtract && ! in_array($history->DateExtract->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) { $end = $history->DateExtract; } } else { - if ($history->DateExtract && !in_array($history->DateExtract->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) { + if ($history->DateExtract && ! in_array($history->DateExtract->format('Y-m-d'), ['1900-01-01', '2222-01-01'], true)) { $end = $history->DateExtract; } } - if (!$end) { + if (! $end) { continue; } @@ -485,7 +475,7 @@ class ReportService $startRaw = $row->DateRecipientHS ?? $row->DateRecipient ?? null; $operationRaw = $row->first_operation ?? null; - if (!$startRaw || !$operationRaw) { + if (! $startRaw || ! $operationRaw) { continue; } @@ -529,6 +519,7 @@ class ReportService ); \Log::info("No discharged patients in report {$report->report_id}, saved 0"); + return; } } @@ -542,9 +533,10 @@ class ReportService $result = $this->calculatePreoperativeDaysFromSnapshots($report); $this->saveMetric($report, 26, $result['total_days']); + $this->saveMetric($report, 27, $result['patient_count']); $this->saveMetric($report, 21, $result['avg_days']); } catch (\Throwable $e) { - \Log::error('Failed to save preoperative total metric: ' . $e->getMessage()); + \Log::error('Failed to save preoperative total metric: '.$e->getMessage()); } } @@ -568,10 +560,10 @@ class ReportService private function clearCacheAfterReportCreation(User $user, Report $report): void { // Очищаем кэш статистики для пользователя -// $this->statisticsService->clearStatisticsCache($user); + // $this->statisticsService->clearStatisticsCache($user); // Также можно очистить кэш для всех пользователей отдела -// $this->statisticsService->clearDepartmentStatisticsCache($user->rf_department_id); + // $this->statisticsService->clearDepartmentStatisticsCache($user->rf_department_id); // Очищаем кэш за сегодня и вчера (так как отчеты влияют на эти даты) $this->clearDailyCache($user, $report->created_at); @@ -595,7 +587,7 @@ class ReportService private function generateDailyCacheKey(User $user, string $date): string { - return 'daily_stats:' . $user->rf_department_id . ':' . $date; + return 'daily_stats:'.$user->rf_department_id.':'.$date; } /** @@ -625,6 +617,7 @@ class ReportService ); } + // Для реанимации всегда берем live-данные из реплики. if ($baseStatus === 'reanimation') { return $this->getPatientsFromReplica( $department, @@ -637,7 +630,7 @@ class ReportService ); } - $useSnapshots = !$this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange) + $useSnapshots = ! $this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange) && $this->shouldUseSnapshots($department, $user, $dateRange, $beforeCreate); if ($useSnapshots) { @@ -675,7 +668,7 @@ class ReportService return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId); } - $useSnapshots = !$this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange) + $useSnapshots = ! $this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange) && $this->shouldUseSnapshots($department, $user, $dateRange); if ($useSnapshots) { @@ -757,7 +750,7 @@ class ReportService } $report = $this->getReportForPeriod($department->department_id, $dateRange); - if (!$report) { + if (! $report) { return false; } @@ -774,7 +767,7 @@ class ReportService return false; } - return in_array($status, ['plan', 'emergency', 'recipient', 'current'], true) + return in_array($status, ['plan', 'emergency', 'recipient', 'current', 'reanimation'], true) && $dateRange->isOneDay && $dateRange->isEndDateToday(); } @@ -819,7 +812,7 @@ class ReportService MetrikaResult::create([ 'rf_report_id' => $report->report_id, 'rf_metrika_item_id' => 1, - 'value' => $beds->value + 'value' => $beds->value, ]); } @@ -832,7 +825,7 @@ class ReportService private function saveMetrics(Report $report, array $metrics): void { foreach ($metrics as $key => $value) { - $metrikaId = (int)str_replace('metrika_item_', '', $key); + $metrikaId = (int) str_replace('metrika_item_', '', $key); MetrikaResult::updateOrCreate( [ @@ -870,6 +863,7 @@ class ReportService if (empty($unwantedEvents)) { $report->unwantedEvents()->delete(); $this->saveMetric($report, 16, 0); + return; } @@ -912,6 +906,7 @@ class ReportService ->delete(); // Обновить метрику $this->saveMetric($report, 14, 0); + return; } @@ -936,12 +931,12 @@ class ReportService private function syncCalculatedMetrics(Report $report, User $user, array $data): void { - if (!isset($data['dates'][0], $data['dates'][1])) { + if (! isset($data['dates'][0], $data['dates'][1])) { return; } $department = Department::query()->where('department_id', $report->rf_department_id)->first(); - if (!$department) { + if (! $department) { return; } @@ -1017,7 +1012,7 @@ class ReportService $reportToday = $this->getReportForPeriod($department->department_id, $dateRange); $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); - $useSnapshots = $isHeadOrAdmin || !$dateRange->isEndDateToday() || $reportToday; + $useSnapshots = $isHeadOrAdmin || ! $dateRange->isEndDateToday() || $reportToday; // Получаем ID пользователя для заполнения отчета if ($useSnapshots && $isHeadOrAdmin && $reportToday) { @@ -1047,19 +1042,19 @@ class ReportService $lpuDoctor = $this->getDoctorInfo($fillableUserId, $dateRange); // Проверяем, является ли диапазон одним днем -// $isRangeOneDay = $this->dateRangeService->isRangeOneDay( -// $endDate->copy()->subDay()->format('Y-m-d H:i:s'), -// $endDate->format('Y-m-d H:i:s') -// ); + // $isRangeOneDay = $this->dateRangeService->isRangeOneDay( + // $endDate->copy()->subDay()->format('Y-m-d H:i:s'), + // $endDate->format('Y-m-d H:i:s') + // ); // Формируем даты для ответа -// $date = $isHeadOrAdmin ? [ -// $endDate->copy()->subDay()->getTimestampMs(), -// $endDate->getTimestampMs() -// ] : $endDate->getTimestampMs(); + // $date = $isHeadOrAdmin ? [ + // $endDate->copy()->subDay()->getTimestampMs(), + // $endDate->getTimestampMs() + // ] : $endDate->getTimestampMs(); $date = $isHeadOrAdmin ? [ $dateRange->startDate->getTimestampMs(), - $dateRange->endDate->getTimestampMs() + $dateRange->endDate->getTimestampMs(), ] : $dateRange->endDate->getTimestampMs(); return [ @@ -1074,7 +1069,7 @@ class ReportService 'isHeadOrAdmin' => $isHeadOrAdmin, 'dates' => $date, 'userId' => $fillableUserId, - 'userName' => $lpuDoctor ? "$lpuDoctor->FAM_V $lpuDoctor->IM_V $lpuDoctor->OT_V" : null + 'userName' => $lpuDoctor ? "$lpuDoctor->FAM_V $lpuDoctor->IM_V $lpuDoctor->OT_V" : null, ]; } @@ -1087,6 +1082,7 @@ class ReportService if ($sourceType === 'manual') { ObservationPatient::where('rf_department_patient_id', $id)->delete(); + return; } @@ -1187,6 +1183,69 @@ class ReportService ->delete(); } + public function saveReanimationIndicator( + User $user, + int $departmentId, + int $medicalHistoryId, + string $indicator, + ?string $comment = null, + ?int $reportId = null + ): ReanimationPatientIndicator { + return ReanimationPatientIndicator::create([ + 'rf_department_id' => $departmentId, + 'rf_report_id' => $reportId, + 'rf_medicalhistory_id' => $medicalHistoryId, + 'indicator' => $indicator, + 'comment' => $comment, + 'created_by' => $user->id, + ]); + } + + public function getLatestReanimationIndicators(int $departmentId, array $medicalHistoryIds) + { + if (empty($medicalHistoryIds)) { + return collect(); + } + + $subQuery = ReanimationPatientIndicator::query() + ->selectRaw('MAX(reanimation_patient_indicator_id) as max_id, rf_medicalhistory_id') + ->where('rf_department_id', $departmentId) + ->whereIn('rf_medicalhistory_id', $medicalHistoryIds) + ->groupBy('rf_medicalhistory_id'); + + return ReanimationPatientIndicator::query() + ->joinSub($subQuery, 'latest', function ($join) { + $join->on('reanimation_patient_indicators.reanimation_patient_indicator_id', '=', 'latest.max_id'); + }) + ->get([ + 'reanimation_patient_indicators.rf_medicalhistory_id', + 'reanimation_patient_indicators.indicator', + 'reanimation_patient_indicators.comment', + ]) + ->keyBy('rf_medicalhistory_id'); + } + + public function getReanimationIndicatorsHistory( + int $departmentId, + int $medicalHistoryId, + int $limit = 50 + ) { + return ReanimationPatientIndicator::query() + ->where('rf_department_id', $departmentId) + ->where('rf_medicalhistory_id', $medicalHistoryId) + ->orderByDesc('reanimation_patient_indicator_id') + ->limit($limit) + ->get([ + 'reanimation_patient_indicator_id', + 'rf_report_id', + 'rf_medicalhistory_id', + 'indicator', + 'comment', + 'created_by', + 'created_at', + ]); + } + public function searchMisPatientsForDepartment(Department $department, string $query) { return $this->unifiedPatientService->searchMisPatients($department, $query); @@ -1198,7 +1257,7 @@ class ReportService ->where('department_patient_id', $departmentPatientId) ->whereIn('source_type', ['manual', 'special']); - if (!$user->isAdmin() && !$user->isHeadOfDepartment()) { + if (! $user->isAdmin() && ! $user->isHeadOfDepartment()) { $query->where('rf_department_id', $user->department->department_id); } @@ -1250,7 +1309,7 @@ class ReportService ->firstOrFail(); } - if (!isset($data['startAt'], $data['endAt']) || !$data['startAt'] || !$data['endAt']) { + if (! isset($data['startAt'], $data['endAt']) || ! $data['startAt'] || ! $data['endAt']) { throw new \InvalidArgumentException('Не указан отчет или диапазон для привязки спецконтингента'); } @@ -1303,11 +1362,11 @@ class ReportService 'outcome' => $this->getMetrikaResultCount(7, $reportIds), 'deceased' => $this->getMetrikaResultCount(9, $reportIds), 'current' => $this->getMetrikaResultCount(8, $reportIds, false), -// 'discharged' => $this->getMetrikaResultCount('discharged', $reportIds), + // 'discharged' => $this->getMetrikaResultCount('discharged', $reportIds), 'transferred' => $this->getMetrikaResultCount(13, $reportIds), 'recipient' => $this->getMetrikaResultCount(3, $reportIds), 'beds' => $this->getMetrikaResultCount(1, $reportIds, false), - 'countStaff' => $this->getMetrikaResultCount(17, [$lastReport], false) + 'countStaff' => $this->getMetrikaResultCount(17, [$lastReport], false), ]; // Получаем ID поступивших пациентов @@ -1319,7 +1378,7 @@ class ReportService // Получаем количество операций из метрик $surgicalCount = [ $this->getMetrikaResultCount(10, $reportIds), // экстренные операции - $this->getMetrikaResultCount(11, $reportIds) // плановые операции + $this->getMetrikaResultCount(11, $reportIds), // плановые операции ]; if ($snapshotStats['outcome'] == 0) { @@ -1332,7 +1391,7 @@ class ReportService return [ 'recipientCount' => $snapshotStats['recipient'] ?? 0, 'extractCount' => $snapshotStats['outcome'] ?? 0, - 'currentCount' => $snapshotStats['current'] ?? 0,//$this->calculateCurrentPatientsFromSnapshots($reportIds, $branchId), + 'currentCount' => $snapshotStats['current'] ?? 0, // $this->calculateCurrentPatientsFromSnapshots($reportIds, $branchId), 'deadCount' => $snapshotStats['deceased'] ?? 0, 'countStaff' => $snapshotStats['countStaff'] ?? 0, 'surgicalCount' => $surgicalCount, @@ -1367,7 +1426,7 @@ class ReportService $branchId, $dateRange, true - ) + ), ]; $manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange); $surgicalCount = [ @@ -1400,7 +1459,7 @@ class ReportService 'planCount' => $planCount, // плановые (поступившие + уже лечащиеся) 'emergencyCount' => $emergencyCount, // экстренные (поступившие + уже лечащиеся) 'percentDead' => $percentDead, - 'beds' => $beds->value + 'beds' => $beds->value, ]; } @@ -1458,11 +1517,12 @@ class ReportService $patientTypeMap = [ 'plan' => 'plan', 'emergency' => 'emergency', + 'current' => 'current', 'recipient' => 'recipient', 'outcome-discharged' => 'discharged', 'outcome-transferred' => 'transferred', 'outcome-deceased' => 'deceased', - 'observation' => 'observation' + 'observation' => 'observation', ]; $patientType = $patientTypeMap[$baseStatus] ?? null; @@ -1471,6 +1531,35 @@ class ReportService return $this->unifiedPatientService->getObservationPatients($department, $onlyIds, $sourceScope); } + if ($baseStatus === 'outcome') { + $discharged = $this->snapshotService->getPatientsFromSnapshots( + 'discharged', + $reportIds, + $branchId, + false, + false, + $recipientReportIds + ); + $deceased = $this->snapshotService->getPatientsFromSnapshots( + 'deceased', + $reportIds, + $branchId, + false, + false, + $recipientReportIds + ); + + $merged = \App\Data\UnifiedPatientData::unique($discharged->concat($deceased)) + ->sortByDesc(fn (\App\Data\UnifiedPatientData $patient) => $patient->admittedAt ?? '') + ->values(); + + return $this->filterSnapshotPatientsByScope($merged, $sourceScope, $onlyIds); + } + + if (! $patientType) { + return collect(); + } + if ($dateRange->isOneDay && in_array($baseStatus, ['plan', 'emergency'], true)) { $patients = $this->snapshotService->getPatientsFromOneDayCurrentSnapshots( $patientType, @@ -1509,7 +1598,7 @@ class ReportService [$baseStatus] = $this->parseScopedStatus($status); // Для плановых и экстренных включаем уже лечащихся - $includeCurrent = $isIncludeCurrent ?? in_array($baseStatus, ['plan', 'emergency'], true); + $includeCurrent = $isIncludeCurrent ?? in_array($baseStatus, ['plan', 'emergency', 'reanimation'], true); return $this->unifiedPatientService->getLivePatientsByStatus( $department, @@ -1558,12 +1647,12 @@ class ReportService 'observation' => 'observation', 'outcome-discharged' => 'discharged', 'outcome-transferred' => 'transferred', - 'outcome-deceased' => 'deceased' + 'outcome-deceased' => 'deceased', ]; $patientType = $patientTypeMap[$baseStatus] ?? null; - if (!$patientType) { + if (! $patientType) { return 0; } @@ -1605,20 +1694,18 @@ class ReportService string $status, DateRange $dateRange, int $branchId - ): int - { + ): int { [$baseStatus] = $this->parseScopedStatus($status); - return match($status) { - 'plan', 'emergency', 'observation', 'reanimation', 'outcome', 'outcome-discharged', 'outcome-transferred', 'outcome-deceased', 'current', 'recipient' => - $this->unifiedPatientService->getLivePatientCountByStatus( - $department, - $user, - $status, - $dateRange, - $branchId, - in_array($status, ['plan', 'emergency'], true) - ), + return match ($status) { + 'plan', 'emergency', 'observation', 'reanimation', 'outcome', 'outcome-discharged', 'outcome-transferred', 'outcome-deceased', 'current', 'recipient' => $this->unifiedPatientService->getLivePatientCountByStatus( + $department, + $user, + $status, + $dateRange, + $branchId, + in_array($status, ['plan', 'emergency'], true) + ), default => $this->unifiedPatientService->getLivePatientCountByStatus( $department, $user, @@ -1700,7 +1787,7 @@ class ReportService private function isSendButtonActive(User $user, DateRange $dateRange, ?Report $reportToday, ?int $fillableUserId): bool { // Для врача: только сегодня и если отчета еще нет - if (!$user->isHeadOfDepartment() && !$user->isAdmin()) { + if (! $user->isHeadOfDepartment() && ! $user->isAdmin()) { if ($reportToday && $reportToday->status === 'submitted') { return false; } @@ -1726,10 +1813,11 @@ class ReportService ->exactPeriod($dateRange->startSql(), $dateRange->endSql()) ->orderByDesc('report_id'); - if ($dateRange->isOneDay) + if ($dateRange->isOneDay) { return $query->first(); - else + } else { return $query->onlySubmitted()->first(); + } } /** @@ -1737,12 +1825,12 @@ class ReportService */ private function getDoctorInfo(?int $doctorId, DateRange $dateRange): ?MisLpuDoctor { - if (!$doctorId) { + if (! $doctorId) { return null; } // Если дата это период, не показываем врача - if (!$dateRange->isOneDay) { + if (! $dateRange->isOneDay) { return null; } @@ -1780,7 +1868,7 @@ class ReportService ->orderBy('created_at', 'DESC') ->get(); - if (!$sum) { + if (! $sum) { foreach ($reports as $report) { $metric = $report->metrikaResults ->firstWhere('rf_metrika_item_id', $metrikaItemId); @@ -1852,7 +1940,7 @@ class ReportService } return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) - ->with(['observationPatient' => function($query) use ($departmentId) { + ->with(['observationPatient' => function ($query) use ($departmentId) { $query->where('rf_department_id', $departmentId) ->select(['rf_medicalhistory_id', 'observation_patient_id', 'comment']); }]) @@ -1863,6 +1951,7 @@ class ReportService ->pluck('comment') ->filter() ->implode('; '); + return $patient; }); } @@ -1875,7 +1964,7 @@ class ReportService $periodPlanModel = $department->recipientPlanOfYear(); // Рассчитываем коэффициент периода (округляем в большую сторону) $monthsInPeriod = ceil($dateRange->startDate->diffInMonths($dateRange->endDate)); - $annualPlan = $periodPlanModel ? (int)$periodPlanModel->value : 0; + $annualPlan = $periodPlanModel ? (int) $periodPlanModel->value : 0; $oneMonthPlan = ceil($annualPlan / 12); $periodPlan = round($oneMonthPlan * $monthsInPeriod); @@ -1897,13 +1986,14 @@ class ReportService foreach ($reports as $report) { $outcome = $report->metrikaResults()->where('rf_metrika_item_id', 7)->first(); - if ($outcome) $progress += (int)$outcome->value; + if ($outcome) { + $progress += (int) $outcome->value; + } } return [ 'plan' => $periodPlan, - 'progress' => $progress + 'progress' => $progress, ]; } - } diff --git a/app/Services/SnapshotService.php b/app/Services/SnapshotService.php index f970d76..22ac57a 100644 --- a/app/Services/SnapshotService.php +++ b/app/Services/SnapshotService.php @@ -38,7 +38,7 @@ class SnapshotService ? $this->getBranchId($department->rf_mis_department_id) : null; - if (!$department || !$branchId) { + if (! $department || ! $branchId) { return; } @@ -61,7 +61,7 @@ class SnapshotService $dateRange, $branchId, false, - !$fillableAuto, + ! $fillableAuto, $fillableAuto, true ); @@ -81,7 +81,7 @@ class SnapshotService $dateRange, $branchId, false, - !$fillableAuto, + ! $fillableAuto, $fillableAuto, true ); @@ -302,7 +302,7 @@ class SnapshotService : []; $operations = collect($misOperations) ->merge($manualOperations) - ->unique(fn (array $operation) => ($operation['code'] ?? '') . '|' . ($operation['name'] ?? '')) + ->unique(fn (array $operation) => ($operation['code'] ?? '').'|'.($operation['name'] ?? '')) ->values() ->all(); @@ -367,7 +367,7 @@ class SnapshotService : []; $operations = collect($misOperations) ->merge($manualOperations) - ->unique(fn (array $operation) => ($operation['code'] ?? '') . '|' . ($operation['name'] ?? '')) + ->unique(fn (array $operation) => ($operation['code'] ?? '').'|'.($operation['name'] ?? '')) ->values() ->all(); @@ -415,7 +415,7 @@ class SnapshotService private function createSnapshotsForType(Report $report, string $type, Collection $patients): void { foreach ($patients as $patient) { - if (!$patient instanceof UnifiedPatientData) { + if (! $patient instanceof UnifiedPatientData) { continue; } @@ -471,7 +471,7 @@ class SnapshotService $history->MedicalHistoryID => $history->surgicalOperations->map(fn ($operation) => [ 'code' => $operation->serviceMedical?->ServiceMedicalCode, 'name' => $operation->serviceMedical?->ServiceMedicalName, - ])->values()->all() + ])->values()->all(), ]; }) ->all(); diff --git a/app/Services/StationarBranchService.php b/app/Services/StationarBranchService.php index 410514e..f24c493 100644 --- a/app/Services/StationarBranchService.php +++ b/app/Services/StationarBranchService.php @@ -9,7 +9,6 @@ class StationarBranchService { /** * Получение идентификаторов приемных отделений. Кешируется на 24ч - * @return array */ public function getWardIds(): array { diff --git a/app/Services/StatisticsService.php b/app/Services/StatisticsService.php index 50247e2..6e90953 100644 --- a/app/Services/StatisticsService.php +++ b/app/Services/StatisticsService.php @@ -1,4 +1,5 @@ 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) + ->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') + ->orderBy((new UserDepartment)->getTable().'.order', 'asc') ->get() ->groupBy('departmentType.name_full'); @@ -46,17 +43,8 @@ class StatisticsService // Рассчитываем коэффициент периода (дни периода / 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. Получаем ВСЕ метрики за период ОДНИМ запросом @@ -72,7 +60,6 @@ class StatisticsService 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'); @@ -123,18 +110,16 @@ class StatisticsService ->first(); // Базовые показатели - $bedsCount = (int)($beds[$deptId]->value ?? 0); - $currentCount = (int)($currentPatients[$deptId]->value ?? 0); + $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; + $annualPlan = $annualPlanModel ? (int) $annualPlanModel->value : 0; $oneMonthPlan = ceil($annualPlan / 12); // Рассчитываем план на период $periodPlan = round($oneMonthPlan * $monthsInPeriod); -// $periodPlan = round($annualPlan * $periodCoefficient); // Счетчики $plan = 0; @@ -149,25 +134,27 @@ class StatisticsService $unwanted = 0; $bedDaysSum = 0; $avgBedDays = 0; + $preoperativeSum = 0; if (isset($metrics[$deptId])) { foreach ($metrics[$deptId] as $item) { - $value = (float)$item->total; + $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, + 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, + 25 => $bedDaysSum += $value, 19 => $lethalitySum = $value, -// 24 => $completePlanProgress = (int)$value, + 26 => $preoperativeSum += $value, + // 24 => $completePlanProgress = (int)$value, default => null }; } @@ -186,11 +173,7 @@ class StatisticsService $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; + $preoperativeValue = // Летальность $lethality = $outcome > 0 ? round(($deceased / $outcome) * 100, 2) : 0; @@ -214,7 +197,7 @@ class StatisticsService 'percentLoadedBeds' => $percentLoaded, 'surgical' => [ 'plan' => $planSurgical, - 'emergency' => $emergencySurgical + 'emergency' => $emergencySurgical, ], 'deceased' => $deceased, 'countStaff' => $staff, @@ -242,7 +225,7 @@ class StatisticsService 'recipientPlanOfYear' => [ 'plan' => $grandRecipientPlan, // Сумма планов по периоду 'progress' => $grandProgressPlan, // Сумма фактов по периоду - ] + ], ]; } @@ -307,6 +290,7 @@ class StatisticsService } } } + return $grand; } @@ -321,14 +305,14 @@ class StatisticsService $final[] = [ 'isGroupHeader' => true, 'groupName' => $type, - 'colspan' => 16 + 'colspan' => 16, ]; foreach ($items as $item) { $final[] = $item; } - if (!empty($items) && isset($totalsByType[$type])) { + if (! empty($items) && isset($totalsByType[$type])) { $final[] = $this->createTotalRow($type, $totalsByType[$type], false); } } @@ -342,7 +326,7 @@ class StatisticsService private function createTotalRow(string $type, array $total, bool $isGrandTotal): array { return [ - 'isTotalRow' => !$isGrandTotal, + 'isTotalRow' => ! $isGrandTotal, 'isGrandTotal' => $isGrandTotal, 'department' => $isGrandTotal ? 'ОБЩИЕ ИТОГИ:' : 'ИТОГО:', 'beds' => '—', @@ -357,7 +341,7 @@ class StatisticsService 'percentLoadedBeds' => '—', 'surgical' => [ 'plan' => $total['plan_surgical_sum'], - 'emergency' => $total['emergency_surgical_sum'] + 'emergency' => $total['emergency_surgical_sum'], ], 'deceased' => $total['deceased_sum'], 'averageBedDays' => '—', @@ -368,7 +352,7 @@ class StatisticsService 'countStaff' => $total['staff_sum'], 'countObservable' => $total['observable_sum'], 'countUnwanted' => $total['unwanted_sum'], - 'isBold' => true + 'isBold' => true, ]; } @@ -380,7 +364,7 @@ class StatisticsService return [ 'data' => [], 'totalsByType' => [], - 'grandTotals' => $this->initTypeTotals() + 'grandTotals' => $this->initTypeTotals(), ]; } } diff --git a/app/Services/UnifiedPatientService.php b/app/Services/UnifiedPatientService.php index 4c5e02a..59679df 100644 --- a/app/Services/UnifiedPatientService.php +++ b/app/Services/UnifiedPatientService.php @@ -93,6 +93,7 @@ class UnifiedPatientService } $specialCount = $this->getManualPatientsCount($department, $baseStatus, $dateRange, self::SPECIAL_SOURCE_TYPES); + return $misCount + $specialCount; } @@ -214,8 +215,7 @@ class UnifiedPatientService Department $department, bool $onlyIds = false, string $sourceScope = 'all' - ): Collection - { + ): Collection { $observationPatients = ObservationPatient::where('rf_department_id', $department->department_id)->get(); $misIds = $observationPatients->pluck('rf_medicalhistory_id')->filter()->unique()->values(); @@ -276,12 +276,12 @@ class UnifiedPatientService bool $forSnapshots = false ): Collection { $misPatients = $this->getMisPatients($user, $status, $dateRange, $branchId, $includeCurrent, $fillableAuto, $forSnapshots); - $manualPatients = $this->getManualPatients($department, $status, $dateRange, self::SPECIAL_SOURCE_TYPES, !$forSnapshots); + $manualPatients = $this->getManualPatients($department, $status, $dateRange, self::SPECIAL_SOURCE_TYPES, ! $forSnapshots); $reportIds = $this->getReportIdsForDepartmentPeriod($department, $dateRange); $linkedManualPatients = DepartmentPatient::query() ->where(function ($builder) use ($department, $reportIds) { - if (!empty($reportIds)) { + if (! empty($reportIds)) { $builder->whereIn('rf_report_id', $reportIds); } @@ -338,7 +338,7 @@ class UnifiedPatientService bool $forSnapshots = false ): Collection { return $this->mapManualPatients( - $this->getManualPatients($department, $status, $dateRange, self::SPECIAL_SOURCE_TYPES, !$forSnapshots), + $this->getManualPatients($department, $status, $dateRange, self::SPECIAL_SOURCE_TYPES, ! $forSnapshots), $dateRange ); } @@ -450,8 +450,7 @@ class UnifiedPatientService DateRange $dateRange, ?array $sourceTypes = self::SPECIAL_SOURCE_TYPES, bool $withOperations = true - ): Collection - { + ): Collection { $query = $this->buildManualPatientsQuery($department, $dateRange, $sourceTypes, $withOperations); return match ($status) { @@ -521,7 +520,7 @@ class UnifiedPatientService $query = DepartmentPatient::query() ->where(function ($builder) use ($department, $reportIds) { - if (!empty($reportIds)) { + if (! empty($reportIds)) { $builder->whereIn('rf_report_id', $reportIds); } diff --git a/config/app.php b/config/app.php index ada063c..498bed1 100644 --- a/config/app.php +++ b/config/app.php @@ -125,6 +125,6 @@ return [ 'version' => env('APP_VERSION', 'vNone'), - 'tag' => env('APP_TAG', 'dev') + 'tag' => env('APP_TAG', 'dev'), ]; diff --git a/config/excel.php b/config/excel.php index 63746e1..9bdfdc9 100644 --- a/config/excel.php +++ b/config/excel.php @@ -15,7 +15,7 @@ return [ | Here you can specify how big the chunk should be. | */ - 'chunk_size' => 1000, + 'chunk_size' => 1000, /* |-------------------------------------------------------------------------- @@ -42,15 +42,15 @@ return [ | Configure e.g. delimiter, enclosure and line ending for CSV exports. | */ - 'csv' => [ - 'delimiter' => ',', - 'enclosure' => '"', - 'line_ending' => PHP_EOL, - 'use_bom' => false, + 'csv' => [ + 'delimiter' => ',', + 'enclosure' => '"', + 'line_ending' => PHP_EOL, + 'use_bom' => false, 'include_separator_line' => false, - 'excel_compatibility' => false, - 'output_encoding' => '', - 'test_auto_detect' => true, + 'excel_compatibility' => false, + 'output_encoding' => '', + 'test_auto_detect' => true, ], /* @@ -61,20 +61,20 @@ return [ | Configure e.g. default title, creator, subject,... | */ - 'properties' => [ - 'creator' => '', + 'properties' => [ + 'creator' => '', 'lastModifiedBy' => '', - 'title' => '', - 'description' => '', - 'subject' => '', - 'keywords' => '', - 'category' => '', - 'manager' => '', - 'company' => '', + 'title' => '', + 'description' => '', + 'subject' => '', + 'keywords' => '', + 'category' => '', + 'manager' => '', + 'company' => '', ], ], - 'imports' => [ + 'imports' => [ /* |-------------------------------------------------------------------------- @@ -87,7 +87,7 @@ return [ | you can enable it by setting read_only to false. | */ - 'read_only' => true, + 'read_only' => true, /* |-------------------------------------------------------------------------- @@ -111,7 +111,7 @@ return [ | Available options: none|slug|custom | */ - 'heading_row' => [ + 'heading_row' => [ 'formatter' => 'slug', ], @@ -123,12 +123,12 @@ return [ | Configure e.g. delimiter, enclosure and line ending for CSV imports. | */ - 'csv' => [ - 'delimiter' => null, - 'enclosure' => '"', + 'csv' => [ + 'delimiter' => null, + 'enclosure' => '"', 'escape_character' => '\\', - 'contiguous' => false, - 'input_encoding' => Csv::GUESS_ENCODING, + 'contiguous' => false, + 'input_encoding' => Csv::GUESS_ENCODING, ], /* @@ -139,16 +139,16 @@ return [ | Configure e.g. default title, creator, subject,... | */ - 'properties' => [ - 'creator' => '', + 'properties' => [ + 'creator' => '', 'lastModifiedBy' => '', - 'title' => '', - 'description' => '', - 'subject' => '', - 'keywords' => '', - 'category' => 'Отчетность', - 'manager' => 'Метрика 1.0', - 'company' => 'ГАУЗ АО "АОКБ"', + 'title' => '', + 'description' => '', + 'subject' => '', + 'keywords' => '', + 'category' => 'Отчетность', + 'manager' => 'Метрика 1.0', + 'company' => 'ГАУЗ АО "АОКБ"', ], /* @@ -159,10 +159,10 @@ return [ | Configure middleware that is executed on getting a cell value | */ - 'cells' => [ + 'cells' => [ 'middleware' => [ - //\Maatwebsite\Excel\Middleware\TrimCellValue::class, - //\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class, + // \Maatwebsite\Excel\Middleware\TrimCellValue::class, + // \Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class, ], ], @@ -178,21 +178,21 @@ return [ | */ 'extension_detector' => [ - 'xlsx' => Excel::XLSX, - 'xlsm' => Excel::XLSX, - 'xltx' => Excel::XLSX, - 'xltm' => Excel::XLSX, - 'xls' => Excel::XLS, - 'xlt' => Excel::XLS, - 'ods' => Excel::ODS, - 'ots' => Excel::ODS, - 'slk' => Excel::SLK, - 'xml' => Excel::XML, + 'xlsx' => Excel::XLSX, + 'xlsm' => Excel::XLSX, + 'xltx' => Excel::XLSX, + 'xltm' => Excel::XLSX, + 'xls' => Excel::XLS, + 'xlt' => Excel::XLS, + 'ods' => Excel::ODS, + 'ots' => Excel::ODS, + 'slk' => Excel::SLK, + 'xml' => Excel::XML, 'gnumeric' => Excel::GNUMERIC, - 'htm' => Excel::HTML, - 'html' => Excel::HTML, - 'csv' => Excel::CSV, - 'tsv' => Excel::TSV, + 'htm' => Excel::HTML, + 'html' => Excel::HTML, + 'csv' => Excel::CSV, + 'tsv' => Excel::TSV, /* |-------------------------------------------------------------------------- @@ -203,7 +203,7 @@ return [ | Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF | */ - 'pdf' => Excel::DOMPDF, + 'pdf' => Excel::DOMPDF, ], /* @@ -223,11 +223,11 @@ return [ | [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class | */ - 'value_binder' => [ + 'value_binder' => [ 'default' => Maatwebsite\Excel\DefaultValueBinder::class, ], - 'cache' => [ + 'cache' => [ /* |-------------------------------------------------------------------------- | Default cell caching driver @@ -244,7 +244,7 @@ return [ | Drivers: memory|illuminate|batch | */ - 'driver' => 'memory', + 'driver' => 'memory', /* |-------------------------------------------------------------------------- @@ -256,7 +256,7 @@ return [ | Here you can tweak the memory limit to your liking. | */ - 'batch' => [ + 'batch' => [ 'memory_limit' => 60000, ], @@ -272,7 +272,7 @@ return [ | at "null" it will use the default store. | */ - 'illuminate' => [ + 'illuminate' => [ 'store' => null, ], @@ -308,7 +308,7 @@ return [ */ 'transactions' => [ 'handler' => 'db', - 'db' => [ + 'db' => [ 'connection' => null, ], ], @@ -326,7 +326,7 @@ return [ | and the create file (file). | */ - 'local_path' => storage_path('framework/cache/laravel-excel'), + 'local_path' => storage_path('framework/cache/laravel-excel'), /* |-------------------------------------------------------------------------- @@ -338,7 +338,7 @@ return [ | If omitted the default permissions of the filesystem will be used. | */ - 'local_permissions' => [ + 'local_permissions' => [ // 'dir' => 0755, // 'file' => 0644, ], @@ -357,8 +357,8 @@ return [ | in conjunction with queued imports and exports. | */ - 'remote_disk' => null, - 'remote_prefix' => null, + 'remote_disk' => null, + 'remote_prefix' => null, /* |-------------------------------------------------------------------------- diff --git a/config/time.php b/config/time.php index 211267d..1d5e627 100644 --- a/config/time.php +++ b/config/time.php @@ -2,4 +2,4 @@ return [ 'eventSourceUrl' => env('TIME_EVENT_SOURCE_URL', null), -]; \ No newline at end of file +]; diff --git a/database/migrations/2025_12_27_055140_create_metrika_group_items_table.php b/database/migrations/2025_12_27_055140_create_metrika_group_items_table.php index 3677935..44789a8 100644 --- a/database/migrations/2025_12_27_055140_create_metrika_group_items_table.php +++ b/database/migrations/2025_12_27_055140_create_metrika_group_items_table.php @@ -13,7 +13,7 @@ return new class extends Migration { Schema::create('metrika_group_items', function (Blueprint $table) { $table->id('metrika_group_item_id'); - $table->foreignIdFor( \App\Models\MetrikaGroup::class, 'rf_metrika_group_id') + $table->foreignIdFor(\App\Models\MetrikaGroup::class, 'rf_metrika_group_id') ->comment('Идентификатор группы метрик') ->constrained() ->cascadeOnDelete(); diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 2600ec0..c91200c 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,7 +2,6 @@ namespace Database\Seeders; -use App\Models\User; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; diff --git a/database/seeders/TestDepartmentDataSeeder.php b/database/seeders/TestDepartmentDataSeeder.php index 74eecd5..88974ea 100644 --- a/database/seeders/TestDepartmentDataSeeder.php +++ b/database/seeders/TestDepartmentDataSeeder.php @@ -5,10 +5,7 @@ namespace Database\Seeders; use App\Models\Department; use App\Models\DepartmentMetrikaDefault; use App\Models\DepartmentType; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; -use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Seeder; -use Illuminate\Support\Facades\Schema; class TestDepartmentDataSeeder extends Seeder { @@ -19,15 +16,15 @@ class TestDepartmentDataSeeder extends Seeder { DepartmentType::create([ 'name_short' => 'Хирургические', - 'name_full' => 'Хирургические отделения' + 'name_full' => 'Хирургические отделения', ]); DepartmentType::create([ 'name_short' => 'Терапевтические', - 'name_full' => 'Терапевтические отделения' + 'name_full' => 'Терапевтические отделения', ]); DepartmentType::create([ 'name_short' => 'Перинатальный', - 'name_full' => 'Перинатальный центр' + 'name_full' => 'Перинатальный центр', ]); /** @@ -37,67 +34,67 @@ class TestDepartmentDataSeeder extends Seeder 'name_full' => 'Гинекологическое отделение', 'name_short' => 'Гинекологическое', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1054 + 'rf_mis_department_id' => 1054, ]); Department::create([ 'name_full' => 'Нейрохирургическое отделение', 'name_short' => 'Нейрохирургическое', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1049 + 'rf_mis_department_id' => 1049, ]); Department::create([ 'name_full' => 'Отделение термических поражений', 'name_short' => 'Термических поражений', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1059 + 'rf_mis_department_id' => 1059, ]); Department::create([ 'name_full' => 'Отоларингологическое отделение', 'name_short' => 'Отоларингологическое', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1061 + 'rf_mis_department_id' => 1061, ]); Department::create([ 'name_full' => 'Проктологическое отделение', 'name_short' => 'Проктологическое', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1065 + 'rf_mis_department_id' => 1065, ]); Department::create([ 'name_full' => 'Отделение сосудистой хирургии', 'name_short' => 'Сосудистой хирургии', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1050 + 'rf_mis_department_id' => 1050, ]); Department::create([ 'name_full' => 'Отделение торакальной хирургии', 'name_short' => 'Торакальной хирургии', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1069 + 'rf_mis_department_id' => 1069, ]); Department::create([ 'name_full' => 'Травматологическое отделение', 'name_short' => 'Травматологическое', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1070 + 'rf_mis_department_id' => 1070, ]); Department::create([ 'name_full' => 'Урологическое отделение', 'name_short' => 'Урологическое', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1071 + 'rf_mis_department_id' => 1071, ]); Department::create([ 'name_full' => 'Хирургическое отделение', 'name_short' => 'Хирургическое', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1072 + 'rf_mis_department_id' => 1072, ]); Department::create([ 'name_full' => 'Отделение ЧЛХ', 'name_short' => 'ЧЛХ', 'rf_department_type' => 1, - 'rf_mis_department_id' => 1073 + 'rf_mis_department_id' => 1073, ]); /** @@ -107,61 +104,61 @@ class TestDepartmentDataSeeder extends Seeder 'name_full' => 'Гастроэнтерологическое отделение', 'name_short' => 'Гастроэнтерологическое', 'rf_department_type' => 2, - 'rf_mis_department_id' => 1052 + 'rf_mis_department_id' => 1052, ]); Department::create([ 'name_full' => 'Гематологическое отделение', 'name_short' => 'Гематологическое', 'rf_department_type' => 2, - 'rf_mis_department_id' => 1053 + 'rf_mis_department_id' => 1053, ]); Department::create([ 'name_full' => 'Кардиологическое отделение', 'name_short' => 'Кардиологическое', 'rf_department_type' => 2, - 'rf_mis_department_id' => 1056 + 'rf_mis_department_id' => 1056, ]); Department::create([ 'name_full' => 'Неврологическое отделение', 'name_short' => 'Неврологическое', 'rf_department_type' => 2, - 'rf_mis_department_id' => 1057 + 'rf_mis_department_id' => 1057, ]); Department::create([ 'name_full' => 'Нефрологическое отделение', 'name_short' => 'Нефрологическое', 'rf_department_type' => 2, - 'rf_mis_department_id' => 1058 + 'rf_mis_department_id' => 1058, ]); Department::create([ 'name_full' => 'Пульмонологическое отделение', 'name_short' => 'Пульмонологическое', 'rf_department_type' => 2, - 'rf_mis_department_id' => 1066 + 'rf_mis_department_id' => 1066, ]); Department::create([ 'name_full' => 'Ревматологическое отделение', 'name_short' => 'Ревматологическое', 'rf_department_type' => 2, - 'rf_mis_department_id' => 1068 + 'rf_mis_department_id' => 1068, ]); Department::create([ 'name_full' => 'РСЦ кардиологическое отделение', 'name_short' => 'РСЦ кардиологическое', 'rf_department_type' => 2, - 'rf_mis_department_id' => 1047 + 'rf_mis_department_id' => 1047, ]); Department::create([ 'name_full' => 'РСЦ неврологическое отделение', 'name_short' => 'РСЦ неврологическое', 'rf_department_type' => 2, - 'rf_mis_department_id' => 2162 + 'rf_mis_department_id' => 2162, ]); Department::create([ 'name_full' => 'Отделение медицинской реабилитации', 'name_short' => 'Медицинской реабилитации', 'rf_department_type' => 2, - 'rf_mis_department_id' => 2042 + 'rf_mis_department_id' => 2042, ]); /** @@ -170,57 +167,57 @@ class TestDepartmentDataSeeder extends Seeder DepartmentMetrikaDefault::create([ 'rf_department_id' => 1, 'rf_metrika_item_id' => 1, - 'value' => '50' + 'value' => '50', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 2, 'rf_metrika_item_id' => 1, - 'value' => '42' + 'value' => '42', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 3, 'rf_metrika_item_id' => 1, - 'value' => '39' + 'value' => '39', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 4, 'rf_metrika_item_id' => 1, - 'value' => '30' + 'value' => '30', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 5, 'rf_metrika_item_id' => 1, - 'value' => '34' + 'value' => '34', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 6, 'rf_metrika_item_id' => 1, - 'value' => '40' + 'value' => '40', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 7, 'rf_metrika_item_id' => 1, - 'value' => '25' + 'value' => '25', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 8, 'rf_metrika_item_id' => 1, - 'value' => '48' + 'value' => '48', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 9, 'rf_metrika_item_id' => 1, - 'value' => '27' + 'value' => '27', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 10, 'rf_metrika_item_id' => 1, - 'value' => '58' + 'value' => '58', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 11, 'rf_metrika_item_id' => 1, - 'value' => '25' + 'value' => '25', ]); /** @@ -229,47 +226,47 @@ class TestDepartmentDataSeeder extends Seeder DepartmentMetrikaDefault::create([ 'rf_department_id' => 12, 'rf_metrika_item_id' => 1, - 'value' => '22' + 'value' => '22', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 13, 'rf_metrika_item_id' => 1, - 'value' => '27' + 'value' => '27', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 14, 'rf_metrika_item_id' => 1, - 'value' => '40' + 'value' => '40', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 15, 'rf_metrika_item_id' => 1, - 'value' => '30' + 'value' => '30', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 16, 'rf_metrika_item_id' => 1, - 'value' => '25' + 'value' => '25', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 17, 'rf_metrika_item_id' => 1, - 'value' => '40' + 'value' => '40', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 18, 'rf_metrika_item_id' => 1, - 'value' => '55' + 'value' => '55', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 19, 'rf_metrika_item_id' => 1, - 'value' => '55' + 'value' => '55', ]); DepartmentMetrikaDefault::create([ 'rf_department_id' => 20, 'rf_metrika_item_id' => 1, - 'value' => '30' + 'value' => '30', ]); } } diff --git a/database/seeders/TestMetrikaSeeder.php b/database/seeders/TestMetrikaSeeder.php index 110414a..8f935f4 100644 --- a/database/seeders/TestMetrikaSeeder.php +++ b/database/seeders/TestMetrikaSeeder.php @@ -5,7 +5,6 @@ namespace Database\Seeders; use App\Models\MetrikaGroup; use App\Models\MetrikaGroupItem; use App\Models\MetrikaItem; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class TestMetrikaSeeder extends Seeder @@ -17,27 +16,27 @@ class TestMetrikaSeeder extends Seeder { MetrikaItem::create([ 'name' => 'Коек', - 'data_type' => 'integer' + 'data_type' => 'integer', ]); MetrikaItem::create([ 'name' => 'Состояло', - 'data_type' => 'integer' + 'data_type' => 'integer', ]); MetrikaItem::create([ 'name' => 'Поступило', - 'data_type' => 'integer' + 'data_type' => 'integer', ]); MetrikaItem::create([ 'name' => 'План', - 'data_type' => 'integer' + 'data_type' => 'integer', ]); MetrikaItem::create([ 'name' => 'Самотек', - 'data_type' => 'integer' + 'data_type' => 'integer', ]); MetrikaItem::create([ 'name' => 'Скорая', - 'data_type' => 'integer' + 'data_type' => 'integer', ]); MetrikaGroup::create([ @@ -47,7 +46,7 @@ class TestMetrikaSeeder extends Seeder foreach (MetrikaItem::all() as $item) { MetrikaGroupItem::create([ 'rf_metrika_group_id' => 1, - 'rf_metrika_item_id' => $item->metrika_item_id + 'rf_metrika_item_id' => $item->metrika_item_id, ]); } @@ -68,15 +67,15 @@ class TestMetrikaSeeder extends Seeder MetrikaGroupItem::create([ 'rf_metrika_group_id' => 2, - 'rf_metrika_item_id' => 3 + 'rf_metrika_item_id' => 3, ]); MetrikaGroupItem::create([ 'rf_metrika_group_id' => 2, - 'rf_metrika_item_id' => 7 + 'rf_metrika_item_id' => 7, ]); MetrikaGroupItem::create([ 'rf_metrika_group_id' => 2, - 'rf_metrika_item_id' => 8 + 'rf_metrika_item_id' => 8, ]); } } diff --git a/database/seeders/TestUserSeeder.php b/database/seeders/TestUserSeeder.php index 0d272c2..2e93eb5 100644 --- a/database/seeders/TestUserSeeder.php +++ b/database/seeders/TestUserSeeder.php @@ -5,7 +5,6 @@ namespace Database\Seeders; use App\Models\Role; use App\Models\User; use App\Models\UserRole; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class TestUserSeeder extends Seeder @@ -37,13 +36,13 @@ class TestUserSeeder extends Seeder 'login' => 'test', 'password' => \Hash::make('test'), 'rf_department_id' => 1, - 'rf_lpudoctor_id' => null + 'rf_lpudoctor_id' => null, ]); UserRole::create([ 'rf_user_id' => 1, 'rf_role_id' => 1, - 'is_default' => true + 'is_default' => true, ]); UserRole::create([ 'rf_user_id' => 1, diff --git a/resources/js/Pages/Report/Components/ReportHeader.vue b/resources/js/Pages/Report/Components/ReportHeader.vue index 9146892..d9328fb 100644 --- a/resources/js/Pages/Report/Components/ReportHeader.vue +++ b/resources/js/Pages/Report/Components/ReportHeader.vue @@ -50,6 +50,19 @@ const currentDate = computed(() => { return formatted.charAt(0).toUpperCase() + formatted.slice(1) }) + +const exportUrl = computed(() => { + const [startAt, endAt] = reportStore.timestampCurrentRange || [] + const departmentId = reportStore.reportInfo?.department?.department_id + const params = new URLSearchParams() + + if (startAt) params.set('startAt', String(startAt)) + if (endAt) params.set('endAt', String(endAt)) + if (departmentId) params.set('departmentId', String(departmentId)) + + const query = params.toString() + return query ? `/report/export?${query}` : '/report/export' +}) Нежелательные события ({{ reportStore.unwantedEvents.length }}) + + Скачать Excel + diff --git a/resources/js/Pages/Report/Components/ReportSectionItem.vue b/resources/js/Pages/Report/Components/ReportSectionItem.vue index e96059c..0004d20 100644 --- a/resources/js/Pages/Report/Components/ReportSectionItem.vue +++ b/resources/js/Pages/Report/Components/ReportSectionItem.vue @@ -18,13 +18,14 @@ import { import {useReportStore} from "../../../Stores/report.js"; import {computed, h, ref, watch} from "vue"; import {storeToRefs} from "pinia"; -import {TbEye, TbExternalLink, TbPencil, TbTrash} from "vue-icons-plus/tb"; +import {TbEye, TbExternalLink, TbPencil, TbTrash, TbLock, TbEdit} from "vue-icons-plus/tb"; import MoveModalComment from "./MoveModalComment.vue"; import OperationInfoModal from "./OperationInfoModal.vue"; import ManualPatientOutcomeModal from "./ManualPatientOutcomeModal.vue"; import ManualPatientLinkModal from "./ManualPatientLinkModal.vue"; import ManualPatientEditModal from "./ManualPatientEditModal.vue"; import ManualPatientOperationsModal from "./ManualPatientOperationsModal.vue"; +import ReanimationIndicatorModal from "./ReanimationIndicatorModal.vue"; import {useDebounceFn} from "@vueuse/core"; const props = defineProps({ @@ -83,6 +84,7 @@ const showManualOutcomeModal = ref(false) const showManualLinkModal = ref(false) const showManualEditModal = ref(false) const showManualOperationsModal = ref(false) +const showReanimationIndicatorModal = ref(false) const currentHistory = ref(null) const latestDropItem = ref(null) const activePatient = ref(null) @@ -90,11 +92,17 @@ const activePatient = ref(null) const hasDisabledEdit = computed(() => { return !Boolean(reportStore.reportInfo?.report?.isActiveSendButton) }) +const isHeadOrAdmin = computed(() => Boolean(reportStore.reportInfo?.report?.isHeadOrAdmin)) const canEditSpecial = computed(() => ( isSpecialStatus.value - && !hasDisabledEdit.value + && (!hasDisabledEdit.value || isHeadOrAdmin.value) && baseStatus.value !== 'observation' )) +const canEditReanimationIndicator = computed(() => ( + baseStatus.value === 'reanimation' + && !isSpecialStatus.value + && (!hasDisabledEdit.value || isHeadOrAdmin.value) +)) const statusState = computed(() => statusStates.value[props.status] ?? { page: 1, perPage: 20, @@ -111,7 +119,11 @@ const showPagination = computed(() => !isObservationStatus.value) const columns = computed(() => { const resolvedBaseColumns = reportStore.getColumnsByKey(props.keys) .filter(Boolean) - .map((column) => ({ ...column })) + .map((column) => ({ + ...column, + align: 'left', + titleAlign: 'left', + })) const newColumns = [] @@ -119,6 +131,8 @@ const columns = computed(() => { title: '', key: 'goToMis', width: 40, + align: 'left', + titleAlign: 'left', render: (row) => { const actions = [] @@ -204,6 +218,8 @@ const columns = computed(() => { const removeColumn = { title: '', key: 'remove', + align: 'left', + titleAlign: 'left', render: (row) => h( NButton, { @@ -226,6 +242,8 @@ const columns = computed(() => { const expandColumn = { title: '', width: '30', + align: 'left', + titleAlign: 'left', render: (rowData) => { return h( NIcon, @@ -246,6 +264,8 @@ const columns = computed(() => { title: '', key: 'fillable', width: '20', + align: 'left', + titleAlign: 'left', render: (row) => h( NBadge, { @@ -265,6 +285,8 @@ const columns = computed(() => { newColumns.push({ title: 'Диагноз', key: 'ds', + align: 'left', + titleAlign: 'left', render: (row) => { if (row.mkb.ds !== null && row.mkb.name !== null) { return h(NPopover, { @@ -298,6 +320,8 @@ const columns = computed(() => { const operationColumn = { title: 'Операции', key: 'operations', + align: 'left', + titleAlign: 'left', render: (row) => canEditSpecial.value && row?.department_patient_id ? h( 'div', @@ -354,6 +378,8 @@ const columns = computed(() => { const typeColumn = { title: 'Причина', key: 'outcome_type', + align: 'left', + titleAlign: 'left', ellipsis: { tooltip: true } @@ -361,6 +387,86 @@ const columns = computed(() => { newColumns.push(typeColumn) } + if (baseStatus.value === 'reanimation') { + const indicatorColumn = { + title: 'Состояние', + key: 'reanimation_indicator', + width: 120, + minWidth: 100, + maxWidth: 140, + align: 'left', + titleAlign: 'left', + render: (row) => { + const labelMap = { + stable: 'Стабильный', + moderate: 'Средней тяжести', + severe: 'Тяжелый', + critical: 'Критический', + } + const value = row.reanimation_indicator ? (labelMap[row.reanimation_indicator] ?? row.reanimation_indicator) : '-' + + if (canEditReanimationIndicator.value) { + return h( + 'div', + { + class: 'inline-flex items-center gap-1', + }, + [ + h( + 'div', + { + class: 'underline decoration-dashed cursor-pointer', + onClick: () => { + activePatient.value = row + showReanimationIndicatorModal.value = true + }, + }, + value + ), + h( + NTooltip, + {}, + { + trigger: () => h( + 'div', + { + class: 'cursor-pointer inline-flex text-slate-500', + onClick: () => { + activePatient.value = row + showReanimationIndicatorModal.value = true + }, + }, + [h(NIcon, { size: 14 }, h(TbEdit))] + ), + default: () => 'Изменить состояние', + } + ), + ] + ) + } + + return h( + NTooltip, + {}, + { + trigger: () => h( + 'div', + { + class: 'inline-flex items-center gap-1 text-slate-500', + }, + [ + h('span', value), + h(NIcon, { size: 14 }, h(TbLock)), + ] + ), + default: () => 'Редактирование недоступно', + } + ) + }, + } + newColumns.push(indicatorColumn) + } + return newColumns }) @@ -540,13 +646,20 @@ watch(() => reportStore.reportInfo?.dates, async () => { + diff --git a/resources/js/Stores/report.js b/resources/js/Stores/report.js index 5cb1d1a..f555765 100644 --- a/resources/js/Stores/report.js +++ b/resources/js/Stores/report.js @@ -394,6 +394,17 @@ export const useReportStore = defineStore('reportStore', () => { await axios.delete(`/api/report/manual-patients/${departmentPatientId}/operations/${operationId}`) } + const saveReanimationIndicator = async (payload) => { + await axios.post('/api/report/reanimation-indicators', { + ...payload, + departmentId: reportInfo.value?.department?.department_id, + startAt: reportInfo.value?.dates?.startAt, + endAt: reportInfo.value?.dates?.endAt, + }) + + await loadPatientsByStatus('mis-reanimation', { resetPage: true }) + } + return { reportFormRef, timestampNow, @@ -439,6 +450,7 @@ export const useReportStore = defineStore('reportStore', () => { createManualPatientOperation, updateManualPatientOperation, deleteManualPatientOperation, + saveReanimationIndicator, sendReportForm, addRowNumbers, } diff --git a/routes/api.php b/routes/api.php index f0f6ea2..6d8cab1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -71,6 +71,8 @@ Route::middleware(['auth:sanctum'])->group(function () { Route::post('/manual-patients/{departmentPatientId}/operations', [ReportController::class, 'createManualPatientOperation']); Route::patch('/manual-patients/{departmentPatientId}/operations/{operationId}', [ReportController::class, 'updateManualPatientOperation']); Route::delete('/manual-patients/{departmentPatientId}/operations/{operationId}', [ReportController::class, 'deleteManualPatientOperation']); + Route::post('/reanimation-indicators', [ReportController::class, 'saveReanimationIndicator']); + Route::get('/reanimation-indicators/history', [ReportController::class, 'getReanimationIndicatorHistory']); Route::post('/observation/remove', [ReportController::class, 'removeObservation']); Route::delete('/unwanted-event/{unwantedEvent}', [ReportController::class, 'removeUnwantedEvent']); diff --git a/routes/web.php b/routes/web.php index d03ae20..b3cc599 100644 --- a/routes/web.php +++ b/routes/web.php @@ -52,11 +52,13 @@ Route::get('/logout', [\App\Http\Controllers\AuthController::class, 'logout']) Route::get('/report', [\App\Http\Controllers\Web\ReportController::class, 'index']) ->middleware(['auth']) ->name('report'); +Route::get('/report/export', [\App\Http\Controllers\Web\ReportController::class, 'export']) + ->middleware(['auth']) + ->name('report.export'); Route::post('/report', [\App\Http\Controllers\Web\ReportController::class, 'store']) ->middleware(['auth']) ->name('report.store'); - Route::post('/user/role/change', [\App\Http\Controllers\AuthController::class, 'changeRole']) ->middleware(['auth']) ->name('user.role.change'); diff --git a/tests/Feature/AutoFillReportsTest.php b/tests/Feature/AutoFillReportsTest.php index 996bc7a..7f8ae3f 100644 --- a/tests/Feature/AutoFillReportsTest.php +++ b/tests/Feature/AutoFillReportsTest.php @@ -176,11 +176,12 @@ it('builds auto fill payload from the same patient metrics that are stored in re 'rf_DepartmentID' => 100, ]); - $department = new Department(); + $department = new Department; $department->department_id = 10; $department->rf_mis_department_id = 100; - $user = new class extends User { + $user = new class extends User + { public function isHeadOfDepartment() { return false; @@ -244,11 +245,11 @@ it('builds auto fill payload from the same patient metrics that are stored in re }); it('creates auto-filled report through report service with auto flag and scoped department user', function () { - $department = new Department(); + $department = new Department; $department->department_id = 10; $department->rf_mis_department_id = 100; - $user = new User(); + $user = new User; $user->id = 15; $user->rf_lpudoctor_id = 5015; $user->rf_department_id = 999; @@ -276,7 +277,7 @@ it('creates auto-filled report through report service with auto flag and scoped && $scopedUser->rf_department_id === 10 && $scopedUser->department->department_id === 10; }) - ->andReturn(new \App\Models\Report()); + ->andReturn(new \App\Models\Report); $service = new AutoReportService($reportService, app(DateRangeService::class)); @@ -284,11 +285,11 @@ it('creates auto-filled report through report service with auto flag and scoped }); it('force recreation removes previous report scoped data before storing a new auto-filled report', function () { - $department = new Department(); + $department = new Department; $department->department_id = 10; $department->rf_mis_department_id = 100; - $user = new User(); + $user = new User; $user->id = 15; $user->rf_lpudoctor_id = 5015; $user->rf_department_id = 10; @@ -340,7 +341,7 @@ it('force recreation removes previous report scoped data before storing a new au $reportService ->shouldReceive('storeReport') ->once() - ->andReturn(new \App\Models\Report()); + ->andReturn(new \App\Models\Report); $service = new AutoReportService($reportService, app(DateRangeService::class)); diff --git a/tests/Feature/ReportPatientsServicesTest.php b/tests/Feature/ReportPatientsServicesTest.php index 88150c5..6ad6471 100644 --- a/tests/Feature/ReportPatientsServicesTest.php +++ b/tests/Feature/ReportPatientsServicesTest.php @@ -10,7 +10,6 @@ use App\Services\SnapshotService; use App\Services\UnifiedPatientService; use Carbon\Carbon; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; @@ -164,8 +163,10 @@ function reportDateRange(string $start, string $end): DateRange function makeUser(bool $isHead = false, bool $isAdmin = false): User { - $user = new class extends User { + $user = new class extends User + { public bool $head = false; + public bool $admin = false; public function isHeadOfDepartment() @@ -364,7 +365,7 @@ it('uses snapshots for head on current-day plan requests', function () { ]); $dateRange = reportDateRange('2026-04-08 06:00:00', '2026-04-09 06:00:00'); - $department = new Department(); + $department = new Department; $department->department_id = 1; $department->rf_mis_department_id = 100; $user = makeUser(isHead: true); @@ -450,7 +451,7 @@ it('uses snapshots for historical head plan requests and counts unique snapshot endDateRaw: '2026-04-08 06:00:00', isOneDay: false, ); - $department = new Department(); + $department = new Department; $department->department_id = 1; $department->rf_mis_department_id = 100; $user = makeUser(isHead: true); @@ -588,7 +589,7 @@ it('filters historical head plan snapshots by latest report and marks only lates isOneDay: false, ); - $department = new Department(); + $department = new Department; $department->department_id = 1; $department->rf_mis_department_id = 100; @@ -704,7 +705,7 @@ it('uses current snapshots for head one-day plan list and marks recipients separ ]); $dateRange = reportDateRange('2026-04-08 06:00:00', '2026-04-09 06:00:00'); - $department = new Department(); + $department = new Department; $department->department_id = 1; $department->rf_mis_department_id = 100; @@ -739,7 +740,7 @@ it('separates special contingent from mis lists while keeping it in aggregate li ]); $dateRange = reportDateRange('2026-04-08 06:00:00', '2026-04-09 06:00:00'); - $department = new Department(); + $department = new Department; $department->department_id = 1; $department->rf_mis_department_id = 100; $user = makeUser(); @@ -813,7 +814,7 @@ it('separates special contingent from mis lists in snapshot mode', function () { endDateRaw: '2026-04-08 06:00:00', isOneDay: false, ); - $department = new Department(); + $department = new Department; $department->department_id = 1; $department->rf_mis_department_id = 100; $user = makeUser(isHead: true); @@ -937,7 +938,7 @@ it('keeps operations for replica patients on live report requests', function () ]); $dateRange = reportDateRange('2026-04-08 06:00:00', '2026-04-09 06:00:00'); - $department = new Department(); + $department = new Department; $department->department_id = 1; $department->rf_mis_department_id = 100; $user = makeUser(); @@ -1000,7 +1001,7 @@ it('keeps completed operations in patient rows even if operation date is outside ]); $dateRange = reportDateRange('2026-04-08 06:00:00', '2026-04-09 06:00:00'); - $department = new Department(); + $department = new Department; $department->department_id = 1; $department->rf_mis_department_id = 100; $user = makeUser();