feat: add per-doctor permissions management in clinics

- Implement DoctorPermissionsModal for managing doctor permissions in clinics.
- Create usePermissions hook to handle user permissions context.
- Add migration for clinic_doctor_permissions table with default permissions.
- Develop ClinicDoctorPermissionController for handling permissions API.
- Create ClinicDoctorPermission entity to manage permissions data.
- Implement ClinicDoctorPermissionRepository for database interactions.
- Add ClinicDoctorPermissionChecker for permission validation logic.
- Write tests for clinic doctor permissions functionality.
This commit is contained in:
hamed
2026-07-18 09:44:13 +03:30
parent 3a23aa242e
commit e4ddd38f0c
17 changed files with 1504 additions and 44 deletions
@@ -0,0 +1,106 @@
<?php
namespace App\Clinic\Controller;
use App\Auth\Entity\User;
use App\Clinic\Entity\Clinic;
use App\Clinic\Entity\ClinicDoctorPermission;
use App\Clinic\Repository\ClinicDoctorPermissionRepository;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Repository\DoctorRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Exception\AppException;
use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[OA\Tag(name: 'Clinic Doctor Permissions')]
class ClinicDoctorPermissionController extends BaseController
{
public function __construct(
private readonly ClinicRepository $clinicRepo,
private readonly DoctorRepository $doctorRepo,
private readonly ClinicDoctorPermissionRepository $permRepo,
private readonly EntityManagerInterface $em,
) {}
#[Route('/api/v1/admin/clinic/{clinicUuid}/doctor-permissions', methods: ['GET'])]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function listPermissions(string $clinicUuid, #[CurrentUser] User $user): JsonResponse
{
$clinic = $this->resolveClinic($clinicUuid, $user);
$data = array_map(
fn(ClinicDoctorPermission $p) => $p->toArray(),
$this->permRepo->findByClinic($clinic),
);
return $this->success($data);
}
#[Route('/api/v1/admin/clinic/{clinicUuid}/doctor/{doctorUuid}/permissions', methods: ['GET'])]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function showPermissions(string $clinicUuid, string $doctorUuid, #[CurrentUser] User $user): JsonResponse
{
$clinic = $this->resolveClinic($clinicUuid, $user);
$doctor = $this->resolveMember($clinic, $doctorUuid);
return $this->success($this->permRepo->getOrCreate($clinic, $doctor)->toArray());
}
#[Route('/api/v1/admin/clinic/{clinicUuid}/doctor/{doctorUuid}/permissions', methods: ['PATCH'])]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function updatePermissions(string $clinicUuid, string $doctorUuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$clinic = $this->resolveClinic($clinicUuid, $user);
$doctor = $this->resolveMember($clinic, $doctorUuid);
$perm = $this->permRepo->getOrCreate($clinic, $doctor);
$body = json_decode($request->getContent(), true) ?? [];
if (array_key_exists('permissions', $body)) {
if (!is_array($body['permissions'])) {
throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'permissions باید آبجکت باشد', 422, 'permissions');
}
$perm->mergePermissions($body['permissions']);
}
if (array_key_exists('active', $body)) {
$perm->setActive((bool) $body['active']);
}
$this->em->flush();
return $this->success($perm->toArray());
}
private function resolveClinic(string $clinicUuid, User $user): Clinic
{
$clinic = $this->clinicRepo->findByUuid($clinicUuid);
if ($clinic === null) {
throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'کلینیک یافت نشد', 404);
}
$isOwner = $clinic->getUser()->getId() === $user->getId();
if (!$user->hasRole('ROLE_ADMIN') && !$isOwner) {
throw new AppException(ErrorCodes::ERR_ACCESS_DENIED, 'دسترسی ندارید', 403);
}
return $clinic;
}
private function resolveMember(Clinic $clinic, string $doctorUuid): \App\Doctor\Entity\Doctor
{
$doctor = $this->doctorRepo->findByUuid($doctorUuid);
if ($doctor === null || !$clinic->hasDoctor($doctor)) {
throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'این پزشک به کلینیک متصل نیست', 404);
}
return $doctor;
}
}