- 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.
112 lines
6.0 KiB
Markdown
112 lines
6.0 KiB
Markdown
# نکات پیادهسازی — تسک ۰۲
|
||
|
||
## ۱. ظرفیت: یک ردیف با ظرفیت ۳، نه سه ردیف
|
||
|
||
مستند بند ۶ صریح است و دلیلش در تسک ۰۶ روشن میشود: با سه ردیف، موتور جستجو باید سه
|
||
تقویم را ادغام کند و «کدام تخت» بشود یک تصمیم که هیچکس نمیخواهد بگیرد. با ظرفیت ۳،
|
||
شرط اشغال یک شمارش ساده است:
|
||
|
||
```
|
||
SELECT COUNT(*) FROM resource_occupancy
|
||
WHERE resource_id = ? AND [بازهها تداخل دارند] AND status IN (…)
|
||
→ اگر < capacity، جا هست
|
||
```
|
||
|
||
این را همین حالا در `docs/api/resource.md` بنویس تا کسی وسوسه نشود «اتاق ۱ تخت ۲» بسازد.
|
||
|
||
## ۲. `ClinicResource` نه `Resource`
|
||
|
||
نام کلاس `Resource` در PHP مشکل ندارد ولی در این کدبیس با `App\Shared\…` و مفهوم
|
||
«منبع API» قاطی میشود و در جستجوی کد نویز شدیدی میسازد. نام جدول `clinic_resources`
|
||
هم به همان دلیل. متغیرها و متدها میتوانند `$resource` باشند.
|
||
|
||
## ۳. پلها و `active`
|
||
|
||
`ClinicStaff.active = false` باید `ClinicResource.active` را هم false کند، وگرنه پرسنل
|
||
غیرفعال همچنان در جستجوی وقت ظاهر میشود. این را در `StaffService` (تسک موجود) با یک
|
||
فراخوانی به `ResourceLinker::syncActive()` انجام بده — نه با Doctrine lifecycle callback،
|
||
چون callback در `getArrayResult()` اجرا نمیشود و رفتار نامتقارن میسازد.
|
||
|
||
عکسش برقرار نیست: غیرفعال کردن منبع، پرسنل را غیرفعال نمیکند (پرسنل ممکن است فقط
|
||
اداری باشد).
|
||
|
||
## ۴. `setup_minutes` / `cleanup_minutes` چه هستند و چه نیستند
|
||
|
||
- **جزو نوبت بیمار نیستند** — بیمار ساعت ۱۰:۰۰ میآید و ۱۰:۳۰ میرود
|
||
- **منبع را اشغال میکنند** — یونیت از ۹:۵۵ تا ۱۰:۴۰ در دسترس نیست
|
||
|
||
پس در تسک ۰۷ بازهٔ ثبتشده در `resource_occupancy` گستردهتر از بازهٔ نوبت است. الان فقط
|
||
ستون را بساز و در `docs/api/resource.md` این تفاوت را بنویس؛ محاسبهاش کار تسک ۰۶ است.
|
||
|
||
اشتباه رایج: این را با `WeeklySchedule.meta.buffer_minutes` موجود یکی گرفتن. آن یکی
|
||
فاصلهٔ سراسری بین دو نوبتِ **پزشک** است؛ این یکی per منبع است. تا وقتی حالت `resource`
|
||
نیامده، هر دو کنار هم زندگی میکنند و `buffer_minutes` دستنخورده میماند.
|
||
|
||
## ۵. مهارت را با `jobTitle` قاطی نکن
|
||
|
||
`ClinicStaff.jobTitle` متن آزاد و برای نمایش است. مهارت یک موجودیت با هویت است که در
|
||
شرط نیازمندی (تسک ۰۵) و شرط قانون (تسک ۰۹) استفاده میشود. هیچجا `jobTitle` را برای
|
||
تصمیمگیری parse نکن.
|
||
|
||
## ۶. edge case ها
|
||
|
||
| حالت | رفتار درست |
|
||
|---|---|
|
||
| منبع بدون هیچ پل (دستگاه) | معتبر — حالت عادی تجهیزات |
|
||
| دو پل همزمان (`doctor_id` و `staff_id`) | `422` در سازنده |
|
||
| حذف `resource_type` که منبع دارد | `422` |
|
||
| حذف `resource_type` با `is_system=1` | `422` همیشه |
|
||
| غیرفعال کردن منبعی که نوبت آیندهٔ فعال دارد | مجاز، ولی پاسخ شامل `warnings[]` با تعداد نوبتها |
|
||
| استخر با صفر عضو | معتبر (در حال ساخت)، ولی تسک ۰۶ آن را «هیچ منبعی» میبیند |
|
||
| `capacity` روی منبع `type=doctor` بزرگتر از ۱ | `422` — پزشک همزمان دو بیمار ندارد |
|
||
| `level` خارج از ۱..۵ | `422` |
|
||
| مهارت محیط A روی منبع محیط B | `404` (پیش از هر بررسی: `TenantOwnershipChecker`) |
|
||
|
||
## ۷. کارایی
|
||
|
||
پرسوجوی داغ تسک ۰۶: «منابع فعالِ شعبهٔ X از نوع Y که مهارت Z را دارند».
|
||
|
||
```php
|
||
// ClinicResourceRepository::findEligible()
|
||
// یک کوئری با JOIN به resource_skills، بدون N+1
|
||
$qb->select('r')
|
||
->from(ClinicResource::class, 'r')
|
||
->join('r.skills', 'rs')
|
||
->where('r.branch = :branch')
|
||
->andWhere('r.type = :type')
|
||
->andWhere('r.active = true')
|
||
->andWhere('rs.skill IN (:skills)')
|
||
->groupBy('r.id')
|
||
->having('COUNT(DISTINCT rs.skill) = :skillCount'); // همهٔ مهارتها، نه یکی
|
||
```
|
||
|
||
`HAVING COUNT(DISTINCT …)` عمدی است: نیازمندی «مهارت الف و ب» یعنی هر دو، نه یکی.
|
||
|
||
## ۸. تست
|
||
|
||
```
|
||
tests/Resource/ResourceCrudTest.php
|
||
- ساخت منبع + خواندن → tenant و branch درست
|
||
- منبع با شعبهٔ محیط دیگر → 404
|
||
- capacity=0 → 422 · capacity=2 روی type=doctor → 422
|
||
- دو پل همزمان → 422
|
||
tests/Resource/SkillAssignmentTest.php
|
||
- PUT skills جایگزینی کامل (حذف ندادهها)
|
||
- level خارج بازه → 422
|
||
- حذف مهارتِ در استفاده → 422
|
||
tests/Resource/ResourcePoolTest.php
|
||
- عضو از شعبهٔ دیگر → 422
|
||
- عضو از نوع دیگر → 422
|
||
tests/Resource/ResourceEligibilityTest.php
|
||
- findEligible با دو مهارت: منبعی که فقط یکی را دارد برنمیگردد
|
||
tests/Resource/BackfillResourceTest.php
|
||
- idempotent: دو بار اجرا = یک بار
|
||
tests/Shared/TenantSchemaCoverageTest.php
|
||
tests/Shared/TenantLookupInventoryTest.php ← شمارنده بهروز شود
|
||
```
|
||
|
||
## ۹. مستندات
|
||
|
||
`docs/api/resource.md` بساز. در `docs/api/staff.md` یک بخش «رابطه با منبع» اضافه کن.
|
||
`docs/architecture/tenancy.md` جدول طبقهبندی را بهروز کن.
|