1])] private int $capacity = 1; /** آماده‌سازی پیش از بیمار — جزو نوبت بیمار نیست، ولی منبع را اشغال می‌کند. */ #[ORM\Column(name: 'setup_minutes', type: 'smallint', options: ['default' => 0])] private int $setupMinutes = 0; #[ORM\Column(name: 'cleanup_minutes', type: 'smallint', options: ['default' => 0])] private int $cleanupMinutes = 0; /** JSON آزاد ولی فقط اسکالر — {@see \App\Resource\Service\ResourceService::normalizeAttributes()} */ #[ORM\Column(type: 'json', nullable: true)] private ?array $attributes = null; #[ORM\ManyToOne(targetEntity: Doctor::class)] #[ORM\JoinColumn(name: 'doctor_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')] private ?Doctor $doctor = null; #[ORM\ManyToOne(targetEntity: ClinicStaff::class)] #[ORM\JoinColumn(name: 'staff_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')] private ?ClinicStaff $staff = null; #[ORM\ManyToOne(targetEntity: Room::class)] #[ORM\JoinColumn(name: 'room_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')] private ?Room $room = null; #[ORM\Column(type: 'boolean', options: ['default' => true])] private bool $active = true; #[ORM\Column(name: 'created_at', type: 'integer')] private int $createdAt; #[ORM\Column(name: 'updated_at', type: 'integer')] private int $updatedAt; /** @var Collection */ #[ORM\OneToMany(targetEntity: ResourceSkill::class, mappedBy: 'resource', cascade: ['persist', 'remove'], orphanRemoval: true)] private Collection $skills; public function __construct(DoctorAddress $address, ResourceType $type, string $name) { $this->uuid = Uuid::v4()->toRfc4122(); $this->address = $address; $this->type = $type; $this->name = $name; $this->createdAt = time(); $this->updatedAt = time(); $this->skills = new ArrayCollection(); // جفت از آدرس مشتق می‌شود، نه از بدنهٔ درخواست — پس هیچ نقطهٔ ساختی // نمی‌تواند فراموشش کند و کلاینت هم نمی‌تواند منبع را به محیط دیگری بچسباند. $this->assignTenantPair($address->tenantEntityType(), $address->tenantEntityId()); } public function getId(): ?int { return $this->id; } public function getUuid(): string { return $this->uuid; } public function getAddress(): DoctorAddress { return $this->address; } public function getType(): ResourceType { return $this->type; } public function getName(): string { return $this->name; } public function getCapacity(): int { return $this->capacity; } public function getSetupMinutes(): int { return $this->setupMinutes; } public function getCleanupMinutes(): int { return $this->cleanupMinutes; } public function getAttributes(): array { return $this->attributes ?? []; } public function getDoctor(): ?Doctor { return $this->doctor; } public function getStaff(): ?ClinicStaff { return $this->staff; } public function getRoom(): ?Room { return $this->room; } public function isActive(): bool { return $this->active; } public function getCreatedAt(): int { return $this->createdAt; } public function getUpdatedAt(): int { return $this->updatedAt; } /** @return Collection */ public function getSkills(): Collection { return $this->skills; } public function setName(string $v): self { $this->name = $v; $this->touch(); return $this; } public function setSetupMinutes(int $v): self { $this->setupMinutes = $this->assertMinutes($v, 'setup_minutes'); $this->touch(); return $this; } public function setCleanupMinutes(int $v): self { $this->cleanupMinutes = $this->assertMinutes($v, 'cleanup_minutes'); $this->touch(); return $this; } public function setAttributes(array $v): self { $this->attributes = $v === [] ? null : $v; $this->touch(); return $this; } public function setActive(bool $v): self { $this->active = $v; $this->touch(); return $this; } public function setType(ResourceType $v): self { $this->type = $v; $this->touch(); return $this; } /** * @throws \InvalidArgumentException ظرفیت کمتر از ۱، یا بزرگ‌تر از ۱ روی منبعی که * یک شخص است — پزشک و اپراتور هم‌زمان دو بیمار ندارند. */ public function setCapacity(int $v): self { if ($v < 1) { throw new \InvalidArgumentException('Resource capacity must be at least 1.'); } if ($v > 1 && $this->isPerson()) { throw new \InvalidArgumentException('A person resource cannot serve more than one patient at a time.'); } $this->capacity = $v; $this->touch(); return $this; } /** منبعی که یک انسان است: ظرفیتش همیشه ۱ می‌ماند. */ public function isPerson(): bool { return $this->doctor !== null || $this->staff !== null || in_array($this->type->getCode(), [ResourceType::CODE_DOCTOR, ResourceType::CODE_STAFF], true); } /** * پل به موجودیت اصلی. حداکثر یکی مجاز است — MariaDB قید چندستونی `CHECK` را * قابل اتکا اجرا نمی‌کند، پس اجبارش اینجاست. * * @throws \InvalidArgumentException روی پل دوم */ public function linkTo(Doctor|ClinicStaff|Room $subject): self { if ($this->subject() !== null) { throw new \InvalidArgumentException('A resource can bridge to at most one subject.'); } match (true) { $subject instanceof Doctor => $this->doctor = $subject, $subject instanceof ClinicStaff => $this->staff = $subject, $subject instanceof Room => $this->room = $subject, }; $this->touch(); return $this; } /** موجودیت اصلی پشت این منبع؛ `null` یعنی دستگاه/تجهیزات. */ public function subject(): Doctor|ClinicStaff|Room|null { return $this->doctor ?? $this->staff ?? $this->room; } private function assertMinutes(int $v, string $field): int { if ($v < 0 || $v > 480) { throw new \InvalidArgumentException(sprintf('%s must be between 0 and 480.', $field)); } return $v; } private function touch(): void { $this->updatedAt = time(); } public function toArray(): array { $subject = $this->subject(); return [ 'uuid' => $this->uuid, 'name' => $this->name, 'address_uuid' => $this->address->getUuid(), 'address_name' => $this->address->getName(), 'type_uuid' => $this->type->getUuid(), 'type_code' => $this->type->getCode(), 'type_name' => $this->type->getName(), 'capacity' => $this->capacity, 'setup_minutes' => $this->setupMinutes, 'cleanup_minutes' => $this->cleanupMinutes, 'attributes' => (object) $this->getAttributes(), 'subject_kind' => match (true) { $this->doctor !== null => 'doctor', $this->staff !== null => 'staff', $this->room !== null => 'room', default => null, }, 'subject_uuid' => $subject?->getUuid(), 'skills' => array_map( static fn (ResourceSkill $rs): array => $rs->toArray(), $this->skills->toArray(), ), 'active' => $this->active, 'created_at' => $this->createdAt, 'updated_at' => $this->updatedAt, ]; } }