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>
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<x-app-layout title="My shifts">
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900">My shifts</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
Your duty roster for
|
||||
{{ $weekStart->format('d M Y') }} – {{ $weekStart->copy()->addDays(6)->format('d M Y') }}.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="{{ route('care.my-shifts', ['week' => $prevWeek]) }}" class="btn-secondary">Previous</a>
|
||||
<a href="{{ route('care.my-shifts', ['week' => now()->startOfWeek(\Carbon\Carbon::MONDAY)->toDateString()]) }}" class="btn-secondary">This week</a>
|
||||
<a href="{{ route('care.my-shifts', ['week' => $nextWeek]) }}" class="btn-secondary">Next</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($todayEntries->isNotEmpty())
|
||||
<div class="rounded-2xl border border-teal-200 bg-teal-50 p-5">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-teal-800">Today</h2>
|
||||
<ul class="mt-3 space-y-2">
|
||||
@foreach ($todayEntries as $entry)
|
||||
<li class="flex flex-wrap items-center justify-between gap-2 rounded-xl bg-white/80 px-4 py-3 text-sm">
|
||||
<div>
|
||||
<p class="font-semibold text-slate-900">{{ $entry->shift?->label ?? 'Shift' }}</p>
|
||||
<p class="text-xs text-slate-500">
|
||||
{{ $entry->shift?->timeLabel() }}
|
||||
· {{ $entry->careUnit?->name ?? 'Unit' }}
|
||||
@if ($entry->careUnit?->department)
|
||||
({{ $entry->careUnit->department->name }})
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
<span class="rounded-full bg-teal-100 px-2.5 py-0.5 text-xs font-medium text-teal-800">
|
||||
{{ config('care.roster_entry_statuses.'.$entry->status, $entry->status) }}
|
||||
</span>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@else
|
||||
<div class="rounded-2xl border border-slate-200 bg-white px-5 py-4 text-sm text-slate-500">
|
||||
No duty assigned for today.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
|
||||
<table class="min-w-full text-sm">
|
||||
<thead class="bg-slate-50 text-left text-xs uppercase text-slate-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Day</th>
|
||||
<th class="px-4 py-3">Shift</th>
|
||||
<th class="px-4 py-3">Unit</th>
|
||||
<th class="px-4 py-3">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-50">
|
||||
@foreach ($days as $day)
|
||||
@php $dayEntries = $entriesByDate[$day->toDateString()] ?? []; @endphp
|
||||
@forelse ($dayEntries as $entry)
|
||||
<tr class="{{ $day->isToday() ? 'bg-teal-50/40' : '' }}">
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<span class="font-medium text-slate-900">{{ $day->format('D') }}</span>
|
||||
<span class="text-slate-400">{{ $day->format('d M') }}</span>
|
||||
@if ($day->isToday())
|
||||
<span class="ml-1 text-[10px] font-semibold uppercase text-teal-700">Today</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="mr-1 inline-block h-2 w-2 rounded-full" style="background: {{ $entry->shift?->color ?? '#94a3b8' }}"></span>
|
||||
{{ $entry->shift?->label ?? '—' }}
|
||||
<div class="text-xs text-slate-400">{{ $entry->shift?->timeLabel() }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
{{ $entry->careUnit?->name ?? '—' }}
|
||||
@if ($entry->careUnit?->department)
|
||||
<div class="text-xs text-slate-400">{{ $entry->careUnit->department->name }}</div>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 capitalize text-slate-600">
|
||||
{{ config('care.roster_entry_statuses.'.$entry->status, $entry->status) }}
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr class="{{ $day->isToday() ? 'bg-teal-50/40' : '' }}">
|
||||
<td class="px-4 py-3 whitespace-nowrap text-slate-500">
|
||||
<span class="font-medium text-slate-700">{{ $day->format('D') }}</span>
|
||||
{{ $day->format('d M') }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-slate-400" colspan="3">Off / not rostered</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -32,6 +32,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
if ($permissions->can($member, 'nursing.my_shifts.view')) {
|
||||
$nav[] = ['name' => 'My shifts', 'route' => route('care.my-shifts'), 'active' => request()->routeIs('care.my-shifts'),
|
||||
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />'];
|
||||
}
|
||||
|
||||
if (! empty($hasPaidPlan) && $permissions->can($member, 'displays.view')) {
|
||||
$nav[] = ['name' => 'Displays', 'route' => route('care.displays.index'), 'active' => request()->routeIs('care.displays.*'),
|
||||
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M6 20.25h12m-7.5-4v4m3-4v4M6.75 4.5h10.5a2.25 2.25 0 0 1 2.25 2.25v6.75a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 13.5V6.75A2.25 2.25 0 0 1 6.75 4.5Z" />'];
|
||||
@@ -124,7 +129,10 @@
|
||||
$adminNav[] = ['name' => 'Departments', 'route' => route('care.departments.index'), 'active' => request()->routeIs('care.departments.*'),
|
||||
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25A2.25 2.25 0 0 1 13.5 18v-2.25Z" />'];
|
||||
}
|
||||
if ($permissions->can($member, 'admin.departments.view') || $permissions->can($member, 'inpatient.placement.view')) {
|
||||
if ($permissions->can($member, 'admin.departments.view')) {
|
||||
$adminNav[] = ['name' => 'Care units', 'route' => route('care.care-units.index'), 'active' => request()->routeIs('care.care-units.*'),
|
||||
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Z" />'];
|
||||
} elseif ($permissions->can($member, 'nursing.services.view') && $permissions->can($member, 'inpatient.placement.view')) {
|
||||
$adminNav[] = ['name' => 'Care units', 'route' => route('care.care-units.index'), 'active' => request()->routeIs('care.care-units.*'),
|
||||
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Z" />'];
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ use App\Http\Controllers\Care\NursePerformanceController;
|
||||
use App\Http\Controllers\Care\ShiftController;
|
||||
use App\Http\Controllers\Care\RosterController;
|
||||
use App\Http\Controllers\Care\NursingServicesController;
|
||||
use App\Http\Controllers\Care\MyShiftsController;
|
||||
use App\Http\Controllers\Care\DeviceController;
|
||||
use App\Http\Controllers\Care\DisplayPublicController;
|
||||
use App\Http\Controllers\Care\DisplayScreenController;
|
||||
@@ -471,6 +472,7 @@ Route::middleware(['auth', 'platform.session'])->group(function () {
|
||||
Route::put('/shifts/{shift}', [ShiftController::class, 'update'])->name('care.shifts.update');
|
||||
|
||||
Route::get('/nursing/services', [NursingServicesController::class, 'index'])->name('care.nursing.services');
|
||||
Route::get('/my-shifts', [MyShiftsController::class, 'index'])->name('care.my-shifts');
|
||||
|
||||
Route::get('/staff-assignments', [StaffAssignmentController::class, 'index'])->name('care.staff-assignments.index');
|
||||
Route::get('/staff-assignments/create', [StaffAssignmentController::class, 'create'])->name('care.staff-assignments.create');
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Http\Middleware\EnsurePlatformSession;
|
||||
use App\Models\Branch;
|
||||
use App\Models\CareUnit;
|
||||
use App\Models\Department;
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use App\Services\Care\CarePermissions;
|
||||
use App\Services\Care\RosterService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class CareMyShiftsAndNavPermissionsTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected User $owner;
|
||||
|
||||
protected Organization $organization;
|
||||
|
||||
protected Branch $branch;
|
||||
|
||||
protected CareUnit $unit;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->withoutMiddleware(EnsurePlatformSession::class);
|
||||
|
||||
$this->owner = User::create([
|
||||
'public_id' => 'my-shifts-owner',
|
||||
'name' => 'Owner',
|
||||
'email' => 'my-shifts-owner@example.com',
|
||||
]);
|
||||
|
||||
$this->organization = Organization::create([
|
||||
'owner_ref' => $this->owner->public_id,
|
||||
'name' => 'Shifts Clinic',
|
||||
'slug' => 'shifts-clinic',
|
||||
'settings' => [
|
||||
'onboarded' => true,
|
||||
'facility_type' => 'hospital',
|
||||
'plan' => 'pro',
|
||||
'plan_expires_at' => now()->addMonth()->toIso8601String(),
|
||||
],
|
||||
]);
|
||||
|
||||
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' => 'Main',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$department = Department::create([
|
||||
'owner_ref' => $this->owner->public_id,
|
||||
'branch_id' => $this->branch->id,
|
||||
'name' => 'Medicine',
|
||||
'type' => 'general',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->unit = CareUnit::create([
|
||||
'owner_ref' => $this->owner->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'department_id' => $department->id,
|
||||
'name' => 'Medical Ward',
|
||||
'kind' => 'inpatient',
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_ambulance_staff_cannot_access_care_units_or_nursing_services_hub(): void
|
||||
{
|
||||
$user = User::create([
|
||||
'public_id' => 'amb-staff',
|
||||
'name' => 'Ambulance',
|
||||
'email' => 'amb@example.com',
|
||||
]);
|
||||
$member = Member::create([
|
||||
'owner_ref' => $this->owner->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'user_ref' => $user->public_id,
|
||||
'role' => 'ambulance_staff',
|
||||
'branch_id' => $this->branch->id,
|
||||
]);
|
||||
|
||||
$permissions = app(CarePermissions::class);
|
||||
$this->assertFalse($permissions->can($member, 'nursing.services.view'));
|
||||
$this->assertFalse($permissions->can($member, 'inpatient.placement.view'));
|
||||
$this->assertFalse($permissions->can($member, 'admin.departments.view'));
|
||||
$this->assertTrue($permissions->can($member, 'nursing.my_shifts.view'));
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('care.care-units.index'))
|
||||
->assertForbidden();
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('care.nursing.services'))
|
||||
->assertForbidden();
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('care.dashboard'))
|
||||
->assertOk()
|
||||
->assertDontSee('>Care units<', false)
|
||||
->assertDontSee('>Nursing Services<', false)
|
||||
->assertSee('My shifts', false);
|
||||
}
|
||||
|
||||
public function test_staff_see_their_assigned_shifts(): void
|
||||
{
|
||||
$user = User::create([
|
||||
'public_id' => 'nurse-shifts',
|
||||
'name' => 'Nurse Ama',
|
||||
'email' => 'nurse-shifts@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);
|
||||
$shifts = $roster->ensureDefaultShifts($this->organization, $this->owner->public_id);
|
||||
$day = $shifts->firstWhere('code', 'day');
|
||||
|
||||
$roster->assign(
|
||||
$this->unit,
|
||||
$day,
|
||||
$member,
|
||||
now()->toDateString(),
|
||||
$this->owner->public_id,
|
||||
$this->owner->public_id,
|
||||
);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('care.my-shifts'))
|
||||
->assertOk()
|
||||
->assertSee('My shifts')
|
||||
->assertSee('Medical Ward')
|
||||
->assertSee('Day shift');
|
||||
}
|
||||
|
||||
public function test_nurse_still_gets_nursing_services_access(): void
|
||||
{
|
||||
$member = Member::create([
|
||||
'owner_ref' => $this->owner->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'user_ref' => 'nurse-hub',
|
||||
'role' => 'nurse',
|
||||
]);
|
||||
|
||||
$permissions = app(CarePermissions::class);
|
||||
$this->assertTrue($permissions->can($member, 'nursing.services.view'));
|
||||
$this->assertTrue($permissions->can($member, 'inpatient.placement.view'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user