Files
ladill-care/app/Http/Controllers/Care/AppointmentController.php
T
isaaccladandCursor 015a4cc7fe
Deploy Ladill Care / deploy (push) Successful in 1m45s
Wire Care lists into Ladill Queue workflows instead of reception panels.
When Queue integration is on, role pages issue tickets into their own
department queues and drive Call next → Serve → Complete on the native
lists. Integration off leaves existing UI unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 16:27:48 +00:00

259 lines
9.4 KiB
PHP

<?php
namespace App\Http\Controllers\Care;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
use App\Models\Appointment;
use App\Models\Branch;
use App\Models\Department;
use App\Models\Patient;
use App\Models\Practitioner;
use App\Services\Care\AppointmentService;
use App\Services\Care\OrganizationResolver;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class AppointmentController extends Controller
{
use ScopesToAccount;
public function __construct(
protected AppointmentService $appointments,
) {}
public function index(Request $request): View
{
$this->authorizeAbility($request, 'appointments.view');
$organization = $this->organization($request);
$member = $this->member($request);
$branchScope = app(OrganizationResolver::class)->branchScope($member);
$appointments = $this->appointments->list(
$this->ownerRef($request),
$organization->id,
$request->only(['status', 'practitioner_id', 'date', 'patient_id']),
$branchScope,
);
$practitioners = $this->activePractitioners($request, $organization->id);
$owner = $this->ownerRef($request);
$appointmentQuery = Appointment::owned($owner)
->where('organization_id', $organization->id)
->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope));
$heroStats = [
'today' => (clone $appointmentQuery)->whereDate('scheduled_at', today())->count(),
'scheduled' => (clone $appointmentQuery)->where('status', Appointment::STATUS_SCHEDULED)->count(),
'waiting' => (clone $appointmentQuery)->whereIn('status', [
Appointment::STATUS_CHECKED_IN,
Appointment::STATUS_WAITING,
])->count(),
];
return view('care.appointments.index', [
'organization' => $organization,
'appointments' => $appointments,
'practitioners' => $practitioners,
'statuses' => config('care.appointment_statuses'),
'heroStats' => $heroStats,
]);
}
public function create(Request $request): View
{
$this->authorizeAbility($request, 'appointments.manage');
$organization = $this->organization($request);
return view('care.appointments.create', $this->formData($request, $organization));
}
public function store(Request $request): RedirectResponse
{
$this->authorizeAbility($request, 'appointments.manage');
$organization = $this->organization($request);
$validated = $this->validatedAppointmentData($request);
$appointment = $this->appointments->book(
$organization,
$this->ownerRef($request),
$validated,
$this->ownerRef($request),
);
return redirect()->route('care.appointments.show', $appointment)
->with('success', 'Appointment booked.');
}
public function walkInCreate(Request $request): View
{
$this->authorizeAbility($request, 'appointments.manage');
$organization = $this->organization($request);
return view('care.appointments.walk-in', $this->formData($request, $organization));
}
public function walkInStore(Request $request): RedirectResponse
{
$this->authorizeAbility($request, 'appointments.manage');
$organization = $this->organization($request);
$validated = $this->validatedWalkInData($request);
$appointment = $this->appointments->walkIn(
$organization,
$this->ownerRef($request),
$validated,
$this->ownerRef($request),
);
return redirect()->route('care.queue.index')
->with('success', 'Walk-in added to queue.');
}
public function show(Request $request, Appointment $appointment): View
{
$this->authorizeAbility($request, 'appointments.view');
$this->authorizeAppointment($request, $appointment);
$appointment->load(['patient', 'practitioner', 'branch', 'department', 'visit', 'consultation']);
$canManage = app(\App\Services\Care\CarePermissions::class)
->can($this->member($request), 'appointments.manage');
$canConsult = app(\App\Services\Care\CarePermissions::class)
->can($this->member($request), 'consultations.manage');
return view('care.appointments.show', [
'appointment' => $appointment,
'statuses' => config('care.appointment_statuses'),
'canManage' => $canManage,
'canConsult' => $canConsult,
]);
}
public function checkIn(Request $request, Appointment $appointment): RedirectResponse
{
$this->authorizeAbility($request, 'appointments.manage');
$this->authorizeAppointment($request, $appointment);
$this->appointments->checkIn($appointment, $this->ownerRef($request), $this->ownerRef($request));
return redirect()->route('care.queue.index')
->with('success', 'Patient checked in and added to queue.');
}
public function cancel(Request $request, Appointment $appointment): RedirectResponse
{
$this->authorizeAbility($request, 'appointments.manage');
$this->authorizeAppointment($request, $appointment);
$this->appointments->cancel($appointment, $this->ownerRef($request), $this->ownerRef($request));
return back()->with('success', 'Appointment cancelled.');
}
public function noShow(Request $request, Appointment $appointment): RedirectResponse
{
$this->authorizeAbility($request, 'appointments.manage');
$this->authorizeAppointment($request, $appointment);
$this->appointments->markNoShow($appointment, $this->ownerRef($request), $this->ownerRef($request));
return back()->with('success', 'Marked as no-show.');
}
protected function authorizeAppointment(Request $request, Appointment $appointment): void
{
$this->authorizeOwner($request, $appointment);
abort_unless($appointment->organization_id === $this->organization($request)->id, 404);
$branchId = app(OrganizationResolver::class)->branchScope($this->member($request));
if ($branchId !== null && $appointment->branch_id !== $branchId) {
abort(404);
}
}
/**
* @return array<string, mixed>
*/
protected function formData(Request $request, \App\Models\Organization $organization): array
{
$ownerRef = $this->ownerRef($request);
$branchQuery = Branch::owned($ownerRef)->where('organization_id', $organization->id)->where('is_active', true);
$branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));
if ($branchScope !== null) {
$branchQuery->where('id', $branchScope);
}
$branches = $branchQuery->orderBy('name')->get();
$defaultBranch = $branchScope ?? $branches->first()?->id;
$patients = Patient::owned($ownerRef)
->where('organization_id', $organization->id)
->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope))
->orderBy('first_name')
->limit(200)
->get();
$departments = Department::owned($ownerRef)
->whereIn('branch_id', $branches->pluck('id'))
->where('is_active', true)
->orderBy('name')
->get();
return [
'organization' => $organization,
'branches' => $branches,
'defaultBranch' => $defaultBranch,
'patients' => $patients,
'practitioners' => $this->activePractitioners($request, $organization->id),
'departments' => $departments,
];
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Practitioner>
*/
protected function activePractitioners(Request $request, int $organizationId)
{
return Practitioner::owned($this->ownerRef($request))
->where('organization_id', $organizationId)
->where('is_active', true)
->orderBy('name')
->get();
}
/**
* @return array<string, mixed>
*/
protected function validatedAppointmentData(Request $request): array
{
return $request->validate([
'branch_id' => ['required', 'integer', 'exists:care_branches,id'],
'patient_id' => ['required', 'integer', 'exists:care_patients,id'],
'practitioner_id' => ['nullable', 'integer', 'exists:care_practitioners,id'],
'department_id' => ['nullable', 'integer', 'exists:care_departments,id'],
'scheduled_at' => ['required', 'date', 'after:now'],
'reason' => ['nullable', 'string', 'max:1000'],
'notes' => ['nullable', 'string', 'max:5000'],
]);
}
/**
* @return array<string, mixed>
*/
protected function validatedWalkInData(Request $request): array
{
return $request->validate([
'branch_id' => ['required', 'integer', 'exists:care_branches,id'],
'patient_id' => ['required', 'integer', 'exists:care_patients,id'],
'practitioner_id' => ['nullable', 'integer', 'exists:care_practitioners,id'],
'department_id' => ['nullable', 'integer', 'exists:care_departments,id'],
'reason' => ['nullable', 'string', 'max:1000'],
'notes' => ['nullable', 'string', 'max:5000'],
]);
}
}