parseScopedStatus($status); $branchId = $this->contextResolver->resolveBranchId($department); if (! $branchId) { return collect(); } if ($sourceScope === 'special' || $baseStatus === 'reanimation') { return $this->getPatientsFromReplica( $department, $user, $status, $dateRange, $branchId, $onlyIds, $includeCurrentPatients ); } $useSnapshots = ! $this->contextResolver->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange) && $this->contextResolver->shouldUseSnapshots($department, $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->contextResolver->resolveBranchId($department); if (! $branchId) { return 0; } if ($sourceScope === 'special' || $baseStatus === 'reanimation') { return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId); } $useSnapshots = ! $this->contextResolver->shouldUseReplicaForLiveStatus($user, $baseStatus, $dateRange) && $this->contextResolver->shouldUseSnapshots($department, $dateRange); if ($useSnapshots) { return $this->getPatientsCountFromSnapshots($department, $status, $dateRange, $branchId); } return $this->getPatientsCountFromReplica($department, $user, $status, $dateRange, $branchId); } /** * Построить карту счётчиков пациентов по scope для интерфейса. */ 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; } /** * Получить пациентскую выборку из submitted-снапшотов. */ public function getPatientsFromSnapshots( Department $department, string $status, DateRange $dateRange, int $branchId, bool $onlyIds = false ) { [$baseStatus, $sourceScope] = $this->parseScopedStatus($status); $reportIds = $this->contextResolver ->getReportsForDateRange($department->department_id, $dateRange) ->pluck('report_id') ->all(); $recipientReportIds = $this->contextResolver->getRecipientReportIds($reportIds); $patientTypeMap = [ 'plan' => 'plan', 'emergency' => 'emergency', 'current' => 'current', '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 ($baseStatus === 'outcome') { $discharged = $this->snapshotService->getPatientsFromSnapshots( 'discharged', $reportIds, false, false, $recipientReportIds ); $deceased = $this->snapshotService->getPatientsFromSnapshots( 'deceased', $reportIds, false, false, $recipientReportIds ); $merged = UnifiedPatientData::unique($discharged->concat($deceased)) ->sortByDesc(fn (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, $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); } /** * Получить пациентов напрямую из live-реплики и manual-источников. */ private function getPatientsFromReplica( Department $department, User $user, string $status, DateRange $dateRange, int $branchId, bool $onlyIds = false, ?bool $includeCurrent = null ) { [$baseStatus] = $this->parseScopedStatus($status); $includeCurrent ??= in_array($baseStatus, ['plan', 'emergency', 'reanimation'], true); return $this->unifiedPatientService->getLivePatientsByStatus( $department, $user, $status, $dateRange, $branchId, $onlyIds, $includeCurrent ); } /** * Посчитать пациентов в снапшотах с той же семантикой scope, что и в legacy-сервисе. */ private function getPatientsCountFromSnapshots( Department $department, string $status, DateRange $dateRange, int $branchId ): int { [$baseStatus, $sourceScope] = $this->parseScopedStatus($status); $reportIds = $this->contextResolver ->getReportsForDateRange($department->department_id, $dateRange) ->pluck('report_id') ->all(); if ($baseStatus === 'outcome') { if ($sourceScope !== 'all') { return $this->getPatientsFromSnapshots($department, $status, $dateRange, $branchId)->count(); } return MedicalHistorySnapshot::query() ->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, $branchId)->count(); } return MedicalHistorySnapshot::query() ->whereIn('rf_report_id', $reportIds) ->where('patient_type', $patientType) ->distinct('rf_medicalhistory_id') ->count('rf_medicalhistory_id'); } /** * Посчитать пациентов из реплики и manual-источников с legacy-правилами include-current. */ 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) ), }; } /** * Применить фильтрацию по MIS/manual scope к коллекции DTO из снапшотов. */ private function filterSnapshotPatientsByScope(Collection $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; } /** * Разбить scoped-статус вроде "mis-plan" на базовый статус и scope источника. * * @return array{0: string, 1: string} */ 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']; } }