- Added `owner_type` and `clinic_id` fields to `DoctorSecretary` entity to distinguish between clinic and personal practice relationships. - Updated repository methods to be scope-aware, allowing for specific queries based on the context of the secretary's relationship (clinic or doctor). - Modified `SecretaryController` to handle secretary creation with appropriate scope based on the current user's role. - Enhanced `AuthController` to build contexts that reflect the scope of the secretary's access. - Updated `DashboardController` and `PatientController` to respect the new scope logic when retrieving data. - Created migration to update the database schema accordingly, dropping the old unique constraint and adding the new fields and constraints.
218 lines
8.8 KiB
PHP
218 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace App\Patient\Controller;
|
|
|
|
use App\Auth\Entity\User;
|
|
use App\Auth\Repository\UserActiveContextRepository;
|
|
use App\Auth\Repository\UserRepository;
|
|
use App\Clinic\Repository\ClinicRepository;
|
|
use App\Doctor\Repository\DoctorRepository;
|
|
use App\Patient\Entity\PatientRecord;
|
|
use App\Secretary\Repository\DoctorSecretaryRepository;
|
|
use App\Patient\Repository\PatientRecordRepository;
|
|
use App\Patient\Repository\PatientSessionRepository;
|
|
use App\Patient\Service\PatientService;
|
|
use App\Shared\Constant\ErrorCodes;
|
|
use App\Shared\Controller\BaseController;
|
|
use App\Shared\Exception\AppException;
|
|
use App\Subscription\Service\SubscriptionService;
|
|
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;
|
|
|
|
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
|
class PatientController extends BaseController
|
|
{
|
|
public function __construct(
|
|
private readonly PatientRecordRepository $recordRepo,
|
|
private readonly PatientSessionRepository $sessionRepo,
|
|
private readonly PatientService $patientService,
|
|
private readonly SubscriptionService $subscriptionService,
|
|
private readonly UserRepository $userRepo,
|
|
private readonly DoctorRepository $doctorRepo,
|
|
private readonly ClinicRepository $clinicRepo,
|
|
private readonly DoctorSecretaryRepository $secretaryRepo,
|
|
private readonly UserActiveContextRepository $contextRepo,
|
|
) {}
|
|
|
|
#[Route('/api/v1/patients', methods: ['GET'])]
|
|
public function list(Request $request, #[CurrentUser] User $user): JsonResponse
|
|
{
|
|
[$entityType, $entityId] = $this->resolveEntity($user);
|
|
$this->assertPatientGate($entityType, $entityId);
|
|
|
|
$page = max(1, (int) $request->query->get('page', 1));
|
|
$limit = min(50, max(10, (int) $request->query->get('limit', 20)));
|
|
$search = $request->query->get('search') ?: null;
|
|
|
|
$records = $this->recordRepo->findByEntity($entityType, $entityId, $page, $limit, $search);
|
|
$total = $this->recordRepo->countByEntity($entityType, $entityId, $search);
|
|
|
|
return $this->paginated(
|
|
array_map(fn(PatientRecord $r) => $r->toArray(), $records),
|
|
$total,
|
|
$page,
|
|
$limit
|
|
);
|
|
}
|
|
|
|
#[Route('/api/v1/patient', methods: ['POST'])]
|
|
public function create(Request $request, #[CurrentUser] User $user): JsonResponse
|
|
{
|
|
[$entityType, $entityId] = $this->resolveEntity($user);
|
|
$this->assertPatientGate($entityType, $entityId);
|
|
|
|
$data = json_decode($request->getContent(), true) ?? [];
|
|
$userUuid = trim($data['user_uuid'] ?? '');
|
|
|
|
if ($userUuid === '') {
|
|
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'user_uuid الزامی است', 422);
|
|
}
|
|
|
|
$patient = $this->userRepo->findByUuid($userUuid);
|
|
if ($patient === null) {
|
|
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کاربر یافت نشد', 404);
|
|
}
|
|
|
|
$existing = $this->recordRepo->findByEntityAndUser($entityType, $entityId, $patient);
|
|
if ($existing !== null) {
|
|
return $this->success($existing->toArray());
|
|
}
|
|
|
|
$record = new PatientRecord($entityType, $entityId, $patient, $user->hasRole('ROLE_DOCTOR') ? 'doctor' : 'clinic', $entityId);
|
|
$this->recordRepo->save($record);
|
|
|
|
return $this->success($record->toArray(), 201);
|
|
}
|
|
|
|
#[Route('/api/v1/patient/{uuid}', methods: ['GET'])]
|
|
public function show(string $uuid, #[CurrentUser] User $user): JsonResponse
|
|
{
|
|
[$entityType, $entityId] = $this->resolveEntity($user);
|
|
$this->assertPatientGate($entityType, $entityId);
|
|
|
|
$record = $this->recordRepo->findByUuid($uuid);
|
|
if ($record === null || !$this->ownsRecord($record, $entityType, $entityId)) {
|
|
return $this->error(ErrorCodes::ERR_PATIENT_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_PATIENT_NOT_FOUND), 404);
|
|
}
|
|
|
|
return $this->success($record->toArray());
|
|
}
|
|
|
|
#[Route('/api/v1/patient/{uuid}/sessions', methods: ['GET'])]
|
|
public function sessions(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
|
|
{
|
|
[$entityType, $entityId] = $this->resolveEntity($user);
|
|
$this->assertPatientGate($entityType, $entityId);
|
|
|
|
$record = $this->recordRepo->findByUuid($uuid);
|
|
if ($record === null || !$this->ownsRecord($record, $entityType, $entityId)) {
|
|
return $this->error(ErrorCodes::ERR_PATIENT_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_PATIENT_NOT_FOUND), 404);
|
|
}
|
|
|
|
$page = max(1, (int) $request->query->get('page', 1));
|
|
$limit = min(50, max(10, (int) $request->query->get('limit', 20)));
|
|
$sessions = $this->sessionRepo->findByRecord($record, $page, $limit);
|
|
$total = $this->sessionRepo->countByRecord($record);
|
|
|
|
return $this->paginated(
|
|
array_map(fn($s) => $s->toArray(), $sessions),
|
|
$total,
|
|
$page,
|
|
$limit
|
|
);
|
|
}
|
|
|
|
#[Route('/api/v1/patient/{uuid}/session', methods: ['POST'])]
|
|
public function createSession(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
|
|
{
|
|
[$entityType, $entityId] = $this->resolveEntity($user);
|
|
$this->assertPatientGate($entityType, $entityId);
|
|
|
|
$record = $this->recordRepo->findByUuid($uuid);
|
|
if ($record === null || !$this->ownsRecord($record, $entityType, $entityId)) {
|
|
return $this->error(ErrorCodes::ERR_PATIENT_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_PATIENT_NOT_FOUND), 404);
|
|
}
|
|
|
|
$data = json_decode($request->getContent(), true) ?? [];
|
|
$session = $this->patientService->createSession($record, $data, $entityType, $entityId);
|
|
|
|
return $this->success($session->toArray(), 201);
|
|
}
|
|
|
|
#[Route('/api/v1/session/{uuid}', methods: ['PATCH'])]
|
|
public function updateSession(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
|
|
{
|
|
[$entityType, $entityId] = $this->resolveEntity($user);
|
|
|
|
$session = $this->sessionRepo->findByUuid($uuid);
|
|
if ($session === null || !$this->ownsRecord($session->getRecord(), $entityType, $entityId)) {
|
|
return $this->error(ErrorCodes::ERR_SESSION_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_SESSION_NOT_FOUND), 404);
|
|
}
|
|
|
|
$data = json_decode($request->getContent(), true) ?? [];
|
|
|
|
if (isset($data['notes'])) { $session->setNotes($data['notes']); }
|
|
if (isset($data['payment_method'])) { $session->setPaymentMethod($data['payment_method']); }
|
|
|
|
$this->sessionRepo->save($session);
|
|
|
|
return $this->success($session->toArray());
|
|
}
|
|
|
|
private function resolveEntity(User $user): array
|
|
{
|
|
if ($user->hasRole('ROLE_DOCTOR')) {
|
|
$doctor = $this->doctorRepo->findByUser($user);
|
|
return $doctor !== null ? ['doctor', $doctor->getId()] : ['doctor', null];
|
|
}
|
|
|
|
if ($user->hasRole('ROLE_CLINIC')) {
|
|
$clinic = $this->clinicRepo->findByUser($user);
|
|
return $clinic !== null ? ['clinic', $clinic->getId()] : ['clinic', null];
|
|
}
|
|
|
|
if ($user->hasRole('ROLE_SECRETARY')) {
|
|
$dbUuid = $this->contextRepo->findByUser($user)?->getDbUuid();
|
|
if ($dbUuid !== null) {
|
|
$clinic = $this->clinicRepo->findByUuid($dbUuid);
|
|
if ($clinic !== null) {
|
|
$rel = $this->secretaryRepo->findActiveBySecretaryForClinic($user, $clinic);
|
|
if ($rel !== null) {
|
|
return ['clinic', $clinic->getId()];
|
|
}
|
|
}
|
|
$doctor = $this->doctorRepo->findByUuid($dbUuid);
|
|
if ($doctor !== null) {
|
|
$rel = $this->secretaryRepo->findActiveBySecretaryForDoctor($user, $doctor);
|
|
if ($rel !== null) {
|
|
return ['doctor', $doctor->getId()];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ['unknown', null];
|
|
}
|
|
|
|
private function assertPatientGate(string $entityType, ?int $entityId): void
|
|
{
|
|
if ($entityId === null) {
|
|
throw new AppException(ErrorCodes::ERR_FORBIDDEN_001, null, 403);
|
|
}
|
|
|
|
if (!$this->subscriptionService->hasFeature($entityType, $entityId, 'patient_records')) {
|
|
throw new AppException(ErrorCodes::ERR_SUBSCRIPTION_REQUIRED, null, 403);
|
|
}
|
|
}
|
|
|
|
private function ownsRecord($record, string $entityType, ?int $entityId): bool
|
|
{
|
|
return $entityId !== null
|
|
&& $record->getEntityType() === $entityType
|
|
&& $record->getEntityId() === $entityId;
|
|
}
|
|
}
|