feat(clinic-doctor): full permission coverage + enforcement, parity with secretary

The clinic-member-doctor permission system (ClinicDoctorPermission) lagged the
secretary system: only 6 resources, enforced in ~6 places, dead toggles
(services.update never checked), and a sidebar showing just appointments+patients.
Bring it to parity so a clinic owner can control exactly what each member doctor
does — while an independent doctor stays completely unrestricted.

Coverage: add insurances, addresses, inventory, tags, staff, discounts, sms to
ClinicDoctorPermission::DEFAULT_PERMISSIONS + DoctorPermissionsModal
(subscription/clinic_doctors stay owner-only by design).

New App\Clinic\Security\ClinicDoctorAccessChecker (parallel to
SecretaryAccessChecker):
- denyUnlessGranted(user, resource, action): 403 only for a clinic-member doctor
  in the clinic context; owner/admin/secretary/independent-doctor pass through.
- memberClinicId(user): resolves the member doctor to the CLINIC's tenant so the
  role-based controllers (Inventory/Tag/Staff/Discount/Sms) stop showing them
  their personal tenant in clinic context.

Enforcement wired into 10 controllers alongside the existing secretary gates:
ClinicService (services), Insurance (insurances), Patient (patients+payments),
Staff, Discount, Inventory, Tag, SmsWallet, Payment, PaymentMethod.

Frontend: the guest-doctor sidebar branch now exposes every permitted resource
(gated by can()) plus a «تنظیمات» entry; both settings navs (PurchaseSubscription
Sidebar + SETTINGS_MENU) are now permission-filtered for a scope=clinic doctor,
not just secretaries; my-payments route gets the missing payments permission.
CRUD-button gating already applies (usePermissions is role-agnostic).

Tests: ClinicDoctorPermissionEnforcementTest (member denied/allowed +
independent-doctor-unrestricted); guest-doctor sidebar gating. Backend 375 pass,
frontend 503 pass. docs/api/clinic.md updated with the full resource set +
enforcement notes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-23 19:09:16 +03:30
co-authored by Claude Opus 4.8
parent 1116ac14cb
commit f33c7a3eab
21 changed files with 617 additions and 31 deletions
@@ -27,6 +27,13 @@ class ClinicDoctorPermission
'payments' => ['view' => true, 'create' => false, 'update' => false, 'delete' => false],
'services' => ['view' => true, 'update' => false],
'clinic_info' => ['view' => true, 'update' => false],
'insurances' => ['view' => true, 'create' => false, 'update' => false, 'delete' => false],
'addresses' => ['view' => true, 'create' => false, 'update' => false, 'delete' => false],
'inventory' => ['view' => false, 'create' => false, 'update' => false, 'delete' => false],
'tags' => ['view' => false, 'create' => false, 'update' => false, 'delete' => false],
'staff' => ['view' => false, 'create' => false, 'update' => false, 'delete' => false],
'discounts' => ['view' => false, 'create' => false, 'update' => false, 'delete' => false],
'sms' => ['view' => false, 'create' => false, 'update' => false, 'delete' => false],
],
];
@@ -0,0 +1,91 @@
<?php
namespace App\Clinic\Security;
use App\Auth\Entity\User;
use App\Auth\Repository\UserActiveContextRepository;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Repository\DoctorRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;
/**
* نقطهٔ واحدِ اعمالِ مجوزِ «پزشکِ عضوِ کلینیک» روی endpointهای چند-نقشه — قرینهٔ
* SecretaryAccessChecker اما برای ClinicDoctorPermission.
*
* فقط پزشکی که در محیطِ فعالش (UserActiveContext.db_uuid = کلینیک) عضوِ همان کلینیک
* است محدود می‌شود. بقیه — مالکِ کلینیک، ادمین، منشی، پزشکِ مطب شخصی، یا هر کاربری
* که محیطش کلینیک نیست — دست‌نخورده عبور می‌کنند (این checker آن‌ها را محدود نمی‌کند).
*/
class ClinicDoctorAccessChecker
{
public function __construct(
private readonly UserActiveContextRepository $contextRepo,
private readonly ClinicRepository $clinicRepo,
private readonly DoctorRepository $doctorRepo,
private readonly ClinicDoctorPermissionChecker $permissions,
) {}
/**
* idِ کلینیکی که این پزشک در محیطِ فعالش عضوِ آن است — برای کنترلرهایی که
* tenant را نقش‌محور حل می‌کنند و بدون این، پزشکِ عضو را به مطبِ شخصی‌اش می‌بردند
* (نه دادهٔ کلینیک). null اگر محیط کلینیک نیست یا کاربر عضو نیست.
*/
public function memberClinicId(User $user): ?int
{
$dbUuid = $this->contextRepo->findByUser($user)?->getDbUuid();
if ($dbUuid === null) {
return null;
}
$clinic = $this->clinicRepo->findByUuid($dbUuid);
if ($clinic === null) {
return null;
}
$doctor = $this->doctorRepo->findByUser($user);
if ($doctor === null || !$clinic->hasDoctor($doctor)) {
return null;
}
return $clinic->getId();
}
/**
* فقط پزشکِ عضوِ کلینیک را با ClinicDoctorPermission محدود کن؛ سایر کاربران true.
*/
public function canOrNonMember(User $user, string $resource, string $action): bool
{
$dbUuid = $this->contextRepo->findByUser($user)?->getDbUuid();
if ($dbUuid === null) {
return true;
}
$clinic = $this->clinicRepo->findByUuid($dbUuid);
if ($clinic === null) {
// محیطِ مطب شخصی (db_uuid پزشک است، نه کلینیک) → محدود نمی‌کنیم.
return true;
}
// مالکِ کلینیک هرگز با مجوزهای عضویت قفل نمی‌شود.
if ($clinic->getUser()->getId() === $user->getId()) {
return true;
}
$doctor = $this->doctorRepo->findByUser($user);
if ($doctor === null || !$clinic->hasDoctor($doctor)) {
// غیرعضو (مثلاً منشی) — این checker مالِ او نیست.
return true;
}
return $this->permissions->can($user, $clinic, $resource, $action);
}
/** 403 اگر پزشکِ عضو مجاز نباشد؛ سایر کاربران بدون تغییر عبور می‌کنند. */
public function denyUnlessGranted(User $user, string $resource, string $action): void
{
if (!$this->canOrNonMember($user, $resource, $action)) {
throw new AppException(ErrorCodes::ERR_FORBIDDEN_001, null, 403);
}
}
}
@@ -8,6 +8,7 @@ use App\ClinicService\Entity\ServiceItemAuditLog;
use App\ClinicService\Entity\ServiceSection;
use App\Insurance\Entity\TenantServiceCoverage;
use App\ClinicService\Entity\Tariff;
use App\Clinic\Security\ClinicDoctorAccessChecker;
use App\Secretary\Security\SecretaryAccessChecker;
use Doctrine\ORM\EntityManagerInterface;
use App\ClinicService\Repository\ServiceItemAuditLogRepository;
@@ -51,8 +52,16 @@ class ClinicServiceController extends BaseController
private readonly EntityContextResolver $contextResolver,
private readonly RequestStack $requestStack,
private readonly SecretaryAccessChecker $secretaryAccess,
private readonly ClinicDoctorAccessChecker $clinicDoctorAccess,
) {}
/** گِیتِ ترکیبی: منشی + پزشکِ عضوِ کلینیک (هرکدام فقط نقشِ خودش را محدود می‌کند). */
private function denyServices(User $user, string $action): void
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', $action);
$this->clinicDoctorAccess->denyUnlessGranted($user, 'services', $action);
}
/**
* uuid و عنوان پکیج کالای هر سرویس را به آرایه‌ی خروجی اضافه می‌کند. پکیج‌ها با یک
* کوئری واکشی می‌شوند تا فهرست سرویس‌ها به N+1 نیفتد.
@@ -140,7 +149,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-sections', methods: ['GET'])]
public function listSections(#[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'view');
$this->denyServices($user, 'view');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertServicesGate($entityType, $entityId);
@@ -158,7 +167,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-section', methods: ['POST'])]
public function createSection(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'create');
$this->denyServices($user, 'create');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertServicesGate($entityType, $entityId);
@@ -178,7 +187,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-section/{uuid}', methods: ['PATCH'])]
public function updateSection(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'update');
$this->denyServices($user, 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertServicesGate($entityType, $entityId);
@@ -203,7 +212,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-section/{uuid}', methods: ['DELETE'])]
public function deleteSection(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'delete');
$this->denyServices($user, 'delete');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertServicesGate($entityType, $entityId);
@@ -223,7 +232,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-items', methods: ['GET'])]
public function listAllItems(#[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'view');
$this->denyServices($user, 'view');
[$entityType, $entityId] = $this->resolveEntity($user);
// A user with neither a doctor profile nor a clinic (admin, secretary,
@@ -242,7 +251,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-items/{sectionUuid}', methods: ['GET'])]
public function listItems(string $sectionUuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'view');
$this->denyServices($user, 'view');
[$entityType, $entityId] = $this->resolveEntity($user);
$section = $this->sectionRepo->findByUuid($sectionUuid);
@@ -256,7 +265,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-item/{uuid}', methods: ['GET'])]
public function getItem(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'view');
$this->denyServices($user, 'view');
[$entityType, $entityId] = $this->resolveEntity($user);
$item = $this->itemRepo->findByUuid($uuid);
@@ -271,7 +280,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-item/{uuid}/audit-logs', methods: ['GET'])]
public function listItemAuditLogs(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'view');
$this->denyServices($user, 'view');
[$entityType, $entityId] = $this->resolveEntity($user);
$item = $this->itemRepo->findByUuid($uuid);
@@ -288,7 +297,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-item', methods: ['POST'])]
public function createItem(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'create');
$this->denyServices($user, 'create');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertServicesGate($entityType, $entityId);
@@ -343,7 +352,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-item/{uuid}', methods: ['PATCH'])]
public function updateItem(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'update');
$this->denyServices($user, 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$item = $this->itemRepo->findByUuid($uuid);
@@ -398,7 +407,7 @@ class ClinicServiceController extends BaseController
#[Route('/api/v1/service-item/{uuid}', methods: ['DELETE'])]
public function deleteItem(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'delete');
$this->denyServices($user, 'delete');
[$entityType, $entityId] = $this->resolveEntity($user);
$item = $this->itemRepo->findByUuid($uuid);
@@ -429,7 +438,7 @@ class ClinicServiceController extends BaseController
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function listTariffs(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'view');
$this->denyServices($user, 'view');
[$entityType, $entityId] = $this->resolveEntity($user);
$item = $this->itemRepo->findByUuid($uuid);
@@ -450,7 +459,7 @@ class ClinicServiceController extends BaseController
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function setTariff(string $uuid, int $year, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'services', 'update');
$this->denyServices($user, 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$item = $this->itemRepo->findByUuid($uuid);
@@ -27,12 +27,17 @@ class DiscountController extends BaseController
private readonly ClinicRepository $clinicRepo,
private readonly PatientSessionRepository $sessionRepo,
private readonly SecretaryAccessChecker $secretaryAccess,
private readonly \App\Clinic\Security\ClinicDoctorAccessChecker $clinicDoctorAccess,
) {}
/** @return array{0: string, 1: ?int} */
private function resolveOwner(User $user): array
{
if ($user->hasRole('ROLE_DOCTOR')) {
$memberClinicId = $this->clinicDoctorAccess->memberClinicId($user);
if ($memberClinicId !== null) {
return ['clinic', $memberClinicId];
}
return ['doctor', $this->doctorRepo->findByUser($user)?->getId()];
}
if ($user->hasRole('ROLE_CLINIC')) {
@@ -52,6 +57,7 @@ class DiscountController extends BaseController
public function list(#[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'discounts', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'discounts', 'view');
[$ownerType, $ownerId] = $this->resolveOwner($user);
if ($ownerId === null) {
return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
@@ -65,6 +71,7 @@ class DiscountController extends BaseController
public function create(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'discounts', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'discounts', 'create');
[$ownerType, $ownerId] = $this->resolveOwner($user);
if ($ownerId === null) {
return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
@@ -93,6 +100,7 @@ class DiscountController extends BaseController
public function update(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'discounts', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'discounts', 'update');
[$ownerType, $ownerId] = $this->resolveOwner($user);
if ($ownerId === null) {
return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
@@ -127,6 +135,7 @@ class DiscountController extends BaseController
public function delete(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'discounts', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'discounts', 'delete');
[$ownerType, $ownerId] = $this->resolveOwner($user);
if ($ownerId === null) {
return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
@@ -46,6 +46,7 @@ class InsuranceController extends BaseController
private readonly \App\Clinic\Security\ClinicDoctorPermissionChecker $permChecker,
private readonly \App\Patient\Security\PatientRecordScopeResolver $scopeResolver,
private readonly SecretaryAccessChecker $secretaryAccess,
private readonly \App\Clinic\Security\ClinicDoctorAccessChecker $clinicDoctorAccess,
private readonly string $projectDir,
) {}
@@ -255,6 +256,7 @@ class InsuranceController extends BaseController
public function getInsurancePricing(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'view');
[$entityType, $entityId, $err] = $this->resolveTargetEntity($user, $request->query->get('doctor_uuid'), 'view');
if ($err !== null) {
return $err;
@@ -305,6 +307,7 @@ class InsuranceController extends BaseController
public function saveInsurancePricing(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'update');
$data = json_decode($request->getContent(), true) ?? [];
[$entityType, $entityId, $err] = $this->resolveTargetEntity($user, $data['doctor_uuid'] ?? null, 'update');
@@ -375,6 +378,7 @@ class InsuranceController extends BaseController
public function listTenantInsurances(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'view');
[$entityType, $entityId, $err] = $this->resolveTargetEntity($user, $request->query->get('doctor_uuid'), 'view');
if ($err !== null) {
return $err;
@@ -406,6 +410,7 @@ class InsuranceController extends BaseController
public function activateTenantInsurance(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'create');
$data = json_decode($request->getContent(), true) ?? [];
[$entityType, $entityId, $err] = $this->resolveTargetEntity($user, $data['doctor_uuid'] ?? null, 'update');
@@ -442,6 +447,7 @@ class InsuranceController extends BaseController
public function updateTenantInsurance(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'update');
$data = json_decode($request->getContent(), true) ?? [];
[$entityType, $entityId, $err] = $this->resolveTargetEntity($user, $data['doctor_uuid'] ?? null, 'update');
@@ -488,6 +494,7 @@ class InsuranceController extends BaseController
public function deactivateTenantInsurance(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'delete');
[$entityType, $entityId, $err] = $this->resolveTargetEntity($user, $request->query->get('doctor_uuid'), 'update');
if ($err !== null) {
return $err;
@@ -508,6 +515,7 @@ class InsuranceController extends BaseController
public function listServiceCoverage(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'view');
[$entityType, $entityId, $err] = $this->resolveTargetEntity($user, $request->query->get('doctor_uuid'), 'view');
if ($err !== null) {
return $err;
@@ -539,6 +547,7 @@ class InsuranceController extends BaseController
public function setServiceCoverage(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'update');
$data = json_decode($request->getContent(), true) ?? [];
[$entityType, $entityId, $err] = $this->resolveTargetEntity($user, $data['doctor_uuid'] ?? null, 'update');
@@ -585,6 +594,7 @@ class InsuranceController extends BaseController
public function addDoctorInsurance(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'create');
$data = json_decode($request->getContent(), true) ?? [];
$doctorId = $data['doctor_id'] ?? null;
$insuranceId = $data['insurance_id'] ?? null;
@@ -626,6 +636,7 @@ class InsuranceController extends BaseController
public function showDoctorInsurance(int $id, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'view');
$doctorInsurance = $this->doctorInsuranceRepo->find($id);
if ($doctorInsurance === null) {
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'بیمه پزشک یافت نشد', 404);
@@ -643,6 +654,7 @@ class InsuranceController extends BaseController
public function updateDoctorInsurance(int $id, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'update');
$doctorInsurance = $this->doctorInsuranceRepo->find($id);
if ($doctorInsurance === null) {
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'بیمه پزشک یافت نشد', 404);
@@ -666,6 +678,7 @@ class InsuranceController extends BaseController
public function deleteDoctorInsurance(int $id, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'insurances', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'insurances', 'delete');
$doctorInsurance = $this->doctorInsuranceRepo->find($id);
if ($doctorInsurance === null) {
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'بیمه پزشک یافت نشد', 404);
@@ -39,6 +39,7 @@ class InventoryController extends BaseController
private readonly ClinicRepository $clinicRepo,
private readonly UserActiveContextRepository $contextRepo,
private readonly SecretaryAccessChecker $secretaryAccess,
private readonly \App\Clinic\Security\ClinicDoctorAccessChecker $clinicDoctorAccess,
) {}
// ── Items ────────────────────────────────────────────────────────────────
@@ -47,6 +48,7 @@ class InventoryController extends BaseController
public function listItems(#[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'view');
[$type, $id] = $this->resolveEntity($user);
if ($id === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -64,6 +66,7 @@ class InventoryController extends BaseController
public function listCategories(#[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'view');
[$type, $id] = $this->resolveEntity($user);
if ($id === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -85,6 +88,7 @@ class InventoryController extends BaseController
public function createItem(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'create');
[$type, $id] = $this->resolveEntity($user);
if ($id === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -107,6 +111,7 @@ class InventoryController extends BaseController
public function updateItem(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'update');
$item = $this->ownedItem($uuid, $user);
if ($item === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کالا یافت نشد', 404);
@@ -130,6 +135,7 @@ class InventoryController extends BaseController
public function deleteItem(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'delete');
$item = $this->ownedItem($uuid, $user);
if ($item === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کالا یافت نشد', 404);
@@ -146,6 +152,7 @@ class InventoryController extends BaseController
public function listPackages(#[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'view');
[$type, $id] = $this->resolveEntity($user);
if ($id === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -161,6 +168,7 @@ class InventoryController extends BaseController
public function createPackage(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'create');
[$type, $id] = $this->resolveEntity($user);
if ($id === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -183,6 +191,7 @@ class InventoryController extends BaseController
public function updatePackage(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'update');
$package = $this->ownedPackage($uuid, $user);
if ($package === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پکیج یافت نشد', 404);
@@ -210,6 +219,7 @@ class InventoryController extends BaseController
public function deletePackage(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'delete');
$package = $this->ownedPackage($uuid, $user);
if ($package === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پکیج یافت نشد', 404);
@@ -280,6 +290,10 @@ class InventoryController extends BaseController
private function resolveEntity(User $user): array
{
if ($user->hasRole('ROLE_DOCTOR')) {
$memberClinicId = $this->clinicDoctorAccess->memberClinicId($user);
if ($memberClinicId !== null) {
return ['clinic', $memberClinicId];
}
$doctor = $this->doctorRepo->findByUser($user);
return ['doctor', $doctor?->getId()];
}
@@ -59,6 +59,7 @@ class PatientController extends BaseController
private readonly \App\Patient\Repository\SessionPaymentRepository $sessionPaymentRepo,
private readonly \App\Patient\Repository\SessionAuditLogRepository $sessionAuditRepo,
private readonly SecretaryAccessChecker $secretaryAccess,
private readonly \App\Clinic\Security\ClinicDoctorAccessChecker $clinicDoctorAccess,
private readonly LoggerInterface $logger,
) {}
@@ -144,6 +145,7 @@ class PatientController extends BaseController
public function chargeWallet(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'payments', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'payments', 'create');
[$entityType, $entityId] = $this->resolveEntity($user);
$record = $this->recordRepo->findByUuid($uuid);
if ($record === null || !$this->ownsRecord($record, $entityType, $entityId, $user)) {
@@ -180,6 +182,7 @@ class PatientController extends BaseController
public function withdrawWallet(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'payments', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'payments', 'create');
[$entityType, $entityId] = $this->resolveEntity($user);
$record = $this->recordRepo->findByUuid($uuid);
if ($record === null || !$this->ownsRecord($record, $entityType, $entityId, $user)) {
@@ -240,6 +243,7 @@ class PatientController extends BaseController
public function createCall(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$record = $this->recordRepo->findByUuid($uuid);
if ($record === null || !$this->ownsRecord($record, $entityType, $entityId, $user)) {
@@ -277,6 +281,7 @@ class PatientController extends BaseController
public function deleteCall(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'delete');
[$entityType, $entityId] = $this->resolveEntity($user);
$call = $this->callRepo->findByUuid($uuid);
if ($call === null || !$this->ownsRecord($call->getRecord(), $entityType, $entityId, $user)) {
@@ -309,6 +314,7 @@ class PatientController extends BaseController
public function createMessage(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$record = $this->recordRepo->findByUuid($uuid);
if ($record === null || !$this->ownsRecord($record, $entityType, $entityId, $user)) {
@@ -335,6 +341,7 @@ class PatientController extends BaseController
public function deleteMessage(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'delete');
[$entityType, $entityId] = $this->resolveEntity($user);
$message = $this->messageRepo->findByUuid($uuid);
if ($message === null || !$this->ownsRecord($message->getRecord(), $entityType, $entityId, $user)) {
@@ -370,6 +377,7 @@ class PatientController extends BaseController
public function createNote(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$record = $this->recordRepo->findByUuid($uuid);
if ($record === null || !$this->ownsRecord($record, $entityType, $entityId, $user)) {
@@ -393,6 +401,7 @@ class PatientController extends BaseController
public function updateNote(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$note = $this->noteRepo->findByUuid($uuid);
if ($note === null || !$this->ownsRecord($note->getRecord(), $entityType, $entityId, $user)) {
@@ -419,6 +428,7 @@ class PatientController extends BaseController
public function deleteNote(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'delete');
[$entityType, $entityId] = $this->resolveEntity($user);
$note = $this->noteRepo->findByUuid($uuid);
if ($note === null || !$this->ownsRecord($note->getRecord(), $entityType, $entityId, $user)) {
@@ -451,6 +461,7 @@ class PatientController extends BaseController
public function createMedicalRecord(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$record = $this->recordRepo->findByUuid($uuid);
if ($record === null || !$this->ownsRecord($record, $entityType, $entityId, $user)) {
@@ -476,6 +487,7 @@ class PatientController extends BaseController
public function updateMedicalRecord(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$medical = $this->medicalRepo->findByUuid($uuid);
if ($medical === null || !$this->ownsRecord($medical->getRecord(), $entityType, $entityId, $user)) {
@@ -507,6 +519,7 @@ class PatientController extends BaseController
public function deleteMedicalRecord(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'delete');
[$entityType, $entityId] = $this->resolveEntity($user);
$medical = $this->medicalRepo->findByUuid($uuid);
if ($medical === null || !$this->ownsRecord($medical->getRecord(), $entityType, $entityId, $user)) {
@@ -539,6 +552,7 @@ class PatientController extends BaseController
public function uploadAttachment(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$record = $this->recordRepo->findByUuid($uuid);
if ($record === null || !$this->ownsRecord($record, $entityType, $entityId, $user)) {
@@ -562,6 +576,7 @@ class PatientController extends BaseController
public function deleteAttachment(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'delete');
[$entityType, $entityId] = $this->resolveEntity($user);
$attachment = $this->attachmentRepo->findByUuid($uuid);
if ($attachment === null || !$this->ownsRecord($attachment->getRecord(), $entityType, $entityId, $user)) {
@@ -700,6 +715,7 @@ class PatientController extends BaseController
public function create(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'create');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertPatientGate($entityType, $entityId);
@@ -794,6 +810,7 @@ class PatientController extends BaseController
public function update(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertPatientGate($entityType, $entityId);
@@ -1004,6 +1021,7 @@ class PatientController extends BaseController
public function createSession(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertPatientGate($entityType, $entityId);
@@ -1048,6 +1066,7 @@ class PatientController extends BaseController
public function updateSession(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'patients', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'patients', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertPatientGate($entityType, $entityId);
@@ -1124,6 +1143,7 @@ class PatientController extends BaseController
public function addSessionPayment(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'payments', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'payments', 'create');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertPatientGate($entityType, $entityId);
@@ -1150,6 +1170,7 @@ class PatientController extends BaseController
public function updateSessionPayment(string $uuid, string $paymentUuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'payments', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'payments', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertPatientGate($entityType, $entityId);
@@ -1172,6 +1193,7 @@ class PatientController extends BaseController
public function deleteSessionPayment(string $uuid, string $paymentUuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'payments', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'payments', 'delete');
[$entityType, $entityId] = $this->resolveEntity($user);
$this->assertPatientGate($entityType, $entityId);
@@ -37,6 +37,7 @@ class PaymentController extends BaseController
private readonly PaymentManager $paymentManager,
private readonly SiteConfigRepository $configRepo,
private readonly SecretaryAccessChecker $secretaryAccess,
private readonly \App\Clinic\Security\ClinicDoctorAccessChecker $clinicDoctorAccess,
private readonly string $appBaseUrl,
private readonly string $allowedFrontendHosts = '',
) {}
@@ -509,6 +510,7 @@ class PaymentController extends BaseController
public function myPayments(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'payments', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'payments', 'view');
$page = max(1, (int) $request->query->get('page', 1));
$limit = min(100, max(1, (int) $request->query->get('limit', 20)));
$status = $request->query->get('status');
@@ -28,6 +28,7 @@ class PaymentMethodController extends BaseController
public function __construct(
private readonly PaymentMethodService $service,
private readonly SecretaryAccessChecker $secretaryAccess,
private readonly \App\Clinic\Security\ClinicDoctorAccessChecker $clinicDoctorAccess,
) {}
/** نقش مجاز + مجوز منشی روی منبع payments (روش‌های پرداخت زیرمجموعهٔ مالی است). */
@@ -35,6 +36,7 @@ class PaymentMethodController extends BaseController
{
$this->assertRole($user);
$this->secretaryAccess->denyUnlessGranted($user, 'payments', $action);
$this->clinicDoctorAccess->denyUnlessGranted($user, 'payments', $action);
}
// ---- Bank accounts -----------------------------------------------------
@@ -39,6 +39,7 @@ class SmsWalletController extends BaseController
private readonly ClinicRepository $clinicRepo,
private readonly \App\Config\Repository\SiteConfigRepository $configRepo,
private readonly \App\Secretary\Security\SecretaryAccessChecker $secretaryAccess,
private readonly \App\Clinic\Security\ClinicDoctorAccessChecker $clinicDoctorAccess,
private readonly string $appBaseUrl,
) {}
@@ -52,6 +53,7 @@ class SmsWalletController extends BaseController
public function balance(#[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'sms', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'sms', 'view');
[$entityType, $entityId] = $this->resolveEntity($user);
if ($entityId === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -72,6 +74,7 @@ class SmsWalletController extends BaseController
public function charge(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'sms', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'sms', 'create');
[$entityType, $entityId] = $this->resolveEntity($user);
if ($entityId === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -106,6 +109,7 @@ class SmsWalletController extends BaseController
public function logs(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'sms', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'sms', 'view');
[$entityType, $entityId] = $this->resolveEntity($user);
if ($entityId === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -130,6 +134,7 @@ class SmsWalletController extends BaseController
public function getSettings(#[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'sms', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'sms', 'view');
[$entityType, $entityId] = $this->resolveEntity($user);
if ($entityId === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -155,6 +160,7 @@ class SmsWalletController extends BaseController
public function updateSettings(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'sms', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'sms', 'update');
[$entityType, $entityId] = $this->resolveEntity($user);
if ($entityId === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -278,6 +284,10 @@ class SmsWalletController extends BaseController
private function resolveEntity(User $user): array
{
if ($user->hasRole('ROLE_DOCTOR')) {
$memberClinicId = $this->clinicDoctorAccess->memberClinicId($user);
if ($memberClinicId !== null) {
return ['clinic', $memberClinicId];
}
$doctor = $this->doctorRepo->findByUser($user);
return $doctor !== null ? ['doctor', $doctor->getId()] : ['doctor', null];
}
+9
View File
@@ -26,12 +26,14 @@ class StaffController extends BaseController
private readonly DoctorRepository $doctorRepo,
private readonly ClinicRepository $clinicRepo,
private readonly SecretaryAccessChecker $secretaryAccess,
private readonly \App\Clinic\Security\ClinicDoctorAccessChecker $clinicDoctorAccess,
) {}
#[Route('/api/v1/staff', methods: ['GET'])]
public function list(#[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'staff', 'view');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'staff', 'view');
[$entityType, $entityId] = $this->resolveEntity($user);
if ($entityId === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -49,6 +51,7 @@ class StaffController extends BaseController
public function create(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'staff', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'staff', 'create');
[$entityType, $entityId] = $this->resolveEntity($user);
if ($entityId === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -76,6 +79,7 @@ class StaffController extends BaseController
public function update(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'staff', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'staff', 'update');
$staff = $this->staffRepo->findByUuid($uuid);
if ($staff === null) {
return $this->error(ErrorCodes::ERR_STAFF_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_STAFF_NOT_FOUND), 404);
@@ -104,6 +108,7 @@ class StaffController extends BaseController
public function toggle(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'staff', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'staff', 'update');
$staff = $this->staffRepo->findByUuid($uuid);
if ($staff === null) {
return $this->error(ErrorCodes::ERR_STAFF_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_STAFF_NOT_FOUND), 404);
@@ -122,6 +127,10 @@ class StaffController extends BaseController
private function resolveEntity(User $user): array
{
if ($user->hasRole('ROLE_DOCTOR')) {
$memberClinicId = $this->clinicDoctorAccess->memberClinicId($user);
if ($memberClinicId !== null) {
return ['clinic', $memberClinicId];
}
$doctor = $this->doctorRepo->findByUser($user);
return $doctor !== null ? ['doctor', $doctor->getId()] : ['doctor', null];
}
@@ -35,6 +35,7 @@ class TenantTagController extends BaseController
private readonly ClinicRepository $clinicRepo,
private readonly UserActiveContextRepository $contextRepo,
private readonly SecretaryAccessChecker $secretaryAccess,
private readonly \App\Clinic\Security\ClinicDoctorAccessChecker $clinicDoctorAccess,
) {}
/** تگ‌ها ابزار پروندهٔ بیمار هم هستند؛ مشاهده با tags.view یا patients.view مجاز است. */
@@ -65,6 +66,7 @@ class TenantTagController extends BaseController
public function create(Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'tags', 'create');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'tags', 'create');
[$type, $id] = $this->resolveEntity($user);
if ($id === null) {
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
@@ -94,6 +96,7 @@ class TenantTagController extends BaseController
public function update(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'tags', 'update');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'tags', 'update');
$tag = $this->ownedTag($uuid, $user);
if ($tag === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'برچسب یافت نشد', 404);
@@ -127,6 +130,7 @@ class TenantTagController extends BaseController
public function delete(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$this->secretaryAccess->denyUnlessGranted($user, 'tags', 'delete');
$this->clinicDoctorAccess->denyUnlessGranted($user, 'tags', 'delete');
$tag = $this->ownedTag($uuid, $user);
if ($tag === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'برچسب یافت نشد', 404);
@@ -154,6 +158,10 @@ class TenantTagController extends BaseController
private function resolveEntity(User $user): array
{
if ($user->hasRole('ROLE_DOCTOR')) {
$memberClinicId = $this->clinicDoctorAccess->memberClinicId($user);
if ($memberClinicId !== null) {
return ['clinic', $memberClinicId];
}
$doctor = $this->doctorRepo->findByUser($user);
return ['doctor', $doctor?->getId()];
}