modified: .gitignore
This commit is contained in:
391
app/Services/UnifiedPatientService.php
Normal file
391
app/Services/UnifiedPatientService.php
Normal file
@@ -0,0 +1,391 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Data\UnifiedPatientData;
|
||||
use App\Models\Department;
|
||||
use App\Models\DepartmentPatient;
|
||||
use App\Models\MisMedicalHistory;
|
||||
use App\Models\ObservationPatient;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class UnifiedPatientService
|
||||
{
|
||||
private const SPECIAL_SOURCE_TYPES = ['manual', 'special'];
|
||||
|
||||
public function __construct(
|
||||
protected PatientService $patientService,
|
||||
) {}
|
||||
|
||||
public function getLivePatientsByStatus(
|
||||
Department $department,
|
||||
User $user,
|
||||
string $status,
|
||||
DateRange $dateRange,
|
||||
int $branchId,
|
||||
bool $onlyIds = false,
|
||||
?bool $includeCurrent = null,
|
||||
bool $fillableAuto = false
|
||||
): Collection {
|
||||
[$baseStatus, $sourceScope] = $this->parseScopedStatus($status);
|
||||
|
||||
if ($baseStatus === 'observation') {
|
||||
return $this->getObservationPatients($department, $onlyIds, $sourceScope);
|
||||
}
|
||||
|
||||
$patients = match ($sourceScope) {
|
||||
'mis' => $this->getMisPatientDtos($user, $baseStatus, $dateRange, $branchId, $includeCurrent, $fillableAuto),
|
||||
'special' => $this->getSpecialPatientDtos($department, $baseStatus, $dateRange),
|
||||
default => $this->getAggregatedPatientDtos($department, $user, $baseStatus, $dateRange, $branchId, $includeCurrent, $fillableAuto),
|
||||
};
|
||||
|
||||
if ($onlyIds) {
|
||||
return $patients->pluck('id');
|
||||
}
|
||||
|
||||
return $patients;
|
||||
}
|
||||
|
||||
public function getLivePatientCountByStatus(
|
||||
Department $department,
|
||||
User $user,
|
||||
string $status,
|
||||
DateRange $dateRange,
|
||||
int $branchId,
|
||||
?bool $includeCurrent = null,
|
||||
bool $fillableAuto = false
|
||||
): int {
|
||||
return $this->getLivePatientsByStatus($department, $user, $status, $dateRange, $branchId, false, $includeCurrent, $fillableAuto)->count();
|
||||
}
|
||||
|
||||
public function createManualPatient(Department $department, User $user, array $data): DepartmentPatient
|
||||
{
|
||||
return DepartmentPatient::create([
|
||||
'rf_department_id' => $department->department_id,
|
||||
'source_type' => 'special',
|
||||
'full_name' => $data['full_name'],
|
||||
'birth_date' => $data['birth_date'],
|
||||
'patient_kind' => $data['patient_kind'],
|
||||
'diagnosis_code' => $data['diagnosis_code'] ?? null,
|
||||
'diagnosis_name' => $data['diagnosis_name'] ?? null,
|
||||
'admitted_at' => $data['admitted_at'] ?? now(),
|
||||
'is_current' => true,
|
||||
'created_by' => $user->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function recordManualOutcome(DepartmentPatient $patient, array $data): DepartmentPatient
|
||||
{
|
||||
$patient->update([
|
||||
'is_current' => false,
|
||||
'outcome_type' => $data['outcome_type'],
|
||||
'outcome_at' => $data['outcome_at'] ?? now(),
|
||||
]);
|
||||
|
||||
return $patient->fresh();
|
||||
}
|
||||
|
||||
public function updateManualPatient(DepartmentPatient $patient, array $data): DepartmentPatient
|
||||
{
|
||||
$patient->update([
|
||||
'full_name' => $data['full_name'],
|
||||
'birth_date' => $data['birth_date'],
|
||||
'patient_kind' => $data['patient_kind'],
|
||||
'diagnosis_code' => $data['diagnosis_code'] ?? null,
|
||||
'diagnosis_name' => $data['diagnosis_name'] ?? null,
|
||||
'admitted_at' => $data['admitted_at'] ?? $patient->admitted_at,
|
||||
]);
|
||||
|
||||
return $patient->fresh();
|
||||
}
|
||||
|
||||
public function linkManualPatientToMis(DepartmentPatient $patient, int $medicalHistoryId): DepartmentPatient
|
||||
{
|
||||
$misPatient = MisMedicalHistory::where('MedicalHistoryID', $medicalHistoryId)->firstOrFail();
|
||||
|
||||
$patient->update([
|
||||
'rf_medicalhistory_id' => $misPatient->MedicalHistoryID,
|
||||
'linked_to_mis_at' => now(),
|
||||
'full_name' => $patient->full_name ?: trim("{$misPatient->FAMILY} {$misPatient->Name} {$misPatient->OT}"),
|
||||
'birth_date' => $patient->birth_date ?: $misPatient->BD,
|
||||
]);
|
||||
|
||||
return $patient->fresh();
|
||||
}
|
||||
|
||||
public function searchMisPatients(Department $department, string $query): Collection
|
||||
{
|
||||
$branchId = \App\Models\MisStationarBranch::where('rf_DepartmentID', $department->rf_mis_department_id)
|
||||
->value('StationarBranchID');
|
||||
|
||||
return MisMedicalHistory::query()
|
||||
->whereHas('migrations', fn ($builder) => $builder->where('rf_StationarBranchID', $branchId))
|
||||
->where(function ($builder) use ($query) {
|
||||
$builder->where('FAMILY', 'like', "%{$query}%")
|
||||
->orWhere('Name', 'like', "%{$query}%")
|
||||
->orWhere('OT', 'like', "%{$query}%");
|
||||
})
|
||||
->with(['outcomeMigration.mainDiagnosis.mkb'])
|
||||
->limit(20)
|
||||
->get()
|
||||
->map(fn (MisMedicalHistory $patient) => UnifiedPatientData::fromMisMedicalHistory($patient));
|
||||
}
|
||||
|
||||
public function getObservationPatients(
|
||||
Department $department,
|
||||
bool $onlyIds = false,
|
||||
string $sourceScope = 'all'
|
||||
): Collection
|
||||
{
|
||||
$observationPatients = ObservationPatient::where('rf_department_id', $department->department_id)->get();
|
||||
|
||||
$misIds = $observationPatients->pluck('rf_medicalhistory_id')->filter()->unique()->values();
|
||||
$manualIds = $observationPatients->pluck('rf_department_patient_id')->filter()->unique()->values();
|
||||
|
||||
$misPatients = MisMedicalHistory::whereIn('MedicalHistoryID', $misIds)
|
||||
->with(['outcomeMigration.mainDiagnosis.mkb'])
|
||||
->get()
|
||||
->keyBy('MedicalHistoryID');
|
||||
|
||||
$manualPatients = DepartmentPatient::whereIn('department_patient_id', $manualIds)->get()->keyBy('department_patient_id');
|
||||
|
||||
$patients = $observationPatients->map(function (ObservationPatient $observation) use ($misPatients, $manualPatients, $sourceScope) {
|
||||
if ($observation->rf_department_patient_id && $manualPatients->has($observation->rf_department_patient_id)) {
|
||||
if ($sourceScope === 'mis') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return UnifiedPatientData::fromDepartmentPatient(
|
||||
$manualPatients[$observation->rf_department_patient_id],
|
||||
false,
|
||||
[],
|
||||
$observation->comment
|
||||
);
|
||||
}
|
||||
|
||||
if ($observation->rf_medicalhistory_id && $misPatients->has($observation->rf_medicalhistory_id)) {
|
||||
if ($sourceScope === 'special') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return UnifiedPatientData::fromMisMedicalHistory(
|
||||
$misPatients[$observation->rf_medicalhistory_id],
|
||||
false,
|
||||
null,
|
||||
$observation->comment
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})->filter()->values();
|
||||
|
||||
if ($onlyIds) {
|
||||
return $patients->pluck('id');
|
||||
}
|
||||
|
||||
return $patients;
|
||||
}
|
||||
|
||||
private function getAggregatedPatientDtos(
|
||||
Department $department,
|
||||
User $user,
|
||||
string $status,
|
||||
DateRange $dateRange,
|
||||
int $branchId,
|
||||
?bool $includeCurrent = null,
|
||||
bool $fillableAuto = false
|
||||
): Collection {
|
||||
$misPatients = $this->getMisPatients($user, $status, $dateRange, $branchId, $includeCurrent, $fillableAuto);
|
||||
$manualPatients = $this->getManualPatients($department, $status, $dateRange);
|
||||
$linkedManualPatients = DepartmentPatient::where('rf_department_id', $department->department_id)
|
||||
->whereIn('source_type', self::SPECIAL_SOURCE_TYPES)
|
||||
->whereNotNull('rf_medicalhistory_id')
|
||||
->get()
|
||||
->keyBy('rf_medicalhistory_id');
|
||||
|
||||
$mergedMisPatients = $misPatients->map(function ($patient) use ($linkedManualPatients) {
|
||||
$linkedManual = $linkedManualPatients->get($patient->MedicalHistoryID);
|
||||
|
||||
return UnifiedPatientData::fromMisMedicalHistory(
|
||||
$patient,
|
||||
(bool) ($patient->is_recipient_today ?? false),
|
||||
$linkedManual,
|
||||
$this->resolveObservationComment($patient->MedicalHistoryID, null)
|
||||
);
|
||||
});
|
||||
|
||||
$manualDtos = $this->mapManualPatients($manualPatients, $dateRange);
|
||||
|
||||
return UnifiedPatientData::unique($mergedMisPatients->concat($manualDtos))
|
||||
->sortByDesc(fn (UnifiedPatientData $patient) => $patient->admittedAt ?? '')
|
||||
->values();
|
||||
}
|
||||
|
||||
private function getMisPatientDtos(
|
||||
User $user,
|
||||
string $status,
|
||||
DateRange $dateRange,
|
||||
int $branchId,
|
||||
?bool $includeCurrent = null,
|
||||
bool $fillableAuto = false
|
||||
): Collection {
|
||||
return $this->getMisPatients($user, $status, $dateRange, $branchId, $includeCurrent, $fillableAuto)
|
||||
->map(fn ($patient) => UnifiedPatientData::fromMisMedicalHistory(
|
||||
$patient,
|
||||
(bool) ($patient->is_recipient_today ?? false),
|
||||
))
|
||||
->sortByDesc(fn (UnifiedPatientData $patient) => $patient->admittedAt ?? '')
|
||||
->values();
|
||||
}
|
||||
|
||||
private function getSpecialPatientDtos(
|
||||
Department $department,
|
||||
string $status,
|
||||
DateRange $dateRange
|
||||
): Collection {
|
||||
return $this->mapManualPatients(
|
||||
$this->getManualPatients($department, $status, $dateRange, self::SPECIAL_SOURCE_TYPES),
|
||||
$dateRange
|
||||
);
|
||||
}
|
||||
|
||||
private function getMisPatients(
|
||||
User $user,
|
||||
string $status,
|
||||
DateRange $dateRange,
|
||||
int $branchId,
|
||||
?bool $includeCurrent = null,
|
||||
bool $fillableAuto = false
|
||||
): Collection {
|
||||
$isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin();
|
||||
$includeCurrent = $includeCurrent ?? in_array($status, ['plan', 'emergency'], true);
|
||||
|
||||
return match ($status) {
|
||||
'plan', 'emergency' => $this->patientService->getPlanOrEmergencyPatients(
|
||||
$status,
|
||||
$isHeadOrAdmin,
|
||||
$branchId,
|
||||
$dateRange,
|
||||
false,
|
||||
false,
|
||||
$includeCurrent,
|
||||
$fillableAuto
|
||||
),
|
||||
'current' => $this->patientService->getAllPatientsInDepartment(
|
||||
$isHeadOrAdmin,
|
||||
$branchId,
|
||||
$dateRange,
|
||||
false,
|
||||
false,
|
||||
$fillableAuto
|
||||
),
|
||||
'recipient' => $this->patientService->getPlanOrEmergencyPatients(
|
||||
null,
|
||||
$isHeadOrAdmin,
|
||||
$branchId,
|
||||
$dateRange,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
$fillableAuto
|
||||
),
|
||||
'outcome' => $this->patientService->getOutcomePatients($branchId, $dateRange, 'without-transferred'),
|
||||
'outcome-discharged' => $this->patientService->getOutcomePatients($branchId, $dateRange, 'discharged'),
|
||||
'outcome-transferred' => $this->patientService->getOutcomePatients($branchId, $dateRange, 'transferred'),
|
||||
'outcome-deceased' => $this->patientService->getOutcomePatients($branchId, $dateRange, 'deceased'),
|
||||
'reanimation' => $this->patientService->getReanimationPatients($branchId, $dateRange),
|
||||
default => collect(),
|
||||
};
|
||||
}
|
||||
|
||||
private function getManualPatients(
|
||||
Department $department,
|
||||
string $status,
|
||||
DateRange $dateRange,
|
||||
?array $sourceTypes = self::SPECIAL_SOURCE_TYPES
|
||||
): Collection
|
||||
{
|
||||
$query = DepartmentPatient::where('rf_department_id', $department->department_id)
|
||||
->with(['operations.serviceMedical']);
|
||||
|
||||
if ($sourceTypes !== null) {
|
||||
$query->whereIn('source_type', $sourceTypes);
|
||||
}
|
||||
|
||||
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(),
|
||||
};
|
||||
}
|
||||
|
||||
private function mapManualPatients(Collection $manualPatients, DateRange $dateRange): Collection
|
||||
{
|
||||
return $manualPatients
|
||||
->map(function (DepartmentPatient $patient) use ($dateRange) {
|
||||
$operations = $patient->operations->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 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 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user