Files
clinicpro/docs/phase2_taskes/task-13-clinic-services/architecture.md
T

3.6 KiB

معماری — تسک ۱۳: سرویس‌های کلینیک

ساختار فایل‌ها

src/ClinicService/
├── Controller/
│   └── ClinicServiceController.php
├── Entity/
│   ├── ServiceSection.php
│   └── ServiceItem.php
└── Repository/
    ├── ServiceSectionRepository.php
    └── ServiceItemRepository.php

Entity: ServiceSection

#[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

#[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

#[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 سرویس با استفاده در پرونده

// اگر 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);
}