Keep consultation context on nested clinical pages and add Call again on patient queue cards.
Deploy Ladill Care / deploy (push) Successful in 1m7s
Deploy Ladill Care / deploy (push) Successful in 1m7s
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>
This commit is contained in:
@@ -12,6 +12,7 @@ use App\Models\Visit;
|
||||
use App\Services\Care\AssessmentService;
|
||||
use App\Services\Care\CareFeatures;
|
||||
use App\Services\Care\CarePermissions;
|
||||
use App\Services\Care\ConsultationReturnContext;
|
||||
use App\Services\Care\FhirAssessmentExporter;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -54,6 +55,7 @@ class AssessmentController extends Controller
|
||||
'statuses' => config('care.assessment_statuses'),
|
||||
'categories' => config('care.assessment_template_categories'),
|
||||
'filters' => $request->only(['status', 'template_code', 'category']),
|
||||
'returnConsultation' => app(ConsultationReturnContext::class)->resolve($request, (int) $patient->id),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -111,8 +113,13 @@ class AssessmentController extends Controller
|
||||
$context,
|
||||
);
|
||||
|
||||
$return = app(ConsultationReturnContext::class);
|
||||
|
||||
return redirect()
|
||||
->route('care.assessments.show', $assessment)
|
||||
->route('care.assessments.show', array_filter([
|
||||
'assessment' => $assessment,
|
||||
...$return->routeQuery($request),
|
||||
]))
|
||||
->with('success', 'Assessment started.');
|
||||
}
|
||||
|
||||
@@ -141,8 +148,14 @@ class AssessmentController extends Controller
|
||||
],
|
||||
);
|
||||
|
||||
$return = app(ConsultationReturnContext::class);
|
||||
$return->remember($consultation);
|
||||
|
||||
return redirect()
|
||||
->route('care.assessments.show', $assessment)
|
||||
->route('care.assessments.show', array_filter([
|
||||
'assessment' => $assessment,
|
||||
...$return->query($consultation),
|
||||
]))
|
||||
->with('success', 'Assessment started.');
|
||||
}
|
||||
|
||||
@@ -177,11 +190,15 @@ class AssessmentController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
$return = app(ConsultationReturnContext::class);
|
||||
|
||||
return view('care.assessments.show', [
|
||||
'assessment' => $assessment,
|
||||
'answersByCode' => $answersByCode,
|
||||
'canEdit' => $canEdit,
|
||||
'statuses' => config('care.assessment_statuses'),
|
||||
'returnConsultation' => $return->resolve($request, (int) $assessment->patient_id)
|
||||
?? $return->resolveLinked($request, $assessment->consultation),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -205,8 +222,13 @@ class AssessmentController extends Controller
|
||||
array_key_exists('notes', $validated) ? $validated['notes'] : null,
|
||||
);
|
||||
|
||||
$return = app(ConsultationReturnContext::class);
|
||||
|
||||
return redirect()
|
||||
->route('care.assessments.show', $assessment)
|
||||
->route('care.assessments.show', array_filter([
|
||||
'assessment' => $assessment,
|
||||
...$return->routeQuery($request),
|
||||
]))
|
||||
->with('success', 'Assessment saved.');
|
||||
}
|
||||
|
||||
@@ -240,8 +262,13 @@ class AssessmentController extends Controller
|
||||
$this->ownerRef($request),
|
||||
);
|
||||
|
||||
$return = app(ConsultationReturnContext::class);
|
||||
|
||||
return redirect()
|
||||
->route('care.assessments.show', $assessment)
|
||||
->route('care.assessments.show', array_filter([
|
||||
'assessment' => $assessment,
|
||||
...$return->routeQuery($request),
|
||||
]))
|
||||
->with('success', 'Assessment completed.');
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ namespace App\Http\Controllers\Care;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
|
||||
use App\Models\Bill;
|
||||
use App\Models\Consultation;
|
||||
use App\Models\Visit;
|
||||
use App\Services\Care\BillService;
|
||||
use App\Services\Care\CareQueueBridge;
|
||||
use App\Services\Care\CareQueueContexts;
|
||||
use App\Services\Care\ConsultationReturnContext;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use App\Services\Payments\MerchantGatewayService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@@ -100,8 +102,12 @@ class BillController extends Controller
|
||||
|
||||
$bill = $this->bills->generateFromVisit($visit, $this->ownerRef($request), $this->ownerRef($request));
|
||||
|
||||
return redirect()->route('care.bills.show', $bill)
|
||||
->with('success', 'Bill generated from visit.');
|
||||
$return = app(ConsultationReturnContext::class);
|
||||
|
||||
return redirect()->route('care.bills.show', array_filter([
|
||||
'bill' => $bill,
|
||||
...$return->routeQuery($request),
|
||||
]))->with('success', 'Bill generated from visit.');
|
||||
}
|
||||
|
||||
public function show(Request $request, Bill $bill): View
|
||||
@@ -124,6 +130,12 @@ class BillController extends Controller
|
||||
->except(['gateway'])
|
||||
->all();
|
||||
|
||||
$return = app(ConsultationReturnContext::class);
|
||||
$linked = Consultation::query()
|
||||
->where('visit_id', $bill->visit_id)
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
|
||||
return view('care.bills.show', [
|
||||
'bill' => $bill,
|
||||
'statuses' => config('care.bill_statuses'),
|
||||
@@ -135,6 +147,8 @@ class BillController extends Controller
|
||||
'gatewayConfigured' => $gatewayConfigured,
|
||||
'gatewayProvider' => $gateway?->provider,
|
||||
'gatewayLabels' => config('care.payment_gateways'),
|
||||
'returnConsultation' => $return->resolve($request, (int) $bill->patient_id)
|
||||
?? $return->resolveLinked($request, $linked),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Models\Assessment;
|
||||
use App\Models\AssessmentTemplate;
|
||||
use App\Services\Care\CareFeatures;
|
||||
use App\Services\Care\CarePermissions;
|
||||
use App\Services\Care\ConsultationReturnContext;
|
||||
use App\Services\Care\ConsultationService;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use App\Services\Care\PathwayService;
|
||||
@@ -37,6 +38,8 @@ class ConsultationController extends Controller
|
||||
'investigationRequests.investigationType', 'prescriptions.items',
|
||||
]);
|
||||
|
||||
app(ConsultationReturnContext::class)->remember($consultation);
|
||||
|
||||
$permissions = app(CarePermissions::class);
|
||||
$member = $this->member($request);
|
||||
$organization = $this->organization($request);
|
||||
@@ -235,6 +238,8 @@ class ConsultationController extends Controller
|
||||
$this->ownerRef($request),
|
||||
);
|
||||
|
||||
app(ConsultationReturnContext::class)->forget();
|
||||
|
||||
return redirect()->route('care.patients.show', $consultation->patient)
|
||||
->with('success', 'Consultation completed.');
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ 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;
|
||||
@@ -149,6 +150,7 @@ class InvestigationController extends Controller
|
||||
$investigation->load([
|
||||
'patient', 'investigationType', 'practitioner', 'branch',
|
||||
'result.values', 'result.attachments', 'assignedMember',
|
||||
'consultation.patient', 'consultation.appointment',
|
||||
]);
|
||||
|
||||
$canManage = app(\App\Services\Care\CarePermissions::class)
|
||||
@@ -156,11 +158,15 @@ class InvestigationController extends Controller
|
||||
$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),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Models\Patient;
|
||||
use App\Models\PatientPathway;
|
||||
use App\Services\Care\CareFeatures;
|
||||
use App\Services\Care\CarePermissions;
|
||||
use App\Services\Care\ConsultationReturnContext;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use App\Services\Care\PathwayService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -51,6 +52,7 @@ class PathwayController extends Controller
|
||||
'catalog' => $catalog,
|
||||
'canManage' => $canManage,
|
||||
'statuses' => config('care.patient_pathway_statuses'),
|
||||
'returnConsultation' => app(ConsultationReturnContext::class)->resolve($request, (int) $patient->id),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -91,8 +93,13 @@ class PathwayController extends Controller
|
||||
],
|
||||
);
|
||||
|
||||
$return = app(ConsultationReturnContext::class);
|
||||
|
||||
return redirect()
|
||||
->route('care.pathways.index', $patient)
|
||||
->route('care.pathways.index', array_filter([
|
||||
'patient' => $patient,
|
||||
...$return->routeQuery($request),
|
||||
]))
|
||||
->with('success', "Pathway “{$pathway->name}” activated.");
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\Prescription;
|
||||
use App\Models\Practitioner;
|
||||
use App\Services\Care\CareQueueBridge;
|
||||
use App\Services\Care\CareQueueContexts;
|
||||
use App\Services\Care\ConsultationReturnContext;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use App\Services\Care\PharmacyService;
|
||||
use App\Services\Care\PrescriptionService;
|
||||
@@ -126,6 +127,8 @@ class PrescriptionController extends Controller
|
||||
|
||||
$consultation->load(['patient', 'practitioner']);
|
||||
|
||||
app(ConsultationReturnContext::class)->remember($consultation);
|
||||
|
||||
$practitioners = Practitioner::owned($this->ownerRef($request))
|
||||
->where('organization_id', $this->organization($request)->id)
|
||||
->where('is_active', true)
|
||||
@@ -151,8 +154,13 @@ class PrescriptionController extends Controller
|
||||
$this->ownerRef($request),
|
||||
);
|
||||
|
||||
return redirect()->route('care.prescriptions.show', $prescription)
|
||||
->with('success', 'Prescription created.');
|
||||
$return = app(ConsultationReturnContext::class);
|
||||
$return->remember($consultation);
|
||||
|
||||
return redirect()->route('care.prescriptions.show', array_filter([
|
||||
'prescription' => $prescription,
|
||||
...$return->query($consultation),
|
||||
]))->with('success', 'Prescription created.');
|
||||
}
|
||||
|
||||
public function show(Request $request, Prescription $prescription): View
|
||||
@@ -167,12 +175,16 @@ class PrescriptionController extends Controller
|
||||
$canDispense = app(\App\Services\Care\CarePermissions::class)
|
||||
->can($this->member($request), 'prescriptions.dispense');
|
||||
|
||||
$return = app(ConsultationReturnContext::class);
|
||||
|
||||
return view('care.prescriptions.show', [
|
||||
'prescription' => $prescription,
|
||||
'statuses' => config('care.prescription_statuses'),
|
||||
'routes' => config('care.medication_routes'),
|
||||
'canManage' => $canManage,
|
||||
'canDispense' => $canDispense,
|
||||
'returnConsultation' => $return->resolve($request, (int) $prescription->patient_id)
|
||||
?? $return->resolveLinked($request, $prescription->consultation),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,21 @@ class QueueController extends Controller
|
||||
return back()->with('success', 'Completed '.$ticket['ticket_number'].'.');
|
||||
}
|
||||
|
||||
public function recall(Request $request, Appointment $appointment): RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'consultations.manage');
|
||||
$this->authorizeAppointment($request, $appointment);
|
||||
$organization = $this->organization($request);
|
||||
abort_unless($this->queueBridge->isEnabled($organization), 404);
|
||||
|
||||
$ticket = $this->queueBridge->recall($organization, $appointment);
|
||||
if (! $ticket) {
|
||||
return back()->with('error', 'Could not call patient again.');
|
||||
}
|
||||
|
||||
return back()->with('success', 'Called again '.$ticket['ticket_number'].'.');
|
||||
}
|
||||
|
||||
public function start(Request $request, Appointment $appointment): RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'consultations.manage');
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Care;
|
||||
|
||||
use App\Models\Consultation;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Preserves "serving from consultation" context when doctors navigate to nested
|
||||
* forms (prescriptions, assessments, lab requests, pathways, bills).
|
||||
*
|
||||
* Preference order: explicit ?from_consultation= query → session remembered on
|
||||
* consultation show. Direct access without either shows no consultation chrome.
|
||||
*/
|
||||
class ConsultationReturnContext
|
||||
{
|
||||
public const QUERY_KEY = 'from_consultation';
|
||||
|
||||
public const SESSION_KEY = 'care.from_consultation';
|
||||
|
||||
public function remember(Consultation $consultation): void
|
||||
{
|
||||
if ($consultation->status === Consultation::STATUS_COMPLETED) {
|
||||
$this->forget();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
session([self::SESSION_KEY => $consultation->uuid]);
|
||||
}
|
||||
|
||||
public function forget(): void
|
||||
{
|
||||
session()->forget(self::SESSION_KEY);
|
||||
}
|
||||
|
||||
public function uuidFrom(Request $request): ?string
|
||||
{
|
||||
foreach ([
|
||||
$request->query(self::QUERY_KEY),
|
||||
$request->input(self::QUERY_KEY),
|
||||
session(self::SESSION_KEY),
|
||||
] as $candidate) {
|
||||
if (is_string($candidate) && Str::isUuid($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture an explicit from_consultation posted/queried value into session
|
||||
* so subsequent redirects keep the serving flow.
|
||||
*/
|
||||
public function capture(Request $request): void
|
||||
{
|
||||
$uuid = $request->query(self::QUERY_KEY) ?? $request->input(self::QUERY_KEY);
|
||||
if (is_string($uuid) && Str::isUuid($uuid)) {
|
||||
session([self::SESSION_KEY => $uuid]);
|
||||
}
|
||||
}
|
||||
|
||||
public function resolve(Request $request, ?int $patientId = null): ?Consultation
|
||||
{
|
||||
$this->capture($request);
|
||||
|
||||
$uuid = $this->uuidFrom($request);
|
||||
if ($uuid === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$consultation = Consultation::query()
|
||||
->where('uuid', $uuid)
|
||||
->with(['patient', 'appointment'])
|
||||
->first();
|
||||
|
||||
if (! $consultation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($patientId !== null && (int) $consultation->patient_id !== $patientId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $consultation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve from an explicit linked consultation (e.g. prescription.consultation)
|
||||
* only when the doctor is already in a serving flow for that consultation.
|
||||
*/
|
||||
public function resolveLinked(Request $request, ?Consultation $linked): ?Consultation
|
||||
{
|
||||
if (! $linked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$active = $this->resolve($request, (int) $linked->patient_id);
|
||||
if ($active && $active->id === $linked->id) {
|
||||
return $active;
|
||||
}
|
||||
|
||||
// Nested under consultation route (create prescription) or still in progress
|
||||
// with an explicit query/session match already handled above. For linked
|
||||
// records opened mid-flow without query (POST redirect), prefer the linked
|
||||
// consultation when session matches its uuid.
|
||||
$uuid = $this->uuidFrom($request);
|
||||
if ($uuid !== null && $linked->uuid === $uuid) {
|
||||
$linked->loadMissing(['patient', 'appointment']);
|
||||
|
||||
return $linked;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{from_consultation: string}
|
||||
*/
|
||||
public function query(Consultation $consultation): array
|
||||
{
|
||||
return [self::QUERY_KEY => $consultation->uuid];
|
||||
}
|
||||
|
||||
public function withQuery(string $url, Consultation $consultation): string
|
||||
{
|
||||
$separator = str_contains($url, '?') ? '&' : '?';
|
||||
|
||||
return $url.$separator.self::QUERY_KEY.'='.urlencode($consultation->uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Route parameters to keep return context on redirects (empty when none).
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function routeQuery(Request $request): array
|
||||
{
|
||||
$this->capture($request);
|
||||
$uuid = $this->uuidFrom($request);
|
||||
|
||||
return $uuid ? [self::QUERY_KEY => $uuid] : [];
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
<x-app-layout :title="'Assessments · '.$patient->fullName()">
|
||||
<x-care.consultation-return :consultation="$returnConsultation ?? null" />
|
||||
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-slate-500">Clinical assessments</p>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<x-app-layout :title="$assessment->template->name.' · '.$assessment->patient->fullName()">
|
||||
<x-care.consultation-return :consultation="$returnConsultation ?? null" />
|
||||
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium uppercase tracking-wide {{ $assessment->status === 'completed' ? 'text-emerald-600' : ($assessment->status === 'cancelled' ? 'text-slate-400' : 'text-amber-600') }}">
|
||||
@@ -8,7 +10,7 @@
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
<a href="{{ route('care.patients.show', $assessment->patient) }}" class="text-sky-600 hover:text-sky-700">{{ $assessment->patient->fullName() }}</a>
|
||||
· {{ $assessment->template->code }} v{{ $assessment->template->version }}
|
||||
@if ($assessment->consultation)
|
||||
@if ($assessment->consultation && ! ($returnConsultation ?? null))
|
||||
· <a href="{{ route('care.consultations.show', $assessment->consultation) }}" class="text-sky-600 hover:text-sky-700">Consultation</a>
|
||||
@endif
|
||||
</p>
|
||||
@@ -58,6 +60,9 @@
|
||||
@csrf
|
||||
@method('PUT')
|
||||
@include('care.assessments._form', ['assessment' => $assessment, 'answersByCode' => $answersByCode])
|
||||
@if (($returnConsultation ?? null)?->uuid)
|
||||
<input type="hidden" name="from_consultation" value="{{ $returnConsultation->uuid }}">
|
||||
@endif
|
||||
<div class="flex flex-wrap gap-3 border-t border-slate-100 pt-6">
|
||||
<button type="submit" class="btn-primary">Save draft</button>
|
||||
<button
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@php $money = fn ($minor) => config('care.billing.currency').' '.number_format($minor / 100, 2); @endphp
|
||||
<x-app-layout :title="$bill->invoice_number">
|
||||
<x-care.consultation-return :consultation="$returnConsultation ?? null" />
|
||||
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<p class="text-xs uppercase text-slate-500">{{ $statuses[$bill->status] ?? $bill->status }}</p>
|
||||
|
||||
@@ -25,11 +25,12 @@
|
||||
</x-confirm-dialog>
|
||||
@endif
|
||||
@if ($canPrescribe)
|
||||
<a href="{{ route('care.prescriptions.create', $consultation) }}" class="rounded-lg border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Prescribe</a>
|
||||
<a href="{{ route('care.prescriptions.create', [$consultation, 'from_consultation' => $consultation->uuid]) }}" class="rounded-lg border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Prescribe</a>
|
||||
@endif
|
||||
@if ($canGenerateBill && $isCompleted && $consultation->visit)
|
||||
<form method="POST" action="{{ route('care.bills.generate', $consultation->visit) }}">
|
||||
@csrf
|
||||
<input type="hidden" name="from_consultation" value="{{ $consultation->uuid }}">
|
||||
<button type="submit" class="rounded-lg bg-slate-800 px-4 py-2 text-sm font-medium text-white hover:bg-slate-900">Generate bill</button>
|
||||
</form>
|
||||
@endif
|
||||
@@ -62,7 +63,7 @@
|
||||
<p class="font-medium text-slate-700">Requested tests</p>
|
||||
@foreach ($consultation->investigationRequests as $req)
|
||||
<p class="mt-1">
|
||||
<a href="{{ route('care.lab.requests.show', $req) }}" class="text-sky-600">{{ $req->investigationType->name }}</a>
|
||||
<a href="{{ route('care.lab.requests.show', [$req, 'from_consultation' => $consultation->uuid]) }}" class="text-sky-600">{{ $req->investigationType->name }}</a>
|
||||
· {{ config('care.investigation_statuses')[$req->status] ?? $req->status }}
|
||||
</p>
|
||||
@endforeach
|
||||
@@ -77,7 +78,7 @@
|
||||
<ul class="mt-4 space-y-2 text-sm">
|
||||
@foreach ($consultation->prescriptions as $rx)
|
||||
<li>
|
||||
<a href="{{ route('care.prescriptions.show', $rx) }}" class="text-sky-600 hover:text-sky-700">
|
||||
<a href="{{ route('care.prescriptions.show', [$rx, 'from_consultation' => $consultation->uuid]) }}" class="text-sky-600 hover:text-sky-700">
|
||||
{{ $rx->created_at->format('d M Y H:i') }}
|
||||
</a>
|
||||
· {{ config('care.prescription_statuses')[$rx->status] ?? $rx->status }}
|
||||
@@ -100,13 +101,14 @@
|
||||
</p>
|
||||
</div>
|
||||
@if ($universalAssessment)
|
||||
<a href="{{ route('care.assessments.show', $universalAssessment) }}" class="rounded-lg bg-sky-600 px-3 py-2 text-sm font-medium text-white hover:bg-sky-700">
|
||||
<a href="{{ route('care.assessments.show', [$universalAssessment, 'from_consultation' => $consultation->uuid]) }}" class="rounded-lg bg-sky-600 px-3 py-2 text-sm font-medium text-white hover:bg-sky-700">
|
||||
{{ $universalAssessment->isDraft() ? 'Continue form' : 'View form' }}
|
||||
</a>
|
||||
@elseif (($canCaptureUniversal ?? false) && ! $isCompleted)
|
||||
<form method="POST" action="{{ route('care.consultations.assessments.store', $consultation) }}">
|
||||
@csrf
|
||||
<input type="hidden" name="template_code" value="universal_intake">
|
||||
<input type="hidden" name="from_consultation" value="{{ $consultation->uuid }}">
|
||||
<button type="submit" class="rounded-lg bg-sky-600 px-3 py-2 text-sm font-medium text-white hover:bg-sky-700">Start patient history form</button>
|
||||
</form>
|
||||
@endif
|
||||
@@ -149,7 +151,7 @@
|
||||
<section class="mt-6 rounded-2xl border border-slate-200 bg-white p-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Condition pathways</h2>
|
||||
<a href="{{ route('care.pathways.index', $consultation->patient) }}" class="text-sm text-sky-600 hover:text-sky-700">View all</a>
|
||||
<a href="{{ route('care.pathways.index', [$consultation->patient, 'from_consultation' => $consultation->uuid]) }}" class="text-sm text-sky-600 hover:text-sky-700">View all</a>
|
||||
</div>
|
||||
|
||||
@if (($activePathways ?? collect())->isNotEmpty())
|
||||
@@ -187,13 +189,14 @@
|
||||
<span class="text-xs text-amber-600">Form not available yet</span>
|
||||
@elseif ($instrument['assessment'])
|
||||
<span class="text-xs text-slate-500">{{ $assessmentStatuses[$instrument['assessment']->status] ?? $instrument['assessment']->status }}</span>
|
||||
<a href="{{ route('care.assessments.show', $instrument['assessment']) }}" class="text-sky-600 hover:text-sky-700">
|
||||
<a href="{{ route('care.assessments.show', [$instrument['assessment'], 'from_consultation' => $consultation->uuid]) }}" class="text-sky-600 hover:text-sky-700">
|
||||
{{ $instrument['assessment']->isDraft() ? 'Continue' : 'View' }}
|
||||
</a>
|
||||
@elseif (($canCaptureAssessment ?? false) && ! $isCompleted)
|
||||
<form method="POST" action="{{ route('care.consultations.assessments.store', $consultation) }}">
|
||||
@csrf
|
||||
<input type="hidden" name="template_code" value="{{ $instrument['template_code'] }}">
|
||||
<input type="hidden" name="from_consultation" value="{{ $consultation->uuid }}">
|
||||
<button type="submit" class="text-sm font-medium text-sky-600 hover:text-sky-700">Start</button>
|
||||
</form>
|
||||
@else
|
||||
@@ -222,6 +225,7 @@
|
||||
@csrf
|
||||
<input type="hidden" name="pathway_code" value="{{ $suggestion['pathway_code'] }}">
|
||||
<input type="hidden" name="consultation_uuid" value="{{ $consultation->uuid }}">
|
||||
<input type="hidden" name="from_consultation" value="{{ $consultation->uuid }}">
|
||||
<button type="submit" class="text-sm font-medium text-sky-600 hover:text-sky-700">Activate</button>
|
||||
</form>
|
||||
@endif
|
||||
@@ -242,14 +246,14 @@
|
||||
<section class="mt-6 rounded-2xl border border-slate-200 bg-white p-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Clinical forms this visit</h2>
|
||||
<a href="{{ route('care.assessments.index', $consultation->patient) }}" class="text-sm text-sky-600 hover:text-sky-700">All patient forms</a>
|
||||
<a href="{{ route('care.assessments.index', [$consultation->patient, 'from_consultation' => $consultation->uuid]) }}" class="text-sm text-sky-600 hover:text-sky-700">All patient forms</a>
|
||||
</div>
|
||||
|
||||
@if (($consultationAssessments ?? collect())->isNotEmpty())
|
||||
<ul class="mt-4 space-y-2 text-sm">
|
||||
@foreach ($consultationAssessments as $item)
|
||||
<li>
|
||||
<a href="{{ route('care.assessments.show', $item) }}" class="text-sky-600 hover:text-sky-700">
|
||||
<a href="{{ route('care.assessments.show', [$item, 'from_consultation' => $consultation->uuid]) }}" class="text-sky-600 hover:text-sky-700">
|
||||
{{ $item->template->name }}
|
||||
</a>
|
||||
· {{ ($assessmentStatuses[$item->status] ?? $item->status) }}
|
||||
@@ -263,6 +267,7 @@
|
||||
@if (($canCaptureAssessment ?? false) && ! $isCompleted && ($startableTemplates ?? collect())->isNotEmpty())
|
||||
<form method="POST" action="{{ route('care.consultations.assessments.store', $consultation) }}" class="mt-4 flex flex-wrap items-end gap-3 border-t border-slate-100 pt-4">
|
||||
@csrf
|
||||
<input type="hidden" name="from_consultation" value="{{ $consultation->uuid }}">
|
||||
<div class="min-w-[12rem] flex-1">
|
||||
<label class="block text-xs text-slate-500">Add another form</label>
|
||||
<select name="template_code" required class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
@@ -439,7 +444,7 @@
|
||||
<section class="rounded-2xl border border-sky-100 bg-white p-6">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Patient history form</h2>
|
||||
<a href="{{ route('care.assessments.show', $universalAssessment) }}" class="text-sm text-sky-600 hover:text-sky-700">Open</a>
|
||||
<a href="{{ route('care.assessments.show', [$universalAssessment, 'from_consultation' => $consultation->uuid]) }}" class="text-sm text-sky-600 hover:text-sky-700">Open</a>
|
||||
</div>
|
||||
<dl class="mt-4 space-y-2 text-sm">
|
||||
@foreach ($universalAssessment->answers->sortBy(fn ($a) => $a->question?->sort_order ?? 0) as $answer)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<x-app-layout :title="$investigation->investigationType->name">
|
||||
<x-care.consultation-return :consultation="$returnConsultation ?? null" />
|
||||
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium uppercase text-slate-500">{{ $statuses[$investigation->status] ?? $investigation->status }}</p>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<x-app-layout :title="'Pathways · '.$patient->fullName()">
|
||||
<x-care.consultation-return :consultation="$returnConsultation ?? null" />
|
||||
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-slate-500">Condition pathways</p>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<x-app-layout :title="'Prescription · '.$consultation->patient->fullName()">
|
||||
<x-care.consultation-return :consultation="$consultation" />
|
||||
|
||||
<h1 class="text-xl font-semibold text-slate-900">New prescription</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">{{ $consultation->patient->fullName() }} ({{ $consultation->patient->patient_number }})</p>
|
||||
|
||||
@@ -34,6 +36,7 @@
|
||||
<label class="block text-sm font-medium text-slate-700">Notes</label>
|
||||
<textarea name="notes" rows="2" class="mt-1 w-full rounded-lg border-slate-300 text-sm"></textarea>
|
||||
<input type="hidden" name="activate" value="1">
|
||||
<input type="hidden" name="from_consultation" value="{{ $consultation->uuid }}">
|
||||
</section>
|
||||
<div class="flex gap-3">
|
||||
<button type="submit" class="btn-primary">Create prescription</button>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<x-app-layout :title="'Prescription · '.$prescription->patient->fullName()">
|
||||
<x-care.consultation-return :consultation="$returnConsultation ?? null" />
|
||||
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<p class="text-xs uppercase text-slate-500">{{ $statuses[$prescription->status] ?? $prescription->status }}</p>
|
||||
|
||||
@@ -67,14 +67,22 @@
|
||||
</p>
|
||||
<p class="text-xs text-slate-500">{{ $appointment->patient->patient_number }} · {{ $appointment->reason ?? '—' }}</p>
|
||||
</div>
|
||||
@if ($canConsult)
|
||||
<form method="POST" action="{{ route('care.queue.start', $appointment) }}">
|
||||
@csrf
|
||||
<button type="submit" class="rounded-lg bg-sky-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-sky-700">
|
||||
{{ ! empty($queueIntegration['enabled']) && ($appointment->queue_ticket_status ?? '') === 'called' ? 'Serve & start' : 'Start' }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
<div class="flex flex-wrap items-center justify-end gap-2">
|
||||
@if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_uuid && in_array($appointment->queue_ticket_status ?? '', ['called', 'serving'], true) && $canConsult)
|
||||
<form method="POST" action="{{ route('care.queue.recall', $appointment) }}">
|
||||
@csrf
|
||||
<button type="submit" class="rounded-lg border border-indigo-200 bg-indigo-50 px-3 py-1.5 text-xs font-medium text-indigo-700 hover:bg-indigo-100">Call again</button>
|
||||
</form>
|
||||
@endif
|
||||
@if ($canConsult)
|
||||
<form method="POST" action="{{ route('care.queue.start', $appointment) }}">
|
||||
@csrf
|
||||
<button type="submit" class="rounded-lg bg-sky-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-sky-700">
|
||||
{{ ! empty($queueIntegration['enabled']) && ($appointment->queue_ticket_status ?? '') === 'called' ? 'Serve & start' : 'Start' }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-slate-500">No patients waiting.</p>
|
||||
@@ -99,9 +107,17 @@
|
||||
</p>
|
||||
<p class="text-xs text-slate-500">{{ $appointment->practitioner?->name ?? 'Unassigned' }}</p>
|
||||
</div>
|
||||
@if ($appointment->consultation)
|
||||
<a href="{{ route('care.consultations.show', $appointment->consultation) }}" class="text-sm text-sky-600 hover:text-sky-700">Open</a>
|
||||
@endif
|
||||
<div class="flex flex-wrap items-center justify-end gap-2">
|
||||
@if (! empty($queueIntegration['enabled']) && $appointment->queue_ticket_uuid && in_array($appointment->queue_ticket_status ?? '', ['called', 'serving'], true) && $canConsult)
|
||||
<form method="POST" action="{{ route('care.queue.recall', $appointment) }}">
|
||||
@csrf
|
||||
<button type="submit" class="rounded-lg border border-indigo-200 bg-indigo-50 px-3 py-1.5 text-xs font-medium text-indigo-700 hover:bg-indigo-100">Call again</button>
|
||||
</form>
|
||||
@endif
|
||||
@if ($appointment->consultation)
|
||||
<a href="{{ route('care.consultations.show', $appointment->consultation) }}" class="text-sm text-sky-600 hover:text-sky-700">Open</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-slate-500">No active consultations.</p>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
@props([
|
||||
'consultation' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
use App\Services\Care\ConsultationReturnContext;
|
||||
|
||||
/** @var \App\Models\Consultation|null $consultation */
|
||||
$consultation = $consultation
|
||||
?? app(ConsultationReturnContext::class)->resolve(request());
|
||||
|
||||
$appointment = $consultation?->appointment;
|
||||
$queueOn = filled($appointment?->queue_ticket_number);
|
||||
@endphp
|
||||
|
||||
@if ($consultation)
|
||||
<div {{ $attributes->merge(['class' => 'mb-6 flex flex-wrap items-center justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50/80 px-4 py-3']) }}>
|
||||
<div class="min-w-0">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-amber-800">Continue consultation</p>
|
||||
<p class="mt-0.5 text-sm text-slate-800">
|
||||
<span class="font-medium">{{ $consultation->patient->fullName() }}</span>
|
||||
@if ($queueOn)
|
||||
<span class="ml-2 inline-flex items-center gap-1.5 align-middle">
|
||||
@include('care.partials.queue-ticket', [
|
||||
'ticketNumber' => $appointment->queue_ticket_number,
|
||||
'ticketStatus' => $appointment->queue_ticket_status,
|
||||
'destination' => $appointment->queue_destination,
|
||||
'staffDisplayName' => $appointment->queue_staff_display_name,
|
||||
])
|
||||
</span>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="{{ route('care.consultations.show', $consultation) }}"
|
||||
class="shrink-0 rounded-lg border border-amber-300 bg-white px-3 py-1.5 text-sm font-medium text-amber-900 hover:bg-amber-100"
|
||||
>
|
||||
Back to consultation
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
@@ -90,6 +90,7 @@ Route::middleware(['auth', 'platform.session'])->group(function () {
|
||||
Route::get('/queue', [QueueController::class, 'index'])->name('care.queue.index');
|
||||
Route::post('/queue/call-next', [QueueController::class, 'callNext'])->name('care.queue.call-next');
|
||||
Route::post('/queue/{appointment}/start', [QueueController::class, 'start'])->name('care.queue.start');
|
||||
Route::post('/queue/{appointment}/recall', [QueueController::class, 'recall'])->name('care.queue.recall');
|
||||
Route::post('/queue/{appointment}/complete-ticket', [QueueController::class, 'completeTicket'])->name('care.queue.complete-ticket');
|
||||
|
||||
Route::middleware('care.paid')->group(function () {
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Http\Middleware\EnsurePlatformSession;
|
||||
use App\Models\Branch;
|
||||
use App\Models\Consultation;
|
||||
use App\Models\InvestigationRequest;
|
||||
use App\Models\InvestigationType;
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use App\Models\Patient;
|
||||
use App\Models\Prescription;
|
||||
use App\Models\User;
|
||||
use App\Models\Visit;
|
||||
use App\Services\Care\CareFeatures;
|
||||
use App\Services\Care\ConsultationReturnContext;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
class CareConsultationReturnContextTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected User $user;
|
||||
|
||||
protected Organization $organization;
|
||||
|
||||
protected Branch $branch;
|
||||
|
||||
protected Patient $patient;
|
||||
|
||||
protected Consultation $consultation;
|
||||
|
||||
protected Visit $visit;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->withoutMiddleware(EnsurePlatformSession::class);
|
||||
|
||||
$this->user = User::create([
|
||||
'public_id' => 'return-ctx-user',
|
||||
'name' => 'Doctor',
|
||||
'email' => 'return-ctx@example.com',
|
||||
]);
|
||||
|
||||
$this->organization = Organization::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'name' => 'Return Clinic',
|
||||
'slug' => 'return-clinic',
|
||||
'timezone' => 'UTC',
|
||||
'settings' => [
|
||||
'onboarded' => true,
|
||||
'plan' => 'pro',
|
||||
'plan_expires_at' => now()->addMonth()->toIso8601String(),
|
||||
'rollout' => [
|
||||
CareFeatures::ASSESSMENTS_ENGINE => true,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
Member::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'user_ref' => $this->user->public_id,
|
||||
'role' => 'hospital_admin',
|
||||
]);
|
||||
|
||||
$this->branch = Branch::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'name' => 'Main',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->patient = Patient::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'branch_id' => $this->branch->id,
|
||||
'patient_number' => 'P-RET-1',
|
||||
'first_name' => 'Ada',
|
||||
'last_name' => 'Lovelace',
|
||||
]);
|
||||
|
||||
$this->visit = Visit::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'branch_id' => $this->branch->id,
|
||||
'patient_id' => $this->patient->id,
|
||||
'status' => Visit::STATUS_OPEN,
|
||||
'checked_in_at' => now(),
|
||||
]);
|
||||
|
||||
$this->consultation = Consultation::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'visit_id' => $this->visit->id,
|
||||
'patient_id' => $this->patient->id,
|
||||
'status' => Consultation::STATUS_DRAFT,
|
||||
'started_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_consultation_show_remembers_return_context(): void
|
||||
{
|
||||
$this->actingAs($this->user)
|
||||
->get(route('care.consultations.show', $this->consultation))
|
||||
->assertOk()
|
||||
->assertSessionHas(ConsultationReturnContext::SESSION_KEY, $this->consultation->uuid);
|
||||
}
|
||||
|
||||
public function test_nested_pages_show_back_to_consultation_when_context_present(): void
|
||||
{
|
||||
$type = InvestigationType::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'name' => 'CBC',
|
||||
'category' => 'haematology',
|
||||
'code' => 'cbc',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$lab = InvestigationRequest::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'branch_id' => $this->branch->id,
|
||||
'patient_id' => $this->patient->id,
|
||||
'visit_id' => $this->visit->id,
|
||||
'consultation_id' => $this->consultation->id,
|
||||
'investigation_type_id' => $type->id,
|
||||
'status' => InvestigationRequest::STATUS_PENDING,
|
||||
'priority' => 'routine',
|
||||
]);
|
||||
|
||||
$rx = Prescription::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'visit_id' => $this->visit->id,
|
||||
'consultation_id' => $this->consultation->id,
|
||||
'patient_id' => $this->patient->id,
|
||||
'status' => Prescription::STATUS_ACTIVE,
|
||||
]);
|
||||
|
||||
$this->actingAs($this->user)
|
||||
->withSession([ConsultationReturnContext::SESSION_KEY => $this->consultation->uuid])
|
||||
->get(route('care.lab.requests.show', [
|
||||
'investigation' => $lab,
|
||||
'from_consultation' => $this->consultation->uuid,
|
||||
]))
|
||||
->assertOk()
|
||||
->assertSee('Back to consultation')
|
||||
->assertSee('Continue consultation')
|
||||
->assertSee('Ada Lovelace');
|
||||
|
||||
$this->actingAs($this->user)
|
||||
->withSession([ConsultationReturnContext::SESSION_KEY => $this->consultation->uuid])
|
||||
->get(route('care.prescriptions.show', [
|
||||
'prescription' => $rx,
|
||||
'from_consultation' => $this->consultation->uuid,
|
||||
]))
|
||||
->assertOk()
|
||||
->assertSee('Back to consultation');
|
||||
|
||||
$this->actingAs($this->user)
|
||||
->withSession([ConsultationReturnContext::SESSION_KEY => $this->consultation->uuid])
|
||||
->get(route('care.pathways.index', [
|
||||
'patient' => $this->patient,
|
||||
'from_consultation' => $this->consultation->uuid,
|
||||
]))
|
||||
->assertOk()
|
||||
->assertSee('Back to consultation');
|
||||
|
||||
$this->actingAs($this->user)
|
||||
->get(route('care.prescriptions.create', $this->consultation))
|
||||
->assertOk()
|
||||
->assertSee('Back to consultation');
|
||||
}
|
||||
|
||||
public function test_direct_access_without_context_hides_consultation_chrome(): void
|
||||
{
|
||||
$type = InvestigationType::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'name' => 'CBC',
|
||||
'category' => 'haematology',
|
||||
'code' => 'cbc2',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$lab = InvestigationRequest::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'branch_id' => $this->branch->id,
|
||||
'patient_id' => $this->patient->id,
|
||||
'visit_id' => $this->visit->id,
|
||||
'consultation_id' => $this->consultation->id,
|
||||
'investigation_type_id' => $type->id,
|
||||
'status' => InvestigationRequest::STATUS_PENDING,
|
||||
'priority' => 'routine',
|
||||
]);
|
||||
|
||||
$this->actingAs($this->user)
|
||||
->get(route('care.lab.requests.show', $lab))
|
||||
->assertOk()
|
||||
->assertDontSee('Back to consultation')
|
||||
->assertDontSee('Continue consultation');
|
||||
|
||||
$this->actingAs($this->user)
|
||||
->get(route('care.pathways.index', $this->patient))
|
||||
->assertOk()
|
||||
->assertDontSee('Back to consultation');
|
||||
}
|
||||
|
||||
public function test_completing_consultation_clears_return_context(): void
|
||||
{
|
||||
$this->actingAs($this->user)
|
||||
->withSession([ConsultationReturnContext::SESSION_KEY => $this->consultation->uuid])
|
||||
->post(route('care.consultations.complete', $this->consultation))
|
||||
->assertRedirect(route('care.patients.show', $this->patient))
|
||||
->assertSessionMissing(ConsultationReturnContext::SESSION_KEY);
|
||||
}
|
||||
}
|
||||
@@ -320,5 +320,53 @@ class CareQueueBridgeTest extends TestCase
|
||||
->assertSessionHas('info', 'No patients waiting at your consultation service point.')
|
||||
->assertSessionMissing('error');
|
||||
}
|
||||
|
||||
public function test_recall_reannounces_called_ticket_from_patient_queue(): void
|
||||
{
|
||||
$appointment = Appointment::create([
|
||||
'owner_ref' => $this->owner->public_id,
|
||||
'organization_id' => $this->organization->id,
|
||||
'branch_id' => $this->branch->id,
|
||||
'patient_id' => $this->patient->id,
|
||||
'type' => Appointment::TYPE_WALK_IN,
|
||||
'status' => Appointment::STATUS_WAITING,
|
||||
'scheduled_at' => now(),
|
||||
'waiting_at' => now(),
|
||||
'queue_position' => 1,
|
||||
'queue_ticket_uuid' => 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee',
|
||||
'queue_ticket_number' => 'C055',
|
||||
'queue_ticket_status' => 'called',
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'*/tickets/*/action*' => Http::response([
|
||||
'data' => [
|
||||
'uuid' => 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee',
|
||||
'ticket_number' => 'C055',
|
||||
'status' => 'called',
|
||||
'customer_name' => 'Ada Lovelace',
|
||||
],
|
||||
], 200),
|
||||
]);
|
||||
|
||||
$this->actingAs($this->owner)
|
||||
->from(route('care.queue.index'))
|
||||
->post(route('care.queue.recall', $appointment))
|
||||
->assertRedirect(route('care.queue.index'))
|
||||
->assertSessionHas('success');
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
return str_contains($request->url(), '/tickets/eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee/action')
|
||||
&& ($request['action'] ?? null) === 'recall';
|
||||
});
|
||||
|
||||
$this->assertSame('called', $appointment->fresh()->queue_ticket_status);
|
||||
|
||||
$this->actingAs($this->owner)
|
||||
->get(route('care.queue.index', ['branch_id' => $this->branch->id]))
|
||||
->assertOk()
|
||||
->assertSee('Call again')
|
||||
->assertSee('C055');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user