# معماری — تسک ۱۳: سرویس‌های کلینیک ## ساختار فایل‌ها ``` src/ClinicService/ ├── Controller/ │ └── ClinicServiceController.php ├── Entity/ │ ├── ServiceSection.php │ └── ServiceItem.php └── Repository/ ├── ServiceSectionRepository.php └── ServiceItemRepository.php ``` ## Entity: ServiceSection ```php #[ORM\Entity(repositoryClass: ServiceSectionRepository::class)] #[ORM\Table(name: 'service_sections')] #[ORM\Index(columns: ['entity_type', 'entity_id'], name: 'idx_service_sections_entity')] class ServiceSection { #[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')] private ?int $id = null; #[ORM\Column(type: 'string', length: 36, unique: true)] private string $uuid; #[ORM\Column(type: 'string', length: 10)] private string $entityType; // 'doctor' | 'clinic' #[ORM\Column(type: 'integer')] private int $entityId; #[ORM\Column(type: 'string', length: 200)] private string $name; #[ORM\Column(type: 'boolean')] private bool $active = true; #[ORM\Column(type: 'integer')] private int $createdAt; #[ORM\Column(type: 'integer')] private int $updatedAt; } ``` ## Entity: ServiceItem ```php #[ORM\Entity(repositoryClass: ServiceItemRepository::class)] #[ORM\Table(name: 'service_items')] class ServiceItem { #[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')] private ?int $id = null; #[ORM\Column(type: 'string', length: 36, unique: true)] private string $uuid; #[ORM\ManyToOne(targetEntity: ServiceSection::class)] #[ORM\JoinColumn(name: 'section_id', nullable: false, onDelete: 'CASCADE')] private ServiceSection $section; // staff nullable — پرسنل انجام‌دهنده #[ORM\ManyToOne(targetEntity: \App\Staff\Entity\ClinicStaff::class)] #[ORM\JoinColumn(name: 'staff_id', nullable: true, onDelete: 'SET NULL')] private ?\App\Staff\Entity\ClinicStaff $staff = null; #[ORM\Column(type: 'string', length: 200)] private string $name; #[ORM\Column(type: 'integer')] private int $priceRials = 0; #[ORM\Column(type: 'boolean')] private bool $active = true; #[ORM\Column(type: 'integer')] private int $createdAt; #[ORM\Column(type: 'integer')] private int $updatedAt; } ``` ## Controller: ClinicServiceController ```php #[Route('/api/v1')] class ClinicServiceController extends BaseController { // برای همه write actions → gate check اول: private function assertServicesFeature(): void { [$entityType, $entityId] = $this->resolveEntityContext(); if (!$this->subscriptionService->hasFeature($entityType, $entityId, 'services')) { throw new AppException(ErrorCodes::ERR_SUBSCRIPTION_REQUIRED, null, 403); } } #[Route('/service-sections', methods: ['GET'])] public function listSections(): JsonResponse { ... } #[Route('/service-section', methods: ['POST'])] public function createSection(Request $request): JsonResponse { $this->assertServicesFeature(); // ... } // سایر endpoint ها ... } ``` ## نکته DELETE سرویس با استفاده در پرونده ```php // اگر session_services.service_item_id → این item باشد: // FK ON DELETE RESTRICT → Doctrine exception → catch و return 409 try { $this->em->remove($item); $this->em->flush(); } catch (\Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException $e) { return $this->error(ErrorCodes::ERR_SERVICE_ITEM_IN_USE, 'این سرویس در پرونده بیمار استفاده شده است', 409); } ```