Files
ladill-care/app/Http/Controllers/Care/InvestigationController.php
T
isaaccladandCursor 94e53d05bf
Deploy Ladill Care / deploy (push) Successful in 1m7s
Keep consultation context on nested clinical pages and add Call again on patient queue cards.
Doctors can return to the active consultation from forms, lab, prescriptions, pathways, and bills; queue cards can re-announce called/serving tickets via the existing Queue recall bridge.

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

282 lines
11 KiB
PHP

<?php
namespace App\Http\Controllers\Care;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
use App\Models\Branch;
use App\Models\Consultation;
use App\Models\InvestigationRequest;
use App\Models\InvestigationType;
use App\Models\Member;
use App\Services\Care\CareQueueBridge;
use App\Services\Care\CareQueueContexts;
use App\Services\Care\ConsultationReturnContext;
use App\Services\Care\InvestigationService;
use App\Services\Care\OrganizationResolver;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class InvestigationController extends Controller
{
use ScopesToAccount;
public function __construct(
protected InvestigationService $investigations,
protected CareQueueBridge $queueBridge,
) {}
public function index(Request $request): View
{
$this->authorizeAbility($request, 'lab.view');
$organization = $this->organization($request);
$branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));
$requests = $this->investigations->list(
$this->ownerRef($request),
$organization->id,
$request->only(['status', 'patient_id']),
$branchScope,
);
return view('care.lab.requests.index', [
'organization' => $organization,
'requests' => $requests,
'statuses' => config('care.investigation_statuses'),
]);
}
public function queue(Request $request): View
{
$this->authorizeAbility($request, 'lab.manage');
$organization = $this->organization($request);
$branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));
$branches = Branch::owned($this->ownerRef($request))
->where('organization_id', $organization->id)
->where('is_active', true)
->when($branchScope, fn ($q) => $q->where('id', $branchScope))
->orderBy('name')
->get();
$branchId = (int) ($request->input('branch_id') ?: $branchScope ?: $branches->first()?->id);
$status = $request->input('status');
$queue = $branchId
? $this->investigations->workQueue($this->ownerRef($request), $branchId, $status)
: collect();
return view('care.lab.queue.index', [
'organization' => $organization,
'branches' => $branches,
'branchId' => $branchId,
'status' => $status,
'queue' => $queue,
'statuses' => config('care.investigation_statuses'),
'members' => Member::owned($this->ownerRef($request))
->where('organization_id', $organization->id)
->orderBy('role')
->get(),
'queueIntegration' => $branchId
? $this->queueBridge->listMeta($organization, CareQueueContexts::LABORATORY, $branchId)
: ['enabled' => false],
]);
}
public function callNext(Request $request): RedirectResponse
{
$this->authorizeAbility($request, 'lab.manage');
$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::LABORATORY, $branchId, $member);
$ticket = $result['ticket'] ?? null;
if (! $ticket) {
return back()->with('info', 'No lab work waiting at your laboratory service point.');
}
return back()->with('success', 'Called '.$ticket['ticket_number'].'.');
}
public function serve(Request $request, InvestigationRequest $investigation): RedirectResponse
{
$this->authorizeAbility($request, 'lab.manage');
$this->authorizeInvestigation($request, $investigation);
$organization = $this->organization($request);
abort_unless($this->queueBridge->isEnabled($organization), 404);
$ticket = $this->queueBridge->serve($organization, $investigation);
if (! $ticket) {
return back()->with('error', 'Could not serve lab ticket.');
}
return back()->with('success', 'Now serving '.$ticket['ticket_number'].'.');
}
public function requestFromConsultation(Request $request, Consultation $consultation): RedirectResponse
{
$this->authorizeAbility($request, 'investigations.request');
$this->authorizeConsultation($request, $consultation);
$validated = $request->validate([
'investigation_type_ids' => ['required', 'array', 'min:1'],
'investigation_type_ids.*' => ['integer', 'exists:care_investigation_types,id'],
'clinical_notes' => ['nullable', 'string', 'max:2000'],
'priority' => ['nullable', 'string', 'in:routine,urgent'],
]);
$this->investigations->requestFromConsultation(
$consultation,
$this->ownerRef($request),
$validated['investigation_type_ids'],
$validated['clinical_notes'] ?? null,
$validated['priority'] ?? 'routine',
$this->ownerRef($request),
);
return back()->with('success', 'Investigation request(s) submitted.');
}
public function show(Request $request, InvestigationRequest $investigation): View
{
$this->authorizeAbility($request, 'lab.view');
$this->authorizeInvestigation($request, $investigation);
$investigation->load([
'patient', 'investigationType', 'practitioner', 'branch',
'result.values', 'result.attachments', 'assignedMember',
'consultation.patient', 'consultation.appointment',
]);
$canManage = app(\App\Services\Care\CarePermissions::class)
->can($this->member($request), 'lab.manage');
$canViewResults = app(\App\Services\Care\CarePermissions::class)
->can($this->member($request), 'lab.results.view');
$return = app(ConsultationReturnContext::class);
return view('care.lab.requests.show', [
'investigation' => $investigation,
'statuses' => config('care.investigation_statuses'),
'canManage' => $canManage,
'canViewResults' => $canViewResults,
'returnConsultation' => $return->resolve($request, (int) $investigation->patient_id)
?? $return->resolveLinked($request, $investigation->consultation),
]);
}
public function collectSample(Request $request, InvestigationRequest $investigation): RedirectResponse
{
$this->authorizeAbility($request, 'lab.manage');
$this->authorizeInvestigation($request, $investigation);
$validated = $request->validate(['sample_barcode' => ['nullable', 'string', 'max:100']]);
$this->investigations->collectSample(
$investigation,
$this->ownerRef($request),
$validated['sample_barcode'] ?? null,
$this->ownerRef($request),
);
return back()->with('success', 'Sample collected.');
}
public function startProcessing(Request $request, InvestigationRequest $investigation): RedirectResponse
{
$this->authorizeAbility($request, 'lab.manage');
$this->authorizeInvestigation($request, $investigation);
$validated = $request->validate(['assigned_member_id' => ['nullable', 'integer', 'exists:care_members,id']]);
$this->investigations->startProcessing(
$investigation,
$this->ownerRef($request),
$validated['assigned_member_id'] ?? null,
$this->ownerRef($request),
);
return redirect()->route('care.lab.requests.show', $investigation)
->with('success', 'Processing started.');
}
public function enterResults(Request $request, InvestigationRequest $investigation): RedirectResponse
{
$this->authorizeAbility($request, 'lab.manage');
$this->authorizeInvestigation($request, $investigation);
$validated = $request->validate([
'value' => ['nullable', 'string', 'max:255'],
'result_summary' => ['nullable', 'string', 'max:5000'],
'interpretation' => ['nullable', 'string', 'max:5000'],
'values' => ['nullable', 'array'],
'values.*.parameter' => ['nullable', 'string', 'max:255'],
'values.*.value' => ['nullable', 'string', 'max:255'],
'attachments' => ['nullable', 'array'],
'attachments.*' => ['file', 'max:10240', 'mimes:pdf,jpeg,png,jpg,webp'],
]);
$this->investigations->enterResults(
$investigation,
$this->ownerRef($request),
$validated,
$this->ownerRef($request),
);
return back()->with('success', 'Results submitted for review.');
}
public function approve(Request $request, InvestigationRequest $investigation): RedirectResponse
{
$this->authorizeAbility($request, 'lab.manage');
$this->authorizeInvestigation($request, $investigation);
$this->investigations->approve($investigation, $this->ownerRef($request), $this->ownerRef($request));
return back()->with('success', 'Results approved.');
}
public function deliver(Request $request, InvestigationRequest $investigation): RedirectResponse
{
$this->authorizeAbility($request, 'lab.manage');
$this->authorizeInvestigation($request, $investigation);
$this->investigations->deliver($investigation, $this->ownerRef($request), $this->ownerRef($request));
return back()->with('success', 'Results delivered to patient record.');
}
public function cancel(Request $request, InvestigationRequest $investigation): RedirectResponse
{
$this->authorizeAbility($request, 'lab.manage');
$this->authorizeInvestigation($request, $investigation);
$this->investigations->cancel($investigation, $this->ownerRef($request), $this->ownerRef($request));
return back()->with('success', 'Investigation 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 authorizeInvestigation(Request $request, InvestigationRequest $investigation): void
{
$this->authorizeOwner($request, $investigation);
abort_unless($investigation->organization_id === $this->organization($request)->id, 404);
$branchId = app(OrganizationResolver::class)->branchScope($this->member($request));
if ($branchId !== null && $investigation->branch_id !== $branchId) {
abort(404);
}
}
}