rf_mis_department_id; $branchId = $this->getBranchId($misDepartmentId); // Определяем, используем ли мы снапшоты $useSnapshots = $this->shouldUseSnapshots($department, $user, $dateRange); if ($useSnapshots) { return $this->getStatisticsFromSnapshots($department, $dateRange, $branchId); } return $this->getStatisticsFromReplica($department, $user, $dateRange, $branchId); } public function shouldUseSnapshotsForPage(Department $department, User $user, DateRange $dateRange): bool { return $this->shouldUseSnapshots($department, $user, $dateRange); } public function getFastReplicaStatisticsFromPatientsPayload( Department $department, User $user, DateRange $dateRange, array $patientsPayload ): array { $branchId = $this->getBranchId($department->rf_mis_department_id); $planCount = count($patientsPayload['mis-plan'] ?? []) + count($patientsPayload['special-plan'] ?? []); $emergencyCount = count($patientsPayload['mis-emergency'] ?? []) + count($patientsPayload['special-emergency'] ?? []); $dischargedCount = count($patientsPayload['mis-outcome-discharged'] ?? []) + count($patientsPayload['special-outcome-discharged'] ?? []); $deadCount = count($patientsPayload['mis-outcome-deceased'] ?? []) + count($patientsPayload['special-outcome-deceased'] ?? []); $outcomeCount = $dischargedCount + $deadCount; $recipientPatients = $this->unifiedPatientService ->getLivePatientsByStatus($department, $user, 'recipient', $dateRange, $branchId); $recipientCount = $recipientPatients->count(); $recipientIds = $recipientPatients->pluck('id')->all(); $currentCount = $this->unifiedPatientService ->getLivePatientCountByStatus($department, $user, 'current', $dateRange, $branchId); $misSurgicalCount = [ $this->patientQueryService->getSurgicalPatients( 'emergency', $branchId, $dateRange, true ), $this->patientQueryService->getSurgicalPatients( 'plan', $branchId, $dateRange, true ) ]; $manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange); $surgicalCount = [ ($misSurgicalCount[0] ?? 0) + ($manualSurgicalCount[0] ?? 0), ($misSurgicalCount[1] ?? 0) + ($manualSurgicalCount[1] ?? 0), ]; $misBranch = MisStationarBranch::where('StationarBranchID', $branchId)->first(); $beds = Department::where('rf_mis_department_id', $misBranch->rf_DepartmentID) ->first()->metrikaDefault->where('rf_metrika_item_id', 1)->first(); if ($outcomeCount == 0) { $percentDead = 0; } else { $percentDead = ($deadCount / $outcomeCount) * 100; $percentDead = round($percentDead, 2); } return [ 'recipientCount' => $recipientCount, 'extractCount' => $outcomeCount, 'currentCount' => $currentCount, 'deadCount' => $deadCount, 'surgicalCount' => $surgicalCount, 'recipientIds' => $recipientIds, 'planCount' => $planCount, 'emergencyCount' => $emergencyCount, 'percentDead' => $percentDead, 'beds' => $beds->value ]; } /** * Создать или обновить отчет */ public function storeReport(array $data, User $user, $fillableAuto = false): Report { $this->prepareMemoryForHeavySave(); try { $report = DB::transaction(function () use ($data, $user, $fillableAuto) { $report = $this->createOrUpdateReport($data, $user); // Сохраняем все, что НЕ зависит от других отчетов $this->saveMetrics($report, $data['metrics'] ?? []); $this->saveUnwantedEvents($report, $data['unwantedEvents'] ?? []); $this->saveObservationPatients($report, $data['observationPatients'] ?? [], $user->rf_department_id); $shouldBuildSnapshots = (bool) $fillableAuto; if ($shouldBuildSnapshots) { $this->snapshotService->createPatientSnapshots($report, $user, $data['dates'], $fillableAuto); $this->syncCalculatedMetrics($report, $user, $data); } else { MedicalHistorySnapshot::query() ->where('rf_report_id', $report->report_id) ->delete(); } return $report; }); $shouldBuildDerivedMetrics = (bool) $fillableAuto; if ($shouldBuildDerivedMetrics) { DB::transaction(function () use ($report) { // Сохраняем метрику койко-дня + среднего койко-дня из снапшотов $this->saveBedDaysMetric($report); $this->saveLethalMetricFromSnapshots($report); $this->savePreoperativeMetric($report); $this->saveDepartmentLoadedMetric($report); }); } } catch (\Throwable $e) { throw $e; } $this->clearCacheAfterReportCreation($user, $report); return $report; } private function prepareMemoryForHeavySave(): void { $connectionNames = array_unique(array_filter([ DB::getDefaultConnection(), (new MisMedicalHistory)->getConnectionName(), (new MisMigrationPatient)->getConnectionName(), (new MisStationarBranch)->getConnectionName(), ])); foreach ($connectionNames as $connectionName) { try { $connection = DB::connection($connectionName); $connection->disableQueryLog(); $connection->flushQueryLog(); } catch (\Throwable) { // best-effort cleanup only } } if (function_exists('gc_collect_cycles')) { gc_collect_cycles(); } } public function buildAutoFillReportPayload(User $user, Department $department, DateRange $dateRange): array { $branchId = $this->getBranchId($department->rf_mis_department_id); $metrics = $this->buildAutoFillMetrics($department, $user, $branchId, $dateRange); return [ 'departmentId' => $department->department_id, 'userId' => $user->rf_lpudoctor_id ?? $user->id, 'dates' => [ $dateRange->startTimestamp(), $dateRange->endTimestamp(), ], 'sent_at' => $dateRange->endSql(), 'created_at' => $dateRange->endSql(), 'status' => 'submitted', 'metrics' => [ 'metrika_item_4' => $metrics['plan'], 'metrika_item_12' => $metrics['emergency'], 'metrika_item_3' => $metrics['recipient'], 'metrika_item_7' => $metrics['discharged'] + $metrics['deceased'], 'metrika_item_8' => $metrics['current'], 'metrika_item_9' => $metrics['deceased'], 'metrika_item_10' => $metrics['emergency_surgery'], 'metrika_item_11' => $metrics['plan_surgery'], 'metrika_item_13' => $metrics['transferred'], 'metrika_item_14' => 0, 'metrika_item_15' => $metrics['discharged'], ], 'observationPatients' => [], 'unwantedEvents' => [], ]; } private function buildAutoFillMetrics(Department $department, User $user, int $branchId, DateRange $dateRange): array { $manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange); $recipientQuery = $this->buildRecipientMedicalHistoryQuery($branchId, $dateRange); $dischargeCodes = [1, 11, 2, 12, 7, 18, 48]; $deceasedCodes = [5, 6, 15, 16]; $transferCodes = [4, 14]; $planRecipient = (clone $recipientQuery) ->where('rf_EmerSignID', 1) ->distinct() ->count('MedicalHistoryID'); $emergencyRecipient = (clone $recipientQuery) ->whereIn('rf_EmerSignID', [2, 4]) ->distinct() ->count('MedicalHistoryID'); $recipientTotal = (clone $recipientQuery) ->distinct() ->count('MedicalHistoryID'); $discharged = $this->countOutcomeByVisitResultIds($branchId, $dateRange, $dischargeCodes); $deceased = $this->countOutcomeByVisitResultIds($branchId, $dateRange, $deceasedCodes); $transferred = $this->countOutcomeByVisitResultIds($branchId, $dateRange, $transferCodes); return [ 'plan' => $planRecipient, 'emergency' => $emergencyRecipient, 'recipient' => $recipientTotal, 'discharged' => $discharged, 'transferred' => $transferred, 'deceased' => $deceased, 'current' => $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'current', $dateRange, $branchId, null, true), 'plan_surgery' => $this->patientQueryService->getSurgicalPatients( 'plan', $branchId, $dateRange, true ) + ($manualSurgicalCount[1] ?? 0), 'emergency_surgery' => $this->patientQueryService->getSurgicalPatients( 'emergency', $branchId, $dateRange, true ) + ($manualSurgicalCount[0] ?? 0), ]; } private function buildRecipientMedicalHistoryQuery(int $branchId, DateRange $dateRange) { $startAt = $dateRange->start()->copy()->subDay()->format('Y-m-d H:i:s'); $endAt = $dateRange->end()->copy()->addDay()->format('Y-m-d H:i:s'); if ($dateRange->isOneDay) { $startAt = $dateRange->startSql(); $endAt = $dateRange->endSql(); } return MisMedicalHistory::query() ->where('MedicalHistoryID', '<>', 0) ->whereExists(function ($query) use ($branchId, $startAt, $endAt) { $query->select(DB::raw(1)) ->from('stt_migrationpatient as mp') ->whereColumn('mp.rf_MedicalHistoryID', 'stt_medicalhistory.MedicalHistoryID') ->where('mp.rf_StationarBranchID', $branchId) ->where('mp.DateIngoing', '>', $startAt) ->where('mp.DateIngoing', '<=', $endAt); }); } private function buildTreatedMedicalHistoryQuery(int $branchId, DateRange $dateRange) { $query = MisMedicalHistory::query() ->where('MedicalHistoryID', '<>', 0) ->whereExists(function ($query) use ($branchId) { $query->select(DB::raw(1)) ->from('stt_migrationpatient as mp') ->whereColumn('mp.rf_MedicalHistoryID', 'stt_medicalhistory.MedicalHistoryID') ->where('mp.rf_StationarBranchID', $branchId); }); if ($dateRange->isOneDay) { return $query ->where('DateExtract', '>', $dateRange->startSql()) ->where('DateExtract', '<=', $dateRange->endSql()); } $startAt = $dateRange->startSql(); $endDate = $dateRange->end()->toDateString(); return $query ->where('DateExtract', '>', $startAt) ->whereDate('DateExtract', '<=', $endDate); } private function countOutcomeByVisitResultIds(int $branchId, DateRange $dateRange, array $visitResultIds): int { return $this->buildTreatedMedicalHistoryQuery($branchId, $dateRange) ->whereExists(function ($query) use ($branchId, $visitResultIds) { $query->select(DB::raw(1)) ->from('stt_migrationpatient as mp') ->whereColumn('mp.rf_MedicalHistoryID', 'stt_medicalhistory.MedicalHistoryID') ->where('mp.rf_StationarBranchID', $branchId) ->whereIn('mp.rf_kl_VisitResultID', $visitResultIds); }) ->distinct() ->count('MedicalHistoryID'); } /** * Сохранить метрику койко-дня из снапшотов отчета */ protected function saveBedDaysMetric(Report $report): void { try { $result = $this->calculateBedDaysFromSnapshots($report); MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, 'rf_metrika_item_id' => 25, // койко-дни ], ['value' => $result['total_days']] ); MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, 'rf_metrika_item_id' => 18, // средний койко-день ], ['value' => $result['avg_days']] ); } catch (\Throwable $e) { \Log::error('Failed to save bed days metric: ' . $e->getMessage()); } } protected function calculateBedDaysFromSnapshots(Report $report): array { $snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id) ->whereIn('patient_type', ['discharged', 'deceased']) ->with('medicalHistory') ->get(); $totalDays = 0; $patientCount = 0; foreach ($snapshots as $snapshot) { $history = $snapshot->medicalHistory; if (!$history) { continue; } $start = $history->DateRecipientHS ?? $history->DateRecipient ?? null; 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)) { $end = $history->DateDeath; } 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)) { $end = $history->DateExtract; } } if (!$end) { continue; } $start = Carbon::parse($start); $end = Carbon::parse($end); if ($end->lt($start)) { continue; } // Календарные койко-дни $days = $start->startOfDay()->diffInDays($end->startOfDay()); $totalDays += $days; $patientCount++; } return [ 'total_days' => $totalDays, 'patient_count' => $patientCount, 'avg_days' => $patientCount > 0 ? round($totalDays / $patientCount, 2) : 0, ]; } /** * Рассчитать предоперационные койко-дни по снапшотам отчета * * Возвращает: * - total_days: общее количество предоперационных койко-дней * - patient_count: количество пациентов, вошедших в расчет * - avg_days: средний предоперационный койко-день */ protected function calculatePreoperativeDaysFromSnapshots(Report $report): array { $patientIds = MedicalHistorySnapshot::where('rf_report_id', $report->report_id) ->whereIn('patient_type', ['discharged', 'deceased']) ->pluck('rf_medicalhistory_id') ->unique() ->values(); if ($patientIds->isEmpty()) { return [ 'total_days' => 0, 'patient_count' => 0, 'avg_days' => 0, ]; } $rows = DB::table('stt_medicalhistory as mh') ->join('stt_surgicaloperation as so', 'so.rf_MedicalHistoryID', '=', 'mh.MedicalHistoryID') ->whereIn('mh.MedicalHistoryID', $patientIds) ->whereNotNull('so.Date') ->select( 'mh.MedicalHistoryID', DB::raw('MIN(so."Date") as first_operation'), 'mh.DateRecipientHS', 'mh.DateRecipient' ) ->groupBy('mh.MedicalHistoryID', 'mh.DateRecipientHS', 'mh.DateRecipient') ->get(); if ($rows->isEmpty()) { return [ 'total_days' => 0, 'patient_count' => 0, 'avg_days' => 0, ]; } $totalDays = 0; $patientCount = 0; foreach ($rows as $row) { $startRaw = $row->DateRecipientHS ?? $row->DateRecipient ?? null; $operationRaw = $row->first_operation ?? null; if (!$startRaw || !$operationRaw) { continue; } $start = Carbon::parse($startRaw); $operation = Carbon::parse($operationRaw); if ($operation->lt($start)) { continue; } // Разница календарных дат $days = $start->startOfDay()->diffInDays($operation->startOfDay()); $totalDays += $days; $patientCount++; } return [ 'total_days' => $totalDays, 'patient_count' => $patientCount, 'avg_days' => $patientCount > 0 ? round($totalDays / $patientCount, 1) : 0, ]; } protected function saveLethalMetricFromSnapshots(Report $report): void { // Получаем все снапшоты выписанных пациентов из этого отчета $snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id) ->whereIn('patient_type', ['discharged', 'deceased']) // выписанные и умершие ->with('medicalHistory') ->get(); if ($snapshots->isEmpty()) { // Если нет выписанных, сохраняем 0 MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, 'rf_metrika_item_id' => 18, ], ['value' => 0] ); \Log::info("No discharged patients in report {$report->report_id}, saved 0"); return; } } /** * Сохранить предоперационный койко-день из снапшотов */ protected function savePreoperativeMetric(Report $report): void { try { $result = $this->calculatePreoperativeDaysFromSnapshots($report); $this->saveMetric($report, 26, $result['total_days']); $this->saveMetric($report, 21, $result['avg_days']); } catch (\Throwable $e) { \Log::error('Failed to save preoperative total metric: ' . $e->getMessage()); } } /** * Сохранить % загруженности */ protected function saveDepartmentLoadedMetric(Report $report): void { // Получаем все снапшоты выписанных пациентов из этого отчета $currentCount = $report->metrikaResults()->where('rf_metrika_item_id', 8)->value('value'); $bedsCount = $report->metrikaResults()->where('rf_metrika_item_id', 1)->value('value'); $percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0; $this->saveMetric($report, 22, $percentLoaded); } /** * Очистить кэш после создания отчета */ private function clearCacheAfterReportCreation(User $user, Report $report): void { // Очищаем кэш статистики для пользователя // $this->statisticsService->clearStatisticsCache($user); // Также можно очистить кэш для всех пользователей отдела // $this->statisticsService->clearDepartmentStatisticsCache($user->rf_department_id); // Очищаем кэш за сегодня и вчера (так как отчеты влияют на эти даты) $this->clearDailyCache($user, $report->created_at); } /** * Очистить дневной кэш */ private function clearDailyCache(User $user, $reportDate): void { $datesToClear = [ Carbon::parse($reportDate)->format('Y-m-d'), Carbon::parse($reportDate)->subDay()->format('Y-m-d'), ]; foreach ($datesToClear as $date) { $cacheKey = $this->generateDailyCacheKey($user, $date); Cache::forget($cacheKey); } } private function generateDailyCacheKey(User $user, string $date): string { return 'daily_stats:' . $user->rf_department_id . ':' . $date; } /** * Получить пациентов по статусу */ public function getPatientsByStatus( Department $department, User $user, string $status, DateRange $dateRange, bool $onlyIds = false, bool $beforeCreate = false, ?bool $includeCurrentPatients = null ) { [$baseStatus, $sourceScope] = $this->parseScopedStatus($status); $branchId = $this->getBranchId($department->rf_mis_department_id); if ($sourceScope === 'special') { return $this->getPatientsFromReplica( $department, $user, $status, $dateRange, $branchId, $onlyIds, $includeCurrentPatients ); } if ($baseStatus === 'reanimation') { return $this->getPatientsFromReplica( $department, $user, $status, $dateRange, $branchId, $onlyIds, $includeCurrentPatients ); } $useSnapshots = !$this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange) && $this->shouldUseSnapshots($department, $user, $dateRange, $beforeCreate); if ($useSnapshots) { return $this->getPatientsFromSnapshots($department, $status, $dateRange, $branchId, $onlyIds); } return $this->getPatientsFromReplica( $department, $user, $status, $dateRange, $branchId, $onlyIds, $includeCurrentPatients ); } /** * Получить количество пациентов по статусу */ public function getPatientsCountByStatus( Department $department, User $user, string $status, DateRange $dateRange ): int { [$baseStatus, $sourceScope] = $this->parseScopedStatus($status); $branchId = $this->getBranchId($department->rf_mis_department_id); if ($sourceScope === 'special') { return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId); } if ($baseStatus === 'reanimation') { return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId); } $useSnapshots = !$this->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange) && $this->shouldUseSnapshots($department, $user, $dateRange); if ($useSnapshots) { return $this->getPatientsCountFromSnapshots($department, $status, $dateRange); } return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId); } public function getPatientsCountsMap(Department $department, User $user, DateRange $dateRange): array { $baseStatuses = [ 'plan', 'emergency', 'observation', 'reanimation', 'outcome-discharged', 'outcome-deceased', 'outcome-transferred', ]; $counts = [ 'mis-plan' => 0, 'mis-emergency' => 0, 'mis-observation' => 0, 'mis-reanimation' => 0, 'mis-outcome' => 0, 'mis-outcome-discharged' => 0, 'mis-outcome-deceased' => 0, 'mis-outcome-transferred' => 0, 'special-plan' => 0, 'special-emergency' => 0, 'special-observation' => 0, 'special-reanimation' => 0, 'special-outcome' => 0, 'special-outcome-discharged' => 0, 'special-outcome-deceased' => 0, 'special-outcome-transferred' => 0, ]; foreach ($baseStatuses as $baseStatus) { $counts["mis-{$baseStatus}"] = $this->getPatientsCountByStatus( $department, $user, "mis-{$baseStatus}", $dateRange ); $counts["special-{$baseStatus}"] = $this->getPatientsCountByStatus( $department, $user, "special-{$baseStatus}", $dateRange ); } // Выбывшие = выписанные + умершие (без переведенных) $counts['mis-outcome'] = ($counts['mis-outcome-discharged'] ?? 0) + ($counts['mis-outcome-deceased'] ?? 0); $counts['special-outcome'] = ($counts['special-outcome-discharged'] ?? 0) + ($counts['special-outcome-deceased'] ?? 0); return $counts; } /** * Получить ID отделения из стационарного отделения */ private function getBranchId(int $misDepartmentId): ?int { return MisStationarBranch::where('rf_DepartmentID', $misDepartmentId) ->value('StationarBranchID'); } /** * Определить, нужно ли использовать снапшоты */ private function shouldUseSnapshots(Department $department, User $user, DateRange $dateRange, bool $beforeCreate = false): bool { if ($beforeCreate) { return false; } $report = $this->getReportForPeriod($department->department_id, $dateRange); if (!$report) { return false; } if ($report->status !== 'submitted') { return false; } return true; } private function shouldUseReplicaForLiveStatus(User $user, string $status, DateRange $dateRange): bool { if ($user->isHeadOfDepartment() || $user->isAdmin()) { return false; } return in_array($status, ['plan', 'emergency', 'recipient', 'current'], true) && $dateRange->isOneDay && $dateRange->isEndDateToday(); } /** * Создать или обновить отчет */ private function createOrUpdateReport(array $data, User $user): Report { $rangeStartAt = isset($data['dates'][0]) ? $this->dateRangeService->toSqlFormat($data['dates'][0]) : null; $rangeEndAt = isset($data['dates'][1]) ? $this->dateRangeService->toSqlFormat($data['dates'][1]) : null; $dateRange = $this->dateRangeService->createDateRangeForDate($this->dateRangeService->toCarbon($data['dates'][1]), $user); $sentAt = $data['sent_at'] ?? $rangeEndAt ?? $this->dateRangeService->toSqlFormat(\Illuminate\Support\Carbon::now()); $createdAt = $data['created_at'] ?? $rangeEndAt ?? $this->dateRangeService->toSqlFormat(\Illuminate\Support\Carbon::now()); $reportData = [ 'rf_department_id' => $data['departmentId'], 'rf_user_id' => $user->id, 'rf_lpudoctor_id' => $data['userId'], 'sent_at' => $sentAt, 'period_start' => $dateRange->startSql(), 'period_end' => $dateRange->endSql(), 'created_at' => $createdAt, 'status' => $data['status'] ?? 'draft', ]; if (isset($data['reportId']) && $data['reportId']) { $report = Report::updateOrCreate( ['report_id' => $data['reportId']], $reportData ); } else { $report = Report::create($reportData); $department = Department::where('department_id', $reportData['rf_department_id'])->first(); $beds = $department->metrikaDefault->where('rf_metrika_item_id', 1)->first(); MetrikaResult::create([ 'rf_report_id' => $report->report_id, 'rf_metrika_item_id' => 1, 'value' => $beds->value ]); } return $report; } /** * Сохранить метрики отчета */ private function saveMetrics(Report $report, array $metrics): void { foreach ($metrics as $key => $value) { $metrikaId = (int)str_replace('metrika_item_', '', $key); MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, 'rf_metrika_item_id' => $metrikaId, ], [ 'value' => $value, ] ); } } /** * Сохранить метрику отчета */ private function saveMetric(Report $report, int $metrikaId, float $value): void { MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, 'rf_metrika_item_id' => $metrikaId, ], [ 'value' => $value, ] ); } /** * Сохранить нежелательные события */ private function saveUnwantedEvents(Report $report, array $unwantedEvents): void { if (empty($unwantedEvents)) { $report->unwantedEvents()->delete(); $this->saveMetric($report, 16, 0); return; } foreach ($unwantedEvents as $event) { if (isset($event['unwanted_event_id']) && $event['unwanted_event_id']) { UnwantedEvent::updateOrCreate( ['unwanted_event_id' => $event['unwanted_event_id']], [ 'rf_report_id' => $report->report_id, 'comment' => $event['comment'] ?? '', 'title' => $event['title'] ?? '', 'is_visible' => $event['is_visible'] ?? true, ] ); } else { UnwantedEvent::create([ 'rf_report_id' => $report->report_id, 'comment' => $event['comment'] ?? '', 'title' => $event['title'] ?? '', 'is_visible' => $event['is_visible'] ?? true, ]); } } // Обновить метрику $this->saveMetric($report, 16, count($unwantedEvents)); } /** * Сохранить пациентов под наблюдением */ private function saveObservationPatients( Report $report, array $observationPatients, int $departmentId ): void { if (empty($observationPatients)) { ObservationPatient::where('rf_department_id', $departmentId) ->where('rf_report_id', $report->report_id) ->delete(); // Обновить метрику $this->saveMetric($report, 14, 0); return; } foreach ($observationPatients as $patient) { ObservationPatient::updateOrCreate( [ 'rf_medicalhistory_id' => $patient['medical_history_id'] ?? null, 'rf_department_patient_id' => $patient['department_patient_id'] ?? null, 'rf_department_id' => $departmentId, ], [ 'rf_report_id' => $report->report_id, 'rf_mkab_id' => null, 'comment' => $patient['comment'] ?? null, ] ); } // Обновить метрику $this->saveMetric($report, 14, count($observationPatients)); } private function syncCalculatedMetrics(Report $report, User $user, array $data): void { if (!isset($data['dates'][0], $data['dates'][1])) { return; } $department = Department::query()->where('department_id', $report->rf_department_id)->first(); if (!$department) { return; } $dateRange = $this->dateRangeService->getNormalizedDateRange( $user, (string) $data['dates'][0], (string) $data['dates'][1] ); $branchId = $this->getBranchId($department->rf_mis_department_id); $planCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['plan']); $emergencyCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['emergency']); $recipientCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['recipient']); $dischargedCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['discharged']); $transferredCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['transferred']); $deceasedCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['deceased']); $currentCount = $this->countUniqueSnapshotsForTypes($report->report_id, ['current']); $outcomeCount = $dischargedCount + $deceasedCount; $manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange); $misEmergencySurgery = $branchId ? $this->patientQueryService->getSurgicalPatients('emergency', $branchId, $dateRange, true) : 0; $misPlanSurgery = $branchId ? $this->patientQueryService->getSurgicalPatients('plan', $branchId, $dateRange, true) : 0; $observationCount = ObservationPatient::query() ->where('rf_department_id', $department->department_id) ->where('rf_report_id', $report->report_id) ->count(); $unwantedEventsCount = UnwantedEvent::query() ->where('rf_report_id', $report->report_id) ->count(); $this->saveMetric($report, 3, $recipientCount); $this->saveMetric($report, 4, $planCount); $this->saveMetric($report, 7, $outcomeCount); $this->saveMetric($report, 8, $currentCount); $this->saveMetric($report, 9, $deceasedCount); $this->saveMetric($report, 10, $misEmergencySurgery + ($manualSurgicalCount[0] ?? 0)); $this->saveMetric($report, 11, $misPlanSurgery + ($manualSurgicalCount[1] ?? 0)); $this->saveMetric($report, 12, $emergencyCount); $this->saveMetric($report, 13, $transferredCount); $this->saveMetric($report, 14, $observationCount); $this->saveMetric($report, 15, $dischargedCount); $this->saveMetric($report, 16, $unwantedEventsCount); } private function countUniqueSnapshotsForTypes(int $reportId, array $patientTypes): int { return MedicalHistorySnapshot::query() ->where('rf_report_id', $reportId) ->whereIn('patient_type', $patientTypes) ->get(['medical_history_snapshot_id', 'patient_uid', 'rf_medicalhistory_id']) ->map(function (MedicalHistorySnapshot $snapshot) { return $snapshot->patient_uid ?: ($snapshot->rf_medicalhistory_id ? "mis:{$snapshot->rf_medicalhistory_id}" : "snapshot:{$snapshot->medical_history_snapshot_id}"); }) ->unique() ->count(); } /** * Получить информацию о текущем отчете */ public function getCurrentReportInfo(Department $department, User $user, DateRange $dateRange): array { $reportToday = $this->getReportForPeriod($department->department_id, $dateRange); $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); $useSnapshots = $isHeadOrAdmin || !$dateRange->isEndDateToday() || $reportToday; // Получаем ID пользователя для заполнения отчета if ($useSnapshots && $isHeadOrAdmin && $reportToday) { $fillableUserId = $reportToday->rf_lpudoctor_id ?? null; } else { $fillableUserId = request()->query('userId', $user->rf_lpudoctor_id); } // Получаем нежелательные события $unwantedEvents = $this->getUnwantedEvents($department, $dateRange); // Определяем активность кнопки отправки $isActiveSendButton = $this->isSendButtonActive($user, $dateRange, $reportToday, $fillableUserId); $message = null; if ($reportToday) { $reportDoctor = $reportToday->lpuDoctor; $message = "Отчет создан пользователем: $reportDoctor->FAM_V $reportDoctor->IM_V $reportDoctor->OT_V"; } $statusMessage = $reportToday ? ($reportToday->status === 'submitted' ? 'Этот отчет в статусе: опубликован' : 'Этот отчет в статусе: черновик') : null; // Получаем информацию о враче $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') // ); // Формируем даты для ответа // $date = $isHeadOrAdmin ? [ // $endDate->copy()->subDay()->getTimestampMs(), // $endDate->getTimestampMs() // ] : $endDate->getTimestampMs(); $date = $isHeadOrAdmin ? [ $dateRange->startDate->getTimestampMs(), $dateRange->endDate->getTimestampMs() ] : $dateRange->endDate->getTimestampMs(); return [ 'report_id' => $reportToday?->report_id, 'unwantedEvents' => $unwantedEvents, 'isActiveSendButton' => $isActiveSendButton, 'message' => $dateRange->isOneDay ? $message : null, 'status' => $reportToday?->status ?? 'draft', 'statusMessage' => $dateRange->isOneDay ? $statusMessage : null, 'canPublish' => (bool) $reportToday && ($reportToday->status === 'draft') && $isActiveSendButton, 'isOneDay' => $dateRange->isOneDay, 'isHeadOrAdmin' => $isHeadOrAdmin, 'dates' => $date, 'userId' => $fillableUserId, 'userName' => $lpuDoctor ? "$lpuDoctor->FAM_V $lpuDoctor->IM_V $lpuDoctor->OT_V" : null ]; } /** * Удалить пациента из наблюдения */ public function removeObservationPatient(string $patientId): void { [$sourceType, $id] = explode(':', $patientId) + [null, null]; if ($sourceType === 'manual') { ObservationPatient::where('rf_department_patient_id', $id)->delete(); return; } ObservationPatient::where('rf_medicalhistory_id', $id)->delete(); } public function createManualPatient(Department $department, User $user, array $data) { $report = $this->resolveReportForManualPatient($department, $user, $data); return $this->unifiedPatientService->createManualPatient($department, $user, $data, $report->report_id); } public function setManualPatientOutcome(User $user, int $departmentPatientId, array $data) { $patient = \App\Models\DepartmentPatient::where('department_patient_id', $departmentPatientId)->firstOrFail(); $updatedPatient = $this->unifiedPatientService->recordManualOutcome($patient, $data); $this->syncManualPatientSnapshots($updatedPatient, $user, []); return $updatedPatient; } public function updateManualPatient(User $user, int $departmentPatientId, array $data) { $patient = $this->resolveManageableManualPatient($user, $departmentPatientId); $updatedPatient = $this->unifiedPatientService->updateManualPatient($patient, $data); $this->syncManualPatientSnapshots($updatedPatient, $user, $data); return $updatedPatient; } public function linkManualPatientToMis(int $departmentPatientId, int $medicalHistoryId) { $patient = \App\Models\DepartmentPatient::where('department_patient_id', $departmentPatientId)->firstOrFail(); return $this->unifiedPatientService->linkManualPatientToMis($patient, $medicalHistoryId); } public function getManualPatientOperations(User $user, int $departmentPatientId) { $patient = $this->resolveManageableManualPatient($user, $departmentPatientId); return $patient->operations() ->with('serviceMedical') ->orderByDesc('started_at') ->get(); } public function createManualPatientOperation(User $user, int $departmentPatientId, array $data): DepartmentPatientOperation { $patient = $this->resolveManageableManualPatient($user, $departmentPatientId); $service = MisServiceMedical::query() ->where('ServiceMedicalID', $data['service_id']) ->firstOrFail(); return $patient->operations()->create([ 'rf_kl_service_medical_id' => $service->ServiceMedicalID, 'service_code' => $service->ServiceMedicalCode, 'service_name' => $service->ServiceMedicalName, 'urgency' => $data['urgency'], 'started_at' => $data['started_at'], 'ended_at' => $data['ended_at'], 'created_by' => $user->id, ])->load('serviceMedical'); } public function updateManualPatientOperation(User $user, int $departmentPatientId, int $operationId, array $data): DepartmentPatientOperation { $patient = $this->resolveManageableManualPatient($user, $departmentPatientId); $service = MisServiceMedical::query() ->where('ServiceMedicalID', $data['service_id']) ->firstOrFail(); $operation = $patient->operations() ->where('department_patient_operation_id', $operationId) ->firstOrFail(); $operation->update([ 'rf_kl_service_medical_id' => $service->ServiceMedicalID, 'service_code' => $service->ServiceMedicalCode, 'service_name' => $service->ServiceMedicalName, 'urgency' => $data['urgency'], 'started_at' => $data['started_at'], 'ended_at' => $data['ended_at'], ]); return $operation->fresh()->load('serviceMedical'); } public function deleteManualPatientOperation(User $user, int $departmentPatientId, int $operationId): void { $patient = $this->resolveManageableManualPatient($user, $departmentPatientId); $patient->operations() ->where('department_patient_operation_id', $operationId) ->firstOrFail() ->delete(); } public function searchMisPatientsForDepartment(Department $department, string $query) { return $this->unifiedPatientService->searchMisPatients($department, $query); } private function resolveManageableManualPatient(User $user, int $departmentPatientId): DepartmentPatient { $query = DepartmentPatient::query() ->where('department_patient_id', $departmentPatientId) ->whereIn('source_type', ['manual', 'special']); if (!$user->isAdmin() && !$user->isHeadOfDepartment()) { $query->where('rf_department_id', $user->department->department_id); } return $query->firstOrFail(); } private function syncManualPatientSnapshots(DepartmentPatient $patient, User $user, array $data): void { $reportIds = $patient->rf_report_id ? [$patient->rf_report_id] : (isset($data['startAt'], $data['endAt']) && $data['startAt'] && $data['endAt'] ? $this->getReportsForDateRange( $patient->rf_department_id, $this->dateRangeService->getNormalizedDateRange( $user, (string) $data['startAt'], (string) $data['endAt'] ) )->pluck('report_id')->values()->all() : []); if (empty($reportIds)) { return; } MedicalHistorySnapshot::query() ->whereIn('rf_report_id', $reportIds) ->where('rf_department_patient_id', $patient->department_patient_id) ->update([ 'patient_kind' => $patient->patient_kind, 'full_name' => $patient->full_name, 'birth_date' => $patient->birth_date, 'diagnosis_code' => $patient->diagnosis_code, 'diagnosis_name' => $patient->diagnosis_name, 'admitted_at' => $patient->admitted_at, 'outcome_type' => $patient->is_current ? null : $patient->outcome_type, 'outcome_at' => $patient->is_current ? null : $patient->outcome_at, 'updated_at' => now(), ]); } private function resolveReportForManualPatient(Department $department, User $user, array $data): Report { $reportId = $data['report_id'] ?? null; if ($reportId) { return Report::query() ->where('report_id', $reportId) ->where('rf_department_id', $department->department_id) ->firstOrFail(); } if (!isset($data['startAt'], $data['endAt']) || !$data['startAt'] || !$data['endAt']) { throw new \InvalidArgumentException('Не указан отчет или диапазон для привязки спецконтингента'); } $dateRange = $this->dateRangeService->getNormalizedDateRange( $user, (string) $data['startAt'], (string) $data['endAt'] ); $existingReport = Report::query() ->where('rf_department_id', $department->department_id) ->exactPeriod($dateRange->startSql(), $dateRange->endSql()) ->first(); if ($existingReport) { return $existingReport; } return Report::query()->create([ 'rf_department_id' => $department->department_id, 'rf_user_id' => $user->id, 'rf_lpudoctor_id' => $data['user_id'] ?? $user->rf_lpudoctor_id, 'sent_at' => $dateRange->endSql(), 'created_at' => $dateRange->endSql(), 'period_start' => $dateRange->startSql(), 'period_end' => $dateRange->endSql(), 'status' => 'draft', ]); } /** * Получить статистику из снапшотов */ private function getStatisticsFromSnapshots(Department $department, DateRange $dateRange, int $branchId): array { // Получаем отчеты за период $reports = $this->getReportsForDateRange( $department->department_id, $dateRange ); $reportIds = $reports->pluck('report_id')->toArray(); $lastReport = array_first($reportIds); $recipientReportIds = $this->getSnapshotRecipientReportIds($reportIds); // Получаем статистику из снапшотов $snapshotStats = [ 'plan' => $this->getMetrikaResultCount(4, $reportIds), 'emergency' => $this->getMetrikaResultCount(12, $reportIds), 'outcome' => $this->getMetrikaResultCount(7, $reportIds), 'deceased' => $this->getMetrikaResultCount(9, $reportIds), 'current' => $this->getMetrikaResultCount(8, $reportIds, false), // '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) ]; // Получаем ID поступивших пациентов $recipientIds = $this->snapshotService ->getPatientsFromSnapshots('recipient', $recipientReportIds) ->pluck('id') ->all(); // Получаем количество операций из метрик $surgicalCount = [ $this->getMetrikaResultCount(10, $reportIds), // экстренные операции $this->getMetrikaResultCount(11, $reportIds) // плановые операции ]; if ($snapshotStats['outcome'] == 0) { $percentDead = 0; } else { $percentDead = ($snapshotStats['deceased'] / $snapshotStats['outcome']) * 100; $percentDead = round($percentDead, 2); } return [ 'recipientCount' => $snapshotStats['recipient'] ?? 0, 'extractCount' => $snapshotStats['outcome'] ?? 0, 'currentCount' => $snapshotStats['current'] ?? 0,//$this->calculateCurrentPatientsFromSnapshots($reportIds, $branchId), 'deadCount' => $snapshotStats['deceased'] ?? 0, 'countStaff' => $snapshotStats['countStaff'] ?? 0, 'surgicalCount' => $surgicalCount, 'recipientIds' => $recipientIds, 'beds' => $snapshotStats['beds'] ?? 0, 'percentDead' => $percentDead, ]; } /** * Получить статистику из реплики БД */ private function getStatisticsFromReplica(Department $department, User $user, DateRange $dateRange, int $branchId): array { $planCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'plan', $dateRange, $branchId, true); $emergencyCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'emergency', $dateRange, $branchId, true); $currentCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'current', $dateRange, $branchId); $recipientCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'recipient', $dateRange, $branchId); $outcomeCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'outcome', $dateRange, $branchId); $deadCount = $this->unifiedPatientService->getLivePatientCountByStatus($department, $user, 'outcome-deceased', $dateRange, $branchId); // Операции $misSurgicalCount = [ $this->patientQueryService->getSurgicalPatients( 'emergency', $branchId, $dateRange, true ), $this->patientQueryService->getSurgicalPatients( 'plan', $branchId, $dateRange, true ) ]; $manualSurgicalCount = $this->getManualSurgicalCounts($department, $dateRange); $surgicalCount = [ ($misSurgicalCount[0] ?? 0) + ($manualSurgicalCount[0] ?? 0), ($misSurgicalCount[1] ?? 0) + ($manualSurgicalCount[1] ?? 0), ]; // ID поступивших сегодня (для отметки в таблице) $recipientIds = $this->unifiedPatientService ->getRecipientIdsForReport($department, $user, $dateRange, $branchId); $misBranch = MisStationarBranch::where('StationarBranchID', $branchId)->first(); $beds = Department::where('rf_mis_department_id', $misBranch->rf_DepartmentID) ->first()->metrikaDefault->where('rf_metrika_item_id', 1)->first(); if ($outcomeCount == 0) { $percentDead = 0; } else { $percentDead = ($deadCount / $outcomeCount) * 100; $percentDead = round($percentDead, 2); } return [ 'recipientCount' => $recipientCount, // только поступившие сегодня 'extractCount' => $outcomeCount, 'currentCount' => $currentCount, // все в отделении 'deadCount' => $deadCount, 'surgicalCount' => $surgicalCount, 'recipientIds' => $recipientIds, // ID поступивших сегодня 'planCount' => $planCount, // плановые (поступившие + уже лечащиеся) 'emergencyCount' => $emergencyCount, // экстренные (поступившие + уже лечащиеся) 'percentDead' => $percentDead, 'beds' => $beds->value ]; } private function getManualSurgicalCounts(Department $department, DateRange $dateRange): array { $baseQuery = DepartmentPatientOperation::query() ->whereBetween('started_at', [$dateRange->startSql(), $dateRange->endSql()]) ->whereHas('patient', function ($query) use ($department) { $query->where('rf_department_id', $department->department_id) ->whereIn('source_type', ['manual', 'special']); }); $emergencyCount = (clone $baseQuery) ->where(function ($query) { $query->where('urgency', 'emergency') ->orWhere(function ($fallback) { $fallback->whereNull('urgency') ->whereHas('patient', fn ($patientQuery) => $patientQuery->where('patient_kind', 'emergency')); }); }) ->count(); $planCount = (clone $baseQuery) ->where(function ($query) { $query->where('urgency', 'plan') ->orWhere(function ($fallback) { $fallback->whereNull('urgency') ->whereHas('patient', fn ($patientQuery) => $patientQuery->where('patient_kind', 'plan')); }); }) ->count(); return [$emergencyCount, $planCount]; } /** * Получить пациентов из снапшотов */ public function getPatientsFromSnapshots( Department $department, string $status, DateRange $dateRange, int $branchId, bool $onlyIds = false ) { [$baseStatus, $sourceScope] = $this->parseScopedStatus($status); $reports = $this->getReportsForDateRange( $department->department_id, $dateRange ); $reportIds = $reports->pluck('report_id')->toArray(); $recipientReportIds = $this->getSnapshotRecipientReportIds($reportIds); $patientTypeMap = [ 'plan' => 'plan', 'emergency' => 'emergency', 'recipient' => 'recipient', 'outcome-discharged' => 'discharged', 'outcome-transferred' => 'transferred', 'outcome-deceased' => 'deceased', 'observation' => 'observation' ]; $patientType = $patientTypeMap[$baseStatus] ?? null; if ($patientType === 'observation') { return $this->unifiedPatientService->getObservationPatients($department, $onlyIds, $sourceScope); } if ($dateRange->isOneDay && in_array($baseStatus, ['plan', 'emergency'], true)) { $patients = $this->snapshotService->getPatientsFromOneDayCurrentSnapshots( $patientType, $reportIds, false, $recipientReportIds ); return $this->filterSnapshotPatientsByScope($patients, $sourceScope, $onlyIds); } $patients = $this->snapshotService->getPatientsFromSnapshots( $patientType, $reportIds, $branchId, false, in_array($baseStatus, ['plan', 'emergency'], true), $recipientReportIds ); return $this->filterSnapshotPatientsByScope($patients, $sourceScope, $onlyIds); } /** * Получить пациентов из реплики БД */ private function getPatientsFromReplica( Department $department, User $user, string $status, DateRange $dateRange, int $branchId, bool $onlyIds = false, ?bool $isIncludeCurrent = null ) { [$baseStatus] = $this->parseScopedStatus($status); // Для плановых и экстренных включаем уже лечащихся $includeCurrent = $isIncludeCurrent ?? in_array($baseStatus, ['plan', 'emergency'], true); return $this->unifiedPatientService->getLivePatientsByStatus( $department, $user, $status, $dateRange, $branchId, $onlyIds, $includeCurrent ); } /** * Получить количество пациентов из снапшотов */ private function getPatientsCountFromSnapshots(Department $department, string $status, DateRange $dateRange): int { [$baseStatus, $sourceScope] = $this->parseScopedStatus($status); $reports = $this->getReportsForDateRange( $department->department_id, $dateRange ); $reportIds = $reports->pluck('report_id')->toArray(); if ($baseStatus === 'outcome') { if ($sourceScope !== 'all') { return $this->getPatientsFromSnapshots( $department, $status, $dateRange, $this->getBranchId($department->rf_mis_department_id), false )->count(); } return MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) ->whereIn('patient_type', ['discharged', 'deceased']) ->distinct('rf_medicalhistory_id') ->count('rf_medicalhistory_id'); } $patientTypeMap = [ 'plan' => 'plan', 'emergency' => 'emergency', 'observation' => 'observation', 'outcome-discharged' => 'discharged', 'outcome-transferred' => 'transferred', 'outcome-deceased' => 'deceased' ]; $patientType = $patientTypeMap[$baseStatus] ?? null; if (!$patientType) { return 0; } if ($patientType === 'observation') { return $this->unifiedPatientService->getObservationPatients($department, false, $sourceScope)->count(); } if ($sourceScope !== 'all') { return $this->getPatientsFromSnapshots( $department, $status, $dateRange, $this->getBranchId($department->rf_mis_department_id), false )->count(); } return MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) ->where('patient_type', $patientType) ->distinct('rf_medicalhistory_id') ->count('rf_medicalhistory_id'); } private function getSnapshotRecipientReportIds(array $reportIds): array { if (empty($reportIds)) { return []; } return [reset($reportIds)]; } /** * Получить количество пациентов из реплики БД */ private function getPatientsCountFromReplica( Department $department, User $user, string $status, DateRange $dateRange, int $branchId ): 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) ), default => $this->unifiedPatientService->getLivePatientCountByStatus( $department, $user, $status, $dateRange, $branchId, in_array($baseStatus, ['plan', 'emergency'], true) ) }; } private function filterSnapshotPatientsByScope($patients, string $sourceScope, bool $onlyIds = false) { if ($sourceScope === 'all') { return $onlyIds ? $patients->pluck('id') : $patients; } $filtered = $patients->filter(function ($patient) use ($sourceScope) { return match ($sourceScope) { 'mis' => $patient->sourceType === 'mis', 'special' => in_array($patient->sourceType, ['manual', 'special'], true), default => true, }; })->values(); return $onlyIds ? $filtered->pluck('id') : $filtered; } private function parseScopedStatus(string $status): array { foreach (['mis', 'special'] as $scope) { $prefix = "{$scope}-"; if (str_starts_with($status, $prefix)) { return [substr($status, strlen($prefix)), $scope]; } } return [$status, 'all']; } private function isSpecialScopedPatient($patient): bool { $sourceType = $patient->sourceType ?? $patient->source_type ?? null; if ($sourceType !== null) { return in_array($sourceType, ['manual', 'special'], true); } return str_starts_with((string) ($patient->id ?? ''), 'manual:'); } /** * Получить нежелательные события за дату */ public function getUnwantedEvents(Department $department, DateRange $dateRange) { return UnwantedEvent::whereHas('report', function ($query) use ($department, $dateRange) { $query->where('rf_department_id', $department->department_id); if ($dateRange->isOneDay) { $query->exactPeriod($dateRange->startSql(), $dateRange->endSql()); } else { $query->withinPeriod($dateRange->startSql(), $dateRange->endSql()); } }) ->get() ->map(function ($item) { return [ ...$item->toArray(), 'created_at' => Carbon::parse($item->created_at)->format('Создано d.m.Y в H:i'), ]; }); } /** * Проверить активность кнопки отправки отчета */ private function isSendButtonActive(User $user, DateRange $dateRange, ?Report $reportToday, ?int $fillableUserId): bool { // Для врача: только сегодня и если отчета еще нет if (!$user->isHeadOfDepartment() && !$user->isAdmin()) { if ($reportToday && $reportToday->status === 'submitted') { return false; } return $dateRange->isEndDateToday(); } // Для заведующего/админа: можно редактировать любой отчет за сутки (включая submitted) if ( $reportToday && $dateRange->isOneDay ) { return true; } return false; } private function getReportForPeriod(int $departmentId, DateRange $dateRange): ?Report { $query = Report::query() ->where('rf_department_id', $departmentId) ->exactPeriod($dateRange->startSql(), $dateRange->endSql()) ->orderByDesc('report_id'); if ($dateRange->isOneDay) return $query->first(); else return $query->onlySubmitted()->first(); } /** * Получить информацию о враче */ private function getDoctorInfo(?int $doctorId, DateRange $dateRange): ?MisLpuDoctor { if (!$doctorId) { return null; } // Если дата это период, не показываем врача if (!$dateRange->isOneDay) { return null; } return MisLpuDoctor::where('LPUDoctorID', $doctorId)->first(); } /** * Получить отчеты за диапазон дат */ public function getReportsForDateRange(int $departmentId, DateRange $dateRange) { if ($dateRange->isOneDay) { return Report::where('rf_department_id', $departmentId) ->exactPeriod($dateRange->startSql(), $dateRange->endSql()) ->onlySubmitted() ->orderBy('period_end', 'DESC') ->get(); } return Report::where('rf_department_id', $departmentId) ->withinPeriod($dateRange->startSql(), $dateRange->endSql()) ->onlySubmitted() ->orderBy('period_end', 'DESC') ->get(); } /** * Получить количество из метрик */ private function getMetrikaResultCount(int $metrikaItemId, array $reportIds, bool $sum = true): int { $count = 0; $reports = Report::whereIn('report_id', $reportIds) ->with('metrikaResults') ->orderBy('created_at', 'DESC') ->get(); if (!$sum) { foreach ($reports as $report) { $metric = $report->metrikaResults ->firstWhere('rf_metrika_item_id', $metrikaItemId); if ($metric) { return intval($metric->value) ?? 0; } } return 0; } foreach ($reports as $report) { foreach ($report->metrikaResults as $metrikaResult) { if ($metrikaResult->rf_metrika_item_id === $metrikaItemId) { $count += intval($metrikaResult->value) ?? 0; } } } return $count; } /** * Рассчитать текущих пациентов из снапшотов */ private function calculateCurrentPatientsFromSnapshots(array $reportIds, int $branchId): int { // Получаем ID всех пациентов из снапшотов $allPatientIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) ->pluck('rf_medicalhistory_id') ->unique() ->toArray(); if (empty($allPatientIds)) { return 0; } // Получаем ID выбывших пациентов $outcomePatientIds = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds) ->whereIn('patient_type', ['discharged', 'transferred', 'deceased']) ->pluck('rf_medicalhistory_id') ->unique() ->toArray(); // Текущие = все - выбывшие $currentPatientIds = array_diff($allPatientIds, $outcomePatientIds); return count($currentPatientIds); } /** * Получить пациентов под наблюдением из снапшотов */ private function getObservationPatientsFromSnapshots(int $departmentId, array $reportIds, bool $onlyIds = false) { $medicalHistoryIds = ObservationPatient::whereIn('rf_report_id', $reportIds) ->where('rf_department_id', $departmentId) ->pluck('rf_medicalhistory_id') ->unique() ->toArray(); if (empty($medicalHistoryIds)) { return collect(); } if ($onlyIds) { return collect($medicalHistoryIds); } return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds) ->with(['observationPatient' => function($query) use ($departmentId) { $query->where('rf_department_id', $departmentId) ->select(['rf_medicalhistory_id', 'observation_patient_id', 'comment']); }]) ->orderBy('DateRecipient', 'DESC') ->get() ->map(function ($patient) { $patient->comment = $patient->observationPatient ->pluck('comment') ->filter() ->implode('; '); return $patient; }); } /** * Получить статистику выполнения плана по госпитализации */ public function getRecipientPlanOfYear(Department $department, DateRange $dateRange): array { $periodPlanModel = $department->recipientPlanOfYear(); // Рассчитываем коэффициент периода (округляем в большую сторону) $monthsInPeriod = ceil($dateRange->startDate->diffInMonths($dateRange->endDate)); $annualPlan = $periodPlanModel ? (int)$periodPlanModel->value : 0; $oneMonthPlan = ceil($annualPlan / 12); $periodPlan = round($oneMonthPlan * $monthsInPeriod); $progress = 0; $query = $department->reports() ->with('metrikaResults') ->where('period_start', '>', $dateRange->startSql()) ->where('period_end', '<=', $dateRange->endSql()); if ($dateRange->isOneDay) { $query->where('period_start', '>=', $dateRange->startFirstOfMonth()) ->where('period_end', '<=', $dateRange->endSql()); } else { $query->where('period_start', '>', $dateRange->startSql()) ->where('period_end', '<=', $dateRange->endSql()); } $reports = $query->get(); foreach ($reports as $report) { $outcome = $report->metrikaResults()->where('rf_metrika_item_id', 7)->first(); if ($outcome) $progress += (int)$outcome->value; } return [ 'plan' => $periodPlan, 'progress' => $progress ]; } }