feat(permissions): render both permission forms from the catalog, fix borrowed gates
The three hardcoded resource lists in the admin panel are gone. MySecretariesPage, SecretariesPage and DoctorPermissionsModal now render from GET /api/v1/permission-catalog, so a resource added to the backend registry shows up in all of them with no frontend change. Each has a test that proves exactly that by adding a resource to the mock and asserting it renders. SecretaryPermissions was an interface with a field per resource, which made "dynamic" impossible in TypeScript — every new resource would have been a compile error. It is now an open map. Only two files consumed it. The borrowed gates are corrected: - five resource pages moved off appointment_settings onto their own 'resources' - treatment-cases moved off appointments onto 'treatment' - service-categories moved onto 'services', which is what ServiceCatalogController actually manages (categories, item groups, service relations) — not resources TreatmentCaseController had no permission gate at all, only IS_AUTHENTICATED_FULLY, so any secretary could read and edit treatment cases. All seven of its actions are now gated on treatment view/update. ResourcePermissionTrait takes the resource from an overridable method instead of hardcoding appointment_settings. HolidayController overrides it back, since the holidays page really is appointment settings. The booking gate keeps its appointments.view fallback so a secretary who may book is not blocked by a resource-config permission. Defaults were picked to preserve today's effective access, so no role gains or loses a page from this move. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,8 @@ use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
use App\Clinic\Security\ClinicDoctorAccessChecker;
|
||||
use App\Secretary\Security\SecretaryAccessChecker;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
#[OA\Tag(name: 'Treatment')]
|
||||
@@ -40,11 +42,28 @@ class TreatmentCaseController extends BaseController
|
||||
private readonly TreatmentCaseEditor $editor,
|
||||
private readonly TreatmentPlanProjector $planner,
|
||||
private readonly \App\UserProfile\Repository\UserProfileRepository $profiles,
|
||||
private readonly SecretaryAccessChecker $secretaryAccess,
|
||||
private readonly ClinicDoctorAccessChecker $clinicDoctorAccess,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* تا پیش از رجیستریِ واحد، این کنترلر هیچ گِیت مجوزی نداشت و صفحهاش در پنل
|
||||
* روی `appointments.view` سوار بود — یعنی هر منشیای که اجازهٔ دیدن نوبت داشت
|
||||
* پروندهٔ درمان را هم میدید.
|
||||
*
|
||||
* @param 'view'|'update' $action
|
||||
*/
|
||||
private function denyUnlessGranted(User $user, string $action): void
|
||||
{
|
||||
$this->secretaryAccess->denyUnlessGranted($user, 'treatment', $action);
|
||||
$this->clinicDoctorAccess->denyUnlessGranted($user, 'treatment', $action);
|
||||
}
|
||||
|
||||
#[Route('/api/v1/treatment-cases', name: 'treatment_case_list', methods: ['GET'])]
|
||||
public function list(#[CurrentUser] User $user, Request $request): JsonResponse
|
||||
{
|
||||
$this->denyUnlessGranted($user, 'view');
|
||||
|
||||
[$entityType, $entityId] = $this->branches->pair($user);
|
||||
|
||||
$status = $request->query->get('status');
|
||||
@@ -88,6 +107,8 @@ class TreatmentCaseController extends BaseController
|
||||
#[Route('/api/v1/treatment-case/{uuid}', name: 'treatment_case_show', methods: ['GET'])]
|
||||
public function show(#[CurrentUser] User $user, string $uuid): JsonResponse
|
||||
{
|
||||
$this->denyUnlessGranted($user, 'view');
|
||||
|
||||
$case = $this->requireCase($user, $uuid);
|
||||
|
||||
/**
|
||||
@@ -111,6 +132,8 @@ class TreatmentCaseController extends BaseController
|
||||
#[Route('/api/v1/treatment-case/{uuid}', name: 'treatment_case_update', methods: ['PATCH'])]
|
||||
public function update(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
|
||||
{
|
||||
$this->denyUnlessGranted($user, 'update');
|
||||
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
if (!is_array($data)) {
|
||||
@@ -131,6 +154,8 @@ class TreatmentCaseController extends BaseController
|
||||
#[Route('/api/v1/treatment-sessions/unbooked', name: 'treatment_sessions_unbooked', methods: ['GET'])]
|
||||
public function unbooked(#[CurrentUser] User $user, Request $request): JsonResponse
|
||||
{
|
||||
$this->denyUnlessGranted($user, 'view');
|
||||
|
||||
[$entityType, $entityId] = $this->branches->pair($user);
|
||||
|
||||
$withinDays = (int) $request->query->get('within_days', 7);
|
||||
@@ -157,6 +182,8 @@ class TreatmentCaseController extends BaseController
|
||||
#[Route('/api/v1/treatment-case/{uuid}/plan', name: 'treatment_case_plan', methods: ['GET'])]
|
||||
public function plan(#[CurrentUser] User $user, string $uuid): JsonResponse
|
||||
{
|
||||
$this->denyUnlessGranted($user, 'view');
|
||||
|
||||
$case = $this->requireCase($user, $uuid);
|
||||
|
||||
/**
|
||||
@@ -203,6 +230,8 @@ class TreatmentCaseController extends BaseController
|
||||
#[Route('/api/v1/treatment-session/{uuid}', name: 'treatment_session_show', methods: ['GET'])]
|
||||
public function showSession(#[CurrentUser] User $user, string $uuid): JsonResponse
|
||||
{
|
||||
$this->denyUnlessGranted($user, 'view');
|
||||
|
||||
$session = $this->requireSession($user, $uuid);
|
||||
$case = $session->getTreatmentCase();
|
||||
|
||||
@@ -225,6 +254,8 @@ class TreatmentCaseController extends BaseController
|
||||
#[Route('/api/v1/treatment-session/{uuid}/slot-suggestions', name: 'treatment_session_slots', methods: ['GET'])]
|
||||
public function slotSuggestions(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
|
||||
{
|
||||
$this->denyUnlessGranted($user, 'view');
|
||||
|
||||
$session = $this->requireSession($user, $uuid);
|
||||
|
||||
$resourceUuid = $request->query->get('resource_uuid');
|
||||
|
||||
Reference in New Issue
Block a user