Lock doctors to their assigned patients and hide branch filters.
Deploy Ladill Care / deploy (push) Successful in 1m2s
Deploy Ladill Care / deploy (push) Successful in 1m2s
Doctors no longer see branch/practitioner pickers; queue, appointments, patients, and dashboard only include visits on their linked desk. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -38,9 +38,15 @@ class AppointmentService
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $filters
|
||||
* @param list<int>|null $practitionerIds When set, only appointments assigned to these desks.
|
||||
*/
|
||||
public function list(string $ownerRef, int $organizationId, array $filters = [], ?int $branchId = null): LengthAwarePaginator
|
||||
{
|
||||
public function list(
|
||||
string $ownerRef,
|
||||
int $organizationId,
|
||||
array $filters = [],
|
||||
?int $branchId = null,
|
||||
?array $practitionerIds = null,
|
||||
): LengthAwarePaginator {
|
||||
$query = $this->queryForOrganization($ownerRef, $organizationId)
|
||||
->with(['patient', 'practitioner', 'branch', 'department'])
|
||||
->orderByDesc('scheduled_at');
|
||||
@@ -49,6 +55,14 @@ class AppointmentService
|
||||
$query->where('branch_id', $branchId);
|
||||
}
|
||||
|
||||
if ($practitionerIds !== null) {
|
||||
if ($practitionerIds === []) {
|
||||
$query->whereRaw('1 = 0');
|
||||
} else {
|
||||
$query->whereIn('practitioner_id', $practitionerIds);
|
||||
}
|
||||
}
|
||||
|
||||
if ($status = $filters['status'] ?? null) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
@@ -69,10 +83,20 @@ class AppointmentService
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int>|null $practitionerIds
|
||||
* @return Collection<int, Appointment>
|
||||
*/
|
||||
public function queue(string $ownerRef, int $branchId, ?int $practitionerId = null): Collection
|
||||
{
|
||||
public function queue(
|
||||
string $ownerRef,
|
||||
int $branchId,
|
||||
?int $practitionerId = null,
|
||||
bool $includeUnassigned = true,
|
||||
?array $practitionerIds = null,
|
||||
): Collection {
|
||||
if ($practitionerIds !== null) {
|
||||
return $this->queueForPractitioners($ownerRef, $practitionerIds, $branchId > 0 ? $branchId : null);
|
||||
}
|
||||
|
||||
$this->repairDuplicateQueuePositions($ownerRef, $branchId);
|
||||
|
||||
$query = Appointment::owned($ownerRef)
|
||||
@@ -84,8 +108,11 @@ class AppointmentService
|
||||
->orderBy('checked_in_at');
|
||||
|
||||
if ($practitionerId !== null) {
|
||||
$query->where(function (Builder $q) use ($practitionerId) {
|
||||
$q->where('practitioner_id', $practitionerId)->orWhereNull('practitioner_id');
|
||||
$query->where(function (Builder $q) use ($practitionerId, $includeUnassigned) {
|
||||
$q->where('practitioner_id', $practitionerId);
|
||||
if ($includeUnassigned) {
|
||||
$q->orWhereNull('practitioner_id');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,6 +132,46 @@ class AppointmentService
|
||||
->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Doctor queue: only appointments assigned to their linked practitioner desk(s).
|
||||
*
|
||||
* @param list<int> $practitionerIds
|
||||
* @return Collection<int, Appointment>
|
||||
*/
|
||||
public function queueForPractitioners(string $ownerRef, array $practitionerIds, ?int $branchId = null): Collection
|
||||
{
|
||||
if ($practitionerIds === []) {
|
||||
return new Collection;
|
||||
}
|
||||
|
||||
$query = Appointment::owned($ownerRef)
|
||||
->whereIn('practitioner_id', $practitionerIds)
|
||||
->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->with(['patient', 'practitioner', 'visit', 'organization'])
|
||||
->orderBy('queue_position')
|
||||
->orderBy('waiting_at')
|
||||
->orderBy('checked_in_at');
|
||||
|
||||
return new Collection(
|
||||
$query->get()
|
||||
->filter(function (Appointment $appointment) {
|
||||
$organization = $appointment->organization;
|
||||
if (! $organization) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->workflowGate->canRelease(
|
||||
$organization,
|
||||
$appointment->visit,
|
||||
$this->queueBridge->contextForAppointment($organization, $appointment),
|
||||
);
|
||||
})
|
||||
->values()
|
||||
->all()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
|
||||
@@ -243,22 +243,21 @@ class DemoTenantSeeder
|
||||
->where('user_ref', $doctorEmail)
|
||||
->first();
|
||||
|
||||
$byBranch = [];
|
||||
foreach ($practitioners as $practitioner) {
|
||||
$byBranch[(int) $practitioner->branch_id] ??= $practitioner;
|
||||
// Link the demo doctor to a single desk on their home branch (not every site).
|
||||
$homeBranchId = (int) ($member?->branch_id ?: ($branches[0]->id ?? 0));
|
||||
$practitioner = collect($practitioners)->first(
|
||||
fn (Practitioner $p) => (int) $p->branch_id === $homeBranchId,
|
||||
) ?? ($practitioners[0] ?? null);
|
||||
|
||||
if (! $practitioner) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($branches as $branch) {
|
||||
$practitioner = $byBranch[(int) $branch->id] ?? null;
|
||||
if (! $practitioner) {
|
||||
continue;
|
||||
}
|
||||
$practitioner->forceFill([
|
||||
'user_ref' => $doctorEmail,
|
||||
'member_id' => $member?->id,
|
||||
'name' => $doctorName !== '' ? $doctorName : $practitioner->name,
|
||||
])->save();
|
||||
}
|
||||
$practitioner->forceFill([
|
||||
'user_ref' => $doctorEmail,
|
||||
'member_id' => $member?->id,
|
||||
'name' => $doctorName !== '' ? $doctorName : $practitioner->name,
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\Department;
|
||||
use App\Models\FacilityWorkflow;
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use App\Models\Practitioner;
|
||||
use App\Models\User;
|
||||
use App\Services\Care\Workflow\WorkflowTemplateInstaller;
|
||||
use App\Services\Care\Workflow\WorkflowTemplateRegistry;
|
||||
@@ -192,4 +193,30 @@ class OrganizationResolver
|
||||
|
||||
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).
|
||||
*
|
||||
* @return list<int>|null
|
||||
*/
|
||||
public function practitionerScope(Organization $organization, ?Member $member): ?array
|
||||
{
|
||||
if ($member === null || $member->role !== 'doctor') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 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);
|
||||
})
|
||||
->orderBy('id')
|
||||
->pluck('id')
|
||||
->map(fn ($id) => (int) $id)
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,15 @@ class PatientService
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $filters
|
||||
* @param list<int>|null $practitionerIds When set, only patients with appointments on these desks.
|
||||
*/
|
||||
public function search(string $ownerRef, int $organizationId, array $filters = [], ?int $branchId = null): LengthAwarePaginator
|
||||
{
|
||||
public function search(
|
||||
string $ownerRef,
|
||||
int $organizationId,
|
||||
array $filters = [],
|
||||
?int $branchId = null,
|
||||
?array $practitionerIds = null,
|
||||
): LengthAwarePaginator {
|
||||
$query = $this->queryForOrganization($ownerRef, $organizationId)
|
||||
->with(['branch'])
|
||||
->orderByDesc('created_at');
|
||||
@@ -41,6 +47,17 @@ class PatientService
|
||||
$query->where('branch_id', $branchId);
|
||||
}
|
||||
|
||||
if ($practitionerIds !== null) {
|
||||
if ($practitionerIds === []) {
|
||||
$query->whereRaw('1 = 0');
|
||||
} else {
|
||||
$query->whereHas(
|
||||
'appointments',
|
||||
fn (Builder $inner) => $inner->whereIn('practitioner_id', $practitionerIds),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($q = trim((string) ($filters['q'] ?? ''))) {
|
||||
$query->where(function (Builder $inner) use ($q) {
|
||||
$inner->where('patient_number', 'like', "%{$q}%")
|
||||
|
||||
Reference in New Issue
Block a user