Files
onboard/app/Services/PatientService.php
2026-04-21 10:08:14 +09:00

687 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services;
use App\Models\MisMedicalHistory;
use App\Models\MisMigrationPatient;
use App\Models\MisReanimation;
use App\Models\MisSurgicalOperation;
use App\Models\ObservationPatient;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class PatientService
{
/**
* Получить плановых или экстренных пациентов
*/
public function getPlanOrEmergencyPatients(
?string $type,
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange,
bool $countOnly = false,
bool $onlyIds = false,
bool $includeCurrent = false,
bool $fillableAuto = false
) {
// Получаем поступивших сегодня
$recipientQuery = $this->buildRecipientQuery($type, $isHeadOrAdmin, $branchId, $dateRange, $fillableAuto);
if ($fillableAuto)
$recipientIds = $recipientQuery->distinct()->pluck('rf_MedicalHistoryID')->toArray();
else
$recipientIds = $recipientQuery->pluck('rf_MedicalHistoryID')->toArray();
// Если нужно добавить уже находящихся в отделении
if ($includeCurrent) {
if ($fillableAuto) {
$currentIds = $this->getHistoricalCurrentMedicalHistoryIds($branchId, $dateRange);
} else {
$currentIds = MisMigrationPatient::currentlyInTreatment($branchId)
->pluck('rf_MedicalHistoryID')
->toArray();
}
$medicalHistoryIds = array_unique(array_merge($recipientIds, $currentIds));
} else {
$medicalHistoryIds = $recipientIds;
}
if (empty($medicalHistoryIds)) {
if ($countOnly) return 0;
if ($onlyIds) return collect();
return collect();
}
// Получаем истории
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->select([
'MedicalHistoryID',
'FAMILY',
'Name',
'OT',
'BD',
'DateRecipient',
'DateExtract',
'rf_EmerSignID',
'rf_kl_VisitResultID',
])
->with([
'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([
'MigrationPatientID',
'rf_MedicalHistoryID',
'DateOut',
'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',
]);
}]);
}]);
},
])
->orderBy('DateRecipient', 'DESC');
// Фильтруем по типу (план/экстренные)
if ($type === 'plan') {
$query->plan();
} elseif ($type === 'emergency') {
$query->emergency();
}
if ($countOnly) {
return $query->count();
}
if ($onlyIds) {
return $query->pluck('MedicalHistoryID');
}
return $query->get()->map(function ($patient) use ($recipientIds) {
// Добавляем флаг "поступил сегодня"
$patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds);
return $patient;
});
}
/**
* Получить всех пациентов в отделении (поступившие сегодня + уже лечащиеся)
*/
public function getAllPatientsInDepartment(
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange,
bool $countOnly = false,
bool $onlyIds = false,
bool $fillableAuto = false
) {
// Поступившие сегодня
$recipientIds = $this->buildRecipientQuery(null, $isHeadOrAdmin, $branchId, $dateRange, $fillableAuto)
->pluck('rf_MedicalHistoryID')
->toArray();
// Уже находящиеся на лечении
if ($fillableAuto) {
$currentIds = $this->getHistoricalCurrentMedicalHistoryIds($branchId, $dateRange);
} else {
$currentIds = MisMigrationPatient::currentlyInTreatment($branchId)
->pluck('rf_MedicalHistoryID')
->toArray();
}
// Объединяем и убираем дубли
$allIds = array_unique(array_merge($recipientIds, $currentIds));
if (empty($allIds)) {
if ($countOnly) return 0;
return collect();
}
if ($countOnly) {
return count($allIds);
}
if ($onlyIds) {
return collect($allIds);
}
return MisMedicalHistory::whereIn('MedicalHistoryID', $allIds)
->select([
'MedicalHistoryID',
'FAMILY',
'Name',
'OT',
'BD',
'DateRecipient',
'DateExtract',
'rf_EmerSignID',
'rf_kl_VisitResultID',
])
->with([
'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([
'MigrationPatientID',
'rf_MedicalHistoryID',
'DateOut',
'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',
]);
}]);
}]);
},
])
->orderBy('DateRecipient', 'DESC')
->get()
->map(function ($patient) use ($recipientIds) {
$patient->is_recipient_today = in_array($patient->MedicalHistoryID, $recipientIds);
return $patient;
});
}
/**
* Получить пациентов под наблюдением
*/
public function getObservationPatients(int $departmentId, bool $onlyIds = false)
{
$query = MisMedicalHistory::whereHas('observationPatient', function ($q) use ($departmentId) {
$q->where('rf_department_id', $departmentId);
});
if (!$onlyIds) {
$query->with(['observationPatient' => function ($query) use ($departmentId) {
$query->where('rf_department_id', $departmentId)
->select(['rf_medicalhistory_id', 'observation_patient_id', 'comment']);
}])
->orderBy('DateRecipient', 'DESC');
}
if ($onlyIds) $patients = $query->pluck('MedicalHistoryID');
else {
// Загрузка отношений, необходимых для FormattedPatientResource
$query->with([
'outcomeMigration.mainDiagnosis.mkb', // mkb через диагноз
]);
$patients = $query->get();
}
return $patients->map(function ($patient) {
$patient->comment = $patient->observationPatient
->pluck('comment')
->filter()
->implode('; ');
return $patient;
});
}
/**
* Получить выбывших пациентов
*/
public function getOutcomePatients(
int $branchId,
DateRange $dateRange,
string $outcomeType = 'all',
bool $onlyIds = false
) {
$query = MisMedicalHistory::query()
->where('MedicalHistoryID', '<>', 0)
->whereHas('migrations', function ($migrationQuery) use ($branchId, $outcomeType) {
$migrationQuery->where('rf_StationarBranchID', $branchId);
if ($outcomeType === 'deceased') {
$migrationQuery->whereIn('rf_kl_VisitResultID', [5, 6, 15, 16]);
} elseif ($outcomeType === 'transferred') {
$migrationQuery->whereIn('rf_kl_VisitResultID', [4, 14]);
} elseif ($outcomeType === 'discharged') {
$migrationQuery->whereIn('rf_kl_VisitResultID', [1, 11, 2, 12, 7, 18, 48]);
} elseif ($outcomeType === 'without-transferred') {
$migrationQuery->whereNotIn('rf_kl_VisitResultID', [4, 14])
->where('rf_kl_VisitResultID', '<>', 0);
}
});
if ($dateRange->isOneDay) {
$query->where('DateExtract', '>', $dateRange->startSql())
->where('DateExtract', '<=', $dateRange->endSql());
} else {
$startAt = $dateRange->startSql();
$endDate = $dateRange->end()->toDateString();
$query->where('DateExtract', '>', $startAt)
->whereDate('DateExtract', '<=', $endDate);
}
if ($onlyIds) {
return $query->pluck('MedicalHistoryID');
}
return $query
->select([
'MedicalHistoryID',
'FAMILY',
'Name',
'OT',
'BD',
'DateRecipient',
'DateExtract',
'rf_EmerSignID',
'rf_kl_VisitResultID',
])
->with([
'outcomeMigration' => function ($q) {
$q->select([
'MigrationPatientID',
'rf_MedicalHistoryID',
'DateOut',
'rf_DiagnosID',
])->with(['mainDiagnosis' => function ($diagnosisQuery) {
$diagnosisQuery->select([
'DiagnosID',
'rf_MKBID',
])->with(['mkb' => function ($mkbQuery) {
$mkbQuery->select([
'MKBID',
'DS',
'NAME',
]);
}]);
}]);
},
])
->orderBy('DateRecipient', 'DESC')
->get()
->map(fn ($patient) => $this->addOutcomeInfo($patient));
}
/**
* Получить пациентов, находящихся в реанимации на конец периода
*/
public function getReanimationPatients(
int $branchId,
DateRange $dateRange,
bool $onlyIds = false
) {
$medicalHistoryIds = MisReanimation::query()
->join('stt_migrationpatient as mp', 'mp.MigrationPatientID', '=', 'stt_reanimation.rf_MigrationPatientID')
->where('stt_reanimation.rf_StationarBranchID', $branchId)
->where('mp.rf_StationarBranchID', $branchId)
->where('mp.rf_MedicalHistoryID', '<>', 0)
->where('stt_reanimation.DateIn', '<=', $dateRange->endSql())
->where(function ($query) use ($dateRange) {
$query->where('stt_reanimation.DateOut', '>=', $dateRange->endSql())
->orWhereNull('stt_reanimation.DateOut')
->orWhereDate('stt_reanimation.DateOut', '1900-01-01')
->orWhereDate('stt_reanimation.DateOut', '2222-01-01');
})
->distinct()
->pluck('mp.rf_MedicalHistoryID')
->toArray();
if (empty($medicalHistoryIds)) {
return collect();
}
if ($onlyIds) {
return collect($medicalHistoryIds);
}
return MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->select([
'MedicalHistoryID',
'FAMILY',
'Name',
'OT',
'BD',
'DateRecipient',
'DateExtract',
'rf_EmerSignID',
'rf_kl_VisitResultID',
])
->with([
'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([
'MigrationPatientID',
'rf_MedicalHistoryID',
'DateOut',
'rf_DiagnosID',
])->with(['mainDiagnosis' => function ($diagnosisQuery) {
$diagnosisQuery->select([
'DiagnosID',
'rf_MKBID',
])->with(['mkb' => function ($mkbQuery) {
$mkbQuery->select([
'MKBID',
'DS',
'NAME',
]);
}]);
}]);
},
])
->orderBy('DateRecipient', 'DESC')
->get();
}
/**
* Получить пациентов с операциями
*/
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());
// ->whereBetween('Date', [$dateRange->startSql(), $dateRange->endSql()]);
if ($type === 'plan') {
$query->where('rf_TypeSurgOperationInTimeID', 6);
} else {
$query->whereIn('rf_TypeSurgOperationInTimeID', [4, 5]);
}
if ($countOnly) {
return $query->count();
}
return $query->get();
}
/**
* Получить текущих пациентов
*/
public function getCurrentPatients(int $branchId, bool $countOnly = false)
{
$medicalHistoryIds = MisMigrationPatient::currentlyInTreatment($branchId)
->pluck('rf_MedicalHistoryID')
->unique()
->toArray();
if (empty($medicalHistoryIds)) {
return $countOnly ? 0 : collect();
}
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds)
->currentlyHospitalized()
->with(['surgicalOperations'])
->orderBy('DateRecipient', 'DESC');
if ($countOnly) {
return $query->count();
}
return $query->get();
}
/**
* Собрать базовый запрос для пациентов
*/
private function buildPatientQuery(
?string $type,
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange
) {
if ($isHeadOrAdmin) {
$query = MisMigrationPatient::whereInDepartment($branchId)
->where('DateIngoing', '>=', $dateRange->startSql())
->where('DateIngoing', '<=', $dateRange->endSql());
// ->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
} else {
$query = MisMigrationPatient::currentlyInTreatment($branchId)
->where('DateIngoing', '>=', $dateRange->startSql())
->where('DateIngoing', '<=', $dateRange->endSql());
// ->whereBetween('DateIngoing', [$dateRange->startSql(), $dateRange->endSql()]);
}
$medicalHistoryIds = $query->pluck('rf_MedicalHistoryID')->toArray();
if (empty($medicalHistoryIds)) {
return MisMedicalHistory::whereRaw('1 = 0');
}
$query = MisMedicalHistory::whereIn('MedicalHistoryID', $medicalHistoryIds);
if ($type === 'plan') {
$query->plan();
} elseif ($type === 'emergency') {
$query->emergency();
}
if (!$isHeadOrAdmin && !in_array($type, ['discharged', 'transferred', 'deceased'])) {
$query->currentlyHospitalized();
}
return $query;
}
/**
* Построить запрос для поступивших пациентов
*/
private function buildRecipientQuery(
?string $type,
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange,
bool $fillableAuto = false
) {
$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();
}
$query = DB::table('stt_medicalhistory as mh')
->selectRaw('mh."MedicalHistoryID" as "rf_MedicalHistoryID"')
->where('mh.MedicalHistoryID', '<>', 0);
$query->whereExists(function ($subQuery) use ($branchId, $startAt, $endAt) {
$subQuery->select(DB::raw(1))
->from('stt_migrationpatient as mp')
->whereColumn('mp.rf_MedicalHistoryID', 'mh.MedicalHistoryID')
->where('mp.rf_StationarBranchID', $branchId)
->where('mp.DateIngoing', '>', $startAt)
->where('mp.DateIngoing', '<=', $endAt);
});
if ($type === 'plan') {
$query->where('mh.rf_EmerSignID', 1);
} elseif ($type === 'emergency') {
$query->whereIn('mh.rf_EmerSignID', [2, 4]);
}
return $query->distinct();
}
private function getHistoricalCurrentMedicalHistoryIds(int $branchId, DateRange $dateRange): array
{
// Исторический срез по основной таблице миграций:
// для каждой истории болезни берём последнюю миграцию в отделении
// на момент конца периода и проверяем, что пациент числился в отделении.
$latestRows = DB::table('stt_migrationpatient')
->select('rf_MedicalHistoryID', DB::raw('MAX("MigrationPatientID") as max_migration_patient_id'))
->where('rf_StationarBranchID', $branchId)
->where('rf_MedicalHistoryID', '<>', 0)
->where('DateIngoing', '<=', $dateRange->endSql())
->groupBy('rf_MedicalHistoryID');
return DB::table('stt_migrationpatient as mp')
->joinSub($latestRows, 'latest', function ($join) {
$join->on('mp.rf_MedicalHistoryID', '=', 'latest.rf_MedicalHistoryID')
->on('mp.MigrationPatientID', '=', 'latest.max_migration_patient_id');
})
->join('stt_medicalhistory as mh', 'mh.MedicalHistoryID', '=', 'mp.rf_MedicalHistoryID')
->where('mp.rf_StationarBranchID', $branchId)
->where('mh.DateRecipient', '<=', $dateRange->endSql())
->where('mp.DateOut', '>=', $dateRange->endSql())
->where(function ($query) use ($dateRange) {
$query->where('mh.DateExtract', '>', $dateRange->endSql())
->orWhereDate('mh.DateExtract', '1900-01-01');
})
->distinct()
->pluck('mp.rf_MedicalHistoryID')
->toArray();
}
/**
* Добавить информацию об исходе пациенту
*/
private function addOutcomeInfo(MisMedicalHistory $patient)
{
$latestMigration = $patient->migrations
->whereNotNull('DateOut')
->where('DateOut', '<>', '1900-01-01')
->sortByDesc('DateOut')
->first();
if ($latestMigration) {
$patient->outcome_type = $this->getOutcomeTypeName($latestMigration->rf_kl_VisitResultID);
$patient->outcome_date = $latestMigration->DateOut;
$patient->visit_result_id = $latestMigration->rf_kl_VisitResultID;
}
return $patient;
}
/**
* Получить количество пациентов по типу с учетом уже находящихся в отделении
*/
public function getPatientsCountWithCurrent(
?string $type,
bool $isHeadOrAdmin,
int $branchId,
DateRange $dateRange
): int {
return $this->getPlanOrEmergencyPatients(
$type,
$isHeadOrAdmin,
$branchId,
$dateRange,
true,
false,
true
);
}
/**
* Получить название типа исхода
*/
private function getOutcomeTypeName(int $visitResultId): string
{
return match($visitResultId) {
1, 7, 8, 9, 10, 11, 48, 49, 124 => 'Выписка',
2, 3, 4, 12, 13, 14 => 'Перевод',
5, 6, 15, 16 => 'Умер',
default => 'Другое (' . $visitResultId . ')'
};
}
}