contextRepo->findByUser($user)?->getDbUuid(); if ($dbUuid === null) { return null; } $clinic = $this->clinicRepo->findByUuid($dbUuid); if ($clinic !== null) { return $this->secretaryRepo->findActiveBySecretaryForClinic($user, $clinic); } $doctor = $this->doctorRepo->findByUuid($dbUuid); if ($doctor !== null) { return $this->secretaryRepo->findActiveBySecretaryForDoctor($user, $doctor); } return null; } public function can(User $user, string $resource, string $action): bool { $relation = $this->activeRelation($user); return $relation !== null && $this->permissions->can($relation, $resource, $action); } /** * جفتِ [entityType, entityId] مالکِ محیطِ فعالِ منشی — کلینیک یا پزشک. * برای کنترلرهایی که دادهٔ tenant را با این جفت واکشی می‌کنند و resolverِ * عمومی (EntityContextResolver) منشی را نمی‌شناسد. مجوز جدا با * denyUnlessGranted بررسی می‌شود؛ این متد فقط owner را حل می‌کند. * * @return array{0: string, 1: int|null} ['clinic'|'doctor'|'unknown', id|null] */ public function resolveOwnerEntity(User $user): array { $dbUuid = $this->contextRepo->findByUser($user)?->getDbUuid(); if ($dbUuid === null) { return ['unknown', null]; } $clinic = $this->clinicRepo->findByUuid($dbUuid); if ($clinic !== null) { return ['clinic', $clinic->getId()]; } $doctor = $this->doctorRepo->findByUuid($dbUuid); if ($doctor !== null) { return ['doctor', $doctor->getId()]; } return ['unknown', null]; } /** * برای مسیرهایی که چند نقش دارند: فقط منشی را محدود کن. سایر نقش‌ها true. */ public function canOrNonSecretary(User $user, string $resource, string $action): bool { if (!$user->hasRole('ROLE_SECRETARY')) { return true; } return $this->can($user, $resource, $action); } /** 403 اگر منشی مجاز نباشد؛ نقش‌های دیگر بدون تغییر عبور می‌کنند. */ public function denyUnlessGranted(User $user, string $resource, string $action): void { if (!$this->canOrNonSecretary($user, $resource, $action)) { throw new AppException(ErrorCodes::ERR_FORBIDDEN_001, null, 403); } } }