Files
clinicpro/docs/new_feture/dental-module/phase-1-preset.md
T
hamed 2f030edef1 Add dental module phase 4 and phase 5 documentation, including treatment estimates, clinical operations, and preset content
- Introduced phase 4 documentation detailing treatment estimates, data models, state machines, API endpoints, and new dashboard metrics.
- Added phase 5 documentation covering consumables, lab operations, periodontal charts, sterilization cycles, clinical images, and a checklist for professional review.
- Created a preset content document outlining default dental service packages and protocols for clinics.
2026-08-20 16:21:08 +03:30

14 KiB
Raw Blame History

فاز ۱ — حوزه در سطح محیط و نصب پیش‌فرض‌های دندانپزشکی

پیش‌نیاز: ندارد. خروجی قابل تست: مطب یا کلینیک، حوزهٔ دندانپزشکی را انتخاب می‌کند و کاتالوگ خدمات دندانپزشکی‌اش ساخته می‌شود.


۱. هدف

سه چیز:

۱. مطب تک‌پزشک هم بتواند حوزهٔ فعالیت انتخاب کند، نه فقط کلینیک. ۲. با انتخاب دندانپزشکی، دسته‌بندی‌ها و خدمات و نوع منبع یونیت و پروتکل‌ها ساخته شوند. ۳. هر خدمت دندانی بداند روی دندان انجام می‌شود یا روی فک یا روی کل دهان.


۲. مدل داده

۲.۱ تغییر روی دامنهٔ موجود

روی Doctor یک رابطهٔ اختیاری به PracticeDomain اضافه می‌شود:

doctors.practice_domain_id  → practice_domains.id   nullable, ON DELETE SET NULL

مقدار NULL یعنی «تنظیم نشده» و همان رفتار امروز است. هرگز خطا نیست.

خواندن حوزهٔ محیط باید از یک نقطه باشد، نه دو if پراکنده:

src/PracticeDomain/Service/PracticeDomainResolver.php

ورودی‌اش EntityContext است و خروجی‌اش کد حوزه یا null. دلیل جداکردنش: هر کسی که حوزه را لازم دارد نباید بداند محیط کلینیک است یا مطب.

۲.۲ موجودیت‌های تازه در دامنهٔ Dental

همه در src/Dental/Entity.

DentalServiceProfile — جدول dental_service_profiles

توسعهٔ یک‌به‌یک روی ServiceItem.

ستون نوع توضیح
id int
uuid string 36
entity_type و entity_id جفت محیط، از TenantOwnedTrait
service_item_id int یکتا، ON DELETE CASCADE
target_scope string 20 دامنهٔ هدف
pricing_basis string 20 مبنای واحد قیمت
tooth_scope string 20 دائمی، شیری، یا هر دو
requires_lab bool فاز ۵ از آن استفاده می‌کند
created_at و updated_at int

مقدار target_scope تعیین می‌کند فرم ثبت خدمت در ویزیت چه چیزی بپرسد:

مقدار معنی ورودی لازم در ویزیت
none خدمت بدون هدف هیچ
tooth یک دندان شمارهٔ دندان
tooth_surface سطوح یک دندان شمارهٔ دندان و حداقل یک سطح
quadrant یک ناحیهٔ فک کد ناحیه از ۱ تا ۴
arch یک فک بالا یا پایین
mouth کل دهان هیچ

مقادیر pricing_basis:

per_tooth | per_surface | per_canal | per_unit | per_arch | per_quadrant | per_session | flat

مقادیر tooth_scope:

any | permanent_only | primary_only

هر سه به‌صورت enum PHP در src/Dental/Enum تعریف می‌شوند و در ستون به‌صورت رشته ذخیره می‌شوند. دلیل رشته‌بودن: افزودن مقدار تازه نباید migration بخواهد.

PresetInstall — جدول dental_preset_installs

ستون نوع توضیح
id int
uuid string 36
entity_type و entity_id جفت محیط
preset_code string 40 مثلاً dental
preset_version int نسخهٔ قالبی که نصب شد
template_key string 80 کلید ثابت ردیف در قالب
target_type string 30 catalog_category, service_item, resource_type, treatment_protocol
target_id int شناسهٔ ردیف واقعی ساخته‌شده
installed_at int

یکتایی: (entity_type, entity_id, preset_code, template_key).

این جدول تنها مبنای idempotency است. اجرای دوم seed، هر کلید قالبی را که اینجا ثبت شده دوباره نمی‌سازد.

چرا این جدول و نه یک ستون origin روی کاتالوگ: جدول‌های کاتالوگ مشترک همهٔ حوزه‌هایند. یک ستون منشأ روی آن‌ها یعنی هر حوزهٔ بعدی هم چیزی به جدول مشترک اضافه می‌کند. جدول نگاشت با حذف ماژول تمیز برداشته می‌شود.


۳. قالب پیش‌فرض

مسیر: src/Dental/Preset/DentalPreset.php

یک کلاس final با آرایه‌های ثابت و یک const VERSION. محتوایش در preset-content.md.

ساختار هر بخش:

GROUPS     : [ template_key, name, sort_order, parent_key|null ]
SERVICES   : [ template_key, name, group_key, duration_minutes, session_count,
               target_scope, pricing_basis, tooth_scope, requires_lab ]
RESOURCE_TYPES : [ template_key, code, name, field_schema|null ]
PROTOCOLS  : [ template_key, service_key, session_count, interval_days ]

قیمت همهٔ خدمات صفر است. دلیل: قیمت جعلی که کسی اصلاحش نکند، به بیمار نشان داده می‌شود. صفر بودن در پنل قابل دیدن و قابل فیلتر کردن است.

VERSION عدد صحیح است و با هر تغییر محتوا یکی زیاد می‌شود. نسخه در dental_preset_installs ثبت می‌شود تا بعداً بشود گفت کدام محیط با کدام نسخه نصب شده.


۴. سرویس نصب

مسیر: src/Dental/Service/DentalPresetInstaller.php

install(EntityContext $context, bool $force = false): PresetInstallReport
preview(EntityContext $context): PresetInstallReport

قواعد:

  • همه‌چیز در یک تراکنش. نصب نیمه‌کاره نداریم.
  • هر ردیف قبل از ساخت، در dental_preset_installs جستجو می‌شود.
  • ردیفی که کلیدش قبلاً نصب شده، دست نمی‌خورد. حتی اگر مدیر نامش را عوض کرده باشد.
  • preview هیچ چیزی نمی‌نویسد و همان گزارش را بدون ساخت برمی‌گرداند.
  • بخش سازمانی: اگر محیط هیچ ServiceSection نداشت، یکی با نام «دندانپزشکی» ساخته می‌شود، وگرنه اولین بخش فعال استفاده می‌شود. دلیل: ServiceItem بدون بخش NOT NULL نمی‌شود.

PresetInstallReport یک شیء ساده است با تعداد ساخته‌شده و تعداد ردشده به تفکیک نوع.

تصمیم لحظهٔ اجرا

هنگام PATCH روی کلینیک یا پزشک، اگر حوزه به دندانپزشکی تغییر کرد:

کاتالوگ محیط خالی است  → نصب خودکار، بدون پرسش
کاتالوگ محیط داده دارد → هیچ چیز ساخته نمی‌شود، پرچم «پیش‌فرض نصب‌نشده» برای پنل برمی‌گردد

تعریف «خالی»: هیچ ServiceItem و هیچ CatalogCategory فعالی در آن محیط نباشد.


۵. API

مستندات در docs/api/dental.md نوشته می‌شود. فایل تازه است.

GET  /api/v1/dental/preset/status
     → { installed: bool, installed_version: int|null,
         current_version: int, catalog_empty: bool }

GET  /api/v1/dental/preset/preview
     → { groups: n, services: n, resource_types: n, protocols: n, skipped: n }

POST /api/v1/dental/preset/install
     → همان گزارش، بعد از نصب واقعی

دسترسی: ROLE_CLINIC یا ROLE_DOCTOR. محیط از EntityContextResolver می‌آید، نه از بدنهٔ درخواست.

خطاها:

کد HTTP حالت
ERR_VALIDATION_002 422 حوزهٔ فعالیت محیط دندانپزشکی نیست
ERR_FORBIDDEN_001 403 نقش مجاز نیست

اندپوینت خدمت هم گسترش پیدا می‌کند:

POST  /api/v1/service-item        بدنه کلید اختیاری dental می‌گیرد
PATCH /api/v1/service-item/{uuid} همان
GET   /api/v1/service-item/{uuid} پاسخ کلید dental دارد اگر پروفایل داشته باشد

docs/api/clinic-services.md همان جلسه به‌روز می‌شود.


۶. پنل ادمین

صفحهٔ حوزهٔ فعالیت

فایل: assets/admin/pages/PracticeDomainSettingsPage.tsx

  • برای کاربر پزشک هم کار کند، نه فقط کلینیک.
  • بعد از انتخاب دندانپزشکی، وضعیت نصب پیش‌فرض نشان داده شود.
  • اگر نصب نشده، کارت با پیش‌نمایش تعداد و دکمهٔ نصب.
  • بعد از نصب موفق، پیام با تعداد ردیف ساخته‌شده و لینک به صفحهٔ خدمات.

فرم خدمت

فایل: assets/admin/pages/ClinicServicesPage.tsx

  • اگر حوزهٔ محیط دندانپزشکی است، بخش «ویژگی‌های دندانی» در فرم خدمت اضافه شود.
  • سه انتخاب: دامنهٔ هدف، مبنای قیمت، نوع دندان. هر سه با SearchableSelect.
  • سوییچ «نیاز به لابراتوار» با Switch.
  • برای حوزه‌های دیگر این بخش اصلاً رندر نشود.

۷. تسک‌ها

کد تسک فایل‌های اصلی معیار پذیرش
DM1-01 افزودن practice_domain_id به Doctor با migration src/Doctor/Entity/Doctor.php, migrations/ migration روی دیتابیس تست اجرا و برگشت می‌خورد
DM1-02 PracticeDomainResolver بر اساس EntityContext src/PracticeDomain/Service/ تست: محیط کلینیک، محیط پزشک، محیط بدون حوزه
DM1-03 اندپوینت تنظیم حوزه برای پزشک src/Doctor/Controller/, docs/api/practice-domain.md پزشک حوزه را ست می‌کند و در GET پروفایل می‌بیند
DM1-04 enumهای دندانی src/Dental/Enum/ مقدار نامعتبر AppException می‌دهد
DM1-05 موجودیت و مخزن DentalServiceProfile src/Dental/Entity/, src/Dental/Repository/ TenantSchemaCoverageTest سبز
DM1-06 موجودیت و مخزن PresetInstall همان یکتایی کلید قالب در محیط تست می‌شود
DM1-07 فایل قالب DentalPreset با محتوای پیش‌نویس src/Dental/Preset/ تست ساختاری: هر خدمت به گروه موجود اشاره می‌کند
DM1-08 DentalPresetInstaller با install و preview src/Dental/Service/ اجرای دوم هیچ ردیف تازه‌ای نمی‌سازد
DM1-09 اتصال نصب خودکار به تغییر حوزه src/Clinic/Controller/ClinicController.php و معادل پزشک کاتالوگ خالی نصب می‌شود، کاتالوگ پرداده نمی‌شود
DM1-10 سه اندپوینت preset src/Dental/Controller/DentalPresetController.php تست موفق، بدون دسترسی، حوزهٔ اشتباه
DM1-11 گسترش service-item برای کلید dental src/ClinicService/Controller/ClinicServiceController.php ساخت خدمت با پروفایل و بدون آن، هر دو کار می‌کند
DM1-12 docs/api/dental.md و به‌روزرسانی دو سند موجود docs/api/ مسیرها و خطاها با کد یکی است
DM1-13 کارت نصب پیش‌فرض در صفحهٔ حوزهٔ فعالیت assets/admin/pages/PracticeDomainSettingsPage.tsx تست: نصب‌نشده، نصب‌شده، حوزهٔ غیر دندانی
DM1-14 بخش ویژگی دندانی در فرم خدمت assets/admin/pages/ClinicServicesPage.tsx برای حوزهٔ زیبایی رندر نمی‌شود
DM1-15 بازبینی تخصصی فهرست خدمات توسط دندانپزشک preset-content.md بدون این، فاز ۱ بسته نمی‌شود

۸. تست‌ها

  • نصب روی محیط خالی: همهٔ ردیف‌ها ساخته می‌شوند.
  • نصب دوم: صفر ردیف تازه، گزارش می‌گوید چند تا رد شد.
  • نصب روی محیطی که حوزه‌اش دندانپزشکی نیست: خطای ۴۲۲.
  • نصب توسط نقش بدون دسترسی: خطای ۴۰۳.
  • شکست وسط نصب: هیچ ردیفی باقی نمی‌ماند، تراکنش برگشت می‌خورد.
  • محیط پزشک و محیط کلینیک، هر دو مسیر.
  • خدمتی که پروفایل دندانی ندارد، در حوزهٔ دندانپزشکی هم بدون خطا کار می‌کند.

۹. ریسک‌ها

فهرست خدمات غلط. اثرش در همهٔ کلینیک‌های نصب‌کننده پخش می‌شود و جمع کردنش ممکن نیست. مهار: تسک DM1-15 مسدودکننده است.

نصب خودکار روی محیطی که کاربر نمی‌خواست. مهار: فقط وقتی کاتالوگ خالی است، و ردیف‌ها با قیمت صفر و قابل غیرفعال کردن.

تعریف «کاتالوگ خالی» ناپایدار. اگر کلینیکی یک دستهٔ آزمایشی ساخته باشد، نصب خودکار انجام نمی‌شود و کاربر گیج می‌شود. مهار: پنل همیشه وضعیت نصب و دکمه را نشان می‌دهد، پس مسیر دوم همیشه در دسترس است.