- Add implementation notes for cancellation and waitlist features. - Create task documentation outlining goals, current status, and acceptance criteria for cancellation policy and resource utilization reporting. - Establish architecture for domain events and outbox pattern to ensure reliable event publishing. - Define database schema for domain events and necessary queries for resource utilization and plan accuracy reports. - Implement detailed implementation notes covering edge cases, testing strategies, and documentation requirements.
5.9 KiB
معماری — تسک ۰۲
ساختار فایل
src/Resource/
├── Controller/
│ ├── ResourceController.php
│ ├── ResourceTypeController.php
│ ├── SkillController.php
│ └── ResourcePoolController.php
├── Entity/
│ ├── ResourceType.php
│ ├── ClinicResource.php # نامِ کلاس عمداً Resource نیست (تداخل با کلمهٔ رزرو PHP نیست، ولی با Symfony/Doctrine ابهام دارد)
│ ├── Skill.php
│ ├── ResourceSkill.php
│ ├── ResourcePool.php
│ └── ResourcePoolMember.php
├── Repository/…
├── Service/
│ ├── ResourceService.php
│ ├── SkillAssignmentService.php
│ ├── ResourcePoolService.php
│ └── ResourceLinker.php # پل بین Doctor/ClinicStaff/Room و ClinicResource
└── Command/
└── BackfillResourceCommand.php
ClinicResource
#[ORM\Entity(repositoryClass: ClinicResourceRepository::class)]
#[ORM\Table(name: 'clinic_resources')]
#[ORM\Index(columns: ['entity_type', 'entity_id', 'active'], name: 'idx_resources_tenant')]
class ClinicResource
{
use TenantOwnedTrait;
private string $uuid;
private Branch $branch; // منابع همیشه مال شعبهاند — فیزیکیاند
private ResourceType $type;
private string $name;
private int $capacity = 1; // ظرفیت همزمان
private int $setupMinutes = 0;
private int $cleanupMinutes = 0;
private array $attributes = []; // JSON آزاد: gender, device_model, floor
private bool $active = true;
// ── پل به موجودیتهای موجود؛ حداکثر یکی غیر-null است ──
private ?Doctor $doctor = null;
private ?ClinicStaff $staff = null;
private ?Room $room = null;
}
چرا پل، نه ادغام
Doctor و ClinicStaff و Room هرکدام هویت مستقل و مصرفکنندهٔ زنده دارند
(appointments.doctor_id، service_item_staff، سایت عمومی). تبدیل آنها به زیرکلاس
Resource یعنی مهاجرت همزمان همهٔ آن مسیرها. بهجایش:
Doctor 1 ──0..1 ClinicResource (type=doctor)
ClinicStaff 1 ──0..1 ClinicResource (type=staff)
Room 1 ──0..1 ClinicResource (type=room)
ClinicResource بدون پل = دستگاه/تجهیزات
ResourceLinker تنها نقطهای است که این نگاشت را میداند:
final class ResourceLinker
{
/** منبعِ متناظر با یک پرسنل؛ اگر نبود میسازد. */
public function forStaff(ClinicStaff $staff): ClinicResource { … }
public function forDoctor(Doctor $doctor, Branch $branch): ClinicResource { … }
/** برعکس: منبع → موجودیت اصلی، برای نمایش در UI. */
public function subject(ClinicResource $r): Doctor|ClinicStaff|Room|null { … }
}
هیچ سرویس دیگری نباید مستقیم $resource->getStaff() را برای تصمیمگیری بخواند.
مهارتها — جدول، نه قانون
مستند بند ۶ صریح است: «کدام اپراتور مجاز است با کدام دستگاه کار کند» یک اطلاعات است، نه یک قانون. با ۵۰ اپراتور و ۲۰۰ سرویس، سپردنش به موتور قوانین یعنی ۱۰٬۰۰۰ قانون.
skills (uuid, name, tenant)
resource_skills (resource_id, skill_id, level) ← جدول واسط ساده
level (۱..۵) از روز اول هست چون تسک ۰۶ استراتژی «حفظ متخصصها» را روی همین میسازد
و افزودنش بعداً یعنی backfill با حدس.
استخر منابع
class ResourcePool
{
use TenantOwnedTrait;
private Branch $branch; // استخر درون یک شعبه است — منبعِ شعبهٔ دیگر جایگزین نیست
private ResourceType $type; // اعضا باید همنوع باشند
private string $name;
private Collection $members; // ResourcePoolMember
}
قاعدهٔ اعتبار در ResourcePoolService::replaceMembers():
همهٔ اعضا باید branch و type یکسان با خود استخر داشته باشند، وگرنه 422.
دلیل: تسک ۰۶ فرض میکند «هر عضو استخر جایگزین کامل دیگری است» — اگر یکی در شعبهٔ
دیگری باشد، بیمار در ساختمان اشتباه میایستد.
اعتبارسنجی attributes
JSON آزاد است ولی نه بیقید:
// ResourceService::normalizeAttributes()
// - کلید: [a-z_]{1,40}
// - مقدار: string|int|bool|float فقط — نه آرایه، نه object
// - حداکثر ۲۰ کلید
دلیل محدودیت اسکالر: تسک ۰۵ قید same_gender و تسک ۰۹ شرطهای منبع را روی همین
مقادیر با مقایسهٔ ساده میسنجند. آرایهٔ تودرتو یعنی مقایسهٔ دلخواه، یعنی همان چیزی که
مستند بند ۸ ممنوع کرده.
کلیدهای شناختهشده (قرارداد، نه اجبار): gender, device_model, floor, brand.
پنل ادمین
ResourcesPage.tsx—DataTableبا فیلتر شعبه/نوع/فعال، همه در URL (useUrlState)ResourceFormPage.tsx—SearchableSelectبرای شعبه و نوع، چیپ برای مهارتها،PriceInputلازم نیست، ولی برایsetup/cleanupعدد ساده با پسوند «دقیقه»SkillsPage.tsx,ResourceTypesPage.tsx,ResourcePoolsPage.tsx— لیستهای ساده- همهٔ زیرصفحهها
backToیا<BackButton />دارند