Files
ladill-care/app/Http/Controllers/Care/PrescriptionController.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

261 lines
10 KiB
PHP

<?php
namespace App\Http\Controllers\Care;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
use App\Models\Consultation;
use App\Models\Prescription;
use App\Models\Practitioner;
use App\Services\Care\CareQueueBridge;
use App\Services\Care\CareQueueContexts;
use App\Services\Care\OrganizationResolver;
use App\Services\Care\PharmacyService;
use App\Services\Care\PrescriptionService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class PrescriptionController extends Controller
{
use ScopesToAccount;
public function __construct(
protected PrescriptionService $prescriptions,
protected PharmacyService $pharmacy,
protected CareQueueBridge $queueBridge,
) {}
public function index(Request $request): View
{
$this->authorizeAbility($request, 'prescriptions.view');
$organization = $this->organization($request);
$branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));
$prescriptions = $this->prescriptions->list(
$this->ownerRef($request),
$organization->id,
$request->only(['status', 'patient_id']),
$branchScope,
);
return view('care.prescriptions.index', [
'organization' => $organization,
'prescriptions' => $prescriptions,
'statuses' => config('care.prescription_statuses'),
]);
}
public function queue(Request $request): View
{
$this->authorizeAbility($request, 'prescriptions.view');
$organization = $this->organization($request);
$member = $this->member($request);
$branchScope = app(OrganizationResolver::class)->branchScope($member);
$branchId = (int) ($branchScope ?: \App\Models\Branch::owned($this->ownerRef($request))
->where('organization_id', $organization->id)
->where('is_active', true)
->orderBy('name')
->value('id'));
$queue = $this->prescriptions->pharmacyQueue($this->ownerRef($request), $organization->id);
if ($branchScope) {
$queue = $queue->filter(fn (Prescription $rx) => (int) ($rx->visit?->branch_id) === $branchScope)->values();
}
$drugs = \App\Models\Drug::owned($this->ownerRef($request))
->where('organization_id', $organization->id)
->where('is_active', true)
->with(['batches' => fn ($q) => $q->where('quantity_on_hand', '>', 0)->orderBy('expiry_date')])
->orderBy('name')
->get();
$canDispense = app(\App\Services\Care\CarePermissions::class)
->can($member, 'prescriptions.dispense');
return view('care.prescriptions.queue', [
'organization' => $organization,
'queue' => $queue,
'drugs' => $drugs,
'canDispense' => $canDispense,
'branchId' => $branchId,
'queueIntegration' => $branchId
? $this->queueBridge->listMeta($organization, CareQueueContexts::PHARMACY, $branchId)
: ['enabled' => false],
]);
}
public function callNext(Request $request): RedirectResponse
{
$this->authorizeAbility($request, 'prescriptions.dispense');
$organization = $this->organization($request);
$member = $this->member($request);
$branchScope = app(OrganizationResolver::class)->branchScope($member);
$branchId = (int) ($request->input('branch_id') ?: $branchScope);
abort_unless($branchId > 0, 422);
abort_unless($this->queueBridge->isEnabled($organization), 404);
$result = $this->queueBridge->callNext($organization, CareQueueContexts::PHARMACY, $branchId);
$ticket = $result['ticket'] ?? null;
if (! $ticket) {
return back()->with('error', 'No prescriptions waiting in the pharmacy queue.');
}
return back()->with('success', 'Called '.$ticket['ticket_number'].'.');
}
public function serve(Request $request, Prescription $prescription): RedirectResponse
{
$this->authorizeAbility($request, 'prescriptions.dispense');
$this->authorizePrescription($request, $prescription);
$organization = $this->organization($request);
abort_unless($this->queueBridge->isEnabled($organization), 404);
$ticket = $this->queueBridge->serve($organization, $prescription);
if (! $ticket) {
return back()->with('error', 'Could not serve pharmacy ticket.');
}
return back()->with('success', 'Now serving '.$ticket['ticket_number'].'.');
}
public function create(Request $request, Consultation $consultation): View
{
$this->authorizeAbility($request, 'prescriptions.manage');
$this->authorizeConsultation($request, $consultation);
$consultation->load(['patient', 'practitioner']);
$practitioners = Practitioner::owned($this->ownerRef($request))
->where('organization_id', $this->organization($request)->id)
->where('is_active', true)
->orderBy('name')
->get();
return view('care.prescriptions.create', [
'consultation' => $consultation,
'practitioners' => $practitioners,
'routes' => config('care.medication_routes'),
]);
}
public function store(Request $request, Consultation $consultation): RedirectResponse
{
$this->authorizeAbility($request, 'prescriptions.manage');
$this->authorizeConsultation($request, $consultation);
$prescription = $this->prescriptions->createFromConsultation(
$consultation,
$this->ownerRef($request),
$this->validatedPrescriptionData($request),
$this->ownerRef($request),
);
return redirect()->route('care.prescriptions.show', $prescription)
->with('success', 'Prescription created.');
}
public function show(Request $request, Prescription $prescription): View
{
$this->authorizeAbility($request, 'prescriptions.view');
$this->authorizePrescription($request, $prescription);
$prescription->load(['patient', 'practitioner', 'items', 'consultation', 'visit']);
$canManage = app(\App\Services\Care\CarePermissions::class)
->can($this->member($request), 'prescriptions.manage');
$canDispense = app(\App\Services\Care\CarePermissions::class)
->can($this->member($request), 'prescriptions.dispense');
return view('care.prescriptions.show', [
'prescription' => $prescription,
'statuses' => config('care.prescription_statuses'),
'routes' => config('care.medication_routes'),
'canManage' => $canManage,
'canDispense' => $canDispense,
]);
}
public function activate(Request $request, Prescription $prescription): RedirectResponse
{
$this->authorizeAbility($request, 'prescriptions.manage');
$this->authorizePrescription($request, $prescription);
$this->prescriptions->activate($prescription, $this->ownerRef($request), $this->ownerRef($request));
return back()->with('success', 'Prescription activated.');
}
public function dispense(Request $request, Prescription $prescription): RedirectResponse
{
$this->authorizeAbility($request, 'prescriptions.dispense');
$this->authorizePrescription($request, $prescription);
$allocations = $request->input('allocations', []);
if (! empty($allocations)) {
$this->pharmacy->dispensePrescription(
$prescription,
$this->ownerRef($request),
$allocations,
$this->ownerRef($request),
);
} else {
$this->prescriptions->dispense($prescription, $this->ownerRef($request), $this->ownerRef($request));
}
return redirect()->route('care.prescriptions.queue')
->with('success', 'Prescription dispensed.');
}
public function cancel(Request $request, Prescription $prescription): RedirectResponse
{
$this->authorizeAbility($request, 'prescriptions.manage');
$this->authorizePrescription($request, $prescription);
$this->prescriptions->cancel($prescription, $this->ownerRef($request), $this->ownerRef($request));
return back()->with('success', 'Prescription cancelled.');
}
protected function authorizeConsultation(Request $request, Consultation $consultation): void
{
$this->authorizeOwner($request, $consultation);
$consultation->loadMissing('visit');
abort_unless($consultation->visit->organization_id === $this->organization($request)->id, 404);
}
protected function authorizePrescription(Request $request, Prescription $prescription): void
{
$this->authorizeOwner($request, $prescription);
abort_unless($prescription->organization_id === $this->organization($request)->id, 404);
$branchId = app(OrganizationResolver::class)->branchScope($this->member($request));
if ($branchId !== null) {
$prescription->loadMissing('visit');
abort_unless($prescription->visit->branch_id === $branchId, 404);
}
}
/**
* @return array<string, mixed>
*/
protected function validatedPrescriptionData(Request $request): array
{
return $request->validate([
'practitioner_id' => ['nullable', 'integer', 'exists:care_practitioners,id'],
'notes' => ['nullable', 'string', 'max:5000'],
'activate' => ['nullable', 'boolean'],
'items' => ['required', 'array', 'min:1'],
'items.*.is_procedure' => ['nullable', 'boolean'],
'items.*.name' => ['required', 'string', 'max:255'],
'items.*.dosage' => ['nullable', 'string', 'max:100'],
'items.*.frequency' => ['nullable', 'string', 'max:100'],
'items.*.duration' => ['nullable', 'string', 'max:100'],
'items.*.route' => ['nullable', 'string', 'max:50'],
'items.*.quantity' => ['nullable', 'string', 'max:50'],
'items.*.instructions' => ['nullable', 'string', 'max:2000'],
]);
}
}