From f1bb4323242cd7e7fa322b1be1c2dcdbc37ae739 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Mon, 20 Jul 2026 17:05:13 +0000 Subject: [PATCH] Include checked-in clinic visits on outpatient nursing boards. 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 --- .../Controllers/Care/CareUnitController.php | 12 +- .../Care/NursingAssessmentController.php | 11 +- .../Care/NursingDocumentationController.php | 16 +- app/Services/Care/CareUnitCensusService.php | 144 +++++++++ app/Services/Care/MarService.php | 7 +- app/Services/Care/NursePerformanceService.php | 5 +- app/Services/Care/NurseStationService.php | 12 +- .../Care/NursingAssessmentService.php | 7 +- .../Care/NursingDocumentationService.php | 7 +- .../care/admin/care-units/show.blade.php | 34 +- .../care/nursing/assessments-board.blade.php | 8 +- .../views/care/nursing/station.blade.php | 13 +- tests/Feature/CareOutpatientCensusTest.php | 306 ++++++++++++++++++ 13 files changed, 522 insertions(+), 60 deletions(-) create mode 100644 app/Services/Care/CareUnitCensusService.php create mode 100644 tests/Feature/CareOutpatientCensusTest.php diff --git a/app/Http/Controllers/Care/CareUnitController.php b/app/Http/Controllers/Care/CareUnitController.php index 12a7302..7ad7038 100644 --- a/app/Http/Controllers/Care/CareUnitController.php +++ b/app/Http/Controllers/Care/CareUnitController.php @@ -9,6 +9,7 @@ use App\Models\Department; use App\Models\Visit; use App\Services\Care\AuditLogger; use App\Services\Care\CarePermissions; +use App\Services\Care\CareUnitCensusService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Validation\Rule; @@ -110,13 +111,8 @@ class CareUnitController extends Controller $careUnit->load(['department.branch', 'beds']); - $placedVisits = Visit::owned($this->ownerRef($request)) - ->where('organization_id', $organization->id) - ->where('care_unit_id', $careUnit->id) - ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]) - ->with(['patient', 'bed']) - ->orderBy('placed_at') - ->get(); + $census = app(CareUnitCensusService::class); + $placedVisits = $census->visitsForUnit($careUnit, $this->ownerRef($request)); $placeableVisits = Visit::owned($this->ownerRef($request)) ->where('organization_id', $organization->id) @@ -126,6 +122,7 @@ class CareUnitController extends Controller $q->whereNull('care_unit_id') ->orWhere('care_unit_id', '!=', $careUnit->id); }) + ->whereNotIn('id', $placedVisits->pluck('id')->all() ?: [0]) ->with(['patient', 'careUnit']) ->orderByDesc('checked_in_at') ->limit(50) @@ -146,6 +143,7 @@ class CareUnitController extends Controller 'placeableVisits' => $placeableVisits, 'availableBeds' => $availableBeds, 'canPlace' => $canPlace, + 'isClinicUnit' => $census->isClinicUnit($careUnit), ]); } diff --git a/app/Http/Controllers/Care/NursingAssessmentController.php b/app/Http/Controllers/Care/NursingAssessmentController.php index 74f20c0..fab8923 100644 --- a/app/Http/Controllers/Care/NursingAssessmentController.php +++ b/app/Http/Controllers/Care/NursingAssessmentController.php @@ -9,6 +9,7 @@ use App\Models\Visit; use App\Services\Care\AssessmentService; use App\Services\Care\CareFeatures; use App\Services\Care\CarePermissions; +use App\Services\Care\CareUnitCensusService; use App\Services\Care\NursingAssessmentService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -50,13 +51,16 @@ class NursingAssessmentController extends Controller Visit $visit, AssessmentService $assessments, CareFeatures $features, + CareUnitCensusService $census, ): RedirectResponse { $this->authorizeAbility($request, 'nursing.assessments.manage'); $this->authorizeOwner($request, $careUnit); $this->authorizeOwner($request, $visit); - abort_unless((int) $visit->care_unit_id === (int) $careUnit->id, 404); + abort_unless((int) $careUnit->organization_id === (int) $this->organization($request)->id, 404); abort_unless($features->enabled($this->organization($request), CareFeatures::ASSESSMENTS_ENGINE), 404); + $visit = $census->ensurePlaced($visit, $careUnit, $this->ownerRef($request), $this->actorRef($request)); + $features->ensureAssessmentCatalog(); $validated = $request->validate([ @@ -84,11 +88,14 @@ class NursingAssessmentController extends Controller CareUnit $careUnit, Visit $visit, NursingAssessmentService $nursing, + CareUnitCensusService $census, ): RedirectResponse { $this->authorizeAbility($request, 'vitals.manage'); $this->authorizeOwner($request, $careUnit); $this->authorizeOwner($request, $visit); - abort_unless((int) $visit->care_unit_id === (int) $careUnit->id, 404); + abort_unless((int) $careUnit->organization_id === (int) $this->organization($request)->id, 404); + + $visit = $census->ensurePlaced($visit, $careUnit, $this->ownerRef($request), $this->actorRef($request)); $validated = $request->validate([ 'bp_systolic' => ['nullable', 'integer', 'min:40', 'max:300'], diff --git a/app/Http/Controllers/Care/NursingDocumentationController.php b/app/Http/Controllers/Care/NursingDocumentationController.php index b05f545..067046e 100644 --- a/app/Http/Controllers/Care/NursingDocumentationController.php +++ b/app/Http/Controllers/Care/NursingDocumentationController.php @@ -8,6 +8,7 @@ use App\Models\CareUnit; use App\Models\Visit; use App\Models\WardHandover; use App\Services\Care\CarePermissions; +use App\Services\Care\CareUnitCensusService; use App\Services\Care\NursingDocumentationService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -18,30 +19,23 @@ class NursingDocumentationController extends Controller { use ScopesToAccount; - public function unitNotes(Request $request, CareUnit $careUnit, NursingDocumentationService $docs): View + public function unitNotes(Request $request, CareUnit $careUnit, NursingDocumentationService $docs, CareUnitCensusService $census): View { $this->authorizeAbility($request, 'nursing.notes.view'); $this->authorizeOwner($request, $careUnit); abort_unless((int) $careUnit->organization_id === (int) $this->organization($request)->id, 404); - $placedVisits = Visit::owned($this->ownerRef($request)) - ->where('care_unit_id', $careUnit->id) - ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]) - ->with(['patient', 'bed']) - ->orderBy('placed_at') - ->get(); - return view('care.nursing.notes', [ 'unit' => $careUnit->load('department.branch'), 'notes' => $docs->notesForUnit($careUnit, $this->ownerRef($request)), - 'placedVisits' => $placedVisits, + 'placedVisits' => $census->visitsForUnit($careUnit, $this->ownerRef($request)), 'canWrite' => app(CarePermissions::class)->can($this->member($request), 'nursing.notes.manage'), 'noteTypes' => config('care.nursing_note_types'), 'shiftCodes' => config('care.staff_shift_codes'), ]); } - public function storeNote(Request $request, CareUnit $careUnit, NursingDocumentationService $docs): RedirectResponse + public function storeNote(Request $request, CareUnit $careUnit, NursingDocumentationService $docs, CareUnitCensusService $census): RedirectResponse { $this->authorizeAbility($request, 'nursing.notes.manage'); $this->authorizeOwner($request, $careUnit); @@ -58,6 +52,8 @@ class NursingDocumentationController extends Controller ->findOrFail($validated['visit_id']); $this->authorizeBranch($request, (int) $visit->branch_id); + $visit = $census->ensurePlaced($visit, $careUnit, $this->ownerRef($request), $this->actorRef($request)); + $docs->addNote( $visit, $this->ownerRef($request), diff --git a/app/Services/Care/CareUnitCensusService.php b/app/Services/Care/CareUnitCensusService.php new file mode 100644 index 0000000..896c34c --- /dev/null +++ b/app/Services/Care/CareUnitCensusService.php @@ -0,0 +1,144 @@ + */ + public const CLINIC_KINDS = ['outpatient', 'service']; + + public function isClinicUnit(CareUnit $unit): bool + { + return in_array($unit->kind, self::CLINIC_KINDS, true); + } + + /** + * Active visits nurses should see for this unit. + * + * @return Collection + */ + public function visitsForUnit(CareUnit $unit, string $ownerRef): Collection + { + $unit->loadMissing('department'); + + return $this->queryForUnit($unit, $ownerRef) + ->with(['patient', 'bed', 'appointment']) + ->orderByRaw('care_unit_id is null') // placed first + ->orderBy('placed_at') + ->orderBy('checked_in_at') + ->get(); + } + + public function visitBelongsToUnit(Visit $visit, CareUnit $unit): bool + { + if ((int) $visit->organization_id !== (int) $unit->organization_id) { + return false; + } + + if (! in_array($visit->status, [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS], true)) { + return false; + } + + if ((int) $visit->care_unit_id === (int) $unit->id) { + return true; + } + + if (! $this->isClinicUnit($unit) || $visit->care_unit_id !== null) { + return false; + } + + $unit->loadMissing('department'); + $branchId = $unit->department?->branch_id; + if (! $branchId || (int) $visit->branch_id !== (int) $branchId) { + return false; + } + + $departmentId = $unit->department_id; + if (! $departmentId) { + return false; + } + + $appointment = $visit->relationLoaded('appointment') + ? $visit->appointment + : $visit->appointment()->first(); + + return $appointment !== null && (int) $appointment->department_id === (int) $departmentId; + } + + /** + * Soft-place an eligible clinic visit onto the unit when a nurse acts on it. + */ + public function ensurePlaced( + Visit $visit, + CareUnit $unit, + string $ownerRef, + ?string $actorRef = null, + ): Visit { + $visit->refresh(); + + if ((int) $visit->care_unit_id === (int) $unit->id) { + return $visit; + } + + if (! $this->visitBelongsToUnit($visit, $unit)) { + abort(404); + } + + if ($visit->care_unit_id !== null) { + abort(404); + } + + app(VisitPlacementService::class)->place( + $visit, + $unit, + null, + $ownerRef, + $actorRef, + 'Auto-placed from outpatient nursing board', + ); + + return $visit->fresh(['patient', 'bed', 'careUnit', 'appointment']); + } + + /** + * @return Builder + */ + protected function queryForUnit(CareUnit $unit, string $ownerRef): Builder + { + $query = Visit::owned($ownerRef) + ->where('organization_id', $unit->organization_id) + ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]); + + if (! $this->isClinicUnit($unit)) { + return $query->where('care_unit_id', $unit->id); + } + + $unit->loadMissing('department'); + $branchId = $unit->department?->branch_id; + $departmentId = $unit->department_id; + + return $query->where(function (Builder $q) use ($unit, $branchId, $departmentId) { + $q->where('care_unit_id', $unit->id); + + if ($branchId && $departmentId) { + $q->orWhere(function (Builder $clinic) use ($branchId, $departmentId) { + $clinic->whereNull('care_unit_id') + ->where('branch_id', $branchId) + ->whereHas('appointment', fn (Builder $a) => $a->where('department_id', $departmentId)); + }); + } + }); + } +} diff --git a/app/Services/Care/MarService.php b/app/Services/Care/MarService.php index baa1905..ab5cda0 100644 --- a/app/Services/Care/MarService.php +++ b/app/Services/Care/MarService.php @@ -101,12 +101,7 @@ class MarService { $day = Carbon::parse($day ?? now())->startOfDay(); - $visits = Visit::owned($ownerRef) - ->where('organization_id', $unit->organization_id) - ->where('care_unit_id', $unit->id) - ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]) - ->with(['patient', 'bed']) - ->get(); + $visits = app(CareUnitCensusService::class)->visitsForUnit($unit, $ownerRef); $rows = []; foreach ($visits as $visit) { diff --git a/app/Services/Care/NursePerformanceService.php b/app/Services/Care/NursePerformanceService.php index 97b32d2..d2af585 100644 --- a/app/Services/Care/NursePerformanceService.php +++ b/app/Services/Care/NursePerformanceService.php @@ -100,9 +100,8 @@ class NursePerformanceService return [ 'from' => $from, 'to' => $to, - 'placed_patients' => Visit::owned($ownerRef) - ->where('care_unit_id', $unit->id) - ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]) + 'placed_patients' => app(CareUnitCensusService::class) + ->visitsForUnit($unit, $ownerRef) ->count(), 'mar' => [ 'total' => $marTotal, diff --git a/app/Services/Care/NurseStationService.php b/app/Services/Care/NurseStationService.php index 185d2c4..06f7b8a 100644 --- a/app/Services/Care/NurseStationService.php +++ b/app/Services/Care/NurseStationService.php @@ -15,6 +15,10 @@ use Illuminate\Support\Collection; */ class NurseStationService { + public function __construct( + protected CareUnitCensusService $census, + ) {} + /** * @return array{ * units: Collection, @@ -89,13 +93,7 @@ class NurseStationService $patientsByUnit = []; foreach ($ordered as $id => $unit) { - $patientsByUnit[$id] = Visit::owned($ownerRef) - ->where('organization_id', $organization->id) - ->where('care_unit_id', $id) - ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]) - ->with(['patient', 'bed']) - ->orderBy('placed_at') - ->get(); + $patientsByUnit[$id] = $this->census->visitsForUnit($unit, $ownerRef); } $dutyByUnit = []; diff --git a/app/Services/Care/NursingAssessmentService.php b/app/Services/Care/NursingAssessmentService.php index bbdb511..36091ca 100644 --- a/app/Services/Care/NursingAssessmentService.php +++ b/app/Services/Care/NursingAssessmentService.php @@ -48,12 +48,7 @@ class NursingAssessmentService public function unitBoard(CareUnit $unit, string $ownerRef): array { $templates = $this->nursingTemplates(); - $visits = Visit::owned($ownerRef) - ->where('care_unit_id', $unit->id) - ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]) - ->with(['patient', 'bed']) - ->orderBy('placed_at') - ->get(); + $visits = app(CareUnitCensusService::class)->visitsForUnit($unit, $ownerRef); $rows = []; foreach ($visits as $visit) { diff --git a/app/Services/Care/NursingDocumentationService.php b/app/Services/Care/NursingDocumentationService.php index 7ac9b5b..c4b0d02 100644 --- a/app/Services/Care/NursingDocumentationService.php +++ b/app/Services/Care/NursingDocumentationService.php @@ -148,12 +148,7 @@ class NursingDocumentationService */ public function defaultPatientSummaries(CareUnit $unit, string $ownerRef): array { - $visits = Visit::owned($ownerRef) - ->where('care_unit_id', $unit->id) - ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]) - ->with(['patient', 'bed']) - ->orderBy('placed_at') - ->get(); + $visits = app(CareUnitCensusService::class)->visitsForUnit($unit, $ownerRef); return $visits->map(fn (Visit $visit) => [ 'visit_id' => $visit->id, diff --git a/resources/views/care/admin/care-units/show.blade.php b/resources/views/care/admin/care-units/show.blade.php index 50d8997..b8d88df 100644 --- a/resources/views/care/admin/care-units/show.blade.php +++ b/resources/views/care/admin/care-units/show.blade.php @@ -69,9 +69,18 @@

Patients on this unit

-

Active visit placements — open MAR, notes, and handovers from the actions above.

+

+ @if ($isClinicUnit ?? false) + Checked-in clinic visits for this department appear here automatically. Nursing actions attach them to the unit. + @else + Active visit placements — open MAR, notes, and handovers from the actions above. + @endif +

-

{{ $placedVisits->count() }} placed

+

+ {{ $placedVisits->count() }} + {{ ($isClinicUnit ?? false) ? 'in clinic' : 'placed' }} +

@@ -80,7 +89,7 @@ Patient Bed - Placed + {{ ($isClinicUnit ?? false) ? 'Checked in' : 'Placed' }} @@ -92,14 +101,19 @@ @if ($visit->patient?->patient_number) {{ $visit->patient->patient_number }} @endif + @if (($isClinicUnit ?? false) && $visit->care_unit_id === null) + Clinic + @endif {{ $visit->bed?->label ?? '—' }} - {{ $visit->placed_at?->format('d M Y H:i') ?? '—' }} + + {{ ($visit->placed_at ?? $visit->checked_in_at)?->format('d M Y H:i') ?? '—' }} + @if ($canMar ?? false) MAR @endif - @if ($canPlace) + @if ($canPlace && $visit->care_unit_id)
@csrf @method('DELETE') @@ -108,7 +122,15 @@ @empty - No patients placed on this unit. + + + @if ($isClinicUnit ?? false) + No checked-in clinic visits for this department right now. + @else + No patients placed on this unit. + @endif + + @endforelse diff --git a/resources/views/care/nursing/assessments-board.blade.php b/resources/views/care/nursing/assessments-board.blade.php index a2a8d22..70a3fc7 100644 --- a/resources/views/care/nursing/assessments-board.blade.php +++ b/resources/views/care/nursing/assessments-board.blade.php @@ -8,7 +8,13 @@ Nursing assessments

Ward assessment board

-

NEWS2, Braden, Morse, pain, and visit vitals for placed patients.

+

+ @if (in_array($unit->kind, ['outpatient', 'service'], true)) + NEWS2, Braden, Morse, pain, and visit vitals for checked-in clinic patients. + @else + NEWS2, Braden, Morse, pain, and visit vitals for placed patients. + @endif +

Performance diff --git a/resources/views/care/nursing/station.blade.php b/resources/views/care/nursing/station.blade.php index a0c41c0..c4c1dfd 100644 --- a/resources/views/care/nursing/station.blade.php +++ b/resources/views/care/nursing/station.blade.php @@ -103,7 +103,7 @@ Patient Bed - Placed + {{ in_array($unit->kind, ['outpatient', 'service'], true) ? 'In clinic' : 'Placed' }} Chart @@ -118,7 +118,7 @@ {{ $visit->bed?->label ?? '—' }} - {{ $visit->placed_at?->format('d M H:i') ?? '—' }} + {{ ($visit->placed_at ?? $visit->checked_in_at)?->format('d M H:i') ?? '—' }} @if ($canMar) @@ -135,11 +135,12 @@ @empty - No patients placed on this unit yet. - @if ($canServicesHub) - Placements are managed from the care unit board. + @if (in_array($unit->kind, ['outpatient', 'service'], true)) + No checked-in clinic patients for this department right now. + @elseif ($canServicesHub) + No patients placed on this unit yet. Placements are managed from the care unit board. @else - Ask a charge nurse to place patients on this unit. + No patients placed on this unit yet. Ask a charge nurse to place patients on this unit. @endif diff --git a/tests/Feature/CareOutpatientCensusTest.php b/tests/Feature/CareOutpatientCensusTest.php new file mode 100644 index 0000000..579575c --- /dev/null +++ b/tests/Feature/CareOutpatientCensusTest.php @@ -0,0 +1,306 @@ +withoutMiddleware(EnsurePlatformSession::class); + + $this->owner = User::create([ + 'public_id' => 'opd-census-owner', + 'name' => 'Owner', + 'email' => 'opd-census-owner@example.com', + ]); + + $this->organization = Organization::create([ + 'owner_ref' => $this->owner->public_id, + 'name' => 'OPD Clinic', + 'slug' => 'opd-clinic', + 'settings' => [ + 'onboarded' => true, + 'facility_type' => 'hospital', + 'plan' => 'pro', + 'plan_expires_at' => now()->addMonth()->toIso8601String(), + 'rollout' => [CareFeatures::ASSESSMENTS_ENGINE => true], + ], + ]); + + Member::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'user_ref' => $this->owner->public_id, + 'role' => 'hospital_admin', + ]); + + $this->branch = Branch::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'name' => 'Tema Harbour Clinic', + 'is_active' => true, + ]); + + $this->department = Department::create([ + 'owner_ref' => $this->owner->public_id, + 'branch_id' => $this->branch->id, + 'name' => 'Outpatient', + 'type' => 'outpatient', + 'is_active' => true, + ]); + + $this->opd = CareUnit::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'department_id' => $this->department->id, + 'name' => 'OPD', + 'kind' => 'outpatient', + 'code' => 'OP', + 'is_active' => true, + ]); + } + + public function test_outpatient_board_includes_checked_in_department_visits_without_manual_place(): void + { + $patient = Patient::create([ + 'uuid' => (string) Str::uuid(), + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_number' => 'P-OPD-1', + 'first_name' => 'Yaw', + 'last_name' => 'Adjei', + ]); + + $visit = Visit::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $patient->id, + 'status' => Visit::STATUS_IN_PROGRESS, + 'checked_in_at' => now(), + ]); + + Appointment::create([ + 'uuid' => (string) Str::uuid(), + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'department_id' => $this->department->id, + 'patient_id' => $patient->id, + 'visit_id' => $visit->id, + 'type' => Appointment::TYPE_WALK_IN, + 'status' => Appointment::STATUS_WAITING, + 'checked_in_at' => now(), + ]); + + $otherDept = Department::create([ + 'owner_ref' => $this->owner->public_id, + 'branch_id' => $this->branch->id, + 'name' => 'Dental', + 'type' => 'dentistry', + 'is_active' => true, + ]); + $otherPatient = Patient::create([ + 'uuid' => (string) Str::uuid(), + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_number' => 'P-OPD-2', + 'first_name' => 'Ama', + 'last_name' => 'Other', + ]); + $otherVisit = Visit::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $otherPatient->id, + 'status' => Visit::STATUS_OPEN, + 'checked_in_at' => now(), + ]); + Appointment::create([ + 'uuid' => (string) Str::uuid(), + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'department_id' => $otherDept->id, + 'patient_id' => $otherPatient->id, + 'visit_id' => $otherVisit->id, + 'type' => Appointment::TYPE_WALK_IN, + 'status' => Appointment::STATUS_WAITING, + 'checked_in_at' => now(), + ]); + + $census = app(CareUnitCensusService::class); + $visits = $census->visitsForUnit($this->opd, $this->owner->public_id); + + $this->assertTrue($census->isClinicUnit($this->opd)); + $this->assertCount(1, $visits); + $this->assertTrue($visits->contains('id', $visit->id)); + $this->assertFalse($visits->contains('id', $otherVisit->id)); + + $user = User::create([ + 'public_id' => 'opd-nurse', + 'name' => 'OPD Nurse', + 'email' => 'opd-nurse@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->opd, + $day, + $member, + now()->toDateString(), + $this->owner->public_id, + $this->owner->public_id, + ); + + $this->actingAs($user) + ->get(route('care.nursing.station', ['unit' => $this->opd->id])) + ->assertOk() + ->assertSee('Yaw Adjei') + ->assertSee('In clinic') + ->assertDontSee('Ama Other'); + + $this->actingAs($user) + ->get(route('care.care-units.show', $this->opd)) + ->assertOk() + ->assertSee('Yaw Adjei') + ->assertSee('in clinic') + ->assertSee('Clinic'); + } + + public function test_nursing_action_auto_places_clinic_visit_onto_opd(): void + { + $this->seed(AssessmentTemplateSeeder::class); + + $patient = Patient::create([ + 'uuid' => (string) Str::uuid(), + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_number' => 'P-OPD-3', + 'first_name' => 'Kofi', + 'last_name' => 'Mensah', + ]); + + $visit = Visit::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $patient->id, + 'status' => Visit::STATUS_OPEN, + 'checked_in_at' => now(), + ]); + + Appointment::create([ + 'uuid' => (string) Str::uuid(), + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'department_id' => $this->department->id, + 'patient_id' => $patient->id, + 'visit_id' => $visit->id, + 'type' => Appointment::TYPE_WALK_IN, + 'status' => Appointment::STATUS_CHECKED_IN, + 'checked_in_at' => now(), + ]); + + $this->actingAs($this->owner) + ->post(route('care.care-units.vitals.store', [$this->opd, $visit]), [ + 'pulse' => 78, + 'spo2' => 98, + ]) + ->assertRedirect(route('care.care-units.assessments', $this->opd)); + + $visit->refresh(); + $this->assertSame($this->opd->id, (int) $visit->care_unit_id); + $this->assertNotNull($visit->placed_at); + } + + public function test_inpatient_unit_still_requires_placement(): void + { + $ward = CareUnit::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'department_id' => $this->department->id, + 'name' => 'Medical Ward', + 'kind' => 'inpatient', + 'is_active' => true, + ]); + + $patient = Patient::create([ + 'uuid' => (string) Str::uuid(), + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_number' => 'P-WARD-X', + 'first_name' => 'Efua', + 'last_name' => 'Boateng', + ]); + + $visit = Visit::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $patient->id, + 'status' => Visit::STATUS_OPEN, + 'checked_in_at' => now(), + ]); + + Appointment::create([ + 'uuid' => (string) Str::uuid(), + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'department_id' => $this->department->id, + 'patient_id' => $patient->id, + 'visit_id' => $visit->id, + 'type' => Appointment::TYPE_WALK_IN, + 'status' => Appointment::STATUS_WAITING, + 'checked_in_at' => now(), + ]); + + $visits = app(CareUnitCensusService::class)->visitsForUnit($ward, $this->owner->public_id); + $this->assertCount(0, $visits); + } +}