- Introduced clinic_id to weekly_schedules, date_overrides, and holidays to differentiate between personal and clinic schedules. - Updated unique constraints and indexes to accommodate the new clinic context. feat(command): create AssignScheduleClinicCommand to move schedules - Added a command to move a doctor's personal weekly schedule into a clinic context. - Implemented checks to ensure sessions align with the target clinic. feat(context): implement EntityContext and EntityContextResolver - Created EntityContext to represent the effective working environment of a request (doctor or clinic). - Developed EntityContextResolver to determine the execution context based on user roles and active contexts. test: add ServiceModeContextTest for appointment scheduling - Implemented tests to ensure service booking respects clinic and personal contexts. - Verified that financial data is omitted in clinic contexts in InvitedDoctorDashboardScopeTest.
166 lines
6.6 KiB
PHP
166 lines
6.6 KiB
PHP
<?php
|
|
|
|
namespace App\Appointment\Entity;
|
|
|
|
use App\Clinic\Entity\Clinic;
|
|
use App\Doctor\Entity\Doctor;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
use App\Appointment\Repository\WeeklyScheduleRepository;
|
|
use Symfony\Component\Uid\Uuid;
|
|
|
|
/**
|
|
* برنامهٔ هفتگی نوبتدهی یک پزشک در یک context مشخص.
|
|
*
|
|
* context با ستون clinic_id بیان میشود: NULL یعنی مطب شخصی پزشک، و مقدار غیر-NULL
|
|
* یعنی همان پزشک در آن کلینیک. یک پزشک میتواند همزمان چند برنامه داشته باشد
|
|
* (شخصی + یکی به ازای هر کلینیک) و این برنامهها کاملاً مستقلاند.
|
|
*/
|
|
#[ORM\Entity(repositoryClass: WeeklyScheduleRepository::class)]
|
|
#[ORM\Table(name: 'weekly_schedules')]
|
|
#[ORM\UniqueConstraint(name: 'idx_weekly_schedules_doctor_clinic', columns: ['doctor_id', 'clinic_key'])]
|
|
class WeeklySchedule
|
|
{
|
|
public const DAYS = ['saturday', 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday'];
|
|
|
|
public const META_KEY = 'meta';
|
|
|
|
public const MODE_SLOT = 'slot'; // نوبتدهی اسلاتی (رفتار پیشفرض)
|
|
public const MODE_SERVICE = 'service'; // نوبتدهی بر اساس مدت سرویس
|
|
|
|
public const DEFAULT_META = [
|
|
'online_booking_enabled' => true,
|
|
'booking_window_value' => 1,
|
|
'booking_window_unit' => 'month',
|
|
'booking_mode' => self::MODE_SLOT,
|
|
'buffer_minutes' => 0,
|
|
];
|
|
|
|
#[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;
|
|
|
|
/**
|
|
* ستون تولیدشدهٔ پایگاهداده: IFNULL(clinic_id, 0).
|
|
*
|
|
* MySQL/MariaDB مقادیر NULL را در unique index متمایز میشمارند، پس
|
|
* UNIQUE(doctor_id, clinic_id) جلوی دو برنامهٔ شخصی برای یک پزشک را نمیگرفت.
|
|
* این ستون NULL را به 0 نگاشت میکند تا یکتایی در سطح دیتابیس تضمین شود.
|
|
*/
|
|
#[ORM\Column(name: 'clinic_key', type: 'integer', insertable: false, updatable: false, generated: 'ALWAYS', columnDefinition: 'INT AS (IFNULL(clinic_id, 0)) STORED')]
|
|
private int $clinicKey = 0;
|
|
|
|
#[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();
|
|
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, ['week', 'month'], true)
|
|
? $meta['booking_window_unit']
|
|
: $current['booking_window_unit'],
|
|
'booking_mode' => in_array($meta['booking_mode'] ?? null, [self::MODE_SLOT, self::MODE_SERVICE], true)
|
|
? $meta['booking_mode']
|
|
: $current['booking_mode'],
|
|
'buffer_minutes' => max(0, (int)($meta['buffer_minutes'] ?? $current['buffer_minutes'])),
|
|
];
|
|
$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,
|
|
];
|
|
}
|
|
}
|