Clarify patient vs nurse unit placement on nursing charts.
Deploy Ladill Care / deploy (push) Successful in 55s
Deploy Ladill Care / deploy (push) Successful in 55s
The empty state referred to the patient but read like the nurse was unassigned; show duty units and patient-specific copy instead. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -214,6 +214,7 @@ class PatientController extends Controller
|
|||||||
$nursingAssessments = collect();
|
$nursingAssessments = collect();
|
||||||
$activePlacedVisit = null;
|
$activePlacedVisit = null;
|
||||||
$latestVitals = null;
|
$latestVitals = null;
|
||||||
|
$nurseDutyUnits = collect();
|
||||||
|
|
||||||
if ($canViewAssessments) {
|
if ($canViewAssessments) {
|
||||||
$branchScope = app(OrganizationResolver::class)->branchScope($member);
|
$branchScope = app(OrganizationResolver::class)->branchScope($member);
|
||||||
@@ -243,10 +244,13 @@ class PatientController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($isNurseStation) {
|
if ($isNurseStation) {
|
||||||
|
$nurseDutyUnits = app(\App\Services\Care\NurseStationService::class)
|
||||||
|
->dutyUnits($organization, $member, $this->ownerRef($request));
|
||||||
|
|
||||||
$activePlacedVisit = $patient->visits()
|
$activePlacedVisit = $patient->visits()
|
||||||
->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS])
|
->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS])
|
||||||
->whereNotNull('care_unit_id')
|
->whereNotNull('care_unit_id')
|
||||||
->with(['careUnit', 'bed'])
|
->with(['careUnit.department.branch', 'bed'])
|
||||||
->orderByDesc('placed_at')
|
->orderByDesc('placed_at')
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
@@ -269,6 +273,7 @@ class PatientController extends Controller
|
|||||||
'canViewPathways' => $canViewPathways,
|
'canViewPathways' => $canViewPathways,
|
||||||
'activePlacedVisit' => $activePlacedVisit,
|
'activePlacedVisit' => $activePlacedVisit,
|
||||||
'latestVitals' => $latestVitals,
|
'latestVitals' => $latestVitals,
|
||||||
|
'nurseDutyUnits' => $nurseDutyUnits,
|
||||||
'nursingAssessments' => $nursingAssessments,
|
'nursingAssessments' => $nursingAssessments,
|
||||||
'messagingSmsReady' => $suiteSmsReady || $credential->hasValidSms(),
|
'messagingSmsReady' => $suiteSmsReady || $credential->hasValidSms(),
|
||||||
'messagingEmailReady' => $suiteEmailReady || $credential->hasValidBird(),
|
'messagingEmailReady' => $suiteEmailReady || $credential->hasValidBird(),
|
||||||
|
|||||||
@@ -118,4 +118,65 @@ class NurseStationService
|
|||||||
'selected_unit' => $selected,
|
'selected_unit' => $selected,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Units the nurse is on duty for today (roster) or has an active home placement.
|
||||||
|
*
|
||||||
|
* @return Collection<int, CareUnit>
|
||||||
|
*/
|
||||||
|
public function dutyUnits(Organization $organization, Member $member, string $ownerRef): Collection
|
||||||
|
{
|
||||||
|
$dutyToday = RosterEntry::owned($ownerRef)
|
||||||
|
->where('organization_id', $organization->id)
|
||||||
|
->where('member_id', $member->id)
|
||||||
|
->whereDate('duty_date', now()->toDateString())
|
||||||
|
->where('status', '!=', RosterEntry::STATUS_CANCELLED)
|
||||||
|
->orderBy('shift_id')
|
||||||
|
->get(['care_unit_id']);
|
||||||
|
|
||||||
|
$placements = StaffAssignment::owned($ownerRef)
|
||||||
|
->where('organization_id', $organization->id)
|
||||||
|
->where('member_id', $member->id)
|
||||||
|
->where('status', 'active')
|
||||||
|
->unitPlacements()
|
||||||
|
->effectiveOn(now())
|
||||||
|
->whereNotNull('care_unit_id')
|
||||||
|
->get(['care_unit_id']);
|
||||||
|
|
||||||
|
$unitIds = $dutyToday->pluck('care_unit_id')
|
||||||
|
->merge($placements->pluck('care_unit_id'))
|
||||||
|
->filter()
|
||||||
|
->map(fn ($id) => (int) $id)
|
||||||
|
->unique()
|
||||||
|
->values();
|
||||||
|
|
||||||
|
if ($unitIds->isEmpty()) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
$units = CareUnit::owned($ownerRef)
|
||||||
|
->where('organization_id', $organization->id)
|
||||||
|
->whereIn('id', $unitIds->all())
|
||||||
|
->where('is_active', true)
|
||||||
|
->with('department.branch')
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
// Preserve roster order, then placement-only units.
|
||||||
|
$ordered = collect();
|
||||||
|
foreach ($dutyToday as $entry) {
|
||||||
|
$id = (int) $entry->care_unit_id;
|
||||||
|
if ($units->has($id) && ! $ordered->has($id)) {
|
||||||
|
$ordered->put($id, $units->get($id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($placements as $assignment) {
|
||||||
|
$id = (int) $assignment->care_unit_id;
|
||||||
|
if ($units->has($id) && ! $ordered->has($id)) {
|
||||||
|
$ordered->put($id, $units->get($id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ordered->values();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,9 +107,12 @@
|
|||||||
@if (($isNurseStation ?? false) && ! ($canConsult ?? false))
|
@if (($isNurseStation ?? false) && ! ($canConsult ?? false))
|
||||||
@if ($activePlacedVisit ?? null)
|
@if ($activePlacedVisit ?? null)
|
||||||
<div class="rounded-xl border border-teal-100 bg-teal-50/60 px-3 py-3">
|
<div class="rounded-xl border border-teal-100 bg-teal-50/60 px-3 py-3">
|
||||||
<p class="text-xs font-semibold uppercase tracking-wide text-teal-800">On unit now</p>
|
<p class="text-xs font-semibold uppercase tracking-wide text-teal-800">Patient on unit</p>
|
||||||
<p class="mt-1 font-medium text-slate-900">
|
<p class="mt-1 font-medium text-slate-900">
|
||||||
{{ $activePlacedVisit->careUnit?->name ?? 'Care unit' }}
|
{{ $activePlacedVisit->careUnit?->name ?? 'Care unit' }}
|
||||||
|
@if ($activePlacedVisit->careUnit?->department?->branch)
|
||||||
|
· {{ $activePlacedVisit->careUnit->department->branch->name }}
|
||||||
|
@endif
|
||||||
@if ($activePlacedVisit->bed)
|
@if ($activePlacedVisit->bed)
|
||||||
· Bed {{ $activePlacedVisit->bed->label }}
|
· Bed {{ $activePlacedVisit->bed->label }}
|
||||||
@endif
|
@endif
|
||||||
@@ -130,14 +133,34 @@
|
|||||||
@if ($canViewMar ?? false)
|
@if ($canViewMar ?? false)
|
||||||
<a href="{{ route('care.visits.mar', $activePlacedVisit) }}" class="text-sm font-medium text-teal-700 hover:text-teal-900">MAR chart</a>
|
<a href="{{ route('care.visits.mar', $activePlacedVisit) }}" class="text-sm font-medium text-teal-700 hover:text-teal-900">MAR chart</a>
|
||||||
@endif
|
@endif
|
||||||
<a href="{{ route('care.care-units.assessments', $activePlacedVisit->careUnit) }}" class="text-sm font-medium text-teal-700 hover:text-teal-900">Vitals & assess</a>
|
@if ($activePlacedVisit->careUnit)
|
||||||
<a href="{{ route('care.care-units.notes', $activePlacedVisit->careUnit) }}" class="text-sm font-medium text-teal-700 hover:text-teal-900">Nursing notes</a>
|
<a href="{{ route('care.care-units.assessments', $activePlacedVisit->careUnit) }}" class="text-sm font-medium text-teal-700 hover:text-teal-900">Vitals & assess</a>
|
||||||
|
<a href="{{ route('care.care-units.notes', $activePlacedVisit->careUnit) }}" class="text-sm font-medium text-teal-700 hover:text-teal-900">Nursing notes</a>
|
||||||
|
@endif
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="rounded-xl border border-dashed border-slate-200 px-3 py-3 text-slate-500">
|
<div class="rounded-xl border border-dashed border-slate-200 px-3 py-3 text-slate-600">
|
||||||
Not placed on a care unit right now.
|
<p class="font-medium text-slate-800">This patient is not on a care unit right now.</p>
|
||||||
<a href="{{ route('care.nursing.station') }}" class="ml-1 font-medium text-indigo-600 hover:text-indigo-800">My care unit</a>
|
@if (($nurseDutyUnits ?? collect())->isNotEmpty())
|
||||||
|
<p class="mt-1 text-sm text-slate-500">
|
||||||
|
You are on duty at
|
||||||
|
{{ $nurseDutyUnits->pluck('name')->join(', ') }} —
|
||||||
|
this chart is about the patient’s placement, not yours.
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 flex flex-wrap gap-3">
|
||||||
|
@foreach ($nurseDutyUnits as $dutyUnit)
|
||||||
|
<a href="{{ route('care.nursing.station', ['unit' => $dutyUnit->id]) }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">
|
||||||
|
Open {{ $dutyUnit->name }}
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</p>
|
||||||
|
@else
|
||||||
|
<p class="mt-1 text-sm text-slate-500">
|
||||||
|
Ask a charge nurse to place them, or open
|
||||||
|
<a href="{{ route('care.nursing.station') }}" class="font-medium text-indigo-600 hover:text-indigo-800">My care unit</a>.
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|||||||
@@ -349,12 +349,68 @@ class CareMyShiftsAndNavPermissionsTest extends TestCase
|
|||||||
->get(route('care.patients.show', $patient))
|
->get(route('care.patients.show', $patient))
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee('Visit & nursing care', false)
|
->assertSee('Visit & nursing care', false)
|
||||||
->assertSee('On unit now')
|
->assertSee('Patient on unit')
|
||||||
|
->assertSee('Medical Ward')
|
||||||
->assertSee('Nursing assessments')
|
->assertSee('Nursing assessments')
|
||||||
->assertSee('My care unit')
|
|
||||||
->assertDontSee('Visit & clinical history', false)
|
->assertDontSee('Visit & clinical history', false)
|
||||||
->assertDontSee('Condition pathways')
|
->assertDontSee('Condition pathways')
|
||||||
->assertDontSee('Follow-up scores')
|
->assertDontSee('Follow-up scores')
|
||||||
->assertDontSee('Start assessment');
|
->assertDontSee('Start assessment')
|
||||||
|
->assertDontSee('This patient is not on a care unit');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_nurse_patient_chart_clarifies_unplaced_patient_vs_nurse_duty(): void
|
||||||
|
{
|
||||||
|
$user = User::create([
|
||||||
|
'public_id' => 'nurse-chart-unplaced',
|
||||||
|
'name' => 'Nurse Duty',
|
||||||
|
'email' => 'nurse-chart-unplaced@example.com',
|
||||||
|
]);
|
||||||
|
$member = Member::create([
|
||||||
|
'owner_ref' => $this->owner->public_id,
|
||||||
|
'organization_id' => $this->organization->id,
|
||||||
|
'user_ref' => $user->public_id,
|
||||||
|
'role' => 'nurse',
|
||||||
|
'branch_id' => $this->branch->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$roster = app(RosterService::class);
|
||||||
|
$day = $roster->ensureDefaultShifts($this->organization, $this->owner->public_id)->firstWhere('code', 'day');
|
||||||
|
$roster->assign(
|
||||||
|
$this->unit,
|
||||||
|
$day,
|
||||||
|
$member,
|
||||||
|
now()->toDateString(),
|
||||||
|
$this->owner->public_id,
|
||||||
|
$this->owner->public_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
$patient = \App\Models\Patient::create([
|
||||||
|
'uuid' => (string) \Illuminate\Support\Str::uuid(),
|
||||||
|
'owner_ref' => $this->owner->public_id,
|
||||||
|
'organization_id' => $this->organization->id,
|
||||||
|
'branch_id' => $this->branch->id,
|
||||||
|
'patient_number' => 'P-CHART-2',
|
||||||
|
'first_name' => 'Kofi',
|
||||||
|
'last_name' => 'Mensah',
|
||||||
|
]);
|
||||||
|
|
||||||
|
\App\Models\Visit::create([
|
||||||
|
'owner_ref' => $this->owner->public_id,
|
||||||
|
'organization_id' => $this->organization->id,
|
||||||
|
'branch_id' => $this->branch->id,
|
||||||
|
'patient_id' => $patient->id,
|
||||||
|
'status' => \App\Models\Visit::STATUS_IN_PROGRESS,
|
||||||
|
'checked_in_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('care.patients.show', $patient))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('This patient is not on a care unit right now.')
|
||||||
|
->assertSee('You are on duty at')
|
||||||
|
->assertSee('Medical Ward')
|
||||||
|
->assertSee('Open Medical Ward')
|
||||||
|
->assertDontSee('Not placed on a care unit right now.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user