Files
ladill-care/app/Services/Care/NurseStationService.php
isaaccladandCursor f1bb432324
Deploy Ladill Care / deploy (push) Successful in 2m12s
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 <cursoragent@cursor.com>
2026-07-20 17:05:13 +00:00

181 lines
5.8 KiB
PHP

<?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
{
public function __construct(
protected CareUnitCensusService $census,
) {}
/**
* @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] = $this->census->visitsForUnit($unit, $ownerRef);
}
$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,
];
}
/**
* Units the nurse is on duty for today (roster) or has an active home placement.
*
* @return Collection<int, CareUnit>
*/
public function dutyUnits(Organization $organization, Member $member, string $ownerRef): Collection
{
$dutyToday = RosterEntry::owned($ownerRef)
->where('organization_id', $organization->id)
->where('member_id', $member->id)
->whereDate('duty_date', now()->toDateString())
->where('status', '!=', RosterEntry::STATUS_CANCELLED)
->orderBy('shift_id')
->get(['care_unit_id']);
$placements = StaffAssignment::owned($ownerRef)
->where('organization_id', $organization->id)
->where('member_id', $member->id)
->where('status', 'active')
->unitPlacements()
->effectiveOn(now())
->whereNotNull('care_unit_id')
->get(['care_unit_id']);
$unitIds = $dutyToday->pluck('care_unit_id')
->merge($placements->pluck('care_unit_id'))
->filter()
->map(fn ($id) => (int) $id)
->unique()
->values();
if ($unitIds->isEmpty()) {
return collect();
}
$units = CareUnit::owned($ownerRef)
->where('organization_id', $organization->id)
->whereIn('id', $unitIds->all())
->where('is_active', true)
->with('department.branch')
->get()
->keyBy('id');
// Preserve roster order, then placement-only units.
$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));
}
}
return $ordered->values();
}
}