Deploy Ladill Care / deploy (push) Successful in 34s
Add ED visit stages, triage-driven routing, vitals, observation, disposition, reports, and print summary so Emergency matches Dentistry’s operational depth without a parallel EHR. Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
3.2 KiB
PHP
116 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Care\Emergency;
|
|
|
|
use App\Models\Appointment;
|
|
use App\Models\Consultation;
|
|
use App\Models\Organization;
|
|
use App\Models\Visit;
|
|
use App\Models\VitalSign;
|
|
use App\Services\Care\ConsultationService;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class EmergencyVitalsService
|
|
{
|
|
public function __construct(
|
|
protected ConsultationService $consultations,
|
|
) {}
|
|
|
|
/**
|
|
* @return Collection<int, VitalSign>
|
|
*/
|
|
public function forVisit(Visit $visit): Collection
|
|
{
|
|
$consultationIds = Consultation::owned($visit->owner_ref)
|
|
->where('visit_id', $visit->id)
|
|
->pluck('id');
|
|
|
|
if ($consultationIds->isEmpty()) {
|
|
return collect();
|
|
}
|
|
|
|
return VitalSign::owned($visit->owner_ref)
|
|
->whereIn('consultation_id', $consultationIds)
|
|
->orderByDesc('recorded_at')
|
|
->orderByDesc('id')
|
|
->get();
|
|
}
|
|
|
|
public function latestForVisit(Visit $visit): ?VitalSign
|
|
{
|
|
return $this->forVisit($visit)->first();
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $vitals
|
|
*/
|
|
public function record(
|
|
Organization $organization,
|
|
Visit $visit,
|
|
string $ownerRef,
|
|
array $vitals,
|
|
?string $actorRef = null,
|
|
): VitalSign {
|
|
$consultation = $this->ensureConsultation($visit, $ownerRef, $actorRef);
|
|
|
|
$saved = $this->consultations->saveVitals(
|
|
$consultation,
|
|
$ownerRef,
|
|
$vitals,
|
|
$actorRef,
|
|
);
|
|
|
|
if (! $saved) {
|
|
throw new \InvalidArgumentException('Enter at least one vital sign value.');
|
|
}
|
|
|
|
return $saved;
|
|
}
|
|
|
|
protected function ensureConsultation(Visit $visit, string $ownerRef, ?string $actorRef): Consultation
|
|
{
|
|
$existing = Consultation::owned($ownerRef)
|
|
->where('visit_id', $visit->id)
|
|
->where('status', '!=', Consultation::STATUS_COMPLETED)
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
if ($existing) {
|
|
return $existing;
|
|
}
|
|
|
|
$completed = Consultation::owned($ownerRef)
|
|
->where('visit_id', $visit->id)
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
if ($completed) {
|
|
return $completed;
|
|
}
|
|
|
|
$visit->loadMissing('appointment', 'patient');
|
|
$appointment = $visit->appointment;
|
|
if (! $appointment instanceof Appointment) {
|
|
$appointment = Appointment::query()->where('visit_id', $visit->id)->first();
|
|
}
|
|
|
|
if ($appointment) {
|
|
try {
|
|
return $this->consultations->startFromAppointment($appointment, $ownerRef, $actorRef);
|
|
} catch (\InvalidArgumentException) {
|
|
// Fall through to draft consultation.
|
|
}
|
|
}
|
|
|
|
return Consultation::create([
|
|
'owner_ref' => $ownerRef,
|
|
'visit_id' => $visit->id,
|
|
'appointment_id' => $appointment?->id,
|
|
'practitioner_id' => $appointment?->practitioner_id,
|
|
'patient_id' => $visit->patient_id,
|
|
'status' => Consultation::STATUS_DRAFT,
|
|
'started_at' => now(),
|
|
]);
|
|
}
|
|
}
|