diff --git a/app/Http/Controllers/Care/QueueController.php b/app/Http/Controllers/Care/QueueController.php index 2c8ecd7..bf5b04a 100644 --- a/app/Http/Controllers/Care/QueueController.php +++ b/app/Http/Controllers/Care/QueueController.php @@ -11,6 +11,7 @@ use App\Services\Care\BranchContext; use App\Services\Care\CarePermissions; use App\Services\Care\CareQueueBridge; use App\Services\Care\CareQueueContexts; +use App\Services\Care\CareQueueSessionAdvance; use App\Services\Care\ConsultationService; use App\Services\Care\OrganizationResolver; use Illuminate\Http\RedirectResponse; @@ -157,7 +158,7 @@ class QueueController extends Controller ]); } - public function callNext(Request $request): RedirectResponse + public function callNext(Request $request, CareQueueSessionAdvance $advance): RedirectResponse { abort_unless(app(CarePermissions::class)->handlesFloorCare($this->member($request)), 403); $this->authorizeAbility($request, 'appointments.view'); @@ -165,6 +166,7 @@ class QueueController extends Controller abort_unless($this->queueBridge->isEnabled($organization), 404); $member = $this->member($request); + $owner = $this->ownerRef($request); $practitionerScope = app(OrganizationResolver::class)->practitionerScope($organization, $member); $branchId = $this->branchContext->resolve($request, $organization, $member); $practitionerId = $request->input('practitioner_id') ? (int) $request->input('practitioner_id') : null; @@ -172,7 +174,7 @@ class QueueController extends Controller if ($practitionerScope !== null) { abort_unless($practitionerScope !== [], 422); $practitionerId = $practitionerScope[0]; - $prac = Practitioner::owned($this->ownerRef($request))->find($practitionerId); + $prac = Practitioner::owned($owner)->find($practitionerId); $branchId = (int) ($prac?->branch_id ?: $branchId); } @@ -180,13 +182,20 @@ class QueueController extends Controller $context = CareQueueContexts::CONSULTATION; if ($practitionerId) { - $prac = Practitioner::owned($this->ownerRef($request))->find($practitionerId); + $prac = Practitioner::owned($owner)->find($practitionerId); $specialtyContext = $this->queueBridge->contextForPractitioner($organization, $prac); if ($specialtyContext !== CareQueueContexts::CONSULTATION) { $context = $specialtyContext; } } + if ($request->filled('appointment_id')) { + $current = Appointment::owned($owner) + ->where('organization_id', $organization->id) + ->findOrFail((int) $request->input('appointment_id')); + $advance->endCurrentEncounter($current, $owner, $owner); + } + $result = $this->queueBridge->callNext( $organization, $context, @@ -203,6 +212,11 @@ class QueueController extends Controller return back()->with('info', "No patients waiting at your {$label} service point."); } + $entity = $result['entity'] ?? null; + if ($entity instanceof Appointment) { + return $advance->redirectToOpenedPatient($entity, $ticket); + } + return back()->with('success', 'Called '.$ticket['ticket_number'].' — '.($ticket['customer_name'] ?? 'patient').'.'); } diff --git a/app/Http/Controllers/Care/SpecialtyModuleController.php b/app/Http/Controllers/Care/SpecialtyModuleController.php index 7b11a79..14c37dc 100644 --- a/app/Http/Controllers/Care/SpecialtyModuleController.php +++ b/app/Http/Controllers/Care/SpecialtyModuleController.php @@ -10,6 +10,7 @@ use App\Models\PatientAttachment; use App\Models\Visit; use App\Services\Care\AppointmentService; use App\Services\Care\CareQueueBridge; +use App\Services\Care\CareQueueSessionAdvance; use App\Services\Care\ConsultationService; use App\Services\Care\OrganizationResolver; use App\Services\Care\SpecialtyClinicalRecordService; @@ -309,12 +310,14 @@ class SpecialtyModuleController extends Controller string $module, SpecialtyModuleService $modules, CareQueueBridge $queueBridge, + CareQueueSessionAdvance $advance, ): RedirectResponse { $definition = $modules->definition($module); abort_unless($definition, 404); $organization = $this->organization($request); $member = $this->member($request); + $owner = $this->ownerRef($request); abort_unless($modules->memberCanAccess($organization, $member, $module), 403); abort_unless($queueBridge->isEnabled($organization), 404); @@ -327,7 +330,7 @@ class SpecialtyModuleController extends Controller if ($practitionerScope !== null) { abort_unless($practitionerScope !== [], 422); $practitionerId = $practitionerScope[0]; - $pracBranch = (int) \App\Models\Practitioner::owned($this->ownerRef($request)) + $pracBranch = (int) \App\Models\Practitioner::owned($owner) ->whereKey($practitionerId) ->value('branch_id'); if ($pracBranch > 0) { @@ -337,10 +340,16 @@ class SpecialtyModuleController extends Controller abort_unless($branchId > 0, 422); + if ($request->filled('appointment_id')) { + $current = Appointment::owned($owner) + ->where('organization_id', $organization->id) + ->findOrFail((int) $request->input('appointment_id')); + $advance->endCurrentEncounter($current, $owner, $owner); + } + $result = $queueBridge->callNext($organization, $module, $branchId, $member, $practitionerId); $ticket = $result['ticket'] ?? null; if (! $ticket) { - $owner = $this->ownerRef($request); $departmentIds = Department::owned($owner) ->where('type', $definition['department_type'] ?? 'general') ->where('is_active', true) @@ -381,6 +390,15 @@ class SpecialtyModuleController extends Controller return back()->with('info', 'No patients waiting in this queue.'); } + $entity = $result['entity'] ?? null; + if ($entity instanceof Appointment) { + return $advance->redirectToOpenedPatient( + $entity, + $ticket, + 'care.queue.index', + ); + } + $name = $ticket['customer_name'] ?? 'patient'; return back()->with('success', 'Called '.$ticket['ticket_number'].' — '.$name.'.'); diff --git a/app/Services/Care/CareQueueSessionAdvance.php b/app/Services/Care/CareQueueSessionAdvance.php new file mode 100644 index 0000000..9566b13 --- /dev/null +++ b/app/Services/Care/CareQueueSessionAdvance.php @@ -0,0 +1,82 @@ +loadMissing(['consultation', 'organization']); + + $consultation = $appointment->consultation; + if ($consultation && $consultation->status !== Consultation::STATUS_COMPLETED) { + $this->consultations->complete($consultation, $ownerRef, $actorRef); + + return; + } + + if ($appointment->status === Appointment::STATUS_IN_CONSULTATION) { + $this->appointments->complete($appointment, $ownerRef, $actorRef); + + return; + } + + if ($appointment->organization && in_array($appointment->queue_ticket_status, ['called', 'serving'], true)) { + $this->queueBridge->complete($appointment->organization, $appointment); + } + } + + /** + * Open the patient who was just called — without starting the session. + * Specialty → workspace overview; GP → appointment (Start + Call again). + */ + public function redirectToOpenedPatient( + Appointment|Model|null $entity, + array $ticket, + string $fallbackRoute = 'care.queue.index', + array $fallbackParams = [], + ): RedirectResponse { + $message = 'Called '.($ticket['ticket_number'] ?? 'next').' — '.($ticket['customer_name'] ?? 'patient').'.'; + + if (! $entity instanceof Appointment) { + return redirect()->route($fallbackRoute, $fallbackParams)->with('success', $message); + } + + $entity->loadMissing(['visit', 'organization', 'department']); + $organization = $entity->organization; + if (! $organization) { + return redirect()->route($fallbackRoute, $fallbackParams)->with('success', $message); + } + + $context = $this->queueBridge->contextForAppointment($organization, $entity); + + if ($context !== CareQueueContexts::CONSULTATION && $entity->visit) { + return redirect() + ->route('care.specialty.workspace', [ + 'module' => $context, + 'visit' => $entity->visit, + 'tab' => 'overview', + ]) + ->with('success', $message); + } + + return redirect() + ->route('care.appointments.show', $entity) + ->with('success', $message); + } +} diff --git a/app/Services/Care/SpecialtyShellService.php b/app/Services/Care/SpecialtyShellService.php index 3c28965..fe04acc 100644 --- a/app/Services/Care/SpecialtyShellService.php +++ b/app/Services/Care/SpecialtyShellService.php @@ -119,8 +119,27 @@ class SpecialtyShellService public function workspaceTabs(string $moduleKey): array { $tabs = $this->definition($moduleKey)['workspace_tabs'] ?? []; + if (! is_array($tabs)) { + return []; + } - return is_array($tabs) ? $tabs : []; + if (array_key_exists('timeline', $tabs)) { + return $tabs; + } + + $merged = []; + foreach ($tabs as $key => $label) { + $merged[$key] = $label; + if ($key === 'overview') { + $merged['timeline'] = 'Timeline'; + } + } + + if (! array_key_exists('timeline', $merged)) { + $merged['timeline'] = 'Timeline'; + } + + return $merged; } /** diff --git a/config/care_specialty_shell.php b/config/care_specialty_shell.php index 081d5e7..a13f6ed 100644 --- a/config/care_specialty_shell.php +++ b/config/care_specialty_shell.php @@ -28,6 +28,7 @@ return [ ], 'workspace_tabs' => [ 'overview' => 'Overview', + 'timeline' => 'Timeline', 'clinical_notes' => 'Clinical notes', 'orders' => 'Orders', 'billing' => 'Billing', diff --git a/resources/views/care/appointments/show.blade.php b/resources/views/care/appointments/show.blade.php index 796a3b3..f5f2fe9 100644 --- a/resources/views/care/appointments/show.blade.php +++ b/resources/views/care/appointments/show.blade.php @@ -19,6 +19,12 @@ No show @endif + @if ($appointment->queue_ticket_status === 'called') +
+ @csrf + +
+ @endif @if (($canStartConsultation ?? $canConsult) && in_array($appointment->status, [\App\Models\Appointment::STATUS_WAITING, \App\Models\Appointment::STATUS_CHECKED_IN], true))
@csrf diff --git a/resources/views/care/consultations/partials/actions-menu.blade.php b/resources/views/care/consultations/partials/actions-menu.blade.php index dd6fbbb..44d80ef 100644 --- a/resources/views/care/consultations/partials/actions-menu.blade.php +++ b/resources/views/care/consultations/partials/actions-menu.blade.php @@ -5,6 +5,8 @@ $btnPrimary = $btn.' bg-indigo-600 text-white hover:bg-indigo-700'; $btnSecondary = $btn.' border border-slate-200 bg-white text-slate-800 hover:bg-slate-50'; $btnAccent = $btn.' border border-sky-200 bg-sky-50 text-sky-900 hover:bg-sky-100'; + $appointment = $consultation->appointment; + $ticketCalled = $appointment && $appointment->queue_ticket_status === 'called'; @endphp @include('care.partials.queue-ops', [ @@ -12,8 +14,16 @@ 'queueCallNextRoute' => 'care.queue.call-next', 'queueCallNextParams' => [], 'branchId' => $queueBranchId ?? null, + 'currentAppointmentId' => $appointment?->id, ]) +@if ($ticketCalled && $appointment) + + @csrf + +
+@endif + @if ($canManage && ! $isCompleted) @endif diff --git a/resources/views/care/specialty/partials/actions-menu.blade.php b/resources/views/care/specialty/partials/actions-menu.blade.php index 9c7d8fc..d2624e3 100644 --- a/resources/views/care/specialty/partials/actions-menu.blade.php +++ b/resources/views/care/specialty/partials/actions-menu.blade.php @@ -5,6 +5,25 @@ $btnSecondary = $btn.' border border-slate-200 bg-white text-slate-800 hover:bg-slate-50'; $btnAccent = $btn.' border border-indigo-200 bg-indigo-50 text-indigo-900 hover:bg-indigo-100'; $onWorkspace = ($section ?? '') === 'workspace'; + $appointment = $workspaceVisit?->appointment; + $openConsultation = $appointment?->consultation; + $ticketCalled = $appointment && $appointment->queue_ticket_status === 'called'; + $canStartConsultation = $appointment && in_array($appointment->status, [ + \App\Models\Appointment::STATUS_WAITING, + \App\Models\Appointment::STATUS_CHECKED_IN, + ], true); + $consultationInProgress = $appointment + && ( + $appointment->status === \App\Models\Appointment::STATUS_IN_CONSULTATION + || ($openConsultation && $openConsultation->status !== \App\Models\Consultation::STATUS_COMPLETED) + ); + $canCompleteConsultation = $consultationInProgress + && $openConsultation + && $openConsultation->status !== \App\Models\Consultation::STATUS_COMPLETED; + $chartTab = collect($workspaceTabs ?? []) + ->keys() + ->first(fn ($key) => ! in_array($key, ['overview', 'timeline', 'orders', 'billing', 'documents'], true)) + ?: 'clinical_notes'; @endphp @if ($onWorkspace) @@ -13,30 +32,31 @@ 'queueCallNextRoute' => 'care.specialty.call-next', 'queueCallNextParams' => ['module' => $moduleKey], 'branchId' => $branchId ?? null, + 'currentAppointmentId' => $appointment?->id, ]) @endif +@if ($ticketCalled && $appointment) +
+ @csrf + +
+@endif + @if ($workspaceVisit) - @php - $appointment = $workspaceVisit->appointment; - $openConsultation = $appointment?->consultation; - $canStartConsultation = $appointment && in_array($appointment->status, [ - \App\Models\Appointment::STATUS_WAITING, - \App\Models\Appointment::STATUS_CHECKED_IN, - ], true); - $consultationInProgress = $appointment - && ( - $appointment->status === \App\Models\Appointment::STATUS_IN_CONSULTATION - || ($openConsultation && $openConsultation->status !== \App\Models\Consultation::STATUS_COMPLETED) - ); - $chartTab = collect($workspaceTabs ?? []) - ->keys() - ->first(fn ($key) => ! in_array($key, ['overview', 'orders', 'billing', 'documents'], true)) - ?: 'clinical_notes'; - @endphp + @if ($canCompleteConsultation) + + @endif + @if ($consultationInProgress) + class="{{ $btnSecondary }}"> Open chart @elseif ($canStartConsultation) diff --git a/resources/views/care/specialty/sections/workspace.blade.php b/resources/views/care/specialty/sections/workspace.blade.php index 35f0153..e0578c1 100644 --- a/resources/views/care/specialty/sections/workspace.blade.php +++ b/resources/views/care/specialty/sections/workspace.blade.php @@ -33,128 +33,124 @@ @endforeach -
-
- @if ($activeTab === 'billing') -
-

Add specialty service

-
- @csrf - - -
- - @if ($workspaceVisit->bill) -
-

Current invoice {{ $workspaceVisit->bill->invoice_number }}

-
    - @forelse ($workspaceVisit->bill->lineItems as $line) -
  • - {{ $line->description }} - {{ number_format($line->total_minor / 100, 2) }} -
  • - @empty -
  • No line items yet.
  • - @endforelse -
- Open bill -
- @endif -
- @elseif (! empty($clinicalFields)) - @include('care.specialty.partials.clinical-form') - @elseif ($activeTab === 'orders') -
-

Orders

-

Order lab / imaging from the consultation flow. Specialty order sets can extend this later.

-
- Lab requests - @if ($workspaceVisit->appointment) - Appointment - @endif -
-
- @elseif ($activeTab === 'documents') -
-

Documents

-

Specialty documents attach to the patient chart for this episode.

- -
- @csrf - - -
- -
    - @forelse ($visitDocuments ?? [] as $doc) -
  • -
    -

    {{ $doc->original_name }}

    -

    {{ $doc->document_type }} · {{ $doc->created_at?->format('d M Y H:i') }}

    -
    - @if ($doc->file_path) - Open - @endif -
  • - @empty -
  • No documents yet.
  • - @endforelse -
- - @if ($workspaceVisit->patient) - Patient chart - @endif -
- @else -
-

Overview

-
-
-
Visit status
-
{{ ucfirst(str_replace('_', ' ', $workspaceVisit->status)) }}
-
-
-
Checked in
-
{{ $workspaceVisit->checked_in_at?->format('d M Y H:i') ?? '—' }}
-
-
-
Department
-
{{ $workspaceVisit->appointment?->department?->name ?? '—' }}
-
-
-
Queue
-
- {{ $workspaceVisit->appointment?->queue_ticket_number ?? '—' }} - @if ($workspaceVisit->appointment?->queue_ticket_status) - ({{ $workspaceVisit->appointment->queue_ticket_status }}) - @endif -
-
-
- - @if (! empty($clinicalAlerts)) -
-

Clinical alerts

- @foreach ($clinicalAlerts as $alert) -

- {{ $alert['message'] ?? '' }} -

- @endforeach -
- @endif -
- @endif -
- -
+
+ @if ($activeTab === 'timeline') @include('care.partials.patient-timeline', ['events' => $timeline ?? []]) -
+ @elseif ($activeTab === 'billing') +
+

Add specialty service

+
+ @csrf + + +
+ + @if ($workspaceVisit->bill) +
+

Current invoice {{ $workspaceVisit->bill->invoice_number }}

+
    + @forelse ($workspaceVisit->bill->lineItems as $line) +
  • + {{ $line->description }} + {{ number_format($line->total_minor / 100, 2) }} +
  • + @empty +
  • No line items yet.
  • + @endforelse +
+ Open bill +
+ @endif +
+ @elseif (! empty($clinicalFields)) + @include('care.specialty.partials.clinical-form') + @elseif ($activeTab === 'orders') +
+

Orders

+

Order lab / imaging from the consultation flow. Specialty order sets can extend this later.

+
+ Lab requests + @if ($workspaceVisit->appointment) + Appointment + @endif +
+
+ @elseif ($activeTab === 'documents') +
+

Documents

+

Specialty documents attach to the patient chart for this episode.

+ +
+ @csrf + + +
+ +
    + @forelse ($visitDocuments ?? [] as $doc) +
  • +
    +

    {{ $doc->original_name }}

    +

    {{ $doc->document_type }} · {{ $doc->created_at?->format('d M Y H:i') }}

    +
    + @if ($doc->file_path) + Open + @endif +
  • + @empty +
  • No documents yet.
  • + @endforelse +
+ + @if ($workspaceVisit->patient) + Patient chart + @endif +
+ @else +
+

Overview

+
+
+
Visit status
+
{{ ucfirst(str_replace('_', ' ', $workspaceVisit->status)) }}
+
+
+
Checked in
+
{{ $workspaceVisit->checked_in_at?->format('d M Y H:i') ?? '—' }}
+
+
+
Department
+
{{ $workspaceVisit->appointment?->department?->name ?? '—' }}
+
+
+
Queue
+
+ {{ $workspaceVisit->appointment?->queue_ticket_number ?? '—' }} + @if ($workspaceVisit->appointment?->queue_ticket_status) + ({{ $workspaceVisit->appointment->queue_ticket_status }}) + @endif +
+
+
+ + @if (! empty($clinicalAlerts)) +
+

Clinical alerts

+ @foreach ($clinicalAlerts as $alert) +

+ {{ $alert['message'] ?? '' }} +

+ @endforeach +
+ @endif +
+ @endif
@endif diff --git a/resources/views/care/specialty/shell.blade.php b/resources/views/care/specialty/shell.blade.php index e9a4a0a..119a0b2 100644 --- a/resources/views/care/specialty/shell.blade.php +++ b/resources/views/care/specialty/shell.blade.php @@ -31,6 +31,20 @@ 'actionsView' => 'care.specialty.partials.actions-menu', 'title' => 'Actions', ]) + + @php + $completeConsultation = $workspaceVisit?->appointment?->consultation; + @endphp + @if ($completeConsultation && $completeConsultation->status !== \App\Models\Consultation::STATUS_COMPLETED) + + @endif @elseif ($section === 'history')
diff --git a/tests/Feature/CareQueueBridgeTest.php b/tests/Feature/CareQueueBridgeTest.php index a220332..858fda7 100644 --- a/tests/Feature/CareQueueBridgeTest.php +++ b/tests/Feature/CareQueueBridgeTest.php @@ -198,18 +198,18 @@ class CareQueueBridgeTest extends TestCase 'queue_position' => 1, ]); - $this->actingAs($this->owner) + $response = $this->actingAs($this->owner) ->from(route('care.queue.index')) ->post(route('care.queue.call-next'), [ 'branch_id' => $this->branch->id, 'practitioner_id' => $practitioner->id, ]) - ->assertRedirect(route('care.queue.index')) ->assertSessionHas('success'); $appointment = Appointment::query()->where('patient_id', $this->patient->id)->first(); $this->assertNotNull($appointment?->queue_ticket_uuid); $this->assertSame('called', $appointment->queue_ticket_status); + $response->assertRedirect(route('care.appointments.show', $appointment)); $this->assertDatabaseHas('care_queue_tickets', [ 'uuid' => $appointment->queue_ticket_uuid, 'status' => CareQueueTicket::STATUS_CALLED, @@ -387,4 +387,114 @@ class CareQueueBridgeTest extends TestCase ]); Http::assertNothingSent(); } + + public function test_call_next_ends_current_session_and_opens_next_patient(): void + { + Http::fake(); + + $practitioner = \App\Models\Practitioner::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'name' => 'Dr. Advance', + 'room' => 'Room 4', + 'is_active' => true, + ]); + + $currentPatient = Patient::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_number' => 'P-CUR', + 'first_name' => 'Current', + 'last_name' => 'Patient', + 'gender' => 'female', + 'date_of_birth' => '1991-01-01', + ]); + + $currentVisit = \App\Models\Visit::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $currentPatient->id, + 'status' => \App\Models\Visit::STATUS_IN_PROGRESS, + 'checked_in_at' => now(), + ]); + + $current = Appointment::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $currentPatient->id, + 'practitioner_id' => $practitioner->id, + 'visit_id' => $currentVisit->id, + 'type' => Appointment::TYPE_WALK_IN, + 'status' => Appointment::STATUS_IN_CONSULTATION, + 'scheduled_at' => now()->subHour(), + 'waiting_at' => now()->subHour(), + 'checked_in_at' => now()->subHour(), + 'started_at' => now()->subMinutes(30), + 'queue_ticket_status' => 'serving', + ]); + + \App\Models\Consultation::create([ + 'owner_ref' => $this->owner->public_id, + 'visit_id' => $currentVisit->id, + 'appointment_id' => $current->id, + 'practitioner_id' => $practitioner->id, + 'patient_id' => $currentPatient->id, + 'status' => \App\Models\Consultation::STATUS_DRAFT, + 'started_at' => now()->subMinutes(30), + ]); + + $nextVisit = \App\Models\Visit::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $this->patient->id, + 'status' => \App\Models\Visit::STATUS_OPEN, + 'checked_in_at' => now(), + ]); + + $next = Appointment::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $this->patient->id, + 'practitioner_id' => $practitioner->id, + 'visit_id' => $nextVisit->id, + 'type' => Appointment::TYPE_WALK_IN, + 'status' => Appointment::STATUS_WAITING, + 'scheduled_at' => now(), + 'waiting_at' => now(), + 'checked_in_at' => now(), + 'queue_position' => 1, + ]); + + app(\App\Services\Care\CareQueueBridge::class)->issueForAppointment( + $this->organization->fresh(), + $next->fresh(['patient', 'practitioner', 'organization']), + ); + + $response = $this->actingAs($this->owner) + ->post(route('care.queue.call-next'), [ + 'branch_id' => $this->branch->id, + 'practitioner_id' => $practitioner->id, + 'appointment_id' => $current->id, + ]) + ->assertSessionHas('success'); + + $this->assertSame(Appointment::STATUS_COMPLETED, $current->fresh()->status); + $this->assertSame(\App\Models\Consultation::STATUS_COMPLETED, $current->consultation->fresh()->status); + + $next = $next->fresh(); + $this->assertSame('called', $next->queue_ticket_status); + $response->assertRedirect(route('care.appointments.show', $next)); + + $this->actingAs($this->owner) + ->get(route('care.appointments.show', $next)) + ->assertOk() + ->assertSee('Call again'); + Http::assertNothingSent(); + } } diff --git a/tests/Feature/CareSpecialtyShellTest.php b/tests/Feature/CareSpecialtyShellTest.php index ad3d12c..c6ec230 100644 --- a/tests/Feature/CareSpecialtyShellTest.php +++ b/tests/Feature/CareSpecialtyShellTest.php @@ -291,4 +291,148 @@ class CareSpecialtyShellTest extends TestCase ])); $this->assertSame(Appointment::STATUS_IN_CONSULTATION, $appointment->fresh()->status); } + + public function test_workspace_includes_timeline_tab_and_complete_action(): void + { + app(SpecialtyModuleService::class)->activate( + $this->organization, + $this->owner->public_id, + 'dentistry', + ); + + $department = Department::query() + ->where('branch_id', $this->branch->id) + ->where('type', 'dental') + ->firstOrFail(); + + $patient = Patient::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_number' => 'P-DEN-TL', + 'first_name' => 'Afia', + 'last_name' => 'Darko', + 'gender' => 'female', + 'date_of_birth' => '1992-01-01', + ]); + + $visit = Visit::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $patient->id, + 'status' => Visit::STATUS_IN_PROGRESS, + 'checked_in_at' => now(), + ]); + + $appointment = Appointment::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $patient->id, + 'department_id' => $department->id, + 'visit_id' => $visit->id, + 'type' => Appointment::TYPE_WALK_IN, + 'status' => Appointment::STATUS_IN_CONSULTATION, + 'scheduled_at' => now(), + 'waiting_at' => now(), + 'checked_in_at' => now(), + 'started_at' => now(), + 'queue_ticket_status' => 'serving', + ]); + + \App\Models\Consultation::create([ + 'owner_ref' => $this->owner->public_id, + 'visit_id' => $visit->id, + 'appointment_id' => $appointment->id, + 'patient_id' => $patient->id, + 'status' => \App\Models\Consultation::STATUS_DRAFT, + 'started_at' => now(), + ]); + + $tabs = app(SpecialtyShellService::class)->workspaceTabs('dentistry'); + $this->assertArrayHasKey('timeline', $tabs); + $this->assertSame(['overview', 'timeline'], array_slice(array_keys($tabs), 0, 2)); + + $this->actingAs($this->owner) + ->get(route('care.specialty.workspace', [ + 'module' => 'dentistry', + 'visit' => $visit, + 'tab' => 'timeline', + ])) + ->assertOk() + ->assertSee('Timeline') + ->assertSee('Patient timeline') + ->assertSee('Complete consultation') + ->assertDontSee('>Call again<', false); + + $this->actingAs($this->owner) + ->get(route('care.specialty.workspace', [ + 'module' => 'dentistry', + 'visit' => $visit, + 'tab' => 'overview', + ])) + ->assertOk() + ->assertDontSee('Patient timeline'); + } + + public function test_called_workspace_shows_call_again_until_session_starts(): void + { + app(SpecialtyModuleService::class)->activate( + $this->organization, + $this->owner->public_id, + 'dentistry', + ); + + $department = Department::query() + ->where('branch_id', $this->branch->id) + ->where('type', 'dental') + ->firstOrFail(); + + $patient = Patient::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_number' => 'P-DEN-CALL', + 'first_name' => 'Kofi', + 'last_name' => 'Mensah', + 'gender' => 'male', + 'date_of_birth' => '1988-01-01', + ]); + + $visit = Visit::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $patient->id, + 'status' => Visit::STATUS_OPEN, + 'checked_in_at' => now(), + ]); + + Appointment::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'patient_id' => $patient->id, + 'department_id' => $department->id, + 'visit_id' => $visit->id, + 'type' => Appointment::TYPE_WALK_IN, + 'status' => Appointment::STATUS_WAITING, + 'scheduled_at' => now(), + 'waiting_at' => now(), + 'checked_in_at' => now(), + 'queue_ticket_status' => 'called', + 'queue_ticket_number' => 'DEN001', + ]); + + $this->actingAs($this->owner) + ->get(route('care.specialty.workspace', [ + 'module' => 'dentistry', + 'visit' => $visit, + ])) + ->assertOk() + ->assertSee('Call again') + ->assertSee('Start') + ->assertDontSee('Complete consultation'); + } }