Restrict specialty modules to doctors assigned to that specialty.
Deploy Ladill Care / deploy (push) Successful in 1m10s

Nav and pages require a linked practitioner desk in the module department; demo seeds specialty doctors and stops overwriting their assigned waiters.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-18 08:37:06 +00:00
co-authored by Cursor
parent dcbc62d1d3
commit bb4dbee81a
7 changed files with 285 additions and 20 deletions
@@ -4,7 +4,9 @@ namespace App\Services\Care;
use App\Models\Branch;
use App\Models\Department;
use App\Models\Member;
use App\Models\Organization;
use App\Models\Practitioner;
use App\Services\Queue\QueueClient;
use Illuminate\Support\Facades\Log;
@@ -79,6 +81,86 @@ class SpecialtyModuleService
return $out;
}
/**
* Enabled modules the member may open (role allowlist + doctor specialty assignment).
*
* @return list<array{key: string, definition: array<string, mixed>}>
*/
public function enabledModulesForMember(Organization $organization, ?Member $member): array
{
return array_values(array_filter(
$this->enabledModules($organization),
fn (array $item) => $this->memberCanAccess($organization, $member, $item['key']),
));
}
/**
* Whether the member may see/serve this specialty module.
* Doctors must be linked to a practitioner desk in that specialty department.
* Admins/receptionists keep org-wide visibility for operations.
*/
public function memberCanAccess(Organization $organization, ?Member $member, string $key): bool
{
if (! $this->isEnabled($organization, $key)) {
return false;
}
$definition = $this->definition($key);
if (! $definition) {
return false;
}
$roles = $definition['roles'] ?? [];
if (is_array($roles) && $roles !== [] && $member && ! in_array($member->role, $roles, true)) {
return false;
}
if ($member && $member->role === 'doctor') {
return $this->doctorAssignedToModule($organization, $member, $key);
}
return true;
}
public function doctorAssignedToModule(Organization $organization, Member $member, string $key): bool
{
$definition = $this->definition($key);
if (! $definition) {
return false;
}
$departmentType = (string) ($definition['department_type'] ?? '');
$provisionedIds = data_get(
$organization->settings,
"specialty_module_provisioning.{$key}.department_ids",
[],
);
$provisionedIds = is_array($provisionedIds)
? array_map('intval', $provisionedIds)
: [];
$practitioners = Practitioner::owned((string) $organization->owner_ref)
->where('organization_id', $organization->id)
->where('is_active', true)
->where(function ($query) use ($member) {
$query->where('member_id', $member->id)
->orWhere('user_ref', $member->user_ref);
})
->with('department')
->get();
foreach ($practitioners as $practitioner) {
if ($provisionedIds !== [] && in_array((int) $practitioner->department_id, $provisionedIds, true)) {
return true;
}
if ($departmentType !== '' && $practitioner->department?->type === $departmentType) {
return true;
}
}
return false;
}
public function canManage(Organization $organization): bool
{
return $this->plans->hasFeature($organization, 'specialty_modules');