Restrict lab catalog pricing to admins only.
Deploy Ladill Care / deploy (push) Successful in 38s

Split catalog CRUD onto lab.catalog.manage so technicians keep lab.manage for queue and results workflow without setting investigation prices.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-18 18:58:57 +00:00
co-authored by Cursor
parent 65e54d1253
commit 656f402e8f
12 changed files with 128 additions and 17 deletions
@@ -1,6 +1,9 @@
<x-app-layout title="Investigation catalog">
<div class="flex items-center justify-between">
<h1 class="text-xl font-semibold text-slate-900">Investigation catalog</h1>
<div>
<h1 class="text-xl font-semibold text-slate-900">Investigation catalog</h1>
<p class="mt-1 text-sm text-slate-500">Admins manage test types and prices. Lab technicians process samples from the laboratory queue.</p>
</div>
<a href="{{ route('care.lab.catalog.create') }}" class="btn-primary">Add test</a>
</div>
<div class="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-white">
@@ -6,7 +6,7 @@
</div>
<div class="flex gap-2">
<a href="{{ route('care.lab.requests.index') }}" class="rounded-lg border border-slate-200 px-4 py-2 text-sm">All requests</a>
@if (app(\App\Services\Care\CarePermissions::class)->can(auth()->user() ? app(\App\Services\Care\OrganizationResolver::class)->memberFor(auth()->user(), $organization) : null, 'lab.manage'))
@if (app(\App\Services\Care\CarePermissions::class)->can(auth()->user() ? app(\App\Services\Care\OrganizationResolver::class)->memberFor(auth()->user(), $organization) : null, 'lab.catalog.manage'))
<a href="{{ route('care.lab.catalog.index') }}" class="rounded-lg border border-slate-200 px-4 py-2 text-sm">Catalog</a>
@endif
</div>
+13 -1
View File
@@ -48,7 +48,7 @@
</div>
</x-settings.card>
@if ($canViewBranches || $canViewTeam || $canViewDevices || $canViewDisplays)
@if ($canViewBranches || $canViewTeam || $canViewDevices || $canViewDisplays || ($canManageLabCatalog ?? false))
<x-settings.card title="Branches & team" description="Locations, staff access, and clinical hardware for this facility. Multi-branch management requires Care Pro.">
<ul class="space-y-3">
@if ($canViewBranches)
@@ -126,6 +126,18 @@
<span class="text-slate-400">&rarr;</span>
</a>
</li>
@if (! empty($hasPaidPlan) && ($canManageLabCatalog ?? false))
<li>
<a href="{{ route('care.lab.catalog.index') }}"
class="flex items-center justify-between rounded-xl border border-slate-100 bg-slate-50 px-4 py-3 text-sm text-slate-700 hover:text-indigo-700">
<span>
Laboratory catalog
<span class="mt-0.5 block text-xs text-slate-500">Investigation types and lab test prices (admins only)</span>
</span>
<span class="text-slate-400">&rarr;</span>
</a>
</li>
@endif
<li>
<a href="{{ route('care.settings.modules') }}"
class="flex items-center justify-between rounded-xl border border-slate-100 bg-slate-50 px-4 py-3 text-sm text-slate-700 hover:text-indigo-700">
@@ -1,7 +1,7 @@
<x-app-layout title="GP pricing · Settings">
<x-settings.page
title="GP products & services"
description="Prices for general practice consultations and clinic services at {{ $organization->name }}. Drug prices are managed in Inventory; lab tests in Laboratory catalog."
description="Prices for general practice consultations and clinic services at {{ $organization->name }}. Drug prices are managed in Inventory; lab test prices are set by admins in Laboratory catalog."
>
<p class="text-sm">
<a href="{{ route('care.settings') }}" class="font-medium text-indigo-600 hover:text-indigo-800"> Facility settings</a>
@@ -14,7 +14,7 @@
<p class="rounded-lg bg-emerald-50 px-3 py-2 text-sm text-emerald-800">{{ session('success') }}</p>
@endif
<x-settings.card title="Status" description="Activate modules from the modules list. Drug prices stay in Inventory; lab test prices in Laboratory catalog.">
<x-settings.card title="Status" description="Activate modules from the modules list. Drug prices stay in Inventory; lab test prices are set by admins in Laboratory catalog.">
<div class="flex flex-wrap items-center gap-3 text-sm">
@if ($enabled)
<span class="rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-800">Active</span>
+5 -1
View File
@@ -52,7 +52,7 @@
// Lab, pharmacy, billing, and Queue service counters are Pro/Enterprise modules.
if (! empty($hasPaidPlan)) {
if ($permissions->can($member, 'lab.view') && $permissions->handlesFloorCare($member)) {
$nav[] = ['name' => 'Laboratory', 'route' => route('care.lab.queue.index'), 'active' => request()->routeIs('care.lab.*'),
$nav[] = ['name' => 'Laboratory', 'route' => route('care.lab.queue.index'), 'active' => request()->routeIs('care.lab.*') && ! request()->routeIs('care.lab.catalog.*'),
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714a2.25 2.25 0 0 0 .659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 0 1-1.59.659H9.06a2.25 2.25 0 0 1-1.591-.659L5 14.5m14 0V17a2.25 2.25 0 0 1-2.25 2.25H7.25A2.25 2.25 0 0 1 5 17v-2.5" />'];
}
@@ -98,6 +98,10 @@
}
$adminNav = [];
if (! empty($hasPaidPlan) && $permissions->can($member, 'lab.catalog.manage')) {
$adminNav[] = ['name' => 'Lab catalog', 'route' => route('care.lab.catalog.index'), 'active' => request()->routeIs('care.lab.catalog.*'),
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714a2.25 2.25 0 0 0 .659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 0 1-1.59.659H9.06a2.25 2.25 0 0 1-1.591-.659L5 14.5m14 0V17a2.25 2.25 0 0 1-2.25 2.25H7.25A2.25 2.25 0 0 1 5 17v-2.5" />'];
}
if ($permissions->can($member, 'admin.departments.view')) {
$adminNav[] = ['name' => 'Departments', 'route' => route('care.departments.index'), 'active' => request()->routeIs('care.departments.*'),
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25A2.25 2.25 0 0 1 13.5 18v-2.25Z" />'];