Deploy Ladill Care / deploy (push) Successful in 2m12s
OPD/service units list open department appointments without manual place; nursing actions soft-place the visit onto the unit. Inpatient wards stay placement-only. Co-authored-by: Cursor <cursoragent@cursor.com>
117 lines
3.6 KiB
PHP
117 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Care;
|
|
|
|
use App\Models\Assessment;
|
|
use App\Models\AssessmentTemplate;
|
|
use App\Models\CareUnit;
|
|
use App\Models\Visit;
|
|
use App\Models\VisitVital;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class NursingAssessmentService
|
|
{
|
|
/** @var list<string> */
|
|
public const NURSING_PACK_CODES = ['news2', 'braden', 'morse', 'pain_nrs'];
|
|
|
|
/** Compact board labels — full names stay on the assessment form. */
|
|
public static function shortLabel(string $code): string
|
|
{
|
|
return match ($code) {
|
|
'news2' => 'NEWS2',
|
|
'braden' => 'Braden',
|
|
'morse' => 'Morse',
|
|
'pain_nrs' => 'Pain',
|
|
default => strtoupper(str_replace('_', ' ', $code)),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return Collection<int, AssessmentTemplate>
|
|
*/
|
|
public function nursingTemplates(): Collection
|
|
{
|
|
return AssessmentTemplate::query()
|
|
->whereNull('organization_id')
|
|
->where('is_current', true)
|
|
->where('is_active', true)
|
|
->whereIn('code', self::NURSING_PACK_CODES)
|
|
->orderBy('name')
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* Board rows for placed patients: latest nursing assessments + latest vitals.
|
|
*
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
public function unitBoard(CareUnit $unit, string $ownerRef): array
|
|
{
|
|
$templates = $this->nursingTemplates();
|
|
$visits = app(CareUnitCensusService::class)->visitsForUnit($unit, $ownerRef);
|
|
|
|
$rows = [];
|
|
foreach ($visits as $visit) {
|
|
$latestByCode = Assessment::owned($ownerRef)
|
|
->where('visit_id', $visit->id)
|
|
->whereIn('status', [Assessment::STATUS_DRAFT, Assessment::STATUS_COMPLETED])
|
|
->whereHas('template', fn ($q) => $q->whereIn('code', $templates->pluck('code')->all()))
|
|
->with(['template', 'score'])
|
|
->orderByDesc('id')
|
|
->get()
|
|
->unique(fn (Assessment $a) => $a->template?->code)
|
|
->keyBy(fn (Assessment $a) => $a->template?->code);
|
|
|
|
$latestVitals = VisitVital::owned($ownerRef)
|
|
->where('visit_id', $visit->id)
|
|
->orderByDesc('recorded_at')
|
|
->first();
|
|
|
|
$rows[] = [
|
|
'visit' => $visit,
|
|
'patient' => $visit->patient,
|
|
'bed' => $visit->bed,
|
|
'assessments' => $latestByCode,
|
|
'vitals' => $latestVitals,
|
|
];
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
public function recordVisitVitals(
|
|
Visit $visit,
|
|
string $ownerRef,
|
|
?string $actorRef,
|
|
array $data,
|
|
): VisitVital {
|
|
$vital = VisitVital::create([
|
|
'owner_ref' => $ownerRef,
|
|
'organization_id' => $visit->organization_id,
|
|
'visit_id' => $visit->id,
|
|
'patient_id' => $visit->patient_id,
|
|
'care_unit_id' => $visit->care_unit_id,
|
|
'bp_systolic' => $data['bp_systolic'] ?? null,
|
|
'bp_diastolic' => $data['bp_diastolic'] ?? null,
|
|
'pulse' => $data['pulse'] ?? null,
|
|
'temperature' => $data['temperature'] ?? null,
|
|
'spo2' => $data['spo2'] ?? null,
|
|
'respiratory_rate' => $data['respiratory_rate'] ?? null,
|
|
'notes' => $data['notes'] ?? null,
|
|
'recorded_by' => $actorRef,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
AuditLogger::record(
|
|
$ownerRef,
|
|
'visit_vitals.recorded',
|
|
$visit->organization_id,
|
|
$actorRef,
|
|
VisitVital::class,
|
|
$vital->id,
|
|
);
|
|
|
|
return $vital;
|
|
}
|
|
}
|