Gate ward admin nav to nursing roles and add My shifts for staff.
Deploy Ladill Care / deploy (push) Successful in 52s
Deploy Ladill Care / deploy (push) Successful in 52s
Ambulance and other non-nursing vitals roles no longer see Care units / Nursing Services; staff get a personal duty roster view of assigned shifts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Care;
|
||||
|
||||
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Care\RosterService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class MyShiftsController extends Controller
|
||||
{
|
||||
use ScopesToAccount;
|
||||
|
||||
public function index(Request $request, RosterService $roster): View
|
||||
{
|
||||
$this->authorizeAbility($request, 'nursing.my_shifts.view');
|
||||
$member = $this->member($request);
|
||||
abort_unless($member, 403);
|
||||
|
||||
$weekStart = $request->filled('week')
|
||||
? Carbon::parse($request->input('week'))->startOfWeek(Carbon::MONDAY)
|
||||
: now()->startOfWeek(Carbon::MONDAY);
|
||||
|
||||
$grid = $roster->memberWeek($member, $this->ownerRef($request), $weekStart);
|
||||
|
||||
$byDate = [];
|
||||
foreach ($grid['entries'] as $entry) {
|
||||
$byDate[$entry->duty_date->toDateString()][] = $entry;
|
||||
}
|
||||
|
||||
return view('care.nursing.my-shifts', [
|
||||
'member' => $member,
|
||||
'weekStart' => $grid['week_start'],
|
||||
'days' => $grid['days'],
|
||||
'entriesByDate' => $byDate,
|
||||
'todayEntries' => $grid['today'],
|
||||
'prevWeek' => $grid['week_start']->copy()->subWeek()->toDateString(),
|
||||
'nextWeek' => $grid['week_start']->copy()->addWeek()->toDateString(),
|
||||
'roles' => config('care.roles'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -498,6 +498,17 @@ class CarePermissions
|
||||
],
|
||||
];
|
||||
|
||||
/** Ward / nursing-ops roles — not every clinical role with vitals (e.g. ambulance, GP). */
|
||||
protected array $nursingOpsRoles = [
|
||||
'nurse',
|
||||
'ed_nurse',
|
||||
'theatre_nurse',
|
||||
'labour_ward_nurse',
|
||||
'fertility_nurse',
|
||||
'dialysis_nurse',
|
||||
'midwife',
|
||||
];
|
||||
|
||||
/** Clinical roles that staff the floor (queues, specialty desks, counters). */
|
||||
protected array $floorCareRoles = [
|
||||
'emergency_physician', 'ed_nurse', 'general_physician', 'doctor',
|
||||
@@ -566,11 +577,14 @@ class CarePermissions
|
||||
}
|
||||
}
|
||||
|
||||
return $this->withDerivedAbilities(array_keys($merged));
|
||||
return $this->withNursingOpsAbilities(
|
||||
$this->withDerivedAbilities(array_keys($merged)),
|
||||
$role,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Floor clinicians who record vitals may also place patients on care units/beds.
|
||||
* Clinical vitals unlock charting surfaces; ward nursing hub abilities are role-gated.
|
||||
*
|
||||
* @param list<string> $abilities
|
||||
* @return list<string>
|
||||
@@ -581,9 +595,8 @@ class CarePermissions
|
||||
return $abilities;
|
||||
}
|
||||
|
||||
// Clinical vitals → charting surfaces (not the Nursing Services admin hub).
|
||||
if (in_array('vitals.manage', $abilities, true)) {
|
||||
$abilities[] = 'inpatient.placement.view';
|
||||
$abilities[] = 'inpatient.placement.manage';
|
||||
$abilities[] = 'mar.view';
|
||||
$abilities[] = 'mar.administer';
|
||||
$abilities[] = 'nursing.notes.view';
|
||||
@@ -593,9 +606,7 @@ class CarePermissions
|
||||
$abilities[] = 'nursing.assessments.view';
|
||||
$abilities[] = 'nursing.assessments.manage';
|
||||
$abilities[] = 'nursing.performance.view';
|
||||
$abilities[] = 'nursing.roster.view';
|
||||
$abilities[] = 'nursing.roster.manage';
|
||||
$abilities[] = 'nursing.services.view';
|
||||
$abilities[] = 'nursing.my_shifts.view';
|
||||
}
|
||||
|
||||
if (in_array('analytics.department.view', $abilities, true)) {
|
||||
@@ -607,11 +618,42 @@ class CarePermissions
|
||||
$abilities[] = 'nursing.performance.view';
|
||||
$abilities[] = 'nursing.roster.view';
|
||||
$abilities[] = 'nursing.services.view';
|
||||
$abilities[] = 'nursing.my_shifts.view';
|
||||
}
|
||||
|
||||
return array_values(array_unique($abilities));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ward nursing abilities (care units, roster manage, Nursing Services hub).
|
||||
* Applied only for nursing-ops roles — not ambulance / GP / specialty MDs.
|
||||
*
|
||||
* @param list<string> $abilities
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function withNursingOpsAbilities(array $abilities, ?string $role): array
|
||||
{
|
||||
if (in_array('*', $abilities, true)) {
|
||||
return $abilities;
|
||||
}
|
||||
|
||||
if ($role === null || ! in_array($role, $this->nursingOpsRoles, true)) {
|
||||
return $abilities;
|
||||
}
|
||||
|
||||
if (! in_array('vitals.manage', $abilities, true)) {
|
||||
return $abilities;
|
||||
}
|
||||
|
||||
$abilities[] = 'inpatient.placement.view';
|
||||
$abilities[] = 'inpatient.placement.manage';
|
||||
$abilities[] = 'nursing.roster.view';
|
||||
$abilities[] = 'nursing.roster.manage';
|
||||
$abilities[] = 'nursing.services.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);
|
||||
|
||||
@@ -20,7 +20,6 @@ class NursingServicesService
|
||||
'fertility_nurse',
|
||||
'dialysis_nurse',
|
||||
'midwife',
|
||||
'ambulance_staff',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -240,6 +240,44 @@ class RosterService
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Roster entries for one staff member over a week (or open-ended upcoming window).
|
||||
*
|
||||
* @return array{week_start: Carbon, days: list<Carbon>, entries: Collection<int, RosterEntry>, today: Collection<int, RosterEntry>}
|
||||
*/
|
||||
public function memberWeek(
|
||||
Member $member,
|
||||
string $ownerRef,
|
||||
?CarbonInterface $weekStart = null,
|
||||
): array {
|
||||
$start = Carbon::parse($weekStart ?? now())->startOfWeek(Carbon::MONDAY)->startOfDay();
|
||||
$end = $start->copy()->addDays(6);
|
||||
$days = [];
|
||||
for ($i = 0; $i < 7; $i++) {
|
||||
$days[] = $start->copy()->addDays($i);
|
||||
}
|
||||
|
||||
$entries = RosterEntry::owned($ownerRef)
|
||||
->where('member_id', $member->id)
|
||||
->whereBetween('duty_date', [$start->toDateString(), $end->toDateString()])
|
||||
->where('status', '!=', RosterEntry::STATUS_CANCELLED)
|
||||
->with(['shift', 'careUnit.department'])
|
||||
->orderBy('duty_date')
|
||||
->orderBy('shift_id')
|
||||
->get();
|
||||
|
||||
$today = $entries->filter(
|
||||
fn (RosterEntry $entry) => $entry->duty_date->isSameDay(now())
|
||||
)->values();
|
||||
|
||||
return [
|
||||
'week_start' => $start,
|
||||
'days' => $days,
|
||||
'entries' => $entries,
|
||||
'today' => $today,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<Member> $members
|
||||
* @return array<int, string>
|
||||
|
||||
Reference in New Issue
Block a user