Embed Ladill Queue on role pages and add specialty modules under Settings.
Deploy Ladill Care / deploy (push) Successful in 53s
Deploy Ladill Care / deploy (push) Successful in 53s
Remove the standalone Service Queues nav so call-next/now-serving lives on clinical queue, appointments, pharmacy, lab, and specialty surfaces; Pro orgs can activate dentistry and related modules with branch departments and queue stubs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,6 +11,7 @@ use App\Models\Patient;
|
||||
use App\Models\Practitioner;
|
||||
use App\Services\Care\AppointmentService;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use App\Services\Care\ServiceQueuePresenter;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -21,13 +22,15 @@ class AppointmentController extends Controller
|
||||
|
||||
public function __construct(
|
||||
protected AppointmentService $appointments,
|
||||
protected ServiceQueuePresenter $serviceQueues,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$this->authorizeAbility($request, 'appointments.view');
|
||||
$organization = $this->organization($request);
|
||||
$branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));
|
||||
$member = $this->member($request);
|
||||
$branchScope = app(OrganizationResolver::class)->branchScope($member);
|
||||
|
||||
$appointments = $this->appointments->list(
|
||||
$this->ownerRef($request),
|
||||
@@ -58,6 +61,13 @@ class AppointmentController extends Controller
|
||||
'practitioners' => $practitioners,
|
||||
'statuses' => config('care.appointment_statuses'),
|
||||
'heroStats' => $heroStats,
|
||||
'serviceQueuePanel' => $this->serviceQueues->panel(
|
||||
$request,
|
||||
$organization,
|
||||
$member,
|
||||
'clinical',
|
||||
$request->input('counter_uuid'),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ use App\Services\Care\AppointmentService;
|
||||
use App\Services\Care\CarePermissions;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use App\Services\Care\ReportService;
|
||||
use App\Services\Care\ServiceQueuePresenter;
|
||||
use App\Services\Care\SpecialtyModuleService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\View\View;
|
||||
@@ -24,6 +26,8 @@ class DashboardController extends Controller
|
||||
protected ReportService $reports,
|
||||
protected CarePermissions $permissions,
|
||||
protected AppointmentService $appointments,
|
||||
protected ServiceQueuePresenter $serviceQueues,
|
||||
protected SpecialtyModuleService $specialtyModules,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): View
|
||||
@@ -76,6 +80,12 @@ class DashboardController extends Controller
|
||||
? $this->patientQueueForMember($request, $organization->id, $owner, $member, $branchScope)
|
||||
: [collect(), collect(), null];
|
||||
|
||||
$serviceQueueContext = match ($member?->role) {
|
||||
'pharmacist' => 'pharmacy',
|
||||
'lab_technician' => 'laboratory',
|
||||
default => 'clinical',
|
||||
};
|
||||
|
||||
return view('care.dashboard', [
|
||||
'organization' => $organization,
|
||||
'member' => $member,
|
||||
@@ -93,6 +103,14 @@ class DashboardController extends Controller
|
||||
'patientQueue' => $queue,
|
||||
'inConsultation' => $inConsultation,
|
||||
'practitionerId' => $practitionerId,
|
||||
'serviceQueuePanel' => $this->serviceQueues->panel(
|
||||
$request,
|
||||
$organization,
|
||||
$member,
|
||||
$serviceQueueContext,
|
||||
$request->input('counter_uuid'),
|
||||
),
|
||||
'specialtyModules' => $this->specialtyModules->enabledModules($organization),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Models\InvestigationType;
|
||||
use App\Models\Member;
|
||||
use App\Services\Care\InvestigationService;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use App\Services\Care\ServiceQueuePresenter;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -21,6 +22,7 @@ class InvestigationController extends Controller
|
||||
|
||||
public function __construct(
|
||||
protected InvestigationService $investigations,
|
||||
protected ServiceQueuePresenter $serviceQueues,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): View
|
||||
@@ -74,6 +76,13 @@ class InvestigationController extends Controller
|
||||
->where('organization_id', $organization->id)
|
||||
->orderBy('role')
|
||||
->get(),
|
||||
'serviceQueuePanel' => $this->serviceQueues->panel(
|
||||
$request,
|
||||
$organization,
|
||||
$this->member($request),
|
||||
'laboratory',
|
||||
$request->input('counter_uuid'),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Models\Practitioner;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use App\Services\Care\PharmacyService;
|
||||
use App\Services\Care\PrescriptionService;
|
||||
use App\Services\Care\ServiceQueuePresenter;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -21,6 +22,7 @@ class PrescriptionController extends Controller
|
||||
public function __construct(
|
||||
protected PrescriptionService $prescriptions,
|
||||
protected PharmacyService $pharmacy,
|
||||
protected ServiceQueuePresenter $serviceQueues,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): View
|
||||
@@ -65,6 +67,13 @@ class PrescriptionController extends Controller
|
||||
'queue' => $queue,
|
||||
'drugs' => $drugs,
|
||||
'canDispense' => $canDispense,
|
||||
'serviceQueuePanel' => $this->serviceQueues->panel(
|
||||
$request,
|
||||
$organization,
|
||||
$this->member($request),
|
||||
'pharmacy',
|
||||
$request->input('counter_uuid'),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Models\Practitioner;
|
||||
use App\Services\Care\AppointmentService;
|
||||
use App\Services\Care\ConsultationService;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use App\Services\Care\ServiceQueuePresenter;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -21,13 +22,15 @@ class QueueController extends Controller
|
||||
public function __construct(
|
||||
protected AppointmentService $appointments,
|
||||
protected ConsultationService $consultations,
|
||||
protected ServiceQueuePresenter $serviceQueues,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$this->authorizeAbility($request, 'appointments.view');
|
||||
$organization = $this->organization($request);
|
||||
$branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));
|
||||
$member = $this->member($request);
|
||||
$branchScope = app(OrganizationResolver::class)->branchScope($member);
|
||||
|
||||
$branches = Branch::owned($this->ownerRef($request))
|
||||
->where('organization_id', $organization->id)
|
||||
@@ -82,6 +85,13 @@ class QueueController extends Controller
|
||||
'canManageQueue' => $canManageQueue,
|
||||
'canConsult' => $canConsult,
|
||||
'heroStats' => $heroStats,
|
||||
'serviceQueuePanel' => $this->serviceQueues->panel(
|
||||
$request,
|
||||
$organization,
|
||||
$member,
|
||||
'clinical',
|
||||
$request->input('counter_uuid'),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Http\Controllers\Care;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
|
||||
use App\Models\Branch;
|
||||
use App\Services\Care\ServiceQueuePresenter;
|
||||
use App\Services\Queue\QueueClient;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@@ -15,40 +15,19 @@ class ServiceQueueController extends Controller
|
||||
{
|
||||
use ScopesToAccount;
|
||||
|
||||
public function index(Request $request, QueueClient $queue): View|RedirectResponse
|
||||
public function index(Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'service_queues.console');
|
||||
$organization = $this->organization($request);
|
||||
|
||||
if (! $this->integrationEnabled($organization)) {
|
||||
return redirect()->route('care.settings')->with('error', 'Enable Ladill Queue integration in settings first.');
|
||||
}
|
||||
|
||||
$owner = $this->ownerRef($request);
|
||||
|
||||
try {
|
||||
$counters = $queue->counters($owner);
|
||||
} catch (RequestException $e) {
|
||||
if ($queue->configured() && $e->response?->status() === 403) {
|
||||
try {
|
||||
$queue->syncIntegration($owner, true);
|
||||
$counters = $queue->counters($owner);
|
||||
} catch (RequestException) {
|
||||
return redirect()->route('care.settings')->with('error', 'Ladill Queue integration is not enabled. Save settings with Queue integration checked, and enable Care in Queue settings.');
|
||||
}
|
||||
} else {
|
||||
return redirect()->route('care.settings')->with('error', 'Could not reach Ladill Queue. Check API keys and enable integration in Queue settings.');
|
||||
}
|
||||
}
|
||||
|
||||
return view('care.service-queues.index', compact('counters', 'organization'));
|
||||
// Natural Queue: counters live on role pages (clinical queue, pharmacy, lab, specialties).
|
||||
return redirect()->route('care.queue.index');
|
||||
}
|
||||
|
||||
public function console(Request $request, string $counterUuid, QueueClient $queue): View|RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'service_queues.console');
|
||||
$organization = $this->organization($request);
|
||||
$owner = $this->ownerRef($request);
|
||||
$owner = (string) $organization->owner_ref;
|
||||
|
||||
if (! $this->integrationEnabled($organization)) {
|
||||
return redirect()->route('care.settings');
|
||||
@@ -57,7 +36,7 @@ class ServiceQueueController extends Controller
|
||||
try {
|
||||
$state = $queue->console($owner, $counterUuid);
|
||||
} catch (RequestException) {
|
||||
return redirect()->route('care.service-queues.index')->with('error', 'Counter console unavailable.');
|
||||
return redirect()->route('care.queue.index')->with('error', 'Counter console unavailable.');
|
||||
}
|
||||
|
||||
return view('care.service-queues.console', [
|
||||
@@ -70,7 +49,8 @@ class ServiceQueueController extends Controller
|
||||
public function action(Request $request, string $counterUuid, QueueClient $queue): RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'service_queues.console');
|
||||
$owner = $this->ownerRef($request);
|
||||
$organization = $this->organization($request);
|
||||
$owner = (string) $organization->owner_ref;
|
||||
|
||||
$validated = $request->validate([
|
||||
'action' => ['required', 'string'],
|
||||
|
||||
@@ -58,6 +58,7 @@ class SettingsController extends Controller
|
||||
'hasBranchesFeature' => $plans->hasFeature($organization, 'branches'),
|
||||
'hasClinicalDevicesFeature' => $plans->hasFeature($organization, 'clinical_devices'),
|
||||
'canUseQueueIntegration' => $plans->canUseQueueIntegration($organization),
|
||||
'canUseSpecialtyModules' => $plans->canUseSpecialtyModules($organization),
|
||||
'assessmentsEngineEnabled' => $careFeatures->enabled($organization, CareFeatures::ASSESSMENTS_ENGINE),
|
||||
'messagingSmsReady' => $suiteSmsReady || $credential->hasValidSms(),
|
||||
'messagingEmailReady' => $suiteEmailReady || $credential->hasValidBird(),
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Care;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
|
||||
use App\Services\Care\PlanService;
|
||||
use App\Services\Care\SpecialtyModuleService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class SettingsModulesController extends Controller
|
||||
{
|
||||
use ScopesToAccount;
|
||||
|
||||
public function edit(Request $request, SpecialtyModuleService $modules, PlanService $plans): View
|
||||
{
|
||||
$this->authorizeAbility($request, 'settings.view');
|
||||
$organization = $this->organization($request);
|
||||
$member = $this->member($request);
|
||||
$canManage = app(\App\Services\Care\CarePermissions::class)->can($member, 'settings.manage');
|
||||
|
||||
return view('care.settings.modules', [
|
||||
'organization' => $organization,
|
||||
'canManage' => $canManage,
|
||||
'canUseSpecialtyModules' => $modules->canManage($organization),
|
||||
'catalog' => $modules->catalog(),
|
||||
'enabled' => collect($modules->enabledKeys($organization))->flip()->all(),
|
||||
'planKey' => $plans->planKey($organization),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, SpecialtyModuleService $modules): RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'settings.manage');
|
||||
$organization = $this->organization($request);
|
||||
|
||||
if (! $modules->canManage($organization)) {
|
||||
return redirect()
|
||||
->route('care.pro.index')
|
||||
->with('upsell', 'Specialty modules are part of Care Pro. Upgrade to activate dentistry, eye care, and other specialty practice surfaces.');
|
||||
}
|
||||
|
||||
$keys = array_keys($modules->catalog());
|
||||
$desired = [];
|
||||
foreach ($keys as $key) {
|
||||
$desired[$key] = $request->boolean("modules.{$key}");
|
||||
}
|
||||
|
||||
$result = $modules->sync($organization, $this->ownerRef($request), $desired);
|
||||
|
||||
if ($result['errors'] !== []) {
|
||||
return back()->with('error', 'Some modules could not be updated: '.implode(' ', $result['errors']));
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
if ($result['activated'] !== []) {
|
||||
$parts[] = 'Activated '.count($result['activated']).' module(s)';
|
||||
}
|
||||
if ($result['deactivated'] !== []) {
|
||||
$parts[] = 'Deactivated '.count($result['deactivated']).' module(s)';
|
||||
}
|
||||
|
||||
return back()->with('success', $parts !== [] ? implode('. ', $parts).'.' : 'Modules saved.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Care;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
|
||||
use App\Models\Appointment;
|
||||
use App\Models\Department;
|
||||
use App\Services\Care\OrganizationResolver;
|
||||
use App\Services\Care\ServiceQueuePresenter;
|
||||
use App\Services\Care\SpecialtyModuleService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class SpecialtyModuleController extends Controller
|
||||
{
|
||||
use ScopesToAccount;
|
||||
|
||||
public function show(
|
||||
Request $request,
|
||||
string $module,
|
||||
SpecialtyModuleService $modules,
|
||||
ServiceQueuePresenter $presenter,
|
||||
): View {
|
||||
$definition = $modules->definition($module);
|
||||
abort_unless($definition, 404);
|
||||
|
||||
$organization = $this->organization($request);
|
||||
abort_unless($modules->isEnabled($organization, $module), 404);
|
||||
|
||||
$roles = $definition['roles'] ?? [];
|
||||
$member = $this->member($request);
|
||||
if (is_array($roles) && $roles !== [] && $member && ! in_array($member->role, $roles, true)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$owner = $this->ownerRef($request);
|
||||
$branchScope = app(OrganizationResolver::class)->branchScope($member);
|
||||
|
||||
$departments = Department::owned($owner)
|
||||
->where('type', $definition['department_type'] ?? 'general')
|
||||
->where('is_active', true)
|
||||
->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope))
|
||||
->with('branch')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
$departmentIds = $departments->pluck('id')->all();
|
||||
|
||||
$waiting = Appointment::owned($owner)
|
||||
->where('organization_id', $organization->id)
|
||||
->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
|
||||
->when($departmentIds !== [], fn ($q) => $q->whereIn('department_id', $departmentIds))
|
||||
->when($departmentIds === [], fn ($q) => $q->whereRaw('1 = 0'))
|
||||
->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope))
|
||||
->with(['patient', 'practitioner', 'department'])
|
||||
->orderBy('queue_position')
|
||||
->orderBy('checked_in_at')
|
||||
->limit(25)
|
||||
->get();
|
||||
|
||||
$serviceQueuePanel = $presenter->panel(
|
||||
$request,
|
||||
$organization,
|
||||
$member,
|
||||
$module,
|
||||
$request->input('counter_uuid'),
|
||||
);
|
||||
|
||||
return view('care.specialty.show', [
|
||||
'organization' => $organization,
|
||||
'moduleKey' => $module,
|
||||
'definition' => $definition,
|
||||
'departments' => $departments,
|
||||
'waiting' => $waiting,
|
||||
'queueStubs' => $modules->queueStubsFor($organization, $module),
|
||||
'serviceQueuePanel' => $serviceQueuePanel,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user