Files
clinicpro/docs/new_feture/taskes/task-02-resource-model/architecture.md
T
hamed 021d0eb6b2 feat: implement cancellation policy, no-show tracking, and waitlist management
- 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.
2026-07-30 11:43:58 +03:30

141 lines
5.9 KiB
Markdown

# معماری — تسک ۰۲
## ساختار فایل
```
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`
```php
#[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` تنها نقطه‌ای است که این نگاشت را می‌داند:
```php
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 با حدس.
## استخر منابع
```php
class ResourcePool
{
use TenantOwnedTrait;
private Branch $branch; // استخر درون یک شعبه است — منبعِ شعبهٔ دیگر جایگزین نیست
private ResourceType $type; // اعضا باید هم‌نوع باشند
private string $name;
private Collection $members; // ResourcePoolMember
}
```
قاعدهٔ اعتبار در `ResourcePoolService::replaceMembers()`:
همهٔ اعضا باید `branch` و `type` یکسان با خود استخر داشته باشند، وگرنه `422`.
دلیل: تسک ۰۶ فرض می‌کند «هر عضو استخر جایگزین کامل دیگری است» — اگر یکی در شعبهٔ
دیگری باشد، بیمار در ساختمان اشتباه می‌ایستد.
## اعتبارسنجی `attributes`
JSON آزاد است ولی نه بی‌قید:
```php
// 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 />` دارند