true, 'booking_window_value' => 3, 'booking_window_unit' => 'month', 'booking_mode' => self::MODE_SLOT, 'buffer_minutes' => 0, // فقط در حالت منبع‌محور معنا دارند؛ در بقیهٔ حالت‌ها خوانده نمی‌شوند. 'step_minutes' => 15, 'resource_strategy' => 'first_available', ]; #[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: Doctor::class)] #[ORM\JoinColumn(name: 'doctor_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] private Doctor $doctor; /** NULL = مطب شخصی پزشک. */ #[ORM\ManyToOne(targetEntity: Clinic::class)] #[ORM\JoinColumn(name: 'clinic_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')] private ?Clinic $clinic = null; #[ORM\Column(type: 'json')] private array $setting = []; #[ORM\Column(name: 'created_at', type: 'integer')] private int $createdAt; #[ORM\Column(name: 'updated_at', type: 'integer')] private int $updatedAt; public function __construct(Doctor $doctor, array $setting, ?Clinic $clinic = null) { $this->uuid = Uuid::v4()->toRfc4122(); $this->doctor = $doctor; $this->clinic = $clinic; $this->setting = $setting; $this->createdAt = time(); $this->updatedAt = time(); } public function getId(): ?int { return $this->id; } public function getUuid(): string { return $this->uuid; } public function getDoctor(): Doctor { return $this->doctor; } public function getClinic(): ?Clinic { return $this->clinic; } /** فقط برای انتقال دستی برنامه‌های قدیمی به محیط کلینیک (app:schedule:assign-clinic). */ public function setClinic(?Clinic $clinic): self { $this->clinic = $clinic; $this->updatedAt = time(); $this->assignTenant(EntityContext::forBooking($this->doctor, $clinic)); return $this; } public function getSetting(): array { return $this->setting; } public function setSetting(array $setting): self { $meta = $this->setting[self::META_KEY] ?? null; unset($setting[self::META_KEY]); if ($meta !== null) { $setting[self::META_KEY] = $meta; } $this->setting = $setting; $this->updatedAt = time(); return $this; } public function getMeta(): array { return array_merge(self::DEFAULT_META, $this->setting[self::META_KEY] ?? []); } /** * booking_mode ذخیره‌شده به‌صورت خام (بدون merge پیش‌فرض). null یعنی هنوز * صریحاً ثبت نشده — تا وقتی null است، انتخاب روش قابل‌تغییر است؛ پس از اولین * ثبت، قفل می‌شود. */ public function getStoredBookingMode(): ?string { return $this->setting[self::META_KEY]['booking_mode'] ?? null; } public function setMeta(array $meta): self { $current = $this->getMeta(); $this->setting[self::META_KEY] = [ 'online_booking_enabled' => (bool)($meta['online_booking_enabled'] ?? $current['online_booking_enabled']), 'booking_window_value' => max(1, (int)($meta['booking_window_value'] ?? $current['booking_window_value'])), 'booking_window_unit' => in_array($meta['booking_window_unit'] ?? null, self::BOOKING_WINDOW_UNITS, true) ? $meta['booking_window_unit'] : $current['booking_window_unit'], 'booking_mode' => in_array($meta['booking_mode'] ?? null, self::MODES, true) ? $meta['booking_mode'] : $current['booking_mode'], 'buffer_minutes' => max(0, (int)($meta['buffer_minutes'] ?? $current['buffer_minutes'])), // گام کمتر از پنج دقیقه، جستجو را بی‌دلیل سنگین می‌کند بی‌آنکه وقت تازه‌ای پیدا شود. 'step_minutes' => max(5, (int)($meta['step_minutes'] ?? $current['step_minutes'])), // اعتبارِ کلید در کنترلر سنجیده می‌شود؛ اینجا فقط نگه داشته می‌شود تا // مقدارِ ناشناخته بی‌صدا به پیش‌فرض تبدیل نشود و کاربر خطایش را ببیند. 'resource_strategy' => is_string($meta['resource_strategy'] ?? null) && $meta['resource_strategy'] !== '' ? $meta['resource_strategy'] : $current['resource_strategy'], ]; $this->updatedAt = time(); return $this; } public function getDaySchedule(): array { $schedule = $this->setting; unset($schedule[self::META_KEY]); return $schedule; } public function toArray(): array { return [ 'uuid' => $this->uuid, 'doctor_uuid' => $this->doctor->getUuid(), 'clinic_uuid' => $this->clinic?->getUuid(), 'context' => $this->clinic === null ? 'personal' : 'clinic', 'schedule' => $this->getDaySchedule(), 'meta' => $this->getMeta(), // نوع نوبت‌دهی پس از اولین ثبت قفل می‌شود (پنل توگل را غیرفعال می‌کند). 'booking_mode_locked' => $this->getStoredBookingMode() !== null, 'created_at' => $this->createdAt, 'updated_at' => $this->updatedAt, ]; } }