feat: implement staff management and subscription system
- Added StaffController for managing clinic staff, including listing, creating, updating, and toggling staff status. - Created ClinicStaff entity and repository for staff data handling. - Developed SubscriptionController to manage subscription plans and periods, including trial subscriptions. - Introduced SubscriptionPlan, SubscriptionPeriod, and ClinicSubscription entities for subscription management. - Implemented SubscriptionService for handling subscription logic, including trial activation and subscription creation from payments. - Added necessary repositories for subscription entities to facilitate data access and manipulation.
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace App\Patient\Controller;
|
||||
|
||||
use App\Auth\Entity\User;
|
||||
use App\Auth\Repository\UserRepository;
|
||||
use App\Clinic\Repository\ClinicRepository;
|
||||
use App\Doctor\Repository\DoctorRepository;
|
||||
use App\Patient\Entity\PatientRecord;
|
||||
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,
|
||||
) {}
|
||||
|
||||
#[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];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user