- Add task for completing service mode in clinicpro with detailed objectives and acceptance criteria. - Create architecture documentation for task 00b, outlining involved components and necessary changes. - Develop checklist for task 00b to ensure all requirements are met. - Document implementation notes for task 00b, emphasizing API contract checks and design system adherence. - Update task documentation for task 00b, specifying goals and current issues with service mode.
205 lines
11 KiB
Markdown
205 lines
11 KiB
Markdown
# نکات پیادهسازی — تسک ۰۰
|
||
|
||
## ۱. اول تست خط سرخ، بعد هر چیز دیگر
|
||
|
||
ترتیب کار:
|
||
|
||
```
|
||
۱. tests/Appointment/SlotModeFrozenTest.php + سه fixture ← اول این
|
||
۲. ddev exec php bin/phpunit --group=slot-mode-frozen ← باید سبز باشد قبل از هر تغییری
|
||
۳. بقیهٔ تسک
|
||
۴. دوباره گام ۲ — باید همچنان سبز باشد
|
||
```
|
||
|
||
اگر fixture را بعد از تغییرات بسازی، هیچ چیزی را تضمین نکردهای — snapshot وضعیت
|
||
تغییریافته را گرفتهای.
|
||
|
||
## ۲. `isServiceMode` گِیت همهچیز است
|
||
|
||
هر خط کد جدید در مسیر مشترک باید داخل این شرط باشد:
|
||
|
||
```php
|
||
if ($this->serviceCalc->isServiceMode($doctor, $clinic)) {
|
||
// … منطق جدید
|
||
}
|
||
```
|
||
|
||
نه بیرونش، نه با `??`، نه با «اگر سرویس دارد». معیار **فقط** `booking_mode` است.
|
||
نوبت اسلاتی هم میتواند `service_item_id` داشته باشد (فیلدهای Figma نوبتها) — آن دلیل
|
||
سرویسی بودن نیست.
|
||
|
||
اشتباه رایج:
|
||
|
||
```php
|
||
// ❌ نوبت اسلاتیِ دارای سرویس را وارد مسیر جدید میکند
|
||
if ($appointment->getServiceItems()->count() > 0) { … }
|
||
```
|
||
|
||
## ۳. جمع سادهٔ مدت را همینجا اصلاح نکن
|
||
|
||
```php
|
||
$totalMinutes += $duration; // ← اشتباه است، ولی دست نزن
|
||
```
|
||
|
||
مستند بند ۵ میگوید این فرمول ظرفیت را الکی پر میکند و راهحلش «زمان تنها / زمان اضافه»
|
||
است — که تسک ۰۴ میسازد. اصلاحش اینجا یعنی:
|
||
|
||
- مدت همهٔ نوبتهای چندسرویسیِ در حال رزرو یکشبه کم میشود
|
||
- سایت و اپ دسکتاپ عدد متفاوت میبینند بدون اینکه چیزی در build بشکند
|
||
- و هیچ دادهای برای «زمان اضافه» وجود ندارد، پس اصلاح بیورودی غیرممکن است
|
||
|
||
کاری که این تسک میکند: محاسبه را به **یک نقطه** منتقل میکند تا تسک ۰۴ یک خط عوض کند.
|
||
|
||
## ۴. `excludeAppointmentId` — الگوی موجود را تکرار کن
|
||
|
||
`AppointmentRepository::isSlotTaken($doctor, $start, $end, $excludeId)` از قبل این پارامتر
|
||
را دارد. `findBusyIntervals` هم همان را بگیرد، با همان نام و همان جای پارامتر و همان
|
||
پیشفرض `null`.
|
||
|
||
دو الگوی متفاوت برای یک کار (مثلاً یکی `?int $excludeId`، دیگری `array $excludeIds`)
|
||
یعنی اولین کسی که هر دو را میبیند یکی را اشتباه صدا میزند.
|
||
|
||
## ۵. اعتبارسنجی زمان: عضویت در فهرست، نه «اشغال نبودن»
|
||
|
||
```php
|
||
// ❌ ناکافی
|
||
if ($this->appointmentRepo->isSlotTaken($doctor, $start, $end, $excludeId)) { /* 409 */ }
|
||
|
||
// ✅
|
||
$starts = $this->slotCalculator->getServiceStartTimes(…);
|
||
if (!in_array($req->start, array_column($starts, 'start'), true)) { /* 422 */ }
|
||
```
|
||
|
||
`isSlotTaken` فقط تداخل با نوبت دیگر را میگوید. `getServiceStartTimes` علاوه بر آن
|
||
شیفت، تعطیلی، `date_override`، پنجرهٔ رزرو و بافر را هم اعمال میکند. با شرط اول،
|
||
منشی میتواند نوبت را ساعت ۳ بامداد بگذارد.
|
||
|
||
## ۶. `warnings[]` بهجای `422` برای سرویس غیرفعال در نوبت موجود
|
||
|
||
```php
|
||
$duration = $this->serviceCalc->calculate(…, allowInactive: true);
|
||
// $duration->warnings === ['سرویس «لیزر صورت» دیگر برای نوبتدهی فعال نیست']
|
||
```
|
||
|
||
نوبت موجود با سرویسی که کلینیک غیرفعالش کرده، باید قابل جابهجایی و لغو بماند. `422`
|
||
یعنی آن نوبت برای همیشه قفل میشود و منشی هیچ کاری نمیتواند بکند.
|
||
|
||
ولی **افزودن** سرویس غیرفعال به نوبت → `422`. تفاوتش `allowInactive` است که فقط برای
|
||
uuid های موجودِ نوبت `true` میشود، نه برای uuid های تازهی درخواست.
|
||
|
||
## ۷. `replaceServiceItems` باید `serviceItem` تکی را همگام کند
|
||
|
||
```php
|
||
$this->serviceItem = $items[0] ?? null;
|
||
```
|
||
|
||
چهار مصرفکننده روی `service_item` تکی خواندهاند (`AppointmentsPage`،
|
||
`ReserveAppointmentsPage`، `nobat724_front/services/response.js`،
|
||
`clinic-pro-tauri/src/service/response.js`). این دقیقاً همان الگویی است که
|
||
`ServiceItem::setStaffMembers()` برای `staff` تکی دارد — تکرارش کن.
|
||
|
||
## ۸. `refreshActiveSlotKey` پس از `setIsReserve(false)`
|
||
|
||
```php
|
||
public function setIsReserve(bool $v): self
|
||
{
|
||
$this->isReserve = $v;
|
||
$this->refreshActiveSlotKey(); // ← اگر نیست، اضافه کن
|
||
$this->updatedAt = time();
|
||
return $this;
|
||
}
|
||
```
|
||
|
||
بدون آن، رزروِ تبدیلشده `active_slot_key = NULL` میماند و دو نفر میتوانند همان ساعت
|
||
را بگیرند. **این تنها تغییر مجاز در مکانیزم `active_slot_key` است** و فقط چون یک شرط
|
||
موجود را اعمال میکند، نه عوضش میکند. تست: `ConvertReserveSlotKeyTest`.
|
||
|
||
## ۹. تبدیل رزرو، اتمی
|
||
|
||
```php
|
||
$this->em->wrapInTransaction(function () use ($reserve, $req) {
|
||
$duration = …; // یا اسلات اسلاتی
|
||
$reserve->setIsReserve(false);
|
||
$reserve->reschedule($req->start, $end);
|
||
$reserve->replaceServiceItems($duration->serviceItems);
|
||
// UniqueConstraintViolationException روی active_slot_key → 409
|
||
});
|
||
```
|
||
|
||
`try/catch` روی `UniqueConstraintViolationException` و ترجمه به `409 ERR_SLOT_TAKEN` —
|
||
همان چیزی که `SlotTakenException` موجود در `src/Appointment/Repository/` انجام میدهد.
|
||
از همان استفاده کن.
|
||
|
||
## ۱۰. `ReserveAppointmentsPage` → `DataTable`
|
||
|
||
صفحه امروز جدول خام با `<td style={td}>` دارد. چون در این تسک دستش میزنیم، همانجا به
|
||
`DataTable` مهاجرت کند: توکن inline خلاف [ui-conventions](../_shared/ui-conventions.md)
|
||
است و در دارکمود میشکند.
|
||
|
||
این «scope creep» نیست — قاعدهٔ پروژه است که صفحهٔ دستخورده باید با دیزاینسیستم بخواند.
|
||
|
||
## ۱۱. edge case ها
|
||
|
||
| حالت | رفتار درست |
|
||
|---|---|
|
||
| نوبت سرویسی بدون هیچ سرویس (داده قدیمی) | مدت موجود حفظ · `warnings[]` · **رد نمیشود** |
|
||
| `PATCH` فقط یادداشت روی نوبت سرویسی | بدون اعتبارسنجی مدت — مثل امروز |
|
||
| `service-reschedule` روی نوبت اسلاتی | `422 ERR_WRONG_BOOKING_MODE` |
|
||
| `service-reschedule` روی نوبت رزرو | `422` — مسیرش `convert-reserve` است |
|
||
| زمان فعلی نوبت با سرویس جدید | با `excludeAppointmentId` در فهرست میآید |
|
||
| بافر عوض شد بعد از ثبت | نوبت موجود سالم؛ فقط جابهجایی جدید بافر جدید میگیرد |
|
||
| سرویس محیط دیگر در `service_item_uuids[]` | `404` — `TenantOwnershipChecker` **پیش از** هر بررسی دیگر |
|
||
| `durations` override با مقدار ۰ یا منفی | نادیده گرفته شود (رفتار موجود `serviceSlots`) |
|
||
| نوبت گذشته | `service-reschedule` → `422` |
|
||
| دو درخواست جابهجایی همزمان به یک زمان | `active_slot_key` → یکی `409` |
|
||
| نوبت در محیطی که وسط کار به اسلاتی برگشت | `booking_mode` قفل است پس رخ نمیدهد؛ ولی اگر داده دستی عوض شد → `422` روشن |
|
||
|
||
## ۱۲. تست
|
||
|
||
```
|
||
tests/Appointment/SlotModeFrozenTest.php ← ⭐ اول از همه
|
||
- قرارداد appointment-slots بیتبهبیت
|
||
- قرارداد month-availability
|
||
- امضای متدهای عمومی SlotCalculatorService
|
||
tests/Appointment/ServiceBookingCalculatorTest.php
|
||
- جمع مدت چند سرویس (رفتار فعلی حفظ شود)
|
||
- override منشی
|
||
- سرویس بدون مدت → 422
|
||
- سرویس غیرفعال با allowInactive → warning نه خطا
|
||
- سرویس محیط دیگر → استثنا، بدون افشای وجود
|
||
tests/Appointment/ServiceRescheduleTest.php ← ⭐
|
||
- جابهجایی با همان سرویسها → مدت یکسان
|
||
- حذف یک سرویس → مدت خودکار کم میشود، کلاینت عددی نفرستاده
|
||
- زمان بیرون getServiceStartTimes → 422
|
||
- زمان فعلی خود نوبت با excludeAppointmentId در فهرست است
|
||
- روی نوبت اسلاتی → 422 ERR_WRONG_BOOKING_MODE
|
||
tests/Appointment/PatchServiceDurationTest.php
|
||
- slot_end ناسازگار → 422 ERR_SERVICE_DURATION_MISMATCH با مدت درست در پیام
|
||
- service_item_uuids[] جایگزینی کامل میکند و serviceItem تکی همگام میشود
|
||
- در حالت اسلاتی هیچکدام از اینها اجرا نمیشود (رفتار امروز)
|
||
tests/Appointment/ConvertReserveTest.php
|
||
- رزرو سرویسی → نوبت زماندار با مدت درست
|
||
- رزرو اسلاتی → مسیر اسلاتی، بدون تغییر
|
||
- active_slot_key بعد از تبدیل پر میشود
|
||
- دو تبدیل همزمان → یکی 409
|
||
tests/Appointment/ServiceModeSectionDurationTest.php ← موجود، باید سبز بماند
|
||
tests/Appointment/BookingTenantTest.php ← موجود، باید سبز بماند
|
||
assets/admin/pages/AppointmentEditPage.test.tsx
|
||
- حالت اسلاتی: سه فیلد ساعت هست، ServiceSlotPicker نیست
|
||
- حالت سرویسی: ServiceSlotPicker هست، فیلد ساعت پنهان است
|
||
- تغییر سرویسها، slot انتخابی را باطل میکند
|
||
```
|
||
|
||
## ۱۳. مستندات
|
||
|
||
`docs/api/appointment.md`:
|
||
- بخش «روشهای نوبتدهی» با ماتریس «کدام endpoint در کدام حالت»
|
||
- دو endpoint جدید
|
||
- توسعهٔ `PATCH` و پارامتر `exclude_appointment_uuid`
|
||
- دو کد خطای جدید
|
||
|
||
`docs/api/appointment-settings.md`: یادآوری قفل بودن `booking_mode` پس از اولین ثبت.
|
||
|
||
و یک سند کوتاه `docs/architecture/booking-modes.md` که ماتریس کامل را ثبت کند —
|
||
تسک ۰۶ حالت سومی به همین ماتریس اضافه میکند.
|