diff --git a/app/Http/Controllers/Care/BillController.php b/app/Http/Controllers/Care/BillController.php index 8c48ab0..21988e1 100644 --- a/app/Http/Controllers/Care/BillController.php +++ b/app/Http/Controllers/Care/BillController.php @@ -8,6 +8,7 @@ use App\Models\Bill; use App\Models\Consultation; use App\Models\Visit; use App\Services\Care\BillService; +use App\Services\Care\BranchContext; use App\Services\Care\CareQueueBridge; use App\Services\Care\CareQueueContexts; use App\Services\Care\ConsultationReturnContext; @@ -26,6 +27,7 @@ class BillController extends Controller protected BillService $bills, protected MerchantGatewayService $gateway, protected CareQueueBridge $queueBridge, + protected BranchContext $branchContext, ) {} public function index(Request $request): View @@ -33,26 +35,23 @@ class BillController extends Controller $this->authorizeAbility($request, 'bills.view'); $organization = $this->organization($request); $member = $this->member($request); - $branchScope = app(OrganizationResolver::class)->branchScope($member); + $branches = $this->branchContext->branches($request, $organization, $member); + $branchId = $this->branchContext->resolve($request, $organization, $member); $bills = $this->bills->list( $this->ownerRef($request), $organization->id, $request->only(['status', 'patient_id']), - $branchScope, + $branchId > 0 ? $branchId : null, ); - $branchId = (int) ($branchScope ?: \App\Models\Branch::owned($this->ownerRef($request)) - ->where('organization_id', $organization->id) - ->where('is_active', true) - ->orderBy('name') - ->value('id')); - return view('care.bills.index', [ 'organization' => $organization, 'bills' => $bills, 'statuses' => config('care.bill_statuses'), + 'branches' => $branches, 'branchId' => $branchId, + 'canSwitchBranch' => $this->branchContext->canSwitch($member), 'queueIntegration' => $branchId ? $this->queueBridge->listMeta($organization, CareQueueContexts::BILLING, $branchId) : ['enabled' => false], @@ -66,8 +65,7 @@ class BillController extends Controller $this->authorizeAbility($request, 'bills.manage'); $organization = $this->organization($request); $member = $this->member($request); - $branchScope = app(OrganizationResolver::class)->branchScope($member); - $branchId = (int) ($request->input('branch_id') ?: $branchScope); + $branchId = $this->branchContext->resolve($request, $organization, $member); abort_unless($branchId > 0, 422); abort_unless($this->queueBridge->isEnabled($organization), 404); diff --git a/app/Http/Controllers/Care/InvestigationController.php b/app/Http/Controllers/Care/InvestigationController.php index 62c3663..26ddd88 100644 --- a/app/Http/Controllers/Care/InvestigationController.php +++ b/app/Http/Controllers/Care/InvestigationController.php @@ -4,11 +4,11 @@ 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\BranchContext; use App\Services\Care\CareQueueBridge; use App\Services\Care\CareQueueContexts; use App\Services\Care\ConsultationReturnContext; @@ -25,19 +25,21 @@ class InvestigationController extends Controller public function __construct( protected InvestigationService $investigations, protected CareQueueBridge $queueBridge, + protected BranchContext $branchContext, ) {} public function index(Request $request): View { $this->authorizeAbility($request, 'lab.view'); $organization = $this->organization($request); - $branchScope = app(OrganizationResolver::class)->branchScope($this->member($request)); + $member = $this->member($request); + $branchId = $this->branchContext->resolve($request, $organization, $member); $requests = $this->investigations->list( $this->ownerRef($request), $organization->id, $request->only(['status', 'patient_id']), - $branchScope, + $branchId > 0 ? $branchId : null, ); return view('care.lab.requests.index', [ @@ -51,16 +53,9 @@ class InvestigationController extends Controller { $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); + $member = $this->member($request); + $branches = $this->branchContext->branches($request, $organization, $member); + $branchId = $this->branchContext->resolve($request, $organization, $member); $status = $request->input('status'); $queue = $branchId @@ -71,6 +66,7 @@ class InvestigationController extends Controller 'organization' => $organization, 'branches' => $branches, 'branchId' => $branchId, + 'canSwitchBranch' => $this->branchContext->canSwitch($member), 'status' => $status, 'queue' => $queue, 'statuses' => config('care.investigation_statuses'), @@ -89,8 +85,7 @@ class InvestigationController extends Controller $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); + $branchId = $this->branchContext->resolve($request, $organization, $member); abort_unless($branchId > 0, 422); abort_unless($this->queueBridge->isEnabled($organization), 404); diff --git a/app/Http/Controllers/Care/PrescriptionController.php b/app/Http/Controllers/Care/PrescriptionController.php index 82d9270..4f59d5d 100644 --- a/app/Http/Controllers/Care/PrescriptionController.php +++ b/app/Http/Controllers/Care/PrescriptionController.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Care\Concerns\ScopesToAccount; use App\Models\Consultation; use App\Models\Prescription; use App\Models\Practitioner; +use App\Services\Care\BranchContext; use App\Services\Care\CareQueueBridge; use App\Services\Care\CareQueueContexts; use App\Services\Care\ConsultationReturnContext; @@ -25,19 +26,21 @@ class PrescriptionController extends Controller protected PrescriptionService $prescriptions, protected PharmacyService $pharmacy, protected CareQueueBridge $queueBridge, + protected BranchContext $branchContext, ) {} public function index(Request $request): View { $this->authorizeAbility($request, 'prescriptions.view'); $organization = $this->organization($request); - $branchScope = app(OrganizationResolver::class)->branchScope($this->member($request)); + $member = $this->member($request); + $branchId = $this->branchContext->resolve($request, $organization, $member); $prescriptions = $this->prescriptions->list( $this->ownerRef($request), $organization->id, $request->only(['status', 'patient_id']), - $branchScope, + $branchId > 0 ? $branchId : null, ); return view('care.prescriptions.index', [ @@ -52,16 +55,12 @@ class PrescriptionController extends Controller $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')); + $branches = $this->branchContext->branches($request, $organization, $member); + $branchId = $this->branchContext->resolve($request, $organization, $member); $queue = $this->prescriptions->pharmacyQueue($this->ownerRef($request), $organization->id); - if ($branchScope) { - $queue = $queue->filter(fn (Prescription $rx) => (int) ($rx->visit?->branch_id) === $branchScope)->values(); + if ($branchId > 0) { + $queue = $queue->filter(fn (Prescription $rx) => (int) ($rx->visit?->branch_id) === $branchId)->values(); } $queueIntegration = ['enabled' => false]; @@ -69,10 +68,6 @@ class PrescriptionController extends Controller // Issue tickets in Care FIFO order so Queue Call Next matches this list. $this->queueBridge->syncMissingTickets($organization, CareQueueContexts::PHARMACY, $branchId, 50); $queueIntegration = $this->queueBridge->listMeta($organization, CareQueueContexts::PHARMACY, $branchId); - $queue = $this->prescriptions->pharmacyQueue($this->ownerRef($request), $organization->id); - if ($branchScope) { - $queue = $queue->filter(fn (Prescription $rx) => (int) ($rx->visit?->branch_id) === $branchScope)->values(); - } } $memberPoint = $branchId @@ -112,7 +107,9 @@ class PrescriptionController extends Controller 'queue' => $queue, 'drugs' => $drugs, 'canDispense' => $canDispense, + 'branches' => $branches, 'branchId' => $branchId, + 'canSwitchBranch' => $this->branchContext->canSwitch($member), 'queueIntegration' => $queueIntegration, 'memberCounterUuid' => $memberCounterUuid, ]); @@ -123,8 +120,7 @@ class PrescriptionController extends Controller $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); + $branchId = $this->branchContext->resolve($request, $organization, $member); abort_unless($branchId > 0, 422); abort_unless($this->queueBridge->isEnabled($organization), 404); diff --git a/app/Http/Controllers/Care/QueueController.php b/app/Http/Controllers/Care/QueueController.php index 33dd3fa..014a733 100644 --- a/app/Http/Controllers/Care/QueueController.php +++ b/app/Http/Controllers/Care/QueueController.php @@ -5,9 +5,9 @@ namespace App\Http\Controllers\Care; use App\Http\Controllers\Care\Concerns\ScopesToAccount; use App\Http\Controllers\Controller; use App\Models\Appointment; -use App\Models\Branch; use App\Models\Practitioner; use App\Services\Care\AppointmentService; +use App\Services\Care\BranchContext; use App\Services\Care\CarePermissions; use App\Services\Care\CareQueueBridge; use App\Services\Care\CareQueueContexts; @@ -25,6 +25,7 @@ class QueueController extends Controller protected AppointmentService $appointments, protected ConsultationService $consultations, protected CareQueueBridge $queueBridge, + protected BranchContext $branchContext, ) {} public function index(Request $request): View @@ -32,16 +33,8 @@ class QueueController extends Controller $this->authorizeAbility($request, 'appointments.view'); $organization = $this->organization($request); $member = $this->member($request); - $branchScope = app(OrganizationResolver::class)->branchScope($member); - - $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); + $branches = $this->branchContext->branches($request, $organization, $member); + $branchId = $this->branchContext->resolve($request, $organization, $member); $practitionerId = $request->input('practitioner_id') ? (int) $request->input('practitioner_id') : null; $queueIntegration = ['enabled' => false]; @@ -83,9 +76,11 @@ class QueueController extends Controller 'organization' => $organization, 'branches' => $branches, 'branchId' => $branchId, + 'canSwitchBranch' => $this->branchContext->canSwitch($member), 'practitioners' => Practitioner::owned($this->ownerRef($request)) ->where('organization_id', $organization->id) ->where('is_active', true) + ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->orderBy('name') ->get(), 'practitionerId' => $practitionerId, @@ -105,8 +100,7 @@ class QueueController extends Controller abort_unless($this->queueBridge->isEnabled($organization), 404); $member = $this->member($request); - $branchScope = app(OrganizationResolver::class)->branchScope($member); - $branchId = (int) ($request->input('branch_id') ?: $branchScope); + $branchId = $this->branchContext->resolve($request, $organization, $member); abort_unless($branchId > 0, 422); $result = $this->queueBridge->callNext( diff --git a/app/Services/Care/BranchContext.php b/app/Services/Care/BranchContext.php new file mode 100644 index 0000000..b351238 --- /dev/null +++ b/app/Services/Care/BranchContext.php @@ -0,0 +1,84 @@ +organizations->branchScope($member) === null; + } + + /** + * Active branches the member may see (one site for scoped staff, all for admins). + * + * @return Collection + */ + public function branches(Request $request, Organization $organization, ?Member $member): Collection + { + $scope = $this->organizations->branchScope($member); + + return Branch::owned((string) $request->user()->public_id) + ->where('organization_id', $organization->id) + ->where('is_active', true) + ->when($scope, fn ($q) => $q->where('id', $scope)) + ->orderBy('name') + ->get(); + } + + /** + * Preferred working branch id (0 when the org has no active branches). + * Persists admin selections from ?branch_id= into the session. + */ + public function resolve(Request $request, Organization $organization, ?Member $member): int + { + $branches = $this->branches($request, $organization, $member); + $allowed = $branches->pluck('id')->map(fn ($id) => (int) $id)->all(); + if ($allowed === []) { + return 0; + } + + $scope = $this->organizations->branchScope($member); + if ($scope !== null) { + return (int) $scope; + } + + if ($request->filled('branch_id')) { + $requested = (int) $request->input('branch_id'); + if (in_array($requested, $allowed, true)) { + $request->session()->put(self::SESSION_KEY, $requested); + + return $requested; + } + } + + $preferred = (int) $request->session()->get(self::SESSION_KEY, 0); + if ($preferred > 0 && in_array($preferred, $allowed, true)) { + return $preferred; + } + + $fallback = (int) $branches->first()->id; + $request->session()->put(self::SESSION_KEY, $fallback); + + return $fallback; + } +} diff --git a/resources/views/care/bills/index.blade.php b/resources/views/care/bills/index.blade.php index 0633776..c04b5b8 100644 --- a/resources/views/care/bills/index.blade.php +++ b/resources/views/care/bills/index.blade.php @@ -9,7 +9,17 @@ Financial gates @endif -
+ + @if (($canSwitchBranch ?? false) && isset($branches) && $branches->count() > 1) + + @elseif (isset($branches) && $branches->isNotEmpty()) + + {{ $branches->first()->name }} + @endif + @foreach ($branches as $branch) + + @endforeach + + @isset($extraFilters) + {!! $extraFilters !!} + @endisset + @unless ($autoSubmit ?? true) + + @endunless + @if ($autoSubmit ?? true) + {{-- Keep non-JS fallback --}} + + @endif +
+@elseif (isset($branches) && $branches->isNotEmpty()) +

{{ $branches->first()->name }}

+@endif diff --git a/resources/views/care/prescriptions/queue.blade.php b/resources/views/care/prescriptions/queue.blade.php index ffe8d67..08c6e98 100644 --- a/resources/views/care/prescriptions/queue.blade.php +++ b/resources/views/care/prescriptions/queue.blade.php @@ -2,6 +2,19 @@

Pharmacy queue

Active prescriptions awaiting dispensing

+ @if (($canSwitchBranch ?? false) && isset($branches) && $branches->count() > 1) +
+ + +
+ @elseif (isset($branches) && $branches->isNotEmpty()) +

{{ $branches->first()->name }}

+ @endif +
@include('care.partials.queue-ops', [ 'queueIntegration' => $queueIntegration ?? null, diff --git a/tests/Feature/CareBranchContextTest.php b/tests/Feature/CareBranchContextTest.php new file mode 100644 index 0000000..6a75925 --- /dev/null +++ b/tests/Feature/CareBranchContextTest.php @@ -0,0 +1,198 @@ +withoutMiddleware(EnsurePlatformSession::class); + + $this->user = User::create([ + 'public_id' => 'test-admin-001', + 'name' => 'Hospital Admin', + 'email' => 'admin@example.com', + ]); + + $this->organization = Organization::create([ + 'owner_ref' => $this->user->public_id, + 'name' => 'Multi Branch Clinic', + 'slug' => 'multi-branch', + 'timezone' => 'UTC', + 'settings' => ['onboarded' => true, 'plan' => 'pro', 'plan_expires_at' => now()->addMonth()->toIso8601String()], + ]); + + Member::create([ + 'owner_ref' => $this->user->public_id, + 'organization_id' => $this->organization->id, + 'user_ref' => $this->user->public_id, + 'role' => 'hospital_admin', + 'branch_id' => null, + ]); + + $this->east = Branch::create([ + 'owner_ref' => $this->user->public_id, + 'organization_id' => $this->organization->id, + 'name' => 'East Legon Care', + 'is_active' => true, + ]); + + $this->west = Branch::create([ + 'owner_ref' => $this->user->public_id, + 'organization_id' => $this->organization->id, + 'name' => 'West Hills Care', + 'is_active' => true, + ]); + + foreach ([$this->east, $this->west] as $branch) { + Department::create([ + 'owner_ref' => $this->user->public_id, + 'branch_id' => $branch->id, + 'name' => 'Pharmacy', + 'type' => 'pharmacy', + 'is_active' => true, + ]); + } + + $this->eastPatient = $this->makePatient($this->east, 'Ama', 'East', 'LC-EAST-1'); + $this->westPatient = $this->makePatient($this->west, 'Kofi', 'West', 'LC-WEST-1'); + + $this->makeWaitingAppointment($this->east, $this->eastPatient); + $this->makeWaitingAppointment($this->west, $this->westPatient); + $this->makeActivePrescription($this->east, $this->eastPatient, 'East Med'); + $this->makeActivePrescription($this->west, $this->westPatient, 'West Med'); + } + + public function test_admin_branch_filter_persists_from_patient_queue_to_pharmacy(): void + { + $this->actingAs($this->user) + ->get(route('care.queue.index', ['branch_id' => $this->west->id])) + ->assertOk() + ->assertSee('West Hills Care') + ->assertSee('Kofi West') + ->assertDontSee('Ama East'); + + $this->assertSame( + $this->west->id, + (int) session(BranchContext::SESSION_KEY), + ); + + $this->actingAs($this->user) + ->get(route('care.prescriptions.queue')) + ->assertOk() + ->assertSee('West Hills Care') + ->assertSee('West Med') + ->assertSee('Kofi West') + ->assertDontSee('East Med') + ->assertDontSee('Ama East'); + } + + public function test_pharmacy_queue_branch_filter_scopes_list_for_admin(): void + { + $this->actingAs($this->user) + ->get(route('care.prescriptions.queue', ['branch_id' => $this->east->id])) + ->assertOk() + ->assertSee('East Legon Care') + ->assertSee('East Med') + ->assertDontSee('West Med'); + } + + protected function makePatient(Branch $branch, string $first, string $last, string $number): Patient + { + return Patient::create([ + 'uuid' => (string) \Illuminate\Support\Str::uuid(), + 'owner_ref' => $this->user->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $branch->id, + 'patient_number' => $number, + 'first_name' => $first, + 'last_name' => $last, + ]); + } + + protected function makeWaitingAppointment(Branch $branch, Patient $patient): void + { + Appointment::create([ + 'uuid' => (string) \Illuminate\Support\Str::uuid(), + 'owner_ref' => $this->user->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $branch->id, + 'patient_id' => $patient->id, + 'status' => Appointment::STATUS_WAITING, + 'scheduled_at' => now(), + 'waiting_at' => now(), + 'reason' => 'Check-up', + ]); + } + + protected function makeActivePrescription(Branch $branch, Patient $patient, string $medName): void + { + $visit = Visit::create([ + 'uuid' => (string) \Illuminate\Support\Str::uuid(), + 'owner_ref' => $this->user->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $branch->id, + 'patient_id' => $patient->id, + 'status' => Visit::STATUS_IN_PROGRESS, + 'checked_in_at' => now(), + ]); + + $consultation = Consultation::create([ + 'uuid' => (string) \Illuminate\Support\Str::uuid(), + 'owner_ref' => $this->user->public_id, + 'visit_id' => $visit->id, + 'patient_id' => $patient->id, + 'status' => Consultation::STATUS_DRAFT, + 'started_at' => now(), + ]); + + $prescription = Prescription::create([ + 'uuid' => (string) \Illuminate\Support\Str::uuid(), + 'owner_ref' => $this->user->public_id, + 'organization_id' => $this->organization->id, + 'visit_id' => $visit->id, + 'consultation_id' => $consultation->id, + 'patient_id' => $patient->id, + 'status' => Prescription::STATUS_ACTIVE, + 'prescribed_by' => $this->user->public_id, + ]); + + $prescription->items()->create([ + 'owner_ref' => $this->user->public_id, + 'name' => $medName, + 'dosage' => '1 tablet', + 'frequency' => 'OD', + 'duration' => '3 days', + ]); + } +}