Allow explicit multi-branch practitioner assignment for telemedicine.
Deploy Ladill Care / deploy (push) Successful in 1m29s

Replace optional “All branches” with required branch checkboxes; doctors may switch only among assigned sites, never org-wide by default.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-18 08:50:03 +00:00
co-authored by Cursor
parent d643687335
commit 899b08179e
17 changed files with 478 additions and 103 deletions
+86 -20
View File
@@ -180,7 +180,73 @@ class OrganizationResolver
};
}
/** Branch ID the member may access; null = all branches (admins only). */
/**
* Branch IDs the member may access.
* null = all branches (admins). Empty array = none assigned.
*
* @return list<int>|null
*/
public function allowedBranchIds(?Member $member): ?array
{
if ($member === null) {
return null;
}
if (in_array($member->role, ['super_admin', 'hospital_admin', 'accountant'], true)) {
return null;
}
if ($member->role === 'doctor') {
return $this->doctorAssignedBranchIds($member);
}
if ($member->branch_id) {
return [(int) $member->branch_id];
}
return null;
}
/**
* Explicit branch IDs for a doctor's linked practitioner desks (pivot + legacy branch_id).
*
* @return list<int>
*/
public function doctorAssignedBranchIds(Member $member): array
{
$practitioners = Practitioner::query()
->where('is_active', true)
->where(function ($query) use ($member) {
$query->where('member_id', $member->id)
->orWhere('user_ref', $member->user_ref);
})
->with('branches')
->orderBy('id')
->get();
$ids = [];
foreach ($practitioners as $practitioner) {
$ids = array_merge($ids, $practitioner->assignedBranchIds());
}
return array_values(array_unique(array_map('intval', $ids)));
}
public function mayAccessBranch(?Member $member, ?int $branchId): bool
{
if ($branchId === null || $branchId <= 0) {
return false;
}
$allowed = $this->allowedBranchIds($member);
if ($allowed === null) {
return true;
}
return in_array((int) $branchId, $allowed, true);
}
/** Current working branch ID; null = all branches (admins only). */
public function branchScope(?Member $member): ?int
{
if ($member === null) {
@@ -191,30 +257,33 @@ class OrganizationResolver
return null;
}
if ($member->branch_id) {
return (int) $member->branch_id;
if ($member->role === 'doctor') {
$ids = $this->doctorAssignedBranchIds($member);
if ($ids === []) {
return 0;
}
if (count($ids) === 1) {
return $ids[0];
}
$preferred = (int) session(BranchContext::SESSION_KEY, 0);
if (in_array($preferred, $ids, true)) {
return $preferred;
}
return $ids[0];
}
// 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;
if ($member->branch_id) {
return (int) $member->branch_id;
}
return $member->branch_id;
}
/**
* Practitioner IDs a doctor is locked to (single branch). Null = no practitioner lock.
* Empty array = doctor with no linked desk on their branch.
* Practitioner IDs a doctor is locked to. Null = no practitioner lock.
* Empty array = doctor with no linked desk.
*
* @return list<int>|null
*/
@@ -224,12 +293,9 @@ 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);