Files
clinicpro/docs/new_feture/taskes/00-current-state-report.md
T
hamed 70739691d1 Add checklists for tasks 11 to 14 covering credit ledger, treatment course, cancellation policies, and event utilization
- Created checklist for task 11: Package and Credit Ledger
- Created checklist for task 12: Treatment Course
- Created checklist for task 13: Cancellation Policy, No-Show, and Waitlist
- Created checklist for task 14: Domain Events and Utilization Reports
2026-07-30 12:12:45 +03:30

18 KiB
Raw Blame History

گزارش وضعیت فعلی سیستم در برابر مستند «موتور نوبت‌دهی Clinic Pro»

مرجع: clinic-pro-mostanad-sade.md دامنه بررسی: clinicpro/src/** (Symfony 7.4) + clinicpro/assets/admin/** (React SPA)


۱. خلاصه اجرایی

سیستم فعلی یک موتور نوبت‌دهی تک‌منبعی (فقط پزشک) است که اخیراً یک حالت «نوبت‌دهی سرویسی» هم گرفته: WeeklySchedule.meta.booking_mode = service باعث می‌شود طول نوبت از ServiceItem.duration_minutes گرفته شود به‌جای اسلات ثابت.

این با مستند در جهت درست است، ولی فقط یک لایه از هفت لایه مستند را پوشش می‌دهد. سه ستون اصلی مستند اصلاً وجود ندارند:

ستون مستند وضعیت
تقویم مال منبع است نه پزشک (بند ۲-۱، ۶) وجود ندارد — تقویم فقط (doctor, clinic) است
نوبت از چند بخش تشکیل شده (بند ۷) وجود ندارد — نوبت یک slot_start/slot_end پیوسته است
موتور قوانین شش‌دسته‌ای (بند ۸) ⚠️ فقط یک دسته (قیمت) به شکل DiscountRule

نکته مثبت و مهم: قانون سوم مستند («جلوگیری از رزرو تکراری کار دیتابیس است») از قبل رعایت شدهAppointment.active_slot_key یک ستون UNIQUE است که فقط در وضعیت‌های اشغال‌کننده مقدار می‌گیرد. همان الگو باید به resource_occupancy تعمیم پیدا کند.


۲. آنچه امروز داریم (کد واقعی)

۲-۱ محیط (tenant)

(entity_type, entity_id) روی ۲۰ جدول، با entity_type ∈ {doctor, clinic}src/Shared/Tenant/TenantOwnedTrait.php، سند کامل: docs/architecture/tenancy.md.

سطح مستند معادل امروز
کلینیک (Tenant) clinic یا doctor (مطب شخصی)
شعبه (Branch) ⚠️ نیم‌بند — DoctorAddress نقش «محل» را بازی می‌کند و در location_id هر شیفت می‌نشیند
اتاق (Room) وجود ندارد

Clinic هیچ فیلد شعبه‌ای ندارد (src/Clinic/Entity/Clinic.php). ساعت کاری شعبه هم وجود ندارد؛ ساعت کاری فقط روی برنامهٔ پزشک است.

۲-۲ تعریف خدمات

ServiceSection (بخش) → ServiceItem (سرویس)، هر دو tenant-دار. src/ClinicService/Entity/ServiceItem.php:

private int  $priceRials = 0;
private ?int $durationMinutes = null;   // مدت، تخت — بدون تفکیک بخش
private bool $bookable = false;         // نمایش در نوبت‌دهی
private Collection $staffMembers;       // ManyToMany به ClinicStaff
private ?int $inventoryPackageId = null;
private Collection $consumables;        // ServiceItemConsumable
  • Tariff (قیمت سالانه per سرویس) و TenantServiceCoverage (پوشش بیمه).
مستند وضعیت
دسته‌بندی درختی خدمات ⚠️ ServiceSection تک‌سطحی است، درختی نیست
گروه آیتم با حداقل/حداکثر انتخاب
آیتم با «زمان تنها» و «زمان اضافه» — فقط یک duration_minutes
ناسازگاری / پیش‌نیاز بین آیتم‌ها
الگوی بخش‌های نوبت (segment template)
قیمت اختصاصی شعبه
تک‌جلسه یا دوره‌ای

۲-۳ منابع

تنها «منبع» مدل‌شده، پرسنل است: src/Staff/Entity/ClinicStaff.php — نام، سمت، فعال/غیرفعال، اتصال اختیاری به User. تقویم ندارد، ظرفیت ندارد، مهارت ندارد.

مستند وضعیت
resource_type تعریف‌شده توسط کلینیک
منبع با ظرفیت همزمان
مهارت‌ها (skill / resource_skill)
استخر منابع
ویژگی آزاد (جنسیت، مدل دستگاه، طبقه)
زمان آماده‌سازی/تمیزکاری per منبع (فقط buffer_minutes سراسری روی برنامه)
نیازمندی منبع per بخش
قید هم‌جنس بودن

۲-۴ تقویم و اسلات

src/Appointment/Entity/WeeklySchedule.php: JSON هفتگی per (doctor, clinic)، هر روز چند session با start_time/end_time/duration_per_patient/has_rest/patient_limit/location_id. meta: online_booking_enabled, booking_window_value|unit, booking_mode, buffer_minutes.

DateOverride (روز خاص)، Holiday (بازه تعطیلی per پزشک/محیط).

کسر لایه‌ها در SlotCalculatorService انجام می‌شود و از هفت لایهٔ مستند، چهار لایه را دارد:

ساعت کاری شعبه            ❌ (ساعت کاری فقط روی برنامه پزشک است)
– شیفت منبع               ❌
– تعطیلات رسمی کشور        ❌ (جدول تعطیلات ملی نداریم؛ Holiday دستی است)
– مرخصی/غیبت              ⚠️ فقط از راه Holiday و DateOverride پزشک
– سرویس دوره‌ای دستگاه      ❌
– نوبت‌های ثبت‌شده          ✅ isSlotTaken / findBusyIntervals
 رزروهای موقت            ✅ pending با expires_at
– آماده‌سازی و تمیزکاری     ⚠️ فقط buffer_minutes ثابت

۲-۵ نوبت‌دهی سرویسی که امروز داریم

جریان فعلی (همانی که کاربر اشاره کرد):

  1. GET /api/v1/appointment-booking-services/{doctorUuid}booking_mode + سرویس‌های bookable
  2. GET /api/v1/appointment-service-slots?doctor_uuid&date&service_item_uuids[]&durations[]SlotCalculatorService::getServiceStartTimes()جمع سادهٔ مدت سرویس‌ها، سپس پر کردن فضای خالی هر شیفت با duration + buffer
  3. POST /api/v1/appointment → یک ردیف appointments با slot_start/slot_end و appointment_service_items (ManyToMany چند سرویس)

محدودیت‌های ساختاری این جریان نسبت به مستند:

  • $totalMinutes += $duration برای هر سرویس (AppointmentController.php:236) — دقیقاً همان «فرمول قدیمی» که مستند بند ۵ ردش می‌کند: آماده‌سازی چند بار حساب می‌شود.
  • زمان اشغال یک بلوک پیوسته است؛ اپراتور در زمان انتظار آزاد نمی‌شود (بند ۷).
  • تنها منبعی که تداخلش بررسی می‌شود پزشک است؛ اگر دو سرویس هم‌زمان به یک پرسنل یا یک دستگاه نیاز داشته باشند، سیستم متوجه نمی‌شود.

۲-۵ب حالت سرویسی نیمه‌کاره است — پنج شکاف در چرخهٔ عمر نوبت

مسیر رزرو کار می‌کند، ولی بقیهٔ چرخهٔ عمر نه. این‌ها پیش‌نیاز موتور چندمنبعی‌اند و تسک‌های ۰۰ و ۰۰ب می‌بندندشان:

# شکاف محل
۱ PATCH /appointment/{uuid} مدت دلخواه می‌پذیرد؛ بافر را نادیده می‌گیرد؛ فقط service_item_uuid تکی را به‌روز می‌کند در حالی که service_items (ManyToMany) دست‌نخورده می‌ماند AppointmentController.php:1077
۲ AppointmentEditPage سه فیلد آزاد date/start/end دارد و هیچ ServiceSlotPicker ای ندارد — منشی نوبت ۴۵ دقیقه‌ای را ۲۰ دقیقه می‌کند و سیستم قبول می‌کند AppointmentEditPage.tsx:74
۳ نوبت رزرو (is_reserve) صریحاً از حالت سرویسی حذف شده (serviceMode = mode === 'service' && !isReserve) و مسیر تبدیل رزرو به نوبت سرویسی وجود ندارد NewAppointmentDrawer.tsx:72
۴ سایت عمومی چهار رنگ hard-code در مرحلهٔ انتخاب سرویس دارد (#5559CE, #3B3B3B, #7A7A7A, bg-white) و در دارک‌مود می‌شکند؛ همچنین مدت را موازی با بک‌اند حساب می‌کند nobat724_front/components/appointment/service/index.js
۵ پنل کاربر سایت نام سرویس و مدت نوبت را نشان نمی‌دهد و مسیر جابه‌جایی سرویس‌آگاه ندارد nobat724_front/.../turns/Card.js · isTurnsDetails/*

نکتهٔ ۴ دو مشکل در یک فایل است: انحراف از دیزاین‌سیستم، و منبع دوم حقیقت برای مدت. دومی مهم‌تر است — وقتی تسک ۰۴ فرمول را به «زمان تنها / زمان اضافه» عوض کند، سایت عدد قدیمی نشان می‌دهد و بیمار مدتی می‌بیند که با مدت واقعی نوبتش نمی‌خواند.

۲-۶ ثبت نوبت و همزمانی

src/Appointment/Entity/Appointment.php:

public const PAYMENT_TTL = 900;                  // رزرو موقت ۱۵ دقیقه‌ای
#[ORM\Column(name:'active_slot_key', unique:true, nullable:true)]
private ?string $activeSlotKey = null;           // "{doctorId}:{slotStart}" یا NULL
#[ORM\Version] private int $version = 1;         // optimistic locking

سه مرحله جستجو → رزرو موقت (pending + expires_at) → ثبت نهایی (confirmed) از قبل هست، و یکتایی در سطح دیتابیس تضمین می‌شود — نه در کد. ولی کلید فقط doctor + slot_start است. با چند منبع، به یک جدول resource_occupancy با محدودیت بازه‌ای نیاز است.

وضعیت‌ها: pending, confirmed, completed, cancelled_by_doctor, cancelled_by_user, expired, no_show, following_up, salon + AppointmentEvent برای تاریخچه. تقریباً کامل؛ rescheduled ندارد.

۲-۷ قیمت

ServiceItem.price_rialsTariff (سالانه) → TenantServiceCoverage/TenantInsurance (بیمه) → DiscountRule + DiscountEngineInvoice/InvoiceItemPayment. بیعانه هم روی نوبت هست (deposit_required, deposit_amount_rials).

مستند وضعیت
لیست قیمت با بازهٔ تاریخ ⚠️ Tariff فقط «سال» دارد، بازهٔ دقیق ندارد
قیمت per شعبه
snapshot فاکتور روی نوبت ⚠️ visit_price_rials تک‌عدد است، تفکیک‌شده نیست
پکیج و دفتر اعتبار جلسات
بیعانه

۲-۸ قوانین

تنها موتور قانونِ موجود DiscountRule است (src/Discount/Entity/DiscountRule.php): type از یک enum بسته، priority، combinable، valid_from/valid_to، tenant-دار.

این دقیقاً الگوی درستی است که مستند می‌خواهد (شرط از فهرست بسته، نه کد دلخواه) — ولی فقط برای دستهٔ «قیمت». پنج دستهٔ دیگر (انتخاب، صلاحیت بیمار، منبع، زمان، فاصله زمانی) و همچنین نسخه‌بندی و محیط آزمایش وجود ندارند.

۲-۹ دوره درمان

کامل غایب. نه course_protocol، نه treatment_course، نه course_session. PatientSession وجود دارد ولی «مراجعهٔ انجام‌شده» است، نه جلسهٔ برنامه‌ریزی‌شدهٔ یک دوره.


۳. جدول شکاف (خلاصه)

بخش مستند دارد ندارد تسک
— نوبت‌دهی سرویسی موجود مسیر رزرو (پنل + سایت) ویرایش، جابه‌جایی، رزرو، پنل بیمار، دیزاین‌سیستم سایت ۰۰، ۰۰ب
۴ کلینیک/شعبه/اتاق tenant دوسطحی Branch، Room، ساعت کاری شعبه ۰۱
۵ تعریف خدمات سرویس، قیمت، مدت، بیمه گروه آیتم، دو نوع زمان، ناسازگاری، override شعبه ۰۴
۶ منابع پرسنل بدون تقویم نوع منبع، ظرفیت، مهارت، استخر، نیازمندی ۰۲، ۰۳
۷ بخش‌های نوبت کل بخش ۰۵
۸ قوانین فقط تخفیف ۵ دستهٔ دیگر، نسخه‌بندی، sandbox ۰۹، ۱۰
۹ تقویم برنامهٔ پزشک، override، تعطیلی تقویم منبع، تعطیلات ملی، سرویس دستگاه ۰۳
۱۰ جستجوی وقت تک‌منبعی و پیوسته چندمنبعی، چندبخشی، کش، استراتژی انتخاب ۰۶
۱۱ ثبت نوبت سه‌مرحله‌ای + یکتایی DB resource_occupancy، قفل چندمنبعی ۰۷
۱۲ قیمت تعرفه، بیمه، تخفیف، بیعانه price_list بازه‌دار، snapshot تفکیک‌شده، پکیج، دفتر اعتبار ۰۸، ۱۱
۱۳ دوره درمان کل بخش ۱۲
۱۶ رویدادها AppointmentEvent bus عمومی دامنه ۱۴

۴. تصمیم معماری پیشنهادی: توسعه، نه بازنویسی

مستند PostgreSQL و یک سیستم نو فرض کرده. پروژه روی MariaDB 11.8 + Doctrine ORM 3.6 است و یک جریان نوبت‌دهی زنده دارد (سایت عمومی nobat724_front و اپ clinic-pro-tauri هر دو مصرف‌کنندهٔ /api/v1/appointment* هستند). پس:

  1. Doctor را به یک Resource تبدیل نمی‌کنیم، بلکه کنارش می‌گذاریم. پزشک منبعی با resource_type = doctor می‌شود که به رکورد Doctor لینک دارد. appointments.doctor_id سر جایش می‌ماند تا API عمومی نشکند.
  2. حالت سوم نوبت‌دهی اضافه می‌شود: WeeklySchedule.meta.booking_mode = resource کنار slot و service موجود. دو حالت قبلی دست‌نخورده کار می‌کنند و مسیر مهاجرت داوطلبانه است، نه اجباری.
  3. resource_occupancy تنها مرجع اشغال می‌شود ولی active_slot_key فعلی هم تا حذف کامل حالت slot می‌ماند (دو تور ایمنی، نه صفر).
  4. قوانین روی الگوی DiscountRule ساخته می‌شوند — enum بسته + priority + بازهٔ اعتبار، نه DSL آزاد. همان‌طور که مستند بند ۸ اصرار دارد.
  5. همهٔ جدول‌های جدید از روز اول TenantOwnedTrait می‌گیرند، وگرنه TenantSchemaCoverageTest قرمز می‌شود.
  6. MariaDB محدودیت بازه‌ای (EXCLUDE) ندارد. جلوگیری از تداخل با کلید یکتای «سطل زمانی» (resource_id + slot_bucket) انجام می‌شود — جزئیات در تسک ۰۷.

۵. ترتیب اجرا

۰۰ تکمیل سرویسی (clinicpro) ── ۰۰ب سازگارسازی سایت      ← فاز ۰، پیش‌نیاز بقیه
  │
  ├─ ۰۱ شعبه/اتاق ─┬─ ۰۲ منابع و مهارت ── ۰۳ تقویم منبع ─┐
  │                └─ ۰۴ کاتالوگ v2 ── ۰۵ بخش‌های نوبت ─┴─ ۰۶ جستجوی وقت ── ۰۷ رزرو و ثبت
  │                                                                              │
  │                                      ۰۸ قیمت‌گذاری و snapshot ───────────────┘
  │                                                    │
  │                        ۰۹ موتور قوانین ── ۱۰ فرم و sandbox قانون
  │                                                    │
  │                  ۱۱ پکیج و دفتر اعتبار ── ۱۲ دوره درمان ── ۱۳ لغو/عدم‌حضور/انتظار
  │                                                    │
  └──────────────────────── ۱۴ رویدادها و گزارش بهره‌وری

فاز ۰ اختیاری نیست. اگر حالت resource روی حالت service نیمه‌کاره ساخته شود، هر باگ موجود سرویسی به موتور جدید ارث می‌رسد و تشخیص منبعش غیرممکن می‌شود.

۶. سه قاعدهٔ حاکم بر همهٔ تسک‌ها

سند چه می‌گوید
_shared/red-lines.md منطق اسلاتی به هیچ عنوان دست‌کاری نمی‌شود · فهرست کامل فایل‌های قفل‌شده · تست --group=slot-mode-frozen
_shared/ui-conventions.md هر صفحه یا بخش جدید عیناً با دیزاین‌سیستم موجود — توکن‌ها، کامپوننت‌های ui/، پنج قاعدهٔ غیرقابل‌مذاکره
_shared/definition-of-done.md هیچ تسکی بدون تکمیل چک‌لیستش تمام نیست — 🔄 ⚠️