Перевод на доменную архитектуру
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
<?php
|
||||
|
||||
namespace App\Infrastructure\Reports\Services;
|
||||
|
||||
use App\Data\UnifiedPatientData;
|
||||
use App\Models\Department;
|
||||
use App\Models\MedicalHistorySnapshot;
|
||||
use App\Models\User;
|
||||
use App\Services\DateRange;
|
||||
use App\Services\SnapshotService;
|
||||
use App\Services\UnifiedPatientService;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Сервис чтения пациентских выборок для отчётов.
|
||||
*
|
||||
* Он решает, откуда обслуживать запрос: из submitted-снапшотов или из live-реплики,
|
||||
* и при этом сохраняет API близким к старым методам ReportService для
|
||||
* постепенной strangler-миграции.
|
||||
*/
|
||||
class ReportPatientsReadService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UnifiedPatientService $unifiedPatientService,
|
||||
private readonly SnapshotService $snapshotService,
|
||||
private readonly ReportReadContextResolver $contextResolver,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Получить пациентов отчёта по запрошенному статусу и области источника.
|
||||
*/
|
||||
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->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,
|
||||
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'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user