Lock nursing note shift from duty roster or clock.
Deploy Ladill Care / deploy (push) Successful in 46s
Deploy Ladill Care / deploy (push) Successful in 46s
Nurses can no longer pick a shift on the form; the server sets it from today's unit roster, falling back to the current clock window. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,7 @@ use App\Models\WardHandover;
|
||||
use App\Services\Care\CarePermissions;
|
||||
use App\Services\Care\CareUnitCensusService;
|
||||
use App\Services\Care\NursingDocumentationService;
|
||||
use App\Services\Care\RosterService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
@@ -19,7 +20,7 @@ class NursingDocumentationController extends Controller
|
||||
{
|
||||
use ScopesToAccount;
|
||||
|
||||
public function unitNotes(Request $request, CareUnit $careUnit, NursingDocumentationService $docs, CareUnitCensusService $census): View
|
||||
public function unitNotes(Request $request, CareUnit $careUnit, NursingDocumentationService $docs, CareUnitCensusService $census, RosterService $roster): View
|
||||
{
|
||||
$this->authorizeAbility($request, 'nursing.notes.view');
|
||||
$this->authorizeOwner($request, $careUnit);
|
||||
@@ -31,18 +32,25 @@ class NursingDocumentationController extends Controller
|
||||
$selectedVisitId = 0;
|
||||
}
|
||||
|
||||
$lockedShiftCode = $roster->dutyShiftCodeForMember(
|
||||
$careUnit,
|
||||
$this->member($request),
|
||||
$this->ownerRef($request),
|
||||
);
|
||||
|
||||
return view('care.nursing.notes', [
|
||||
'unit' => $careUnit->load('department.branch'),
|
||||
'notes' => $docs->notesForUnit($careUnit, $this->ownerRef($request)),
|
||||
'placedVisits' => $placedVisits,
|
||||
'selectedVisitId' => $selectedVisitId ?: (int) old('visit_id', 0),
|
||||
'lockedShiftCode' => $lockedShiftCode,
|
||||
'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, CareUnitCensusService $census): RedirectResponse
|
||||
public function storeNote(Request $request, CareUnit $careUnit, NursingDocumentationService $docs, CareUnitCensusService $census, RosterService $roster): RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'nursing.notes.manage');
|
||||
$this->authorizeOwner($request, $careUnit);
|
||||
@@ -50,7 +58,6 @@ class NursingDocumentationController extends Controller
|
||||
$validated = $request->validate([
|
||||
'visit_id' => ['required', 'integer', 'exists:care_visits,id'],
|
||||
'note_type' => ['required', 'string', Rule::in(array_keys(config('care.nursing_note_types')))],
|
||||
'shift_code' => ['nullable', 'string', Rule::in(array_keys(config('care.staff_shift_codes')))],
|
||||
'body' => ['required', 'string', 'max:5000'],
|
||||
]);
|
||||
|
||||
@@ -61,13 +68,19 @@ class NursingDocumentationController extends Controller
|
||||
|
||||
$visit = $census->ensurePlaced($visit, $careUnit, $this->ownerRef($request), $this->actorRef($request));
|
||||
|
||||
$shiftCode = $roster->dutyShiftCodeForMember(
|
||||
$careUnit,
|
||||
$this->member($request),
|
||||
$this->ownerRef($request),
|
||||
);
|
||||
|
||||
$docs->addNote(
|
||||
$visit,
|
||||
$this->ownerRef($request),
|
||||
$this->actorRef($request),
|
||||
$validated['body'],
|
||||
$validated['note_type'],
|
||||
$validated['shift_code'] ?? null,
|
||||
$shiftCode,
|
||||
$careUnit->id,
|
||||
);
|
||||
|
||||
|
||||
@@ -271,4 +271,76 @@ class RosterService
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift code for nursing notes: today's roster on this unit, else clock-based default.
|
||||
*/
|
||||
public function dutyShiftCodeForMember(
|
||||
CareUnit $unit,
|
||||
?Member $member,
|
||||
string $ownerRef,
|
||||
?CarbonInterface $at = null,
|
||||
): ?string {
|
||||
$at = Carbon::parse($at ?? now());
|
||||
$allowed = array_keys(config('care.staff_shift_codes', []));
|
||||
|
||||
if ($member) {
|
||||
$entry = RosterEntry::owned($ownerRef)
|
||||
->where('care_unit_id', $unit->id)
|
||||
->where('member_id', $member->id)
|
||||
->whereDate('duty_date', $at->toDateString())
|
||||
->where('status', '!=', RosterEntry::STATUS_CANCELLED)
|
||||
->with('shift')
|
||||
->orderBy('shift_id')
|
||||
->first();
|
||||
|
||||
$code = $entry?->shift?->code;
|
||||
if ($code && in_array($code, $allowed, true)) {
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
|
||||
$organization = $unit->organization
|
||||
?? Organization::query()->find($unit->organization_id);
|
||||
if (! $organization) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$shifts = $this->ensureDefaultShifts($organization, $ownerRef);
|
||||
foreach ($shifts as $shift) {
|
||||
if (! in_array($shift->code, $allowed, true)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->clockFallsInShift($at, (string) $shift->start_time, (string) $shift->end_time)) {
|
||||
return $shift->code;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function clockFallsInShift(CarbonInterface $at, string $startTime, string $endTime): bool
|
||||
{
|
||||
$minutes = ((int) $at->format('H') * 60) + (int) $at->format('i');
|
||||
$start = $this->timeToMinutes($startTime);
|
||||
$end = $this->timeToMinutes($endTime);
|
||||
|
||||
if ($start === $end) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Overnight (e.g. 23:00–07:00)
|
||||
if ($start > $end) {
|
||||
return $minutes >= $start || $minutes < $end;
|
||||
}
|
||||
|
||||
return $minutes >= $start && $minutes < $end;
|
||||
}
|
||||
|
||||
protected function timeToMinutes(string $time): int
|
||||
{
|
||||
$parts = explode(':', substr($time, 0, 5));
|
||||
|
||||
return ((int) ($parts[0] ?? 0) * 60) + (int) ($parts[1] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user