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; } }