Add My ward nurse station for bedside clinical work.
Deploy Ladill Care / deploy (push) Successful in 39s
Deploy Ladill Care / deploy (push) Successful in 39s
Floor nurses keep Nursing Services admin-only, but now get a My ward home keyed off today's roster or unit placement with patient lists and direct links to MAR, vitals/assessments, notes, and handovers. Care unit show and board crumbs open for station users so clinical pages are reachable without department admin rights. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -155,7 +155,8 @@ class CareUnitController extends Controller
|
||||
$member = $this->member($request);
|
||||
abort_unless(
|
||||
$permissions->can($member, 'admin.departments.view')
|
||||
|| $permissions->can($member, 'inpatient.placement.view'),
|
||||
|| $permissions->can($member, 'inpatient.placement.view')
|
||||
|| $permissions->can($member, 'nursing.station.view'),
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Care;
|
||||
|
||||
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Care\CarePermissions;
|
||||
use App\Services\Care\NurseStationService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class NurseStationController extends Controller
|
||||
{
|
||||
use ScopesToAccount;
|
||||
|
||||
public function index(Request $request, NurseStationService $station): View
|
||||
{
|
||||
$this->authorizeAbility($request, 'nursing.station.view');
|
||||
$member = $this->member($request);
|
||||
abort_unless($member, 403);
|
||||
|
||||
$organization = $this->organization($request);
|
||||
$unitId = $request->filled('unit') ? (int) $request->input('unit') : null;
|
||||
$board = $station->board($organization, $member, $this->ownerRef($request), $unitId);
|
||||
|
||||
$permissions = app(CarePermissions::class);
|
||||
|
||||
return view('care.nursing.station', [
|
||||
'units' => $board['units'],
|
||||
'patientsByUnit' => $board['patients_by_unit'],
|
||||
'dutyByUnit' => $board['duty_by_unit'],
|
||||
'placementByUnit' => $board['placement_by_unit'],
|
||||
'selectedUnit' => $board['selected_unit'],
|
||||
'canMar' => $permissions->can($member, 'mar.view'),
|
||||
'canAdminister' => $permissions->can($member, 'mar.administer'),
|
||||
'canNotes' => $permissions->can($member, 'nursing.notes.view'),
|
||||
'canHandover' => $permissions->can($member, 'nursing.handover.view'),
|
||||
'canAssess' => $permissions->can($member, 'nursing.assessments.view'),
|
||||
'canVitals' => $permissions->can($member, 'vitals.manage'),
|
||||
'canServicesHub' => $permissions->can($member, 'nursing.services.view'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -576,8 +576,11 @@ class CarePermissions
|
||||
}
|
||||
}
|
||||
|
||||
return $this->withNursingOpsAbilities(
|
||||
$this->withDerivedAbilities(array_keys($merged)),
|
||||
return $this->withNursingStationAbility(
|
||||
$this->withNursingOpsAbilities(
|
||||
$this->withDerivedAbilities(array_keys($merged)),
|
||||
$role,
|
||||
),
|
||||
$role,
|
||||
);
|
||||
}
|
||||
@@ -653,6 +656,42 @@ class CarePermissions
|
||||
return array_values(array_unique($abilities));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bedside nurse station (My ward) — floor + ward nursing roles with clinical vitals.
|
||||
* Not granted to GPs / ambulance solely because they also chart vitals.
|
||||
*
|
||||
* @param list<string> $abilities
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function withNursingStationAbility(array $abilities, ?string $role): array
|
||||
{
|
||||
if (in_array('*', $abilities, true)) {
|
||||
return $abilities;
|
||||
}
|
||||
|
||||
$nursingRoles = [
|
||||
'nurse',
|
||||
'ed_nurse',
|
||||
'theatre_nurse',
|
||||
'labour_ward_nurse',
|
||||
'fertility_nurse',
|
||||
'dialysis_nurse',
|
||||
'midwife',
|
||||
];
|
||||
|
||||
if ($role === null || ! in_array($role, $nursingRoles, true)) {
|
||||
return $abilities;
|
||||
}
|
||||
|
||||
if (! in_array('vitals.manage', $abilities, true)) {
|
||||
return $abilities;
|
||||
}
|
||||
|
||||
$abilities[] = 'nursing.station.view';
|
||||
|
||||
return array_values(array_unique($abilities));
|
||||
}
|
||||
|
||||
public function isAdmin(?Member $member): bool
|
||||
{
|
||||
return $member !== null && in_array($member->role, ['super_admin', 'hospital_admin'], true);
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Care;
|
||||
|
||||
use App\Models\CareUnit;
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use App\Models\RosterEntry;
|
||||
use App\Models\StaffAssignment;
|
||||
use App\Models\Visit;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Bedside nurse workplace: units from today's duty + home placements, with placed patients.
|
||||
*/
|
||||
class NurseStationService
|
||||
{
|
||||
/**
|
||||
* @return array{
|
||||
* units: Collection<int, CareUnit>,
|
||||
* patients_by_unit: array<int, Collection<int, Visit>>,
|
||||
* duty_by_unit: array<int, Collection<int, RosterEntry>>,
|
||||
* placement_by_unit: array<int, StaffAssignment>,
|
||||
* selected_unit: ?CareUnit,
|
||||
* }
|
||||
*/
|
||||
public function board(
|
||||
Organization $organization,
|
||||
Member $member,
|
||||
string $ownerRef,
|
||||
?int $unitId = null,
|
||||
): array {
|
||||
$dutyToday = RosterEntry::owned($ownerRef)
|
||||
->where('organization_id', $organization->id)
|
||||
->where('member_id', $member->id)
|
||||
->whereDate('duty_date', now()->toDateString())
|
||||
->where('status', '!=', RosterEntry::STATUS_CANCELLED)
|
||||
->with(['shift', 'careUnit.department.branch'])
|
||||
->orderBy('shift_id')
|
||||
->get();
|
||||
|
||||
$placements = StaffAssignment::owned($ownerRef)
|
||||
->where('organization_id', $organization->id)
|
||||
->where('member_id', $member->id)
|
||||
->where('status', 'active')
|
||||
->unitPlacements()
|
||||
->effectiveOn(now())
|
||||
->whereNotNull('care_unit_id')
|
||||
->with(['careUnit.department.branch'])
|
||||
->get();
|
||||
|
||||
$unitIds = $dutyToday->pluck('care_unit_id')
|
||||
->merge($placements->pluck('care_unit_id'))
|
||||
->filter()
|
||||
->map(fn ($id) => (int) $id)
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
$units = CareUnit::owned($ownerRef)
|
||||
->where('organization_id', $organization->id)
|
||||
->whereIn('id', $unitIds->all() ?: [0])
|
||||
->where('is_active', true)
|
||||
->with('department.branch')
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->keyBy('id');
|
||||
|
||||
// Preserve duty-first ordering, then remaining placements.
|
||||
$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));
|
||||
}
|
||||
}
|
||||
|
||||
$selected = null;
|
||||
if ($unitId && $ordered->has($unitId)) {
|
||||
$selected = $ordered->get($unitId);
|
||||
} elseif ($ordered->isNotEmpty()) {
|
||||
$selected = $ordered->first();
|
||||
}
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
$dutyByUnit = [];
|
||||
foreach ($dutyToday as $entry) {
|
||||
$id = (int) $entry->care_unit_id;
|
||||
$dutyByUnit[$id] ??= collect();
|
||||
$dutyByUnit[$id]->push($entry);
|
||||
}
|
||||
|
||||
$placementByUnit = [];
|
||||
foreach ($placements as $assignment) {
|
||||
$placementByUnit[(int) $assignment->care_unit_id] = $assignment;
|
||||
}
|
||||
|
||||
return [
|
||||
'units' => $ordered->values(),
|
||||
'patients_by_unit' => $patientsByUnit,
|
||||
'duty_by_unit' => $dutyByUnit,
|
||||
'placement_by_unit' => $placementByUnit,
|
||||
'selected_unit' => $selected,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user