From 5c394cec2e6e65a2ecb38f7b5875df52b15951ac Mon Sep 17 00:00:00 2001 From: isaacclad Date: Mon, 20 Jul 2026 16:36:35 +0000 Subject: [PATCH] Clarify patient vs nurse unit placement on nursing charts. 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 --- .../Controllers/Care/PatientController.php | 7 ++- app/Services/Care/NurseStationService.php | 61 ++++++++++++++++++ resources/views/care/patients/show.blade.php | 35 +++++++++-- .../CareMyShiftsAndNavPermissionsTest.php | 62 ++++++++++++++++++- 4 files changed, 155 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Care/PatientController.php b/app/Http/Controllers/Care/PatientController.php index cca75e6..ac7ae2d 100644 --- a/app/Http/Controllers/Care/PatientController.php +++ b/app/Http/Controllers/Care/PatientController.php @@ -214,6 +214,7 @@ class PatientController extends Controller $nursingAssessments = collect(); $activePlacedVisit = null; $latestVitals = null; + $nurseDutyUnits = collect(); if ($canViewAssessments) { $branchScope = app(OrganizationResolver::class)->branchScope($member); @@ -243,10 +244,13 @@ class PatientController extends Controller } if ($isNurseStation) { + $nurseDutyUnits = app(\App\Services\Care\NurseStationService::class) + ->dutyUnits($organization, $member, $this->ownerRef($request)); + $activePlacedVisit = $patient->visits() ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]) ->whereNotNull('care_unit_id') - ->with(['careUnit', 'bed']) + ->with(['careUnit.department.branch', 'bed']) ->orderByDesc('placed_at') ->first(); @@ -269,6 +273,7 @@ class PatientController extends Controller 'canViewPathways' => $canViewPathways, 'activePlacedVisit' => $activePlacedVisit, 'latestVitals' => $latestVitals, + 'nurseDutyUnits' => $nurseDutyUnits, 'nursingAssessments' => $nursingAssessments, 'messagingSmsReady' => $suiteSmsReady || $credential->hasValidSms(), 'messagingEmailReady' => $suiteEmailReady || $credential->hasValidBird(), diff --git a/app/Services/Care/NurseStationService.php b/app/Services/Care/NurseStationService.php index e812b49..185d2c4 100644 --- a/app/Services/Care/NurseStationService.php +++ b/app/Services/Care/NurseStationService.php @@ -118,4 +118,65 @@ class NurseStationService 'selected_unit' => $selected, ]; } + + /** + * Units the nurse is on duty for today (roster) or has an active home placement. + * + * @return Collection + */ + 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(); + } } diff --git a/resources/views/care/patients/show.blade.php b/resources/views/care/patients/show.blade.php index c4eac86..9bacad1 100644 --- a/resources/views/care/patients/show.blade.php +++ b/resources/views/care/patients/show.blade.php @@ -107,9 +107,12 @@ @if (($isNurseStation ?? false) && ! ($canConsult ?? false)) @if ($activePlacedVisit ?? null)
-

On unit now

+

Patient on unit

{{ $activePlacedVisit->careUnit?->name ?? 'Care unit' }} + @if ($activePlacedVisit->careUnit?->department?->branch) + · {{ $activePlacedVisit->careUnit->department->branch->name }} + @endif @if ($activePlacedVisit->bed) · Bed {{ $activePlacedVisit->bed->label }} @endif @@ -130,14 +133,34 @@ @if ($canViewMar ?? false) MAR chart @endif - Vitals & assess - Nursing notes + @if ($activePlacedVisit->careUnit) + Vitals & assess + Nursing notes + @endif

@else -
- Not placed on a care unit right now. - My care unit +
+

This patient is not on a care unit right now.

+ @if (($nurseDutyUnits ?? collect())->isNotEmpty()) +

+ You are on duty at + {{ $nurseDutyUnits->pluck('name')->join(', ') }} — + this chart is about the patient’s placement, not yours. +

+

+ @foreach ($nurseDutyUnits as $dutyUnit) + + Open {{ $dutyUnit->name }} + + @endforeach +

+ @else +

+ Ask a charge nurse to place them, or open + My care unit. +

+ @endif
@endif diff --git a/tests/Feature/CareMyShiftsAndNavPermissionsTest.php b/tests/Feature/CareMyShiftsAndNavPermissionsTest.php index 84e5f8d..24f76b4 100644 --- a/tests/Feature/CareMyShiftsAndNavPermissionsTest.php +++ b/tests/Feature/CareMyShiftsAndNavPermissionsTest.php @@ -349,12 +349,68 @@ class CareMyShiftsAndNavPermissionsTest extends TestCase ->get(route('care.patients.show', $patient)) ->assertOk() ->assertSee('Visit & nursing care', false) - ->assertSee('On unit now') + ->assertSee('Patient on unit') + ->assertSee('Medical Ward') ->assertSee('Nursing assessments') - ->assertSee('My care unit') ->assertDontSee('Visit & clinical history', false) ->assertDontSee('Condition pathways') ->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.'); } }