misPatientSource = $misPatientSource ?? app(MisPatientSource::class); $this->specialPatientSource = $specialPatientSource ?? app(SpecialPatientSource::class); } protected MisPatientSource $misPatientSource; protected SpecialPatientSource $specialPatientSource; public function getLivePatientsByStatus( Department $department, User $user, string $status, DateRange $dateRange, int $branchId, bool $onlyIds = false, ?bool $includeCurrent = null, bool $fillableAuto = false, bool $forSnapshots = false ): Collection { [$baseStatus, $sourceScope] = $this->parseScopedStatus($status); if ($baseStatus === 'observation') { return $this->getObservationPatients($department, $onlyIds, $sourceScope); } $patients = match ($sourceScope) { 'mis' => $this->misPatientSource->getDtos($user, $baseStatus, $dateRange, $branchId, $includeCurrent, $fillableAuto, $forSnapshots), 'special' => $this->specialPatientSource->getDtos($department, $baseStatus, $dateRange, self::SPECIAL_SOURCE_TYPES, $forSnapshots), default => $this->getAggregatedPatientDtos($department, $user, $baseStatus, $dateRange, $branchId, $includeCurrent, $fillableAuto, $forSnapshots), }; 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 { [$baseStatus, $sourceScope] = $this->parseScopedStatus($status); if ($baseStatus === 'observation') { $query = ObservationPatient::query() ->where('rf_department_id', $department->department_id); if ($sourceScope === 'special') { return $query->whereNotNull('rf_department_patient_id')->count(); } if ($sourceScope === 'mis') { return $query->whereNull('rf_department_patient_id')->count(); } return $query->count(); } if ($sourceScope === 'special') { return $this->specialPatientSource->getCount($department, $baseStatus, $dateRange, self::SPECIAL_SOURCE_TYPES); } $misCount = $this->misPatientSource->getCount( $user, $baseStatus, $dateRange, $branchId, $includeCurrent, $fillableAuto ); if ($sourceScope === 'mis') { return $misCount; } $specialCount = $this->specialPatientSource->getCount($department, $baseStatus, $dateRange, self::SPECIAL_SOURCE_TYPES); return $misCount + $specialCount; } public function getRecipientIdsForReport( Department $department, User $user, DateRange $dateRange, int $branchId, bool $fillableAuto = false ): array { $isHeadOrAdmin = $user->isHeadOfDepartment() || $user->isAdmin(); $misIds = $this->patientService->getPlanOrEmergencyPatients( null, $isHeadOrAdmin, $branchId, $dateRange, false, true, false, $fillableAuto ); $manualIds = $this->specialPatientSource->getPatients($department, 'recipient', $dateRange, self::SPECIAL_SOURCE_TYPES, false) ->whereBetween('admitted_at', [$dateRange->startSql(), $dateRange->endSql()]) ->pluck('department_patient_id'); $misUids = collect($misIds)->map(fn ($id) => "mis:{$id}")->all(); $manualUids = collect($manualIds)->map(fn ($id) => "manual:{$id}")->all(); return array_values(array_unique(array_merge($misUids, $manualUids))); } public function createManualPatient(Department $department, User $user, array $data, int $reportId): DepartmentPatient { return DepartmentPatient::create([ 'rf_department_id' => $department->department_id, 'rf_report_id' => $reportId, '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 { $manualStatus = $data['manual_status'] ?? null; $isCurrent = $manualStatus === 'current' || $manualStatus === null; $outcomeType = match ($manualStatus) { 'discharged', 'deceased', 'transferred' => $manualStatus, default => null, }; $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, 'is_current' => $isCurrent, 'outcome_type' => $outcomeType, 'outcome_at' => $isCurrent ? null : ($data['outcome_at'] ?? now()), ]); return $patient->fresh(); } public function linkManualPatientToMis(DepartmentPatient $patient, int $medicalHistoryId): DepartmentPatient { $history = MedicalHistory::query() ->where('original_id', $medicalHistoryId) ->firstOrFail(); $patient->update([ 'rf_medicalhistory_id' => $history->original_id, 'linked_to_mis_at' => now(), 'full_name' => $patient->full_name ?: $history->full_name, 'birth_date' => $patient->birth_date ?: $history->birth_date, ]); return $patient->fresh(); } public function searchMisPatients(Department $department, string $query): Collection { return MedicalHistory::query() ->whereLike('full_name', "%{$query}%") ->whereHas('migrations', fn ($builder) => $builder->department($department->rf_mis_department_id)) ->with(['latestMigration', 'operations']) ->limit(20) ->get() ->map(fn (MedicalHistory $patient) => UnifiedPatientData::fromMedicalHistory($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 = MedicalHistory::query() ->whereIn('original_id', $misIds) ->with(['latestMigration', 'operations']) ->get() ->keyBy('original_id'); $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::fromMedicalHistory( $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, bool $forSnapshots = false ): Collection { $misPatients = $this->misPatientSource->getPatients($user, $status, $dateRange, $branchId, $includeCurrent, $fillableAuto, $forSnapshots); $linkedManualPatients = $this->specialPatientSource->getLinkedManualPatientsForPeriod($department, $dateRange); $mergedMisPatients = $misPatients->map(function ($patient) use ($linkedManualPatients) { $medicalHistoryId = $patient instanceof MedicalHistory ? ($patient->original_id ?? $patient->id) : $patient->MedicalHistoryID; $linkedManual = $linkedManualPatients->get($medicalHistoryId); return $patient instanceof MedicalHistory ? UnifiedPatientData::fromMedicalHistory( $patient, (bool) ($patient->is_recipient_today ?? false), $linkedManual, $this->resolveObservationComment($medicalHistoryId, null) ) : UnifiedPatientData::fromMisMedicalHistory( $patient, (bool) ($patient->is_recipient_today ?? false), $linkedManual, $this->resolveObservationComment($medicalHistoryId, null) ); }); $manualDtos = $this->specialPatientSource->getDtos($department, $status, $dateRange, self::SPECIAL_SOURCE_TYPES, $forSnapshots); return UnifiedPatientData::unique($mergedMisPatients->concat($manualDtos)) ->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; } }