# معماری — تسک ۰۵: ماژول دکتر ## ساختار فایل‌ها ``` src/Module/Doctor/ ├── Controller/ │ ├── DoctorController.php ← CRUD دکتر + لیست + جستجو │ ├── DoctorImageController.php ← آپلود تصویر │ └── DoctorAddressController.php ← CRUD آدرس مطب ├── Service/ │ ├── DoctorService.php │ └── DoctorAddressService.php ├── Repository/ │ ├── DoctorRepository.php │ └── DoctorAddressRepository.php ├── Entity/ │ ├── Doctor.php │ └── DoctorAddress.php ├── DTO/ │ ├── Request/ │ │ ├── CreateDoctorRequest.php │ │ ├── UpdateDoctorRequest.php │ │ ├── DoctorFilterRequest.php │ │ ├── CreateDoctorAddressRequest.php │ │ └── UpdateDoctorAddressRequest.php │ └── Response/ │ ├── DoctorResponse.php ← view کامل با آمار │ ├── DoctorListItemResponse.php ← لیست/جستجو │ └── DoctorAddressResponse.php └── Voter/ └── DoctorVoter.php ``` ## Entity: Doctor (بر اساس فیلدهای واقعی Drupal) ```php #[ORM\Entity] #[ORM\Table(name: 'doctors')] class Doctor { #[ORM\Id, ORM\GeneratedValue, ORM\Column] private int $id; #[ORM\Column(type: UuidType::NAME, unique: true)] private Uuid $uuid; #[ORM\OneToOne(targetEntity: User::class)] #[ORM\JoinColumn(nullable: false)] private User $user; // uid در Drupal #[ORM\Column(length: 100)] private string $name; // field_name #[ORM\Column(length: 10, nullable: true)] private ?string $gender; // field_gender #[ORM\Column(length: 50)] private string $medicalSystemCode; // field_doctor_id (شماره نظام پزشکی) #[ORM\Column(type: 'integer', nullable: true)] private ?int $activityTime; // field_activity_time (timestamp سال شروع) #[ORM\Column(length: 100, nullable: true)] private ?string $degree; // field_degree #[ORM\Column(type: 'text', nullable: true)] private ?string $info; // field_info #[ORM\Column(length: 255, nullable: true)] private ?string $imagePath; // field_image (media) #[ORM\Column(type: 'decimal', precision: 3, scale: 1, options: ['default' => 3.5])] private float $doctorRate = 3.5; // field_doctor_rate #[ORM\Column(type: 'decimal', precision: 5, scale: 1, options: ['default' => 60])] private float $doctorRatePercentage = 60; // field_doctor_rate_percentage #[ORM\Column(type: 'boolean', options: ['default' => true])] private bool $activeDoctorAppointment = true; // field_active_doctor_appointmen #[ORM\ManyToOne(targetEntity: Representation::class)] #[ORM\JoinColumn(nullable: true)] private ?Representation $representation; // field_representation (multi-tenant) // ManyToMany relations (field_specialty, field_doctor_services, field_state, field_city) #[ORM\ManyToMany(targetEntity: Category::class)] #[ORM\JoinTable(name: 'doctor_specialties')] private Collection $specialties; // field_specialty (چند مقداری) #[ORM\ManyToMany(targetEntity: Category::class)] #[ORM\JoinTable(name: 'doctor_services')] private Collection $services; // field_doctor_services (چند مقداری) #[ORM\ManyToMany(targetEntity: Category::class)] #[ORM\JoinTable(name: 'doctor_states')] private Collection $states; // field_state #[ORM\ManyToMany(targetEntity: Category::class)] #[ORM\JoinTable(name: 'doctor_cities')] private Collection $cities; // field_city #[ORM\OneToMany(targetEntity: DoctorAddress::class, mappedBy: 'doctor')] private Collection $addresses; // TimestampableTrait } ``` ## Entity: DoctorAddress (بر اساس فیلدهای واقعی Drupal) ```php #[ORM\Entity] #[ORM\Table(name: 'doctor_addresses')] class DoctorAddress { #[ORM\Id, ORM\GeneratedValue, ORM\Column] private int $id; #[ORM\Column(type: UuidType::NAME, unique: true)] private Uuid $uuid; #[ORM\ManyToOne(targetEntity: Doctor::class, inversedBy: 'addresses')] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] private Doctor $doctor; // field_doctor #[ORM\Column(length: 100, nullable: true)] private ?string $name; // field_name (نام مطب/شعبه) #[ORM\Column(type: 'text', nullable: true)] private ?string $address; // field_address #[ORM\Column(length: 20, nullable: true)] private ?string $telephone; // field_telephone (نه phone!) #[ORM\Column(type: 'decimal', precision: 10, scale: 8, nullable: true)] private ?float $latitude; // field_latitude #[ORM\Column(type: 'decimal', precision: 11, scale: 8, nullable: true)] private ?float $longitude; // field_longitude // TimestampableTrait } ``` ## Response کامل دکتر (finalizeData) ```json { "id": 5, "uuid": "...", "name": "دکتر محمدی", "gender": "male", "experience": 12, "activity_time": 1388534400, "medical_system_code": "12345", "detail": "متخصص قلب و عروق...", "degree": "دکترای تخصصی", "specialties": [{"id": 3, "uuid": "...", "name": "قلب و عروق"}], "img": ["https://..."], "expertise": [{"id": 10, "uuid": "...", "name": "اکوکاردیوگرافی"}], "satisfaction": 87.5, "point": 4.4, "free_turn": "اولین نوبت آزاد: دوشنبه ۱۲ اردیبهشت ساعت ۱۰:۳۰", "hours_of_work": ["شنبه", "یکشنبه", "دوشنبه"], "address": [...], "average_rate": {"average_stars": 4.3, "total_rates": 87}, "state": [{"id": 2, "uuid": "...", "name": "فارس"}], "city": [{"id": 5, "uuid": "...", "name": "شیراز"}] } ``` ## منطق create دکتر (از DoctorService.php) ۱. اگر `doctor_mobile_number` داده شد: - کاربر با این موبایل را پیدا کن - اگر نقش `doctor` ندارد → 403 - اگر قبلاً profile دکتر دارد → 403 - اگر کاربر وجود ندارد → کاربر جدید با نقش doctor بساز ۲. اگر `doctor_mobile_number` نداده شد → کاربر جاری استفاده شود ۳. اعتبارسنجی همه فیلدهای اجباری ۴. ایجاد entity