diff --git a/app/Http/Controllers/Care/QueueController.php b/app/Http/Controllers/Care/QueueController.php index 308980c..367ab1a 100644 --- a/app/Http/Controllers/Care/QueueController.php +++ b/app/Http/Controllers/Care/QueueController.php @@ -73,7 +73,11 @@ class QueueController extends Controller } if ($lockToPractitioner) { - $queue = $this->appointments->queueForPractitioners($owner, $practitionerScope ?? []); + $queue = $this->appointments->queueForPractitioners( + $owner, + $practitionerScope ?? [], + $branchId > 0 ? $branchId : null, + ); $inConsultation = Appointment::owned($owner) ->where('organization_id', $organization->id) ->where('status', Appointment::STATUS_IN_CONSULTATION) diff --git a/app/Services/Care/BranchContext.php b/app/Services/Care/BranchContext.php index bd56c32..e526ee9 100644 --- a/app/Services/Care/BranchContext.php +++ b/app/Services/Care/BranchContext.php @@ -25,6 +25,22 @@ class BranchContext public function canSwitch(?Member $member): bool { + if ($member === null) { + return false; + } + + // Site clinicians are assigned to one branch โ€” never a branch picker. + if (in_array($member->role, [ + 'doctor', + 'nurse', + 'pharmacist', + 'lab_technician', + 'cashier', + 'receptionist', + ], true)) { + return false; + } + return $this->organizations->branchScope($member) === null; } @@ -40,7 +56,8 @@ class BranchContext return Branch::owned((string) $organization->owner_ref) ->where('organization_id', $organization->id) ->where('is_active', true) - ->when($scope, fn ($q) => $q->where('id', $scope)) + // Scope 0 = no site assigned โ€” must not fall through to every branch. + ->when($scope !== null, fn ($q) => $q->where('id', $scope)) ->orderBy('name') ->get(); } diff --git a/app/Services/Care/DemoTenantSeeder.php b/app/Services/Care/DemoTenantSeeder.php index 142b06e..84ad671 100644 --- a/app/Services/Care/DemoTenantSeeder.php +++ b/app/Services/Care/DemoTenantSeeder.php @@ -822,6 +822,9 @@ class DemoTenantSeeder $member = $moduleMembers[$key]; $staff = $member ? \App\Support\DemoWorld::staffByEmail($staffEmail) : null; + $homeBranchId = (int) ($member?->branch_id ?? 0); + // One doctor = one branch. Only link the specialty desk on their home site. + $assignMember = $member && $homeBranchId > 0 && (int) $branch->id === $homeBranchId; $stableRef = sprintf('demo-specialty-%s-%s', $key, $branch->id); $practitioner = Practitioner::withTrashed()->updateOrCreate( [ @@ -832,8 +835,8 @@ class DemoTenantSeeder 'owner_ref' => $ownerRef, 'branch_id' => $branch->id, 'department_id' => $department->id, - 'member_id' => $member?->id, - 'name' => is_array($staff) + 'member_id' => $assignMember ? $member->id : null, + 'name' => $assignMember && is_array($staff) ? (string) $staff['name'] : (($definition['label'] ?? ucfirst($key)).' Clinic'), 'specialty' => (string) ($definition['label'] ?? $key), diff --git a/app/Services/Care/OrganizationResolver.php b/app/Services/Care/OrganizationResolver.php index 964d174..49ebd4b 100644 --- a/app/Services/Care/OrganizationResolver.php +++ b/app/Services/Care/OrganizationResolver.php @@ -180,7 +180,7 @@ class OrganizationResolver }; } - /** Branch ID the member may access; null = all branches. */ + /** Branch ID the member may access; null = all branches (admins only). */ public function branchScope(?Member $member): ?int { if ($member === null) { @@ -191,12 +191,30 @@ class OrganizationResolver return null; } + if ($member->branch_id) { + return (int) $member->branch_id; + } + + // Doctors are always one site. Prefer linked desk when Identity omitted branch_id. + if ($member->role === 'doctor') { + $deskBranch = Practitioner::query() + ->where('is_active', true) + ->where(function ($query) use ($member) { + $query->where('member_id', $member->id) + ->orWhere('user_ref', $member->user_ref); + }) + ->orderBy('id') + ->value('branch_id'); + + return $deskBranch ? (int) $deskBranch : 0; + } + return $member->branch_id; } /** - * Practitioner IDs a doctor is locked to. Null = no practitioner lock (non-doctors). - * Empty array = doctor with no linked practitioner desk (see no clinical queue). + * Practitioner IDs a doctor is locked to (single branch). Null = no practitioner lock. + * Empty array = doctor with no linked desk on their branch. * * @return list|null */ @@ -206,9 +224,12 @@ class OrganizationResolver return null; } + $branchId = $this->branchScope($member); + return Practitioner::owned((string) $organization->owner_ref) ->where('organization_id', $organization->id) ->where('is_active', true) + ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->where(function ($query) use ($member) { $query->where('member_id', $member->id) ->orWhere('user_ref', $member->user_ref); diff --git a/app/Services/Care/SpecialtyModuleService.php b/app/Services/Care/SpecialtyModuleService.php index b37e174..2f7e193 100644 --- a/app/Services/Care/SpecialtyModuleService.php +++ b/app/Services/Care/SpecialtyModuleService.php @@ -139,9 +139,12 @@ class SpecialtyModuleService ? array_map('intval', $provisionedIds) : []; + $branchId = app(OrganizationResolver::class)->branchScope($member); + $practitioners = Practitioner::owned((string) $organization->owner_ref) ->where('organization_id', $organization->id) ->where('is_active', true) + ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->where(function ($query) use ($member) { $query->where('member_id', $member->id) ->orWhere('user_ref', $member->user_ref); diff --git a/app/Services/Care/TeamMemberProvisioner.php b/app/Services/Care/TeamMemberProvisioner.php index 6742e90..ccee8ed 100644 --- a/app/Services/Care/TeamMemberProvisioner.php +++ b/app/Services/Care/TeamMemberProvisioner.php @@ -53,7 +53,12 @@ class TeamMemberProvisioner [ 'owner_ref' => $ownerRef !== '' ? $ownerRef : $user->ownerRef(), 'role' => (string) ($appMeta['role'] ?? 'member'), - 'branch_id' => $appMeta['branch_id'] ?? null, + 'branch_id' => array_key_exists('branch_id', $appMeta) + ? $appMeta['branch_id'] + : Member::query() + ->where('organization_id', $organizationId) + ->where('user_ref', $user->ownerRef()) + ->value('branch_id'), ], ); } diff --git a/resources/views/care/lab/queue/index.blade.php b/resources/views/care/lab/queue/index.blade.php index 8e8c36e..0ca54e2 100644 --- a/resources/views/care/lab/queue/index.blade.php +++ b/resources/views/care/lab/queue/index.blade.php @@ -13,11 +13,16 @@
- + @if (($canSwitchBranch ?? false) && isset($branches) && $branches->count() > 1) + + @elseif (isset($branches) && $branches->isNotEmpty()) + + {{ $branches->first()->name }} + @endif @foreach ($branches as $branch) @endforeach - @elseif (($lockToPractitioner ?? false) === false && isset($branches) && $branches->isNotEmpty()) - - {{ $branches->first()->name }} - @endif - - @unless ($lockToPractitioner ?? false) @else - Showing patients assigned to you - @endunless + @if (isset($branches) && $branches->isNotEmpty()) + + {{ $branches->first()->name }} + @endif + + + @endif
@include('care.partials.queue-ops', [ diff --git a/tests/Feature/CareAppointmentTest.php b/tests/Feature/CareAppointmentTest.php index 5149c6f..dec975e 100644 --- a/tests/Feature/CareAppointmentTest.php +++ b/tests/Feature/CareAppointmentTest.php @@ -142,7 +142,15 @@ class CareAppointmentTest extends TestCase $this->assertNotNull($appointment->visit_id); $this->assertDatabaseHas('care_visits', ['patient_id' => $this->patient->id]); - Member::where('user_ref', $this->user->public_id)->update(['role' => 'doctor']); + $member = Member::where('user_ref', $this->user->public_id)->firstOrFail(); + $member->update([ + 'role' => 'doctor', + 'branch_id' => $this->branch->id, + ]); + $this->practitioner->update([ + 'member_id' => $member->id, + 'user_ref' => $this->user->public_id, + ]); $this->actingAs($this->user) ->post(route('care.queue.start', $appointment)) diff --git a/tests/Feature/CareStaffTenantScopeTest.php b/tests/Feature/CareStaffTenantScopeTest.php index d9423dc..646fbd0 100644 --- a/tests/Feature/CareStaffTenantScopeTest.php +++ b/tests/Feature/CareStaffTenantScopeTest.php @@ -142,13 +142,32 @@ class CareStaffTenantScopeTest extends TestCase ->get(route('care.queue.index')) ->assertOk() ->assertSee('Showing patients assigned to you') + ->assertSee('East Legon Care') ->assertDontSee('All practitioners') ->assertDontSee('name="branch_id"', false) + ->assertDontSee('assertSee('Ama Mensah') ->assertSee('Toothache') ->assertDontSee('Kofi Boateng') ->assertDontSee('Fever'); + // Even if Identity clears member.branch_id, doctors stay on their desk's branch โ€” no picker. + $doctorMember->update(['branch_id' => null]); + Branch::create([ + 'owner_ref' => $owner->public_id, + 'organization_id' => $organization->id, + 'name' => 'West Hills Care', + 'is_active' => true, + ]); + + $this->actingAs($doctor) + ->get(route('care.queue.index')) + ->assertOk() + ->assertSee('East Legon Care') + ->assertDontSee('West Hills Care') + ->assertDontSee('name="branch_id"', false) + ->assertDontSee('actingAs($doctor) ->get(route('care.patients.index')) ->assertOk()