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

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

@@ -0,0 +1,323 @@
<?php
namespace App\Infrastructure\Reports\Sources;
use App\Services\DateRange;
use App\Services\OutcomePatientService;
use App\Services\RecipientPatientService;
use App\Services\CurrentPatientService;
use App\Models\MisMedicalHistory;
use App\Models\MisMigrationPatient;
use App\Models\MisReanimation;
use App\Models\MisSurgicalOperation;
use Illuminate\Support\Collection;
/**
* Query-источник для пациентских выборок из МИС и связанных клинических агрегатов.
*
* Этот класс удерживает детали Eloquent/query-builder вне application/domain слоёв
* и централизует read-model логику, специфичную для реплики МИС.
*/
class MisClinicalDataSource
{
public function __construct(
private readonly RecipientPatientService $recipientPatientService,
private readonly CurrentPatientService $currentPatientService,
private readonly OutcomePatientService $outcomePatientService,
) {}
/**
* Загрузить плановых или экстренных пациентов, при необходимости объединив с текущими в отделении.
*/
public function getPlanOrEmergencyPatients(
?string $type,
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange,
bool $countOnly = false,
bool $onlyIds = false,
bool $includeCurrent = false,
bool $fillableAuto = false
) {
$medicalHistoryIds = $this->recipientPatientService->resolvePlanOrEmergencyMedicalHistoryIds(
$type,
$isHeadOrAdmin,
$branchId,
$dateRange,
$includeCurrent,
$fillableAuto
);
if (empty($medicalHistoryIds)) {
return $countOnly ? 0 : collect();
}
if ($countOnly) {
return count($medicalHistoryIds);
}
if ($onlyIds) {
return collect($medicalHistoryIds);
}
$recipientIds = $this->recipientPatientService->getRecipientMedicalHistoryIds(
$type,
$isHeadOrAdmin,
$branchId,
$dateRange
);
return $this->buildPatientCardsQuery($medicalHistoryIds, $branchId)
->get()
->map(function ($patient) use ($recipientIds) {
$patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds, true);
return $patient;
});
}
/**
* Загрузить всех MIS-пациентов, входящих в выборку отчётного отделения.
*/
public function getAllPatientsInDepartment(
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange,
bool $countOnly = false,
bool $onlyIds = false,
bool $fillableAuto = false
) {
$recipientIds = $this->recipientPatientService
->buildRecipientQuery(null, $isHeadOrAdmin, $branchId, $dateRange, $fillableAuto)
->pluck('rf_MedicalHistoryID')
->toArray();
$currentIds = $fillableAuto
? $this->currentPatientService->getHistoricalCurrentMedicalHistoryIds(null, $branchId, $dateRange)
: MisMigrationPatient::currentlyInTreatment($branchId)->pluck('rf_MedicalHistoryID')->toArray();
$allIds = array_unique(array_merge($recipientIds, $currentIds));
if (empty($allIds)) {
return $countOnly ? 0 : collect();
}
if ($countOnly) {
return count($allIds);
}
if ($onlyIds) {
return collect($allIds);
}
return $this->buildPatientCardsQuery($allIds, $branchId)
->get()
->map(function ($patient) use ($recipientIds) {
$patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds, true);
return $patient;
});
}
/**
* Загрузить пациентов с записями о реанимации, входящих в отчётную выборку.
*/
public function getReanimationPatients(
int $branchId,
DateRange $dateRange,
bool $onlyIds = false
) {
$currentIds = $this->getAllPatientsInDepartment(true, $branchId, $dateRange, false, true, false)->all();
$outcomeIds = $this->outcomePatientService->getOutcomePatients($branchId, $dateRange, 'all', true)->all();
$reportCohortIds = array_values(array_unique(array_merge($currentIds, $outcomeIds)));
if (empty($reportCohortIds)) {
return collect();
}
$reanimationByMedicalHistory = MisReanimation::query()
->join('stt_migrationpatient as mp', 'mp.MigrationPatientID', '=', 'stt_reanimation.rf_MigrationPatientID')
->where('mp.rf_StationarBranchID', $branchId)
->where('mp.rf_MedicalHistoryID', '<>', 0)
->whereIn('mp.rf_MedicalHistoryID', $reportCohortIds)
->selectRaw('
mp."rf_MedicalHistoryID" as medical_history_id,
MAX(stt_reanimation."DateIn") as reanimation_date_in,
BOOL_OR(COALESCE(stt_reanimation."isComplete", false)) as reanimation_is_complete
')
->groupBy('mp.rf_MedicalHistoryID')
->get();
$medicalHistoryIds = $reanimationByMedicalHistory
->pluck('medical_history_id')
->map(fn ($id) => (int) $id)
->values()
->all();
if (empty($medicalHistoryIds)) {
return collect();
}
if ($onlyIds) {
return collect($medicalHistoryIds);
}
$reanimationDateByMedicalHistory = $reanimationByMedicalHistory->pluck('reanimation_date_in', 'medical_history_id');
$reanimationCompleteByMedicalHistory = $reanimationByMedicalHistory->pluck('reanimation_is_complete', 'medical_history_id');
return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->select($this->patientSelect())
->with($this->patientRelations($branchId))
->orderBy('DateRecipient', 'DESC')
->get()
->map(function ($patient) use ($reanimationDateByMedicalHistory, $reanimationCompleteByMedicalHistory) {
$reanimationDateIn = $reanimationDateByMedicalHistory->get($patient->MedicalHistoryID);
if ($reanimationDateIn) {
$patient->DateRecipient = $reanimationDateIn;
}
$patient->reanimation_is_complete = (bool) $reanimationCompleteByMedicalHistory->get($patient->MedicalHistoryID, false);
return $patient;
});
}
/**
* Загрузить или посчитать хирургические операции в отчётном периоде.
*/
public function getSurgicalPatients(
string $type,
int $branchId,
DateRange $dateRange,
bool $countOnly = false
) {
$query = MisSurgicalOperation::where('rf_StationarBranchID', $branchId)
->completed()
->where('Date', '>=', $dateRange->startSql())
->where('Date', '<=', $dateRange->endSql());
if ($type === 'plan') {
$query->where('rf_TypeSurgOperationInTimeID', 6);
} else {
$query->whereIn('rf_TypeSurgOperationInTimeID', [4, 5]);
}
return $countOnly ? $query->count() : $query->get();
}
/**
* Посчитать плановых или экстренных пациентов, когда нужно включать текущих.
*/
public function getPatientsCountWithCurrent(
?string $type,
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange
): int {
return $this->getPlanOrEmergencyPatients(
$type,
$isHeadOrAdmin,
$branchId,
$dateRange,
true,
false,
true
);
}
/**
* Общий MIS-запрос карточек пациентов с операциями и диагнозами.
*/
public function buildPatientCardsQuery(array $medicalHistoryIds, int $branchId)
{
return MisMedicalHistory::query()
->whereIn('MedicalHistoryID', $medicalHistoryIds)
->select($this->patientSelect())
->with($this->patientRelations($branchId))
->orderBy('DateRecipient', 'DESC');
}
/**
* Базовый select, используемый во всех выборках пациентов из МИС.
*/
private function patientSelect(): array
{
return [
'MedicalHistoryID',
'FAMILY',
'Name',
'OT',
'BD',
'DateRecipient',
'DateExtract',
'DateDeath',
'rf_EmerSignID',
'rf_kl_VisitResultID',
];
}
/**
* Общие eager-loaded связи для MIS-карточек пациентов.
*/
private function patientRelations(int $branchId): array
{
return [
'surgicalOperations' => function ($q) {
$q->select([
'SurgicalOperationID',
'rf_MedicalHistoryID',
'rf_kl_ServiceMedicalID',
'Date',
])->with(['serviceMedical' => function ($serviceQuery) {
$serviceQuery->select([
'ServiceMedicalID',
'ServiceMedicalCode',
'ServiceMedicalName',
]);
}]);
},
'outcomeMigration' => function ($q) {
$q->select([
'stt_migrationpatient.MigrationPatientID',
'stt_migrationpatient.rf_MedicalHistoryID',
'stt_migrationpatient.DateOut',
'stt_migrationpatient.rf_DiagnosID',
])->with(['mainDiagnosis' => function ($diagnosisQuery) {
$diagnosisQuery->select([
'DiagnosID',
'rf_MKBID',
])->with(['mkb' => function ($mkbQuery) {
$mkbQuery->select([
'MKBID',
'DS',
'NAME',
]);
}]);
}]);
},
'migrations' => function ($q) use ($branchId) {
$q->where('rf_StationarBranchID', $branchId)
->select([
'MigrationPatientID',
'rf_MedicalHistoryID',
'rf_DiagnosID',
'DateIngoing',
'rf_StationarBranchID',
])
->orderByDesc('DateIngoing')
->with(['mainDiagnosis' => function ($diagnosisQuery) {
$diagnosisQuery->select([
'DiagnosID',
'rf_MKBID',
'rf_MigrationPatientID',
])->with(['mkb' => function ($mkbQuery) {
$mkbQuery->select([
'MKBID',
'DS',
'NAME',
]);
}]);
}]);
},
];
}
}