Deploy Ladill Care / deploy (push) Successful in 2m12s
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>
181 lines
5.8 KiB
PHP
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();
|
|
}
|
|
}
|