Перевод на доменную архитектуру

This commit is contained in:
brusnitsyn
2026-04-26 23:37:50 +09:00
parent 75ca01ffd8
commit f107ebd167
70 changed files with 4656 additions and 2070 deletions

View File

@@ -2,6 +2,11 @@
namespace App\Services;
use App\Domain\Reports\ValueObjects\MetrikaConfig;
use App\Infrastructure\Reports\Services\AutoFillReportPayloadBuilder;
use App\Infrastructure\Reports\Services\CalculatedMetricsSynchronizer;
use App\Infrastructure\Reports\Services\ReportPatientsReadService;
use App\Infrastructure\Reports\Services\ReportMetricsFinalizer;
use App\Models\Department;
use App\Models\DepartmentPatient;
use App\Models\DepartmentPatientOperation;
@@ -28,8 +33,25 @@ class ReportService
protected UnifiedPatientService $unifiedPatientService,
protected PatientService $patientQueryService,
protected SnapshotService $snapshotService,
protected StatisticsService $statisticsService
) {}
protected StatisticsService $statisticsService,
?ReportMetricsFinalizer $reportMetricsFinalizer = null,
?CalculatedMetricsSynchronizer $calculatedMetricsSynchronizer = null,
?AutoFillReportPayloadBuilder $autoFillReportPayloadBuilder = null,
?ReportPatientsReadService $reportPatientsReadService = null,
) {
$this->reportMetricsFinalizer = $reportMetricsFinalizer ?? app(ReportMetricsFinalizer::class);
$this->calculatedMetricsSynchronizer = $calculatedMetricsSynchronizer ?? app(CalculatedMetricsSynchronizer::class);
$this->autoFillReportPayloadBuilder = $autoFillReportPayloadBuilder ?? app(AutoFillReportPayloadBuilder::class);
$this->reportPatientsReadService = $reportPatientsReadService ?? app(ReportPatientsReadService::class);
}
protected ReportMetricsFinalizer $reportMetricsFinalizer;
protected CalculatedMetricsSynchronizer $calculatedMetricsSynchronizer;
protected AutoFillReportPayloadBuilder $autoFillReportPayloadBuilder;
protected ReportPatientsReadService $reportPatientsReadService;
/**
* Получить статистику для отчета
@@ -49,78 +71,6 @@ class ReportService
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,
];
}
/**
* Создать или обновить отчет
*/
@@ -147,14 +97,8 @@ class ReportService
});
DB::transaction(function () use ($report) {
// Сохраняем метрику койко-дня + среднего койко-дня из снапшотов
$this->saveBedDaysMetric($report);
$this->reportMetricsFinalizer->finalize($report);
$this->saveLethalMetricFromSnapshots($report);
$this->savePreoperativeMetric($report);
$this->saveDepartmentLoadedMetric($report);
});
} catch (\Throwable $e) {
throw $e;
@@ -165,6 +109,31 @@ class ReportService
return $report;
}
public function prepareForHeavySave(): void
{
$this->prepareMemoryForHeavySave();
}
public function syncCalculatedMetricsForStoredReport(Report $report, User $user, array $data): void
{
$this->calculatedMetricsSynchronizer->sync($report, $user, $data);
}
public function finalizeStoredReport(Report $report): void
{
$this->reportMetricsFinalizer->finalize($report);
}
public function saveLethalMetricForStoredReport(Report $report): void
{
$this->saveLethalMetricFromSnapshots($report);
}
public function clearCacheAfterStoredReport(User $user, Report $report): void
{
$this->clearCacheAfterReportCreation($user, $report);
}
private function prepareMemoryForHeavySave(): void
{
$connectionNames = array_unique(array_filter([
@@ -191,146 +160,7 @@ class ReportService
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');
return $this->autoFillReportPayloadBuilder->build($user, $department, $dateRange);
}
/**
@@ -344,7 +174,7 @@ class ReportService
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 25, // койко-дни
'rf_metrika_item_id' => MetrikaConfig::TOTAL_BED_DAYS,
],
['value' => $result['total_days']]
);
@@ -352,7 +182,7 @@ class ReportService
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 18, // средний койко-день
'rf_metrika_item_id' => MetrikaConfig::AVERAGE_BED_DAYS,
],
['value' => $result['avg_days']]
);
@@ -513,7 +343,7 @@ class ReportService
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 18,
'rf_metrika_item_id' => MetrikaConfig::AVERAGE_BED_DAYS,
],
['value' => 0]
);
@@ -532,9 +362,9 @@ class ReportService
try {
$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']);
$this->saveMetric($report, MetrikaConfig::TOTAL_PREOPERATIVE_DAYS, $result['total_days']);
$this->saveMetric($report, MetrikaConfig::PREOPERATIVE_PATIENT_COUNT, $result['patient_count']);
$this->saveMetric($report, MetrikaConfig::PREOPERATIVE_AVERAGE_DAYS, $result['avg_days']);
} catch (\Throwable $e) {
\Log::error('Failed to save preoperative total metric: '.$e->getMessage());
}
@@ -546,12 +376,12 @@ class ReportService
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');
$currentCount = $report->metrikaResults()->where('rf_metrika_item_id', MetrikaConfig::CURRENT)->value('value');
$bedsCount = $report->metrikaResults()->where('rf_metrika_item_id', MetrikaConfig::BEDS)->value('value');
$percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
$this->saveMetric($report, 22, $percentLoaded);
$this->saveMetric($report, MetrikaConfig::DEPARTMENT_LOADED, $percentLoaded);
}
/**
@@ -602,48 +432,13 @@ class ReportService
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
);
}
// Для реанимации всегда берем live-данные из реплики.
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(
return $this->reportPatientsReadService->getPatientsByStatus(
$department,
$user,
$status,
$dateRange,
$branchId,
$onlyIds,
$beforeCreate,
$includeCurrentPatients
);
}
@@ -657,78 +452,12 @@ class ReportService
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);
return $this->reportPatientsReadService->getPatientsCountByStatus($department, $user, $status, $dateRange);
}
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;
return $this->reportPatientsReadService->getPatientsCountsMap($department, $user, $dateRange);
}
/**
@@ -761,17 +490,6 @@ class ReportService
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', 'reanimation'], true)
&& $dateRange->isOneDay
&& $dateRange->isEndDateToday();
}
/**
* Создать или обновить отчет
*/
@@ -811,7 +529,7 @@ class ReportService
$beds = $department->metrikaDefault->where('rf_metrika_item_id', 1)->first();
MetrikaResult::create([
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 1,
'rf_metrika_item_id' => MetrikaConfig::BEDS,
'value' => $beds->value,
]);
}
@@ -862,7 +580,7 @@ class ReportService
{
if (empty($unwantedEvents)) {
$report->unwantedEvents()->delete();
$this->saveMetric($report, 16, 0);
$this->saveMetric($report, MetrikaConfig::UNWANTED_EVENTS, 0);
return;
}
@@ -889,7 +607,7 @@ class ReportService
}
// Обновить метрику
$this->saveMetric($report, 16, count($unwantedEvents));
$this->saveMetric($report, MetrikaConfig::UNWANTED_EVENTS, count($unwantedEvents));
}
/**
@@ -905,7 +623,7 @@ class ReportService
->where('rf_report_id', $report->report_id)
->delete();
// Обновить метрику
$this->saveMetric($report, 14, 0);
$this->saveMetric($report, MetrikaConfig::OBSERVATION, 0);
return;
}
@@ -926,82 +644,12 @@ class ReportService
}
// Обновить метрику
$this->saveMetric($report, 14, count($observationPatients));
$this->saveMetric($report, MetrikaConfig::OBSERVATION, 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();
$this->calculatedMetricsSynchronizer->sync($report, $user, $data);
}
/**
@@ -1463,38 +1111,6 @@ class ReportService
];
}
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];
}
/**
* Получить пациентов из снапшотов
*/
@@ -1505,177 +1121,15 @@ class ReportService
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',
'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,
$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,
$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', 'reanimation'], true);
return $this->unifiedPatientService->getLivePatientsByStatus(
return $this->reportPatientsReadService->getPatientsFromSnapshots(
$department,
$user,
$status,
$dateRange,
$branchId,
$onlyIds,
$includeCurrent
$onlyIds
);
}
/**
* Получить количество пациентов из снапшотов
*/
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)) {
@@ -1685,79 +1139,6 @@ class ReportService
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:');
}
/**
* Получить нежелательные события за дату
*/
@@ -1892,70 +1273,6 @@ class ReportService
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;
});
}
/**
* Получить статистику выполнения плана по госпитализации
*/