208 lines
8.2 KiB
PHP
208 lines
8.2 KiB
PHP
<?php
|
||
|
||
namespace App\Infrastructure\Reports\Sources;
|
||
|
||
use App\Data\UnifiedPatientData;
|
||
use App\Models\Department;
|
||
use App\Models\DepartmentPatient;
|
||
use App\Models\ObservationPatient;
|
||
use App\Models\Report;
|
||
use App\Services\DateRange;
|
||
use Illuminate\Support\Collection;
|
||
|
||
class SpecialPatientSource
|
||
{
|
||
/**
|
||
* Загрузить manual/special пациентов и нормализовать их в DTO для отчёта.
|
||
*
|
||
* @param array<int, string>|null $sourceTypes
|
||
*/
|
||
public function getDtos(
|
||
Department $department,
|
||
string $status,
|
||
DateRange $dateRange,
|
||
?array $sourceTypes = ['manual', 'special'],
|
||
bool $forSnapshots = false
|
||
): Collection {
|
||
return $this->mapManualPatients(
|
||
$this->getPatients($department, $status, $dateRange, $sourceTypes, ! $forSnapshots),
|
||
$dateRange
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Загрузить сырые manual/special записи пациентов для запрошенного статуса отчёта.
|
||
*
|
||
* @param array<int, string>|null $sourceTypes
|
||
*/
|
||
public function getPatients(
|
||
Department $department,
|
||
string $status,
|
||
DateRange $dateRange,
|
||
?array $sourceTypes = ['manual', 'special'],
|
||
bool $withOperations = true
|
||
): Collection {
|
||
$query = $this->buildManualPatientsQuery($department, $dateRange, $sourceTypes, $withOperations);
|
||
|
||
return match ($status) {
|
||
'plan', 'emergency' => $query->current()->where('patient_kind', $status)->get(),
|
||
'current' => $query->current()->get(),
|
||
'recipient' => $query->whereBetween('admitted_at', [$dateRange->startSql(), $dateRange->endSql()])->get(),
|
||
'outcome' => $query
|
||
->whereNotNull('outcome_type')
|
||
->whereIn('outcome_type', ['discharged', 'deceased'])
|
||
->whereBetween('outcome_at', [$dateRange->startSql(), $dateRange->endSql()])
|
||
->get(),
|
||
'outcome-discharged', 'outcome-transferred', 'outcome-deceased' => $query
|
||
->where('outcome_type', str_replace('outcome-', '', $status))
|
||
->whereBetween('outcome_at', [$dateRange->startSql(), $dateRange->endSql()])
|
||
->get(),
|
||
'reanimation' => collect(),
|
||
default => collect(),
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Посчитать manual/special пациентов для статуса отчёта.
|
||
*
|
||
* @param array<int, string>|null $sourceTypes
|
||
*/
|
||
public function getCount(
|
||
Department $department,
|
||
string $status,
|
||
DateRange $dateRange,
|
||
?array $sourceTypes = ['manual', 'special']
|
||
): int {
|
||
$query = $this->buildManualPatientsQuery($department, $dateRange, $sourceTypes, false);
|
||
|
||
return match ($status) {
|
||
'plan', 'emergency' => $query->current()->where('patient_kind', $status)->count(),
|
||
'current' => $query->current()->count(),
|
||
'recipient' => $query->whereBetween('admitted_at', [$dateRange->startSql(), $dateRange->endSql()])->count(),
|
||
'outcome' => $query
|
||
->whereNotNull('outcome_type')
|
||
->whereIn('outcome_type', ['discharged', 'deceased'])
|
||
->whereBetween('outcome_at', [$dateRange->startSql(), $dateRange->endSql()])
|
||
->count(),
|
||
'outcome-discharged', 'outcome-transferred', 'outcome-deceased' => $query
|
||
->where('outcome_type', str_replace('outcome-', '', $status))
|
||
->whereBetween('outcome_at', [$dateRange->startSql(), $dateRange->endSql()])
|
||
->count(),
|
||
default => 0,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Загрузить manual-пациентов, уже связанных с MIS-картами в пределах отчётного окна.
|
||
*/
|
||
public function getLinkedManualPatientsForPeriod(Department $department, DateRange $dateRange): Collection
|
||
{
|
||
$reportIds = $this->getReportIdsForDepartmentPeriod($department, $dateRange);
|
||
|
||
return DepartmentPatient::query()
|
||
->where(function ($builder) use ($department, $reportIds) {
|
||
if (! empty($reportIds)) {
|
||
$builder->whereIn('rf_report_id', $reportIds);
|
||
}
|
||
|
||
$builder->orWhere(function ($legacyQuery) use ($department) {
|
||
$legacyQuery->whereNull('rf_report_id')
|
||
->where('rf_department_id', $department->department_id);
|
||
});
|
||
})
|
||
->whereIn('source_type', ['manual', 'special'])
|
||
->whereNotNull('rf_medicalhistory_id')
|
||
->get()
|
||
->keyBy('rf_medicalhistory_id');
|
||
}
|
||
|
||
/**
|
||
* @param array<int, string>|null $sourceTypes
|
||
*/
|
||
private function buildManualPatientsQuery(
|
||
Department $department,
|
||
DateRange $dateRange,
|
||
?array $sourceTypes,
|
||
bool $withOperations
|
||
) {
|
||
$reportIds = $this->getReportIdsForDepartmentPeriod($department, $dateRange);
|
||
|
||
$query = DepartmentPatient::query()
|
||
->where(function ($builder) use ($department, $reportIds) {
|
||
if (! empty($reportIds)) {
|
||
$builder->whereIn('rf_report_id', $reportIds);
|
||
}
|
||
|
||
$builder->orWhere(function ($legacyQuery) use ($department) {
|
||
$legacyQuery->whereNull('rf_report_id')
|
||
->where('rf_department_id', $department->department_id);
|
||
});
|
||
});
|
||
|
||
if ($withOperations) {
|
||
$query->with(['operations.serviceMedical']);
|
||
}
|
||
|
||
if ($sourceTypes !== null) {
|
||
$query->whereIn('source_type', $sourceTypes);
|
||
}
|
||
|
||
return $query;
|
||
}
|
||
|
||
private function getReportIdsForDepartmentPeriod(Department $department, DateRange $dateRange): array
|
||
{
|
||
return Report::query()
|
||
->where('rf_department_id', $department->department_id)
|
||
->when(
|
||
$dateRange->isOneDay,
|
||
fn ($query) => $query->exactPeriod($dateRange->startSql(), $dateRange->endSql()),
|
||
fn ($query) => $query->withinPeriod($dateRange->startSql(), $dateRange->endSql()),
|
||
)
|
||
->pluck('report_id')
|
||
->all();
|
||
}
|
||
|
||
private function mapManualPatients(Collection $manualPatients, DateRange $dateRange): Collection
|
||
{
|
||
return $manualPatients
|
||
->map(function (DepartmentPatient $patient) use ($dateRange) {
|
||
$operationsRelation = $patient->relationLoaded('operations')
|
||
? $patient->operations
|
||
: collect();
|
||
|
||
$operations = $operationsRelation->map(fn ($operation) => [
|
||
'id' => $operation->department_patient_operation_id,
|
||
'code' => $operation->serviceMedical?->ServiceMedicalCode ?? $operation->service_code,
|
||
'name' => $operation->serviceMedical?->ServiceMedicalName ?? $operation->service_name,
|
||
'startAt' => $operation->started_at?->toIso8601String(),
|
||
'endAt' => $operation->ended_at?->toIso8601String(),
|
||
])->filter(fn ($operation) => $operation['code'] || $operation['name'])->values()->all();
|
||
|
||
return UnifiedPatientData::fromDepartmentPatient(
|
||
$patient,
|
||
$patient->admitted_at?->betweenIncluded($dateRange->startDate, $dateRange->endDate) ?? false,
|
||
$operations,
|
||
$this->resolveObservationComment(null, $patient->department_patient_id)
|
||
);
|
||
})
|
||
->sortByDesc(fn (UnifiedPatientData $patient) => $patient->admittedAt ?? '')
|
||
->values();
|
||
}
|
||
|
||
private function resolveObservationComment(?int $medicalHistoryId, ?int $departmentPatientId): ?string
|
||
{
|
||
$query = ObservationPatient::query();
|
||
|
||
if ($departmentPatientId) {
|
||
$query->where('rf_department_patient_id', $departmentPatientId);
|
||
} elseif ($medicalHistoryId) {
|
||
$query->where('rf_medicalhistory_id', $medicalHistoryId);
|
||
} else {
|
||
return null;
|
||
}
|
||
|
||
return $query->pluck('comment')->filter()->implode('; ') ?: null;
|
||
}
|
||
}
|