diff --git a/CONTEXT.md b/CONTEXT.md index ea18150c..9d114030 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -8,9 +8,9 @@ ClinicPro product. This glossary records the language the backend uses for its o ### Clinic configuration **Practice Domain**: -The single field of practice a clinic declares it operates in — beauty, dental, orthopaedics. It is a -configuration key: it selects which dashboard, forms and treatment workflows the clinic gets. A -clinic has exactly one. +The single field of practice a tenant declares it operates in — beauty, dental, orthopaedics. It is a +configuration key: it selects which dashboard, forms and treatment workflows the tenant gets. A clinic +or a solo doctor's practice has exactly one. _Avoid_: Specialty, clinic type, field, discipline **Specialty**: @@ -67,3 +67,32 @@ _Avoid_: Zone, body part, region What the operator actually did to one Treatment Area in one Treatment Session — the device used, the parameters it was set to, how long it took, and any note. _Avoid_: Treatment log, area result, shot record + +### Dental + +**Dental Preset**: +The package of default data a tenant receives when it declares the dental practice domain — service +groups, services, resource types and protocols. Defined in code and versioned; it is product content, +not tenant data. +_Avoid_: Seed, fixture, template, sample data + +**Preset Install**: +The record that one preset template key became one real row in one tenant. It is what makes installing +a preset twice a no-op. +_Avoid_: Migration, sync record, import log + +**Tooth Chart**: +The current condition of every tooth of one patient in one tenant. It is a snapshot, never a history, +and it holds conditions that predate the tenant — a tooth lost years before the first visit. +_Avoid_: Odontogram record, dental record, chart entry + +**Tooth Site**: +One tooth, identified by its two-digit FDI number, together with the surfaces of it a service targets. +It is what a dental service is performed on, and it is not a Treatment Area. +_Avoid_: Treatment Area, tooth record, position, location + +**Treatment Estimate**: +The priced list of services proposed to a patient across their teeth, which the patient accepts in whole +or in part. In Persian it is «طرح درمان»; the English name stays distinct because Treatment Plan is +ambiguous between a Treatment Protocol and a Treatment Case. +_Avoid_: Treatment plan, quote, proposal, estimate sheet diff --git a/docs/adr/0007-practice-domain-is-tenant-level.md b/docs/adr/0007-practice-domain-is-tenant-level.md new file mode 100644 index 00000000..a3ff7c36 --- /dev/null +++ b/docs/adr/0007-practice-domain-is-tenant-level.md @@ -0,0 +1,13 @@ +# Practice domain belongs to the tenant, not only to the clinic + +`practice_domain_id` originally lived on `clinics`, but every piece of operational data in this +codebase is owned by an `(entity_type, entity_id)` pair where `entity_type` is `doctor` or `clinic`. +A solo practice is a `doctor` tenant, so it could never declare a practice domain at all — the beauty +domain has the same hole, it simply had not been noticed. The column is therefore added to `doctors` +as well and read through a single `PracticeDomainResolver` that takes an `EntityContext`, so no +caller has to know which kind of tenant it is looking at. + +## Considered Options + +Letting a doctor inherit the domain of a clinic they work at was rejected: a doctor with no clinic +would stay domainless, and a doctor working at two clinics with different domains would be ambiguous. diff --git a/docs/adr/0008-teeth-are-not-treatment-areas.md b/docs/adr/0008-teeth-are-not-treatment-areas.md new file mode 100644 index 00000000..b540a7e2 --- /dev/null +++ b/docs/adr/0008-teeth-are-not-treatment-areas.md @@ -0,0 +1,15 @@ +# Teeth are not Treatment Areas + +A Treatment Area is a `CatalogCategory` snapshotted onto a case, which made "one category per tooth" +look like a free way to get dental charting. It was rejected: FDI tooth numbering is a universal fact, +not a per-clinic taxonomy, so it would duplicate 32 to 52 identical rows into every clinic's service +tree, surfaces would need a further level below that, and the persistent condition of a tooth — missing, +crowned, implanted years before the patient ever arrived — has nowhere to live on a settings row. +A tooth is instead an FDI `smallint` on the record that targets it, and tooth condition is its own +snapshot table in the Dental context. + +## Consequences + +Tooth condition must be updated after each visit, and that projection lives in exactly one class rather +than being spread across controllers. In exchange, rendering a chart is one query and never a replay of +history. diff --git a/docs/adr/0009-dental-service-attributes-live-in-an-extension-table.md b/docs/adr/0009-dental-service-attributes-live-in-an-extension-table.md new file mode 100644 index 00000000..fb37220f --- /dev/null +++ b/docs/adr/0009-dental-service-attributes-live-in-an-extension-table.md @@ -0,0 +1,15 @@ +# Dental attributes of a service live in an extension table, not on ServiceItem + +Whether a service is priced per tooth, per surface or per canal — and whether booking it must ask for a +tooth at all — is dental-only knowledge, but `ServiceItem` is shared by every practice domain. Those +attributes therefore sit in a one-to-one `dental_service_profiles` row in the Dental context, keyed by +`service_item_id`, so a beauty clinic carries no dental columns and the next domain is not invited to add +its own set to the shared table. The cost is a join whenever the dental profile is needed, which is the +same pattern the codebase already uses elsewhere. + +## Consequences + +The reverse choice was made deliberately one level down: the tooth and surfaces a visit line was actually +billed for are columns on `SessionService` itself, because that row is the clinical and financial record +of the visit rather than shared configuration, and splitting it would allow a billed line to lose its +target unnoticed. diff --git a/docs/adr/0010-domain-metrics-come-from-tagged-providers.md b/docs/adr/0010-domain-metrics-come-from-tagged-providers.md new file mode 100644 index 00000000..d1cc7184 --- /dev/null +++ b/docs/adr/0010-domain-metrics-come-from-tagged-providers.md @@ -0,0 +1,13 @@ +# Domain-specific dashboard metrics come from tagged providers + +`DashboardController` already serves four role dashboards from 803 lines and fifteen dependencies, so +branching each of them on practice domain would double four code paths and make every clinic pay for +queries only dentists need. Domain metrics instead come from a `DomainMetricProvider` interface resolved +by a tagged-service registry keyed on the practice domain code — the same shape as `TreatmentWorkflow` +in ADR 0005 — and the role endpoints simply attach whatever the provider returns. + +## Consequences + +Unlike the workflow registry there is no default implementation: a tenant with no domain, or a domain with +no provider, gets `null` and the panel renders no extra section. An empty metrics block is worse than an +absent one. diff --git a/docs/new_feture/dental-module/README.md b/docs/new_feture/dental-module/README.md new file mode 100644 index 00000000..2978a44c --- /dev/null +++ b/docs/new_feture/dental-module/README.md @@ -0,0 +1,239 @@ +# ماژول دندانپزشکی ClinicPro + +> **دامنه:** `clinicpro` — بک‌اند Symfony و پنل ادمین React +> **وضعیت:** سند طراحی. کد نوشته نشده. +> **تاریخ:** 2026-08 +> **خارج از دامنه:** بیمه. فقط نقطهٔ اتصال رزرو می‌شود. + +این سند حاصل یک جلسهٔ تصمیم‌گیری روی کد واقعی ریپو است. +هر بند «چه» را می‌گوید و «چرا» را کنارش. +تصمیم‌ها در بخش ۳ فهرست‌اند و بقیهٔ سند نتیجهٔ آن‌هاست. + +منبع اولیه‌اش سند `clinicpro-dental-module-technical-spec.md` در ریشهٔ workspace بود. +آن سند بدون دسترسی به کد نوشته شده بود و بخش بزرگی از چیزی که پیشنهاد داده، از قبل ساخته شده است. +بخش ۲ تفاوت‌ها را نشان می‌دهد. + +--- + +## ۱. مسئله + +خواسته یک جمله است. +وقتی یک کلینیک حوزهٔ فعالیت «دندانپزشکی» را انتخاب می‌کند، باید دسته‌بندی‌ها و تنظیمات دندانپزشکی برایش ساخته شود و داشبورد مخصوص خودش را ببیند. + +امروز انتخاب حوزهٔ فعالیت فقط یک کلید خارجی روی جدول کلینیک می‌نویسد. +هیچ دسته‌ای، هیچ خدمتی، هیچ تنظیمی ساخته نمی‌شود. +یعنی کلینیک بعد از انتخاب حوزه، دقیقاً همان‌قدر خالی است که قبلش بود. + +سه تکهٔ خواسته: + +۱. با انتخاب دندانپزشکی، کاتالوگ خدمات دندانپزشکی ساخته شود. +۲. دندانپزشک بتواند وضعیت دندان‌های بیمار را ثبت و ببیند. +۳. مدیر و پزشک و پذیرش، شاخص‌های دندانپزشکی را در داشبورد ببینند. + +--- + +## ۲. آنچه امروز واقعاً هست + +این بخش از روی کد نوشته شده، نه از روی مستندات. + +### ۲.۱ حوزهٔ فعالیت — هست، ولی فقط برچسب است + +| چیز | مسیر | +|---|---| +| موجودیت | `src/PracticeDomain/Entity/PracticeDomain.php` | +| کنترلر | `src/PracticeDomain/Controller/PracticeDomainController.php` | +| مستندات | `docs/api/practice-domain.md` | +| اتصال به کلینیک | `src/Clinic/Entity/Clinic.php` ستون `practice_domain_id` | +| صفحهٔ پنل | `assets/admin/pages/PracticeDomainSettingsPage.tsx` | + +جدول سراسری است و در `GlobalTables::ENTITIES` ثبت شده. +انتخاب حوزه از `PATCH` روی کلینیک با کلید `practice_domain_uuid` انجام می‌شود. + +**نکتهٔ مهم:** `Doctor` این فیلد را ندارد. +ولی مالکیت داده در کل پروژه دو حالته است، `entity_type` برابر `doctor` یا `clinic`. +یعنی مطب تک‌پزشک امروز اصلاً نمی‌تواند حوزهٔ فعالیت انتخاب کند. +این یک نقص عمومی است و فقط به دندانپزشکی مربوط نیست؛ کلینیک زیبایی تک‌پزشکه هم همین مشکل را دارد. + +### ۲.۲ رفتار مخصوص هر حوزه — الگویش ساخته شده + +| چیز | مسیر | +|---|---| +| اینترفیس | `src/Treatment/Workflow/TreatmentWorkflow.php` | +| رجیستری | `src/Treatment/Workflow/TreatmentWorkflowRegistry.php` | +| پیش‌فرض | `src/Treatment/Workflow/DefaultTreatmentWorkflow.php` | +| نمونهٔ حوزه‌ای | `src/Treatment/Workflow/LaserTreatmentWorkflow.php` | +| تصمیم ثبت‌شده | `docs/adr/0005-treatment-workflows-are-tagged-services.md` | + +سرویس‌ها با تگ `app.treatment_workflow` ثبت می‌شوند و رجیستری بر اساس کد حوزه انتخاب می‌کند. +افزودن دندانپزشکی یعنی یک کلاس تازه، نه دست‌بردن در مسیر رزرو. + +### ۲.۳ کاتالوگ خدمات — کامل است + +| موجودیت | نقش | +|---|---| +| `ServiceSection` | بخش سازمانی کلینیک | +| `CatalogCategory` | تاکسونومی درختی خدمات تا عمق ۴ | +| `ServiceItem` | خدمت با قیمت، مدت، تعداد جلسه، دستهٔ کاتالوگ | +| `ItemGroup` و `ItemGroupMember` | گروه‌بندی آیتم | +| `ServiceItemRelation` | رابطهٔ بین خدمات | +| `ServiceBranchOverride` | بازنویسی قیمت در شعبه | + +مسیرها در `src/ClinicService/`. +اندپوینت‌های موجود در `docs/api/clinic-services.md`. +صفحات پنل: `ClinicServicesPage.tsx` و `CatalogCategoriesPage.tsx`. + +### ۲.۴ یونیت و صندلی — لازم نیست ساخته شود + +سند اولیه پیشنهاد جدول `chairs` و ستون `chairId` روی نوبت داده بود. +هر دو از قبل هستند و بهترند: + +| چیز | مسیر | +|---|---| +| نوع منبع با `CODE_ROOM` | `src/Resource/Entity/ResourceType.php` | +| خود منبع | `src/Resource/Entity/ClinicResource.php` | +| تقویم و استثنا | `src/Resource/Entity/ResourceCalendar.php` و `ResourceException.php` | +| اتصال به نوبت | `src/Appointment/Entity/Appointment.php` ستون `resource_id` | +| اشغال واقعی | `resource_occupancy` | +| تصمیم ثبت‌شده | `docs/adr/0003-resource-backed-appointments-drop-the-doctor-slot-key.md` | + +منبع، ظرفیت و زمان آماده‌سازی و زمان تمیزکاری هم دارد. +یعنی «۱۵ دقیقه بین دو بیمار برای ضدعفونی یونیت» بدون کد جدید قابل تنظیم است. + +### ۲.۵ دوره درمان و جلسه — هست، ولی معنایش با دندانپزشکی یکی نیست + +| موجودیت | معنی امروز | +|---|---| +| `TreatmentProtocol` | قالب دوره‌ای یک خدمت، تعریف‌شده توسط مدیر | +| `TreatmentCase` | یک بیمار، **یک خدمت**، از اولین رزرو تا پایان دوره | +| `TreatmentSession` | جلسهٔ شماره‌دار همان دوره، بدون هیچ ستون پولی | +| `TreatmentCaseArea` | ناحیهٔ بدن، اسنپ‌شات از `CatalogCategory` | +| `SessionAreaRecord` | آنچه اپراتور روی هر ناحیه انجام داد | +| `PatientSession` | سابقهٔ مالی یک ویزیت انجام‌شده | +| `SessionService` | ردیف خدمت همان ویزیت با قیمت | + +واژگان در `CONTEXT.md` تعریف شده و صریحاً «Treatment Plan» را ممنوع کرده، چون بین قالب و دوره ابهام می‌ساخت. + +فرق بنیادی: `TreatmentCase` یک خدمت دارد. +طرح درمان دندانپزشکی ده‌ها خدمت روی دندان‌های مختلف دارد که بیمار بخشی را می‌پذیرد. +این دو یکی نیستند و نباید یکی شوند. + +### ۲.۶ داشبورد — چهار اندپوینت نقشی + +`src/Dashboard/Controller/DashboardController.php` با ۸۰۳ خط و ۱۵ وابستگی، چهار مسیر دارد: + +``` +GET /api/v1/dashboard/clinic +GET /api/v1/dashboard/doctor +GET /api/v1/dashboard/secretary +GET /api/v1/dashboard/staff +``` + +هیچ‌کدام مفهوم حوزهٔ فعالیت را نمی‌شناسند. + +### ۲.۷ آنچه واقعاً غایب است + +فقط چهار چیز: + +۱. حوزهٔ فعالیت روی مطب پزشک. +۲. ساخته‌شدن خودکار دادهٔ پیش‌فرض هنگام انتخاب حوزه. +۳. دندان به‌عنوان یک مفهوم — چارت، سطح، هدف‌گیری خدمت روی دندان. +۴. برآورد چندخدمتی و نرخ پذیرش آن. + +هر چیز دیگری که سند اولیه پیشنهاد داده بود، از قبل هست. + +--- + +## ۳. تصمیم‌ها + +| # | تصمیم | چرا | +|---|---|---| +| ۱ | ماژول داده‌محور است، نه یک دامنهٔ موازی | موتور درمان و منبع و کاتالوگ از قبل هست. دامنهٔ موازی یعنی دو منبع حقیقت برای جلسه و اتاق. | +| ۲ | seed خودکار وقتی کاتالوگ خالی است، وگرنه دکمهٔ صریح با پیش‌نمایش | کلینیک خالی حالت اصلی است. کلینیک پرداده نباید بی‌اجازه ادغام شود، چون بعداً تشخیص «این ردیف را من ساختم یا سیستم» ممکن نیست. | +| ۳ | قالب پیش‌فرض‌ها در کد است، در فایل نسخه‌دار | محتوای محصول است نه دادهٔ کلینیک. باید در git تاریخچه و review و تست داشته باشد. تغییرش نادر است. | +| ۴ | دندان مفهوم مستقل است با شمارهٔ FDI از نوع `smallint` | FDI واقعیت جهانی است نه تاکسونومی هر کلینیک. دندان به‌عنوان دستهٔ کاتالوگ یعنی ۳۲ ردیف تکراری در هر کلینیک، و «سطح دندان» و «وضعیت ماندگار» جایی برای نشستن ندارند. | +| ۵ | فاز ۱ بدون برآورد چندخدمتی جلو می‌رود | زودتر به خروجی قابل استفاده می‌رسیم. هزینه‌اش این است که نرخ پذیرش تا فاز ۴ وجود ندارد و این باید در سند صریح بماند. | +| ۶ | داشبورد از یک registry متریک بر اساس حوزه سرو می‌شود | همان الگوی `TreatmentWorkflow` که پروژه از قبل دارد. شرط‌گذاری داخل کنترلر ۸۰۳ خطی، چهار مسیر کد را دوبرابر می‌کند. | +| ۷ | حوزهٔ فعالیت به سطح محیط منتقل می‌شود، هم کلینیک هم مطب پزشک | مطب تک‌پزشک بخش بزرگ بازار است. این نقص امروز حوزهٔ زیبایی را هم خراب می‌کند، پس اصلاحش عمومی است نه دندانی. | +| ۸ | شاخص‌ها در فاز ۱ زنده محاسبه می‌شوند | هیچ اندازه‌گیری‌ای نشان نداده کند است. جدول تجمیع یعنی job شبانه، مسیر backfill، و منبع حقیقت دومی که می‌تواند واگرا شود. | +| ۹ | چارت هم از خدمت ویزیت پر می‌شود هم دستی ویرایش می‌شود | چارت باید وضعیت‌هایی را نشان دهد که هرگز در این کلینیک فاکتور نشده‌اند، مثل دندان کشیده‌شدهٔ سال‌ها قبل. پس مشتق کامل ممکن نیست. ثبت فقط دستی هم یعنی ثبت دوباره و واگرایی از صورتحساب. | +| ۱۰ | ویژگی‌های دندانی خدمت در جدول توسعهٔ یک‌به‌یک می‌نشیند | `ServiceItem` مشترک همهٔ حوزه‌هاست. ستون دندانی روی آن یعنی هر حوزهٔ بعدی هم ستون خودش را اضافه می‌کند. | +| ۱۱ | بستهٔ seed شامل دسته‌ها، خدمات با قیمت صفر، نوع منبع یونیت و پروتکل‌هاست | دسته بدون خدمت کلینیک را دست‌خالی می‌گذارد. نمونهٔ یونیت ساختن، دادهٔ ساختگی در محیط واقعی جا می‌گذارد. تعداد یونیت را فقط خود مدیر می‌داند. | +| ۱۲ | نصب seed در یک جدول نگاشت در دامنهٔ Dental ثبت می‌شود؛ تغییر حوزه چیزی را حذف نمی‌کند | با تصمیم ۱۰ قرار شد جدول‌های مشترک آلوده نشوند. خدمتی که یک بار در ویزیت استفاده شده اصلاً حذف‌شدنی نیست، کلید خارجی `RESTRICT` است. | +| ۱۳ | چارت یک تب تازه در پروندهٔ بیمار است؛ شاخص‌های دندانی بخشی از همان داشبورد فعلی | دندانپزشک همیشه داخل پروندهٔ بیمار کار می‌کند. منوی جدا یعنی خروج مکرر از کانتکست بیمار. | +| ۱۴ | فهرست خدمات پیش‌نویس است و قبل از merge باید دندانپزشک تأییدش کند | فهرست غلط بدتر از نبودن فهرست است، چون پاک‌کردنش از ده‌ها کلینیک دیگر ممکن نیست. | + +--- + +## ۴. واژگان تازه + +این‌ها به `CONTEXT.md` اضافه شده‌اند. اینجا فقط خلاصه است. + +**Tooth Chart** — نمای وضعیت جاری همهٔ دندان‌های یک بیمار در یک محیط. اسنپ‌شات است نه تاریخچه. +پرهیز از: dental chart record, odontogram record. + +**Tooth Site** — یک دندان مشخص با شمارهٔ FDI، به‌همراه سطوح درگیر. هدف یک خدمت دندانی. +پرهیز از: Treatment Area, tooth record. + +**Dental Preset** — بستهٔ دادهٔ پیش‌فرض حوزهٔ دندانپزشکی که با انتخاب حوزه در محیط نصب می‌شود. +پرهیز از: seed, fixture, template. + +**Preset Install** — رکورد نصب یک کلید قالب روی یک ردیف واقعی در یک محیط. مبنای idempotency. +پرهیز از: migration, sync record. + +--- + +## ۵. تصمیم‌های ثبت‌شده + +| ADR | موضوع | +|---|---| +| `docs/adr/0007-practice-domain-is-tenant-level.md` | حوزهٔ فعالیت مال محیط است نه فقط کلینیک | +| `docs/adr/0008-teeth-are-not-treatment-areas.md` | دندان دستهٔ کاتالوگ نیست | +| `docs/adr/0009-dental-service-attributes-live-in-an-extension-table.md` | ویژگی دندانی خدمت در جدول جدا | +| `docs/adr/0010-domain-metrics-come-from-tagged-providers.md` | شاخص حوزه‌ای از provider تگ‌خورده | + +--- + +## ۶. فازها + +| فاز | محتوا | سند | +|---|---|---| +| ۱ | حوزه در سطح محیط، نصب پیش‌فرض‌ها، پروفایل دندانی خدمت | [phase-1-preset.md](phase-1-preset.md) | +| ۲ | چارت دندان، هدف‌گیری دندان روی خدمت ویزیت، پروجکتور | [phase-2-tooth-chart.md](phase-2-tooth-chart.md) | +| ۳ | registry متریک و شاخص‌های دندانپزشکی روی داشبورد | [phase-3-dashboard.md](phase-3-dashboard.md) | +| ۴ | برآورد درمان و نرخ پذیرش | [phase-4-treatment-estimate.md](phase-4-treatment-estimate.md) | +| ۵ | پریو، لابراتوار، استریلیزاسیون، مواد مصرفی | [phase-5-clinical-ops.md](phase-5-clinical-ops.md) | + +محتوای پیشنهادی بستهٔ پیش‌فرض در [preset-content.md](preset-content.md). + +هر فاز بدون تست موفق و خطا و مرزی، و بدون به‌روزرسانی `docs/api/`، تمام‌شده نیست. + +--- + +## ۷. قواعد مشترک همهٔ فازها + +از `CLAUDE.md` پروژه، اینجا فقط یادآوری: + +- شناسه عددی به‌همراه `uuid` نسخهٔ ۴. `uuid` در پاسخ API، `id` هرگز. +- تایم‌استمپ از نوع `integer` یونیکس. +- نام جدول جمع و snake_case. +- پاسخ از `BaseController`، خطا با `AppException` و `ErrorCodes`. +- هر entity تازه باید جفت `entity_type` و `entity_id` داشته باشد، وگرنه `TenantSchemaCoverageTest` قرمز می‌شود. +- کوئری‌های لیست با `getArrayResult()`. +- در پنل: `SearchableSelect` به‌جای `select` بومی، `Switch` به‌جای checkbox، رنگ فقط از `var(--...)`. +- تاریخ در دیتابیس میلادی ذخیره، در نمایش جلالی. + +--- + +## ۸. مرزهای رزروشده + +**بیمه:** دامنهٔ `Insurance` از قبل کامل است و شامل `TenantServiceCoverage` و `TenantInsuranceCategoryCoverage` و `EntityInsurancePricing` می‌شود. +هیچ منطق بیمه‌ای در این ماژول نوشته نمی‌شود. +تنها اثرش این است که ردیف برآورد در فاز ۴ باید ستون `payer_type` داشته باشد تا بعداً سند بیمه رویش بنشیند. + +**اپ Tauri:** در فاز ۱ تا ۳ هیچ تغییری نمی‌گیرد. +اگر بعداً چارت آفلاین لازم شد، `tooth_chart` و وضعیت دندان کاندیدهای خوبی هستند چون تک‌نویسنده و کم‌تعارض‌اند. +برآورد درمان چون مالی است نباید `last-write-wins` بگیرد. +این موضوع به سند sync ارجاع می‌شود، نه اینجا. + +**سایت عمومی nobat724:** خدمات دندانپزشکی مثل هر خدمت دیگری در سایت دیده می‌شوند. +هیچ تغییر اختصاصی لازم نیست مگر اینکه بخواهیم انتخاب دندان در رزرو آنلاین باشد، که خارج از این سند است. diff --git a/docs/new_feture/dental-module/phase-1-preset.md b/docs/new_feture/dental-module/phase-1-preset.md new file mode 100644 index 00000000..a66e3283 --- /dev/null +++ b/docs/new_feture/dental-module/phase-1-preset.md @@ -0,0 +1,273 @@ +# فاز ۱ — حوزه در سطح محیط و نصب پیش‌فرض‌های دندانپزشکی + +> پیش‌نیاز: ندارد. +> خروجی قابل تست: مطب یا کلینیک، حوزهٔ دندانپزشکی را انتخاب می‌کند و کاتالوگ خدمات دندانپزشکی‌اش ساخته می‌شود. + +--- + +## ۱. هدف + +سه چیز: + +۱. مطب تک‌پزشک هم بتواند حوزهٔ فعالیت انتخاب کند، نه فقط کلینیک. +۲. با انتخاب دندانپزشکی، دسته‌بندی‌ها و خدمات و نوع منبع یونیت و پروتکل‌ها ساخته شوند. +۳. هر خدمت دندانی بداند روی دندان انجام می‌شود یا روی فک یا روی کل دهان. + +--- + +## ۲. مدل داده + +### ۲.۱ تغییر روی دامنهٔ موجود + +روی `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](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` مسدودکننده است. + +**نصب خودکار روی محیطی که کاربر نمی‌خواست.** +مهار: فقط وقتی کاتالوگ خالی است، و ردیف‌ها با قیمت صفر و قابل غیرفعال کردن. + +**تعریف «کاتالوگ خالی» ناپایدار.** +اگر کلینیکی یک دستهٔ آزمایشی ساخته باشد، نصب خودکار انجام نمی‌شود و کاربر گیج می‌شود. +مهار: پنل همیشه وضعیت نصب و دکمه را نشان می‌دهد، پس مسیر دوم همیشه در دسترس است. diff --git a/docs/new_feture/dental-module/phase-2-tooth-chart.md b/docs/new_feture/dental-module/phase-2-tooth-chart.md new file mode 100644 index 00000000..de1eb07e --- /dev/null +++ b/docs/new_feture/dental-module/phase-2-tooth-chart.md @@ -0,0 +1,299 @@ +# فاز ۲ — چارت دندان و هدف‌گیری دندان روی خدمت ویزیت + +> پیش‌نیاز: فاز ۱. +> خروجی قابل تست: دندانپزشک وضعیت دندان‌های بیمار را می‌بیند، ثبت خدمت روی دندان انجام می‌دهد و چارت خودکار به‌روز می‌شود. + +--- + +## ۱. هدف + +سه چیز: + +۱. هر بیمار در هر محیط یک چارت دندان داشته باشد. +۲. ثبت خدمت در ویزیت بتواند دندان و سطح را هدف بگیرد. +۳. چارت بعد از ثبت خدمت خودکار به‌روز شود، و وضعیت‌های قدیمی هم دستی قابل ثبت باشند. + +--- + +## ۲. شماره‌گذاری دندان + +استاندارد `FDI` دو رقمی، همان `ISO 3950`. + +``` +دائمی : 11–18, 21–28, 31–38, 41–48 +شیری : 51–55, 61–65, 71–75, 81–85 +``` + +ذخیره به‌صورت `smallint`، نه رشته. +دلیل: مقایسه و بازه و ایندکس روی عدد کار می‌کند و «۱۱» و «11» دو مقدار جدا نمی‌سازد. + +اعتبارسنجی در یک نقطه: + +``` +src/Dental/Validator/ToothNumberValidator.php +``` + +استانداردهای `Universal` و `Palmer` در لایهٔ داده استفاده نمی‌شوند. +اگر بعداً لازم شد، فقط لایهٔ نمایش تبدیل می‌کند. + +سطوح دندان: + +``` +M مزیال · D دیستال · O اکلوزال · B باکال · L لینگوال · P پالاتال · I اینسایزال +``` + +--- + +## ۳. مدل داده + +همه در `src/Dental/Entity`. + +### `ToothChart` — جدول `dental_tooth_charts` + +| ستون | نوع | توضیح | +|---|---|---| +| `id`, `uuid` | | | +| `entity_type`, `entity_id` | | جفت محیط | +| `patient_record_id` | int | یکتا در هر محیط | +| `dentition_type` | string 20 | `permanent`, `primary`, `mixed` | +| `last_examined_at` | int, nullable | | +| `created_at`, `updated_at` | int | | + +یکتایی: `(entity_type, entity_id, patient_record_id)`. + +چارت با اولین نیاز ساخته می‌شود، نه با ساخت پرونده. +دلیل: پروندهٔ بیماری که هرگز درمان دندانی نمی‌گیرد نباید ردیف خالی بسازد. + +### `ToothStatus` — جدول `dental_tooth_statuses` + +| ستون | نوع | توضیح | +|---|---|---| +| `id`, `uuid` | | | +| `chart_id` | int | `ON DELETE CASCADE` | +| `tooth_number` | smallint | FDI | +| `condition` | string 30 | وضعیت کلی دندان | +| `surface_map` | json, nullable | وضعیت هر سطح | +| `note` | string 500, nullable | | +| `source` | string 20 | `manual` یا `visit` | +| `recorded_by_user_id` | int, nullable | | +| `updated_at` | int | | + +یکتایی: `(chart_id, tooth_number)`. + +مقادیر `condition`: + +``` +healthy | caries | filled | crown | bridge_pontic | root_canal +implant | missing | extracted | impacted | to_extract | unerupted +``` + +`surface_map` شکل ثابت دارد: + +```json +{ "O": "filled", "M": "caries", "D": "healthy" } +``` + +**چرا وضعیت جدا از تاریخچه ذخیره می‌شود:** +رندر چارت باید با یک کوئری انجام شود. +اگر وضعیت هر بار از بازپخش تاریخچهٔ ویزیت‌ها ساخته شود، هر باز کردن تب یک محاسبهٔ سنگین است و وضعیت قبل از اولین مراجعه اصلاً قابل ثبت نیست. +هزینهٔ پذیرفته‌شده: این جدول باید بعد از هر ویزیت به‌روز شود و این کار فقط در یک کلاس انجام می‌شود، نه پراکنده در کنترلرها. + +### `ToothStatusLog` — جدول `dental_tooth_status_logs` + +هر تغییر وضعیت یک ردیف اضافه می‌کند. فقط افزودنی است. + +| ستون | نوع | +|---|---| +| `id`, `uuid` | | +| `chart_id`, `tooth_number` | | +| `from_condition`, `to_condition` | string 30 | +| `surface_map_before`, `surface_map_after` | json, nullable | +| `session_service_id` | int, nullable | +| `changed_by_user_id` | int, nullable | +| `changed_at` | int | + +دلیل وجودش: چارت سند پزشکی است. +«چه کسی دندان ۱۶ را کشیده‌شده علامت زد» باید قابل جواب دادن باشد. + +--- + +## ۴. هدف‌گیری دندان روی خدمت ویزیت + +### تغییر روی دامنهٔ موجود + +روی `SessionService` سه ستون اختیاری اضافه می‌شود: + +``` +tooth_number smallint nullable +surfaces json nullable +target_code string 10 nullable کد فک یا ناحیه +``` + +**چرا اینجا و نه در جدول دندانی جدا:** +این‌ها ویژگی همان ردیف خدمتِ فاکتورشده‌اند. +جدا کردنشان یعنی برای هر ردیف فاکتور یک join اضافه، و امکان اینکه ردیف فاکتور بدون هدف بماند بدون اینکه کسی بفهمد. +برخلاف `ServiceItem` که تنظیمات است و مشترک همهٔ حوزه‌هاست، `SessionService` سند یک ویزیت است و این سه ستون بخشی از همان سند. + +### اعتبارسنجی + +در `src/Dental/Service/ToothTargetValidator.php`. + +قاعده بر اساس `target_scope` پروفایل خدمت: + +| `target_scope` | لازم | ممنوع | +|---|---|---| +| `none` و `mouth` | — | هر سه | +| `tooth` | `tooth_number` | `surfaces`, `target_code` | +| `tooth_surface` | `tooth_number` و حداقل یک سطح | `target_code` | +| `quadrant` | `target_code` از ۱ تا ۴ | `tooth_number`, `surfaces` | +| `arch` | `target_code` برابر `upper` یا `lower` | `tooth_number`, `surfaces` | + +قاعدهٔ دوم: `tooth_scope` خدمت با شمارهٔ دندان بخواند. +خدمت `permanent_only` روی دندان ۵۱ خطا می‌دهد. + +قاعدهٔ سوم: خدمتی که پروفایل دندانی ندارد، هیچ هدفی نمی‌پذیرد. + +خطا با `ERR_VALIDATION_002` و نام فیلد. + +### پروجکتور چارت + +مسیر: `src/Dental/Service/ToothChartProjector.php` + +بعد از ثبت یا ویرایش ردیف خدمت با هدف دندانی: + +``` +سرویس ترمیمی روی سطوح → همان سطوح در surface_map مقدار filled می‌گیرند +سرویس کشیدن دندان → condition برابر extracted +سرویس درمان ریشه → condition برابر root_canal +سرویس روکش → condition برابر crown +سرویس ایمپلنت → condition برابر implant +بقیه → وضعیت دست نمی‌خورد، فقط لاگ ثبت می‌شود +``` + +نگاشت خدمت به اثر، در همان `DentalPreset` تعریف می‌شود با کلید `chart_effect`. +دلیل: مدیر می‌تواند خدمت دلخواه بسازد و اثرش را انتخاب کند، بدون اینکه کد عوض شود. + +حذف ردیف خدمت، وضعیت را به عقب برنمی‌گرداند. +دلیل: دندان کشیده‌شده با حذف یک ردیف فاکتور برنمی‌گردد. +به‌جایش یک لاگ با توضیح ثبت می‌شود و اصلاح دستی می‌ماند. + +--- + +## ۵. API + +`docs/api/dental.md` گسترش پیدا می‌کند. + +``` +GET /api/v1/dental/chart/{patientRecordUuid} + → { chart: {...}, teeth: [ { tooth_number, condition, surfaces, note } ] } + +PUT /api/v1/dental/chart/{patientRecordUuid}/tooth/{toothNumber} + → ثبت یا اصلاح دستی وضعیت یک دندان + +GET /api/v1/dental/chart/{patientRecordUuid}/tooth/{toothNumber}/history + → لاگ تغییرات همان دندان +``` + +دسترسی: + +| عملیات | clinic | doctor | secretary | staff | +|---|---|---|---|---| +| دیدن چارت | بله | بیماران خودش | خواندنی | نه | +| ویرایش دستی چارت | نه | بله | نه | نه | +| ثبت هدف دندانی در ویزیت | نه | بله | نه | نه | + +خطاها: + +| کد | HTTP | حالت | +|---|---|---| +| `ERR_VALIDATION_002` | 422 | شمارهٔ دندان نامعتبر یا هدف ناسازگار | +| `ERR_NOT_FOUND_001` | 404 | پرونده در این محیط نیست | +| `ERR_FORBIDDEN_001` | 403 | نقش مجاز نیست | + +اندپوینت ثبت خدمت ویزیت هم کلیدهای تازه می‌گیرد و `docs/api/patient.md` همان جلسه به‌روز می‌شود. + +--- + +## ۶. پنل ادمین + +### تب تازه + +فایل: `assets/admin/pages/PatientDetailPage.tsx` + +- کلید تب: `dental`، برچسب «چارت دندان». +- فقط وقتی حوزهٔ محیط دندانپزشکی است رندر می‌شود. +- بین «پرونده پزشکی» و «ضمیمه» می‌نشیند. + +### کامپوننت چارت + +فایل: `assets/admin/components/dental/ToothChart.tsx` + +این تنها جایی است که ساخت کامپوننت تازه موجه است، چون هیچ کامپوننت موجودی این کار را نمی‌کند. + +قواعد: + +- `SVG` دست‌نویس، بدون کتابخانهٔ بیرونی. +- هر دندان یک گروه قابل کلیک با شمارهٔ FDI. +- هر سطح یک مسیر جدا، تا کلیک روی سطح جدا از کلیک روی دندان باشد. +- رنگ‌ها فقط از توکن‌های `styles.css`. هیچ رنگ ثابتی در کد کامپوننت نیست. +- چیدمان `RTL` و سازگار با تم تیره. +- فک بالا در ردیف بالا، فک پایین در ردیف پایین، سمت راست بیمار در سمت راست تصویر. این قرارداد در بالای فایل به‌صورت کامنت نوشته شود چون خطای رایج همین است. +- حالت شیری و مختلط: دندان‌های شیری در همان گرید، کوچکتر. +- بدون تعامل هم باید خوانا باشد، چون در چاپ پرونده استفاده می‌شود. + +### فرم ثبت خدمت در ویزیت + +فایل: `assets/admin/pages/EditSessionPage.tsx` + +- بعد از انتخاب خدمت، اگر پروفایل دندانی دارد، انتخابگر هدف نشان داده شود. +- انتخاب دندان از روی همان `ToothChart` انجام شود، نه از یک `select` با ۳۲ گزینه. +- انتخاب سطح فقط وقتی `target_scope` برابر `tooth_surface` است. + +--- + +## ۷. تسک‌ها + +| کد | تسک | فایل‌های اصلی | معیار پذیرش | +|---|---|---|---| +| DM2-01 | `ToothNumberValidator` و ثابت‌های FDI | `src/Dental/Validator/` | همهٔ شماره‌های معتبر و نامعتبر تست می‌شوند | +| DM2-02 | موجودیت `ToothChart` | `src/Dental/Entity/` | یکتایی پرونده در محیط | +| DM2-03 | موجودیت `ToothStatus` با `surface_map` | همان | شکل json اعتبارسنجی می‌شود | +| DM2-04 | موجودیت `ToothStatusLog` | همان | فقط افزودنی، بدون متد حذف | +| DM2-05 | سه ستون هدف روی `SessionService` با migration | `src/Patient/Entity/SessionService.php` | ردیف بدون هدف مثل قبل کار می‌کند | +| DM2-06 | `ToothTargetValidator` | `src/Dental/Service/` | هر پنج حالت `target_scope` تست می‌شود | +| DM2-07 | `ToothChartProjector` و نگاشت `chart_effect` | `src/Dental/Service/`, `src/Dental/Preset/` | ثبت کشیدن دندان، وضعیت را عوض می‌کند و لاگ می‌زند | +| DM2-08 | سه اندپوینت چارت | `src/Dental/Controller/DentalChartController.php` | موفق، بدون دسترسی، پروندهٔ محیط دیگر | +| DM2-09 | گسترش ثبت خدمت ویزیت برای هدف دندانی | `src/Patient/Controller/PatientController.php` | هدف ناسازگار ۴۲۲ می‌دهد | +| DM2-10 | کامپوننت `ToothChart` | `assets/admin/components/dental/` | تست: کلیک دندان، کلیک سطح، حالت فقط‌خواندنی | +| DM2-11 | تب چارت در پروندهٔ بیمار | `assets/admin/pages/PatientDetailPage.tsx` | برای حوزهٔ غیر دندانی رندر نمی‌شود | +| DM2-12 | انتخابگر هدف در فرم ثبت خدمت | `assets/admin/pages/EditSessionPage.tsx` | خدمت بدون پروفایل، انتخابگر نشان نمی‌دهد | +| DM2-13 | به‌روزرسانی `docs/api/dental.md` و `docs/api/patient.md` | `docs/api/` | مسیرها با کد یکی است | + +--- + +## ۸. تست‌ها + +- ثبت خدمت روی دندان شیری با خدمت `permanent_only`: خطای ۴۲۲. +- ثبت خدمت `tooth_surface` بدون سطح: خطای ۴۲۲. +- ثبت خدمت `arch` با شمارهٔ دندان: خطای ۴۲۲. +- ثبت کشیدن دندان: وضعیت `extracted` و یک ردیف لاگ. +- ویرایش دستی وضعیت: منبع `manual` ثبت می‌شود. +- خواندن چارت بیمار محیط دیگر: خطای ۴۰۴، نه ۴۰۳. دلیل: نباید وجود پرونده در محیط دیگر لو برود. +- منشی چارت را می‌بیند ولی نمی‌تواند ویرایش کند. +- چارت بیماری که هیچ درمانی نگرفته: ساخته می‌شود و همهٔ دندان‌ها `healthy` برمی‌گردند بدون اینکه ۳۲ ردیف در دیتابیس ساخته شود. + +--- + +## ۹. ریسک‌ها + +**واگرایی چارت از فاکتور.** +اگر کاربر خدمت را ثبت کند ولی هدف را خالی بگذارد، چارت به‌روز نمی‌شود و کسی نمی‌فهمد. +مهار: برای خدمتی که پروفایل دندانی دارد، هدف اجباری است و ردیف بدون هدف اصلاً ذخیره نمی‌شود. + +**تعداد ردیف وضعیت.** +اگر برای هر بیمار ۳۲ ردیف ساخته شود، جدول سریع بزرگ می‌شود. +مهار: فقط دندان‌هایی که وضعیتشان از `healthy` فاصله گرفته ردیف می‌گیرند. بقیه در پاسخ API از پیش‌فرض ساخته می‌شوند. + +**سمت چپ و راست جابه‌جا.** +خطای رایج در چارت دندان و در سند پزشکی خطرناک است. +مهار: قرارداد جهت در کامنت بالای کامپوننت، و یک تست که دندان ۱۱ را در جای درست ادعا می‌کند. diff --git a/docs/new_feture/dental-module/phase-3-dashboard.md b/docs/new_feture/dental-module/phase-3-dashboard.md new file mode 100644 index 00000000..a9c82eea --- /dev/null +++ b/docs/new_feture/dental-module/phase-3-dashboard.md @@ -0,0 +1,200 @@ +# فاز ۳ — داشبورد دندانپزشکی + +> پیش‌نیاز: فاز ۱ و ۲. +> خروجی قابل تست: مدیر و پزشک و پذیرش، شاخص‌های دندانپزشکی را در همان داشبورد فعلی خودشان می‌بینند. + +--- + +## ۱. هدف + +شاخص‌های مخصوص حوزهٔ فعالیت به داشبورد نقشی اضافه شوند، بدون دست‌زدن به منطق داشبورد عمومی و بدون کند کردن داشبورد حوزه‌های دیگر. + +--- + +## ۲. الگو + +همان الگوی `TreatmentWorkflow` که در `docs/adr/0005` ثبت شده. + +``` +src/Dashboard/Metric/DomainMetricProvider.php اینترفیس، با تگ app.domain_metric_provider +src/Dashboard/Metric/DomainMetricRegistry.php انتخاب بر اساس کد حوزه +src/Dashboard/Metric/MetricRequest.php بازه، نقش، محیط، فیلترها +src/Dashboard/Metric/MetricSet.php خروجی استاندارد +src/Dental/Metric/DentalMetricProvider.php پیاده‌سازی دندانپزشکی +``` + +اینترفیس: + +```php +interface DomainMetricProvider +{ + public function supports(?string $practiceDomainCode): bool; + + /** @return list کلید شاخص‌هایی که این نقش می‌بیند */ + public function keysFor(string $role): array; + + public function collect(MetricRequest $request): MetricSet; +} +``` + +محیطی که حوزه‌اش `null` است یا provider ندارد، هیچ بخش تازه‌ای نمی‌گیرد. +برخلاف `TreatmentWorkflowRegistry` اینجا پیاده‌سازی پیش‌فرض لازم نیست؛ نبودن provider یعنی بخش دندانی رندر نمی‌شود. +دلیل: شاخص خالی بدتر از نبودن بخش است. + +هر متد `collect` باید تعداد کوئری ثابت داشته باشد، مستقل از تعداد شاخص. +یعنی شاخص‌های هم‌منبع در یک کوئری جمع شوند. + +--- + +## ۳. رجیستری شاخص‌ها + +هر شاخص یک کلید ثابت دارد. +فرانت هیچ فرمولی محاسبه نمی‌کند و فقط مصرف‌کنندهٔ عدد است. +دلیل: اگر تعریف «تولید» در دو جا نوشته شود، دیر یا زود دو عدد متفاوت نشان داده می‌شود. + +### شاخص‌های قابل محاسبه در فاز ۳ + +| کلید | تعریف | منبع | +|---|---|---| +| `production` | جمع مبلغ ناخالص ویزیت‌های انجام‌شده در بازه | `patient_sessions` | +| `collection` | جمع پرداخت‌های ثبت‌شده در بازه | `payments` و `session_payments` | +| `collection_rate` | وصولی تقسیم بر تولید | مشتق | +| `avg_per_visit` | تولید تقسیم بر تعداد ویزیت | مشتق | +| `production_per_doctor` | تولید به تفکیک پزشک | `patient_sessions` | +| `service_mix` | سهم هر گروه کاتالوگ از تولید | `session_services` و `service_catalog_categories` | +| `chair_utilization` | دقایق رزروشدهٔ منابع نوع اتاق تقسیم بر دقایق ظرفیت | `resource_occupancy` و `resource_calendars` | +| `no_show_rate` | نوبت‌های حاضرنشده تقسیم بر کل نوبت‌ها | `appointments` | +| `cancellation_rate` | نوبت‌های لغوشده تقسیم بر کل | `appointments` | +| `new_patients` | بیماران با اولین ویزیت در بازه | `patient_sessions` | +| `ar_outstanding` | جمع بدهی معوق بیماران | `patient_sessions` | +| `treatments_by_group` | تعداد خدمت انجام‌شده به تفکیک گروه دندانی | `session_services` | +| `teeth_treated` | تعداد دندان‌های درمان‌شدهٔ یکتا در بازه | `session_services` | + +### شاخص‌هایی که در این فاز وجود ندارند + +| کلید | چرا | +|---|---| +| `case_acceptance_rate` | ورودی‌اش برآورد درمان است که فاز ۴ ساخته می‌شود | +| `unscheduled_treatment` | همان | +| `redo_rate` | نیازمند حالت درمان مجدد روی ردیف برآورد | +| `lab_cost_ratio` | فاز ۵ | +| `consumable_cost_ratio` | فاز ۵ | +| `recall_response_rate` | نیازمند سازوکار ریکال که پروژه هنوز ندارد | + +این فهرست عمداً در سند مانده تا کسی فکر نکند فراموش شده‌اند. + +### دو تعریف که نباید قاطی شوند + +**تولید** جمع مبلغ کاری است که انجام شده. +**وصولی** جمع پولی است که رسیده. + +این دو از دو جدول متفاوت می‌آیند و هیچ‌کدام نباید از دیگری استنتاج شود. +فاصلهٔ بینشان همان چیزی است که نرخ وصول را معنادار می‌کند. + +--- + +## ۴. نماها + +| نقش | شاخص‌های صفحهٔ اصلی | +|---|---| +| مدیر کلینیک یا مطب | `collection`, `collection_rate`, `chair_utilization`, `new_patients`, `ar_outstanding`, `service_mix` | +| پزشک | تولید شخصی، `avg_per_visit` خودش، `treatments_by_group` خودش، `teeth_treated` | +| پذیرش | اشغال یونیت امروز، `no_show_rate`, `cancellation_rate`, صف نوبت امروز | + +حداکثر شش کارت در صفحهٔ اصلی هر نقش. +بقیه پشت drill-down. +دلیل: داشبورد با بیست کارت خوانده نمی‌شود و کاربر به‌جای تصمیم، اسکرول می‌کند. + +فیلتر مشترک: بازهٔ تاریخ جلالی، پزشک، گروه خدمت، یونیت. + +--- + +## ۵. API + +اندپوینت‌های نقشی موجود دست نمی‌خورند. +پاسخشان یک کلید تازه می‌گیرد: + +``` +GET /api/v1/dashboard/clinic + → { ..., domain_metrics: { code: "dental", metrics: { ... } } | null } +``` + +اگر محیط حوزه ندارد یا provider ندارد، مقدار `null` است. + +یک اندپوینت تازه برای بازه و روند: + +``` +GET /api/v1/dashboard/domain-metrics?from&to&doctorUuid?&resourceUuid?&groupUuid? + → { code, metrics: { : { value, previous_value, change_percent } } } + +GET /api/v1/dashboard/domain-metrics/trend?metric=production&from&to&interval=day|week|month + → { points: [ { date, value } ] } +``` + +`date` میلادی برمی‌گردد و تبدیل جلالی در فرانت انجام می‌شود. +دلیل: رشتهٔ جلالی در پاسخ، مرتب‌سازی و بازه‌گیری را در فرانت می‌شکند. + +بازهٔ پیش‌فرض سی روز. +حداکثر بازهٔ مجاز یک سال، وگرنه خطای ۴۲۲. +دلیل: بدون سقف، یک درخواست می‌تواند کل جدول ویزیت را اسکن کند. + +`docs/api/dashboard.md` همان جلسه به‌روز می‌شود. + +--- + +## ۶. پنل ادمین + +فایل: `assets/admin/pages/DashboardPage.tsx` + +- بخش «شاخص‌های دندانپزشکی» بعد از کارت‌های عمومی، فقط وقتی `domain_metrics` مقدار دارد. +- کارت‌ها با `StatCard` موجود. +- روند با `Recharts` که پروژه از قبل دارد. +- drill-down با `DataTable` موجود. +- انتخاب بازه با `PersianDateInput` موجود. +- فیلتر پزشک و یونیت با `SearchableSelect`. +- `TanStack Query` با `staleTime` معقول، چون این اعداد ثانیه‌ای عوض نمی‌شوند. + +هیچ کامپوننت تازه‌ای ساخته نمی‌شود. + +--- + +## ۷. تسک‌ها + +| کد | تسک | فایل‌های اصلی | معیار پذیرش | +|---|---|---|---| +| DM3-01 | اینترفیس و رجیستری `DomainMetricProvider` | `src/Dashboard/Metric/` | محیط بدون حوزه، `null` می‌گیرد | +| DM3-02 | `MetricRequest` و `MetricSet` | همان | بازهٔ بزرگتر از یک سال ۴۲۲ می‌دهد | +| DM3-03 | `DentalMetricProvider` بخش مالی | `src/Dental/Metric/` | تولید و وصولی با داده‌ی ساختگی درست است | +| DM3-04 | بخش اشغال یونیت | همان | منبع بدون تقویم، صفر می‌دهد نه خطا | +| DM3-05 | بخش نوبت، حاضرنشده و لغو | همان | مخرج صفر، `null` می‌دهد نه تقسیم بر صفر | +| DM3-06 | بخش ترکیب خدمات و دندان‌های درمان‌شده | همان | خدمت بدون گروه در «سایر» می‌رود | +| DM3-07 | اتصال `domain_metrics` به چهار اندپوینت نقشی | `src/Dashboard/Controller/DashboardController.php` | داشبورد حوزه‌های دیگر کوئری اضافه نمی‌زند | +| DM3-08 | اندپوینت بازه و روند | همان | فیلترها ترکیبی کار می‌کنند | +| DM3-09 | بخش دندانی در صفحهٔ داشبورد | `assets/admin/pages/DashboardPage.tsx` | برای حوزهٔ زیبایی رندر نمی‌شود | +| DM3-10 | نمودار روند و drill-down | همان | خالی بودن داده، حالت خالی نشان می‌دهد نه خطا | +| DM3-11 | به‌روزرسانی `docs/api/dashboard.md` | `docs/api/` | نمونهٔ پاسخ با کد یکی است | + +--- + +## ۸. تست‌ها + +- محیط بدون حوزه: `domain_metrics` برابر `null` و هیچ کوئری اضافه‌ای اجرا نمی‌شود. +- محیط زیبایی: همان. +- محیط دندانپزشکی بدون هیچ ویزیت: همهٔ شاخص‌ها صفر یا `null`، بدون خطا. +- تقسیم بر صفر در هر نرخ: `null` برمی‌گردد و فرانت خط تیره نشان می‌دهد. +- پزشک فقط عدد خودش را می‌بیند، حتی با فیلتر پزشک دیگر. +- منشی به شاخص‌های مالی دسترسی ندارد. +- بازهٔ بزرگتر از یک سال: ۴۲۲. +- تست کارایی: تعداد کوئری با افزایش تعداد شاخص ثابت می‌ماند. + +--- + +## ۹. ریسک‌ها + +**کندی داشبورد با رشد داده.** +تصمیم فاز ۳ محاسبهٔ زنده است. +مهار: سقف بازه، ایندکس روی ستون‌های تاریخ و محیط، و یک تست کارایی که تعداد کوئری را قفل می‌کند. +اگر بعداً کند شد، جدول تجمیع پشت همین سرویس اضافه می‌شود بدون تغییر API. + +**دو عدد متفاوت برای یک شاخص.** +مهار: هیچ فرمولی در فرانت نوشته نمی‌شود. diff --git a/docs/new_feture/dental-module/phase-4-treatment-estimate.md b/docs/new_feture/dental-module/phase-4-treatment-estimate.md new file mode 100644 index 00000000..4f35db49 --- /dev/null +++ b/docs/new_feture/dental-module/phase-4-treatment-estimate.md @@ -0,0 +1,218 @@ +# فاز ۴ — برآورد درمان و نرخ پذیرش + +> پیش‌نیاز: فاز ۱ تا ۳. +> خروجی قابل تست: دندانپزشک برآورد چندخدمتی می‌سازد، بیمار تصمیم می‌گیرد، و نرخ پذیرش در داشبورد دیده می‌شود. + +--- + +## ۱. چرا این فاز جدا افتاد + +در جلسهٔ تصمیم‌گیری قرار شد فاز ۱ بدون برآورد جلو برود تا زودتر به خروجی برسیم. +هزینه‌اش این است که تا این فاز، چهار شاخص وجود ندارند: +نرخ پذیرش، درمان زمان‌بندی‌نشده، درمان مجدد، و ارزش معوق طرح. + +--- + +## ۲. نام‌گذاری + +واژهٔ انگلیسی `Treatment Plan` در `CONTEXT.md` ممنوع است، چون بین `TreatmentProtocol` و `TreatmentCase` ابهام می‌ساخت. + +واژهٔ این مفهوم: + +**Treatment Estimate** — فهرست پیشنهادی خدمات روی دندان‌های مشخص، با قیمت، که به بیمار ارائه می‌شود و بیمار کل یا بخشی از آن را می‌پذیرد. +در فارسی همان «طرح درمان» است، چون زبان روزمرهٔ دندانپزشک همین است. +جدایی نام انگلیسی و فارسی عمدی است: کد باید بدون ابهام باشد، رابط کاربری باید آشنا باشد. + +--- + +## ۳. مدل داده + +### `TreatmentEstimate` — جدول `dental_treatment_estimates` + +| ستون | نوع | توضیح | +|---|---|---| +| `id`, `uuid` | | | +| `entity_type`, `entity_id` | | جفت محیط | +| `patient_record_id` | int | | +| `doctor_id` | int, nullable | پزشک ارائه‌دهنده | +| `title` | string 150 | | +| `status` | string 20 | | +| `total_rials` | int | جمع همهٔ ردیف‌ها | +| `accepted_rials` | int | جمع ردیف‌های پذیرفته‌شده | +| `presented_at` | int, nullable | لحظهٔ ارائه به بیمار | +| `decided_at` | int, nullable | لحظهٔ تصمیم بیمار | +| `expires_at` | int, nullable | | +| `created_at`, `updated_at` | int | | + +### `TreatmentEstimateItem` — جدول `dental_treatment_estimate_items` + +| ستون | نوع | توضیح | +|---|---|---| +| `id`, `uuid` | | | +| `estimate_id` | int | `ON DELETE CASCADE` | +| `service_item_id` | int | `ON DELETE RESTRICT` | +| `name_snapshot` | string 200 | نام خدمت در لحظهٔ ارائه | +| `tooth_number` | smallint, nullable | | +| `surfaces` | json, nullable | | +| `target_code` | string 10, nullable | | +| `quantity` | smallint | | +| `unit_price_rials` | int | | +| `amount_rials` | int | | +| `status` | string 20 | | +| `phase` | smallint | فاز درمان، برای اولویت‌بندی | +| `sort_order` | smallint | | +| `payer_type` | string 20 | پیش‌فرض `self_pay` | +| `insurance_ref_id` | int, nullable | فقط رزرو شده، بدون منطق | +| `session_service_id` | int, nullable | وقتی انجام شد به ردیف فاکتور وصل می‌شود | + +`name_snapshot` و `unit_price_rials` عمدی‌اند. +همان دلیلی که `TreatmentCaseArea` اسنپ‌شات می‌گیرد و در `docs/adr/0002` ثبت شده: +برآوردی که به بیمار داده شده، سند است و با تغییر تعرفهٔ فردا نباید بازنویسی شود. + +دو ستون `payer_type` و `insurance_ref_id` تنها نقطهٔ اتصال بیمه‌اند. +در این فاز هیچ محاسبه‌ای رویشان نوشته نمی‌شود. + +--- + +## ۴. ماشین حالت + +### برآورد + +``` +draft ──▶ presented ──▶ accepted ──▶ in_progress ──▶ completed + │ │ │ + ├──▶ partially_accepted ─────┤ + ├──▶ rejected └──▶ cancelled + └──▶ expired +``` + +قواعد گذار: + +- `draft → presented`: حداقل یک ردیف و جمع بزرگتر از صفر. `presented_at` ثبت می‌شود. +- `presented → accepted | partially_accepted | rejected`: با تصمیم بیمار. `decided_at` ثبت می‌شود و `accepted_rials` از جمع ردیف‌های پذیرفته‌شده حساب می‌شود. +- `presented → expired`: با یک job زمان‌بندی‌شده بعد از N روز. پیش‌فرض پیشنهادی ۹۰ روز، قابل تنظیم در `Config`. +- `accepted → in_progress`: با اولین ردیفی که انجام می‌شود. +- `→ completed`: وقتی همهٔ ردیف‌های پذیرفته‌شده انجام یا لغو شده‌اند. توسط پروجکتور، نه دستی. + +### ردیف + +``` +proposed ──▶ accepted ──▶ scheduled ──▶ done + │ │ │ │ + └▶ rejected └▶ cancelled └▶ cancelled └▶ redo ──▶ scheduled +``` + +`redo` حالت مستقل است، نه حذف رکورد. +دلیل: ورودی شاخص کیفیت است. +اگر درمان مجدد با ویرایش رکورد قبلی جایگزین شود، آن شاخص برای همیشه از بین می‌رود. + +--- + +## ۵. چرا `expired` لازم است + +نرخ پذیرش باید مخرجش برآوردهایی باشد که در آن بازه **ارائه** شده‌اند، نه برآوردهایی که در آن بازه **تصمیم‌گیری** شده‌اند. + +اگر مخرج بر اساس تصمیم باشد، برآوردهایی که هنوز جواب نگرفته‌اند از مخرج بیرون می‌مانند و نرخ به‌صورت مصنوعی بالا می‌رود. +`expired` همان چیزی است که برآورد بی‌جواب قدیمی را از حالت معلق در می‌آورد. + +--- + +## ۶. اتصال به موتور موجود + +ردیف پذیرفته‌شده وقتی زمان‌بندی می‌شود: + +- اگر خدمتش پروتکل فعال دارد، همان مسیر موجود `TreatmentCaseStarter` یک `TreatmentCase` باز می‌کند. +- اگر ندارد، فقط یک نوبت ساخته می‌شود. + +هیچ مسیر رزرو تازه‌ای نوشته نمی‌شود. + +وقتی ردیف انجام شد و در ویزیت فاکتور شد، `session_service_id` پر می‌شود و وضعیت ردیف `done` می‌گیرد. +از همان‌جا پروجکتور فاز ۲ چارت را به‌روز می‌کند. + +هیچ ستون پولی از برآورد به `TreatmentSession` نمی‌رود. +قاعدهٔ `docs/adr/0006` سر جایش می‌ماند. + +--- + +## ۷. API + +``` +GET /api/v1/dental/estimates?patientRecordUuid=&status= +POST /api/v1/dental/estimate +GET /api/v1/dental/estimate/{uuid} +PATCH /api/v1/dental/estimate/{uuid} +POST /api/v1/dental/estimate/{uuid}/present +POST /api/v1/dental/estimate/{uuid}/decision +DELETE /api/v1/dental/estimate/{uuid} + +POST /api/v1/dental/estimate/{uuid}/items +PATCH /api/v1/dental/estimate-item/{uuid} +DELETE /api/v1/dental/estimate-item/{uuid} +POST /api/v1/dental/estimate-item/{uuid}/schedule +``` + +دسترسی: + +| عملیات | clinic | doctor | secretary | +|---|---|---|---| +| ساخت و ویرایش برآورد | نه | بله | نه | +| ارائه به بیمار | نه | بله | بله | +| ثبت تصمیم بیمار | بله | بله | بله | +| زمان‌بندی ردیف پذیرفته‌شده | بله | بله | بله | + +دلیل اینکه ثبت تصمیم را منشی هم دارد: تصمیم بیمار معمولاً پشت میز پذیرش گفته می‌شود. + +`docs/api/dental.md` گسترش پیدا می‌کند. + +--- + +## ۸. شاخص‌های تازه در داشبورد + +| کلید | تعریف | +|---|---| +| `case_acceptance_rate` | جمع پذیرفته‌شده تقسیم بر جمع ارائه‌شده، در بازهٔ ارائه | +| `unscheduled_treatment` | جمع مبلغ ردیف‌های پذیرفته‌شده بدون نوبت | +| `redo_rate` | ردیف‌های درمان مجدد تقسیم بر ردیف‌های انجام‌شده | +| `estimate_backlog` | جمع مبلغ برآوردهای ارائه‌شدهٔ بی‌جواب | + +اضافه‌شدنشان به `DentalMetricProvider` است، بدون تغییر اینترفیس. + +--- + +## ۹. پنل ادمین + +- تب تازه در پروندهٔ بیمار: «طرح درمان». +- ساخت ردیف با انتخاب خدمت و انتخاب دندان از روی همان `ToothChart` فاز ۲. +- نمای چاپی برای دادن به بیمار. +- ثبت تصمیم به‌صورت ردیف‌به‌ردیف با `Switch`، نه یک دکمهٔ کلی. دلیل: پذیرش جزئی حالت رایج است. +- کارت‌های تازه در بخش دندانی داشبورد. + +--- + +## ۱۰. تسک‌ها + +| کد | تسک | معیار پذیرش | +|---|---|---| +| DM4-01 | موجودیت‌های برآورد و ردیف | `TenantSchemaCoverageTest` سبز | +| DM4-02 | ماشین حالت برآورد در یک کلاس جدا | گذار غیرمجاز `AppException` می‌دهد | +| DM4-03 | ماشین حالت ردیف | همان | +| DM4-04 | محاسبهٔ جمع و جمع پذیرفته‌شده در پروجکتور | ویرایش ردیف، جمع را همگام نگه می‌دارد | +| DM4-05 | job انقضا با مهلت قابل تنظیم | برآورد قدیمی `expired` می‌شود، برآورد پذیرفته‌شده نه | +| DM4-06 | اندپوینت‌های برآورد | همهٔ حالت‌های دسترسی تست می‌شوند | +| DM4-07 | زمان‌بندی ردیف و اتصال به `TreatmentCaseStarter` | خدمت پروتکل‌دار دوره باز می‌کند، بقیه فقط نوبت | +| DM4-08 | اتصال ردیف به `SessionService` هنگام انجام | وضعیت `done` و به‌روزرسانی چارت | +| DM4-09 | چهار شاخص تازه | مخرج صفر، `null` می‌دهد | +| DM4-10 | تب طرح درمان در پنل | پذیرش جزئی درست ثبت می‌شود | +| DM4-11 | نمای چاپی | در تم تیره هم درست چاپ می‌شود | +| DM4-12 | مستندات API | مسیرها با کد یکی است | + +--- + +## ۱۱. تصمیم‌های باز + +این‌ها قبل از شروع فاز ۴ باید جواب بگیرند: + +۱. مهلت انقضای برآورد چند روز باشد؟ پیشنهاد ۹۰ روز. +۲. درمان مجدد هزینه‌دار است یا صفر؟ روی شاخص تولید اثر مستقیم دارد. +۳. قیمت ردیف برآورد از تعرفهٔ لحظهٔ ارائه می‌آید یا قابل ویرایش دستی است؟ پیشنهاد: پیش‌فرض از تعرفه، قابل ویرایش با ثبت لاگ. +۴. آیا یک بیمار می‌تواند همزمان دو برآورد ارائه‌شده داشته باشد؟ پیشنهاد: بله، ولی داشبورد باید هشدار بدهد. diff --git a/docs/new_feture/dental-module/phase-5-clinical-ops.md b/docs/new_feture/dental-module/phase-5-clinical-ops.md new file mode 100644 index 00000000..b7c005cd --- /dev/null +++ b/docs/new_feture/dental-module/phase-5-clinical-ops.md @@ -0,0 +1,232 @@ +# فاز ۵ — پریو، لابراتوار، استریلیزاسیون، مواد مصرفی و تصاویر + +> پیش‌نیاز: فاز ۱ تا ۴. +> خروجی قابل تست: شاخص‌های هزینه و کیفیت، و ثبت بالینی کامل‌تر. + +این فاز چهار موضوع مستقل دارد. +هرکدام جداگانه قابل اجراست و ترتیبشان اجباری نیست. + +--- + +## ۱. مواد مصرفی — عمدتاً موجود است + +`Inventory` و `SessionConsumable` از قبل هستند. + +| موجودیت | مسیر | +|---|---| +| `InventoryItem` | `src/Inventory/Entity/InventoryItem.php` | +| `InventoryPackage` و `InventoryPackageItem` | همان پوشه | +| `SessionConsumable` | `src/Patient/Entity/SessionConsumable.php` | +| `ServiceItemConsumable` | `src/ClinicService/Entity/ServiceItemConsumable.php` | + +قیمت در `SessionConsumable` اسنپ‌شات می‌شود، مثل `SessionService`. +`ServiceItem` هم می‌تواند به یک بستهٔ مصرفی وصل شود. + +**پس کار این فاز فقط این است:** + +- بستهٔ پیش‌فرض دندانپزشکی به `DentalPreset` اضافه شود: کامپوزیت، ماده بی‌حسی، فایل روتاری، سوزن، ماسک، دستکش. +- شاخص `consumable_cost_ratio` به `DentalMetricProvider` اضافه شود. + +هیچ موجودیت تازه‌ای لازم نیست. +اگر کسی جدول مصرف مواد دندانپزشکی جدا ساخت، منبع حقیقت دوم ساخته است. + +### تسک‌ها + +| کد | تسک | معیار پذیرش | +|---|---|---| +| DM5-01 | بستهٔ مصرفی دندانپزشکی در قالب پیش‌فرض | نصب دوم چیزی تکرار نمی‌کند | +| DM5-02 | شاخص `consumable_cost_ratio` | مخرج صفر، `null` می‌دهد | + +--- + +## ۲. لابراتوار — تازه است + +### مدل داده + +`Lab` — جدول `dental_labs` + +| ستون | نوع | +|---|---| +| `id`, `uuid` | | +| `entity_type`, `entity_id` | جفت محیط | +| `title` | string 150 | +| `phone` | string 20, nullable | +| `active` | bool | +| `created_at`, `updated_at` | int | + +`LabOrder` — جدول `dental_lab_orders` + +| ستون | نوع | توضیح | +|---|---|---| +| `id`, `uuid` | | | +| `entity_type`, `entity_id` | | | +| `lab_id` | int | | +| `patient_record_id` | int | | +| `estimate_item_id` | int, nullable | ردیف برآوردی که این سفارش برایش است | +| `tooth_numbers` | json | دندان‌های درگیر | +| `description` | string 500 | | +| `status` | string 20 | | +| `cost_rials` | int | | +| `sent_at`, `due_at`, `received_at` | int, nullable | | +| `created_at`, `updated_at` | int | | + +### ماشین حالت + +``` +draft ─▶ sent ─▶ in_lab ─▶ ready ─▶ received ─▶ delivered + └────▶ returned_for_fix ─▶ in_lab +``` + +`due_at` مبنای هشدار تأخیر است. +یک job روزانه سفارش‌های گذشته از موعد و در حالت غیرنهایی را برای داشبورد علامت می‌زند. + +اتصال به `estimate_item_id` اختیاری است ولی توصیه‌شده. +بدون آن، بهای تمام‌شدهٔ آن ردیف قابل محاسبه نیست و شاخص حاشیهٔ سود بی‌معنا می‌شود. + +### API + +``` +GET /api/v1/dental/labs +POST /api/v1/dental/lab +PATCH /api/v1/dental/lab/{uuid} + +GET /api/v1/dental/lab-orders?status=&overdue= +POST /api/v1/dental/lab-order +PATCH /api/v1/dental/lab-order/{uuid} +POST /api/v1/dental/lab-order/{uuid}/transition +``` + +دسترسی: هر چهار نقش می‌بینند و ثبت می‌کنند. لابراتوار کار مشترک درمانگاه است. + +### شاخص‌ها + +| کلید | تعریف | +|---|---| +| `lab_cost_ratio` | جمع هزینهٔ لابراتوار تقسیم بر تولید | +| `lab_overdue_count` | تعداد سفارش گذشته از موعد | +| `lab_turnaround_days` | میانگین فاصلهٔ ارسال تا دریافت | + +### تسک‌ها + +| کد | تسک | معیار پذیرش | +|---|---|---| +| DM5-03 | موجودیت `Lab` و مخزن | یکتایی نام در محیط | +| DM5-04 | موجودیت `LabOrder` و ماشین حالت | گذار غیرمجاز `AppException` | +| DM5-05 | اندپوینت‌های لابراتوار | همهٔ حالت‌های دسترسی | +| DM5-06 | job هشدار تأخیر | سفارش نهایی‌شده علامت نمی‌خورد | +| DM5-07 | سه شاخص لابراتوار | مخرج صفر | +| DM5-08 | صفحهٔ لابراتوار در پنل | فیلتر وضعیت و تأخیر | + +--- + +## ۳. چارت پریودنتال — تازه است + +### مدل داده + +`PeriodontalExam` — جدول `dental_periodontal_exams` + +| ستون | نوع | +|---|---| +| `id`, `uuid` | | +| `chart_id` | int | +| `examined_at` | int | +| `examined_by_user_id` | int, nullable | +| `note` | string 500, nullable | + +`PeriodontalMeasurement` — جدول `dental_periodontal_measurements` + +| ستون | نوع | توضیح | +|---|---|---| +| `exam_id` | int | `ON DELETE CASCADE` | +| `tooth_number` | smallint | | +| `site` | smallint | ۱ تا ۶ | +| `pocket_depth` | smallint | میلی‌متر | +| `recession` | smallint | | +| `bleeding_on_probing` | bool | | +| `mobility` | smallint | ۰ تا ۳ | + +**چرا معاینه جدا از اندازه‌گیری:** +پریو دنباله‌ای است. مقایسهٔ معاینهٔ امروز با شش ماه پیش تمام ارزش این چارت است. +اگر اندازه‌ها روی خود دندان بازنویسی شوند، آن مقایسه از بین می‌رود. +این دقیقاً قرینهٔ `ToothStatus` است که عمداً فقط وضعیت جاری را نگه می‌دارد. + +ثبت کامل یک معاینه ۱۹۲ عدد است. +پس فرم باید صفحه‌کلیدمحور باشد و با `Tab` پیش برود، وگرنه کسی استفاده‌اش نمی‌کند. + +### تسک‌ها + +| کد | تسک | معیار پذیرش | +|---|---|---| +| DM5-09 | دو موجودیت پریو | یکتایی دندان و سایت در معاینه | +| DM5-10 | اندپوینت ثبت و خواندن معاینه | ثبت دسته‌ای در یک درخواست | +| DM5-11 | فرم پریو صفحه‌کلیدمحور | حرکت با `Tab` بین سایت‌ها | +| DM5-12 | نمای مقایسهٔ دو معاینه | اختلاف با رنگ نشان داده می‌شود | + +--- + +## ۴. استریلیزاسیون — تازه است + +### مدل داده + +`SterilizationCycle` — جدول `dental_sterilization_cycles` + +| ستون | نوع | +|---|---| +| `id`, `uuid` | | +| `entity_type`, `entity_id` | | +| `device_resource_id` | int, nullable | +| `program` | string 50 | +| `started_at`, `finished_at` | int | +| `chemical_indicator_ok` | bool | +| `biological_test_at` | int, nullable | +| `result` | string 20 | +| `operator_user_id` | int, nullable | +| `note` | string 500, nullable | + +اتوکلاو به‌عنوان `ClinicResource` تعریف می‌شود، نه یک جدول دستگاه تازه. +دلیل: نوع منبع از قبل قابل تعریف است و تقویم و دسترسی‌اش هم همان‌جاست. + +در این فاز، سیکل استریل به گردش کار درمان گره نمی‌خورد. +فقط ثبت و گزارش است. +گره‌زدن ست ابزار به جلسهٔ درمان کار بزرگی است و باید جدا تصمیم‌گیری شود. + +### تسک‌ها + +| کد | تسک | معیار پذیرش | +|---|---|---| +| DM5-13 | موجودیت سیکل استریل | ثبت بدون دستگاه هم ممکن است | +| DM5-14 | اندپوینت ثبت و فهرست | فیلتر بازه و نتیجه | +| DM5-15 | یادآور تست بیولوژیک هفتگی | نبود تست در هفته، هشدار داشبورد | +| DM5-16 | صفحهٔ استریلیزاسیون در پنل | گزارش قابل چاپ | + +--- + +## ۵. تصاویر بالینی — روی سیستم موجود + +`PatientAttachment` از قبل هست و به پرونده وصل است. + +کار این بخش فقط افزودن دو ستون اختیاری است: + +``` +tooth_numbers json nullable +image_type string 20 nullable periapical | bitewing | opg | cbct | photo +``` + +**چرا ستون روی همان جدول و نه جدول دندانی جدا:** +برخلاف ویژگی خدمت که تنظیمات مشترک همهٔ حوزه‌هاست، ضمیمه سند خود پرونده است و هر حوزه‌ای می‌تواند تصویر داشته باشد. +جدول جدا یعنی یک ضمیمه در دو جا و دو مسیر آپلود. + +### تسک‌ها + +| کد | تسک | معیار پذیرش | +|---|---|---| +| DM5-17 | دو ستون روی `PatientAttachment` | ضمیمهٔ بدون دندان مثل قبل کار می‌کند | +| DM5-18 | فیلتر ضمیمه بر اساس دندان در تب چارت | کلیک روی دندان، تصاویرش را نشان می‌دهد | + +--- + +## ۶. رضایت آگاهانه — خارج از این سند + +فرم رضایت آگاهانه در همهٔ حوزه‌ها لازم است، نه فقط دندانپزشکی. +ساختنش داخل ماژول دندانپزشکی یعنی حوزهٔ بعدی باید دوباره بسازدش. +پیشنهاد: سند جدا، در سطح پرونده بیمار. diff --git a/docs/new_feture/dental-module/preset-content.md b/docs/new_feture/dental-module/preset-content.md new file mode 100644 index 00000000..18508b54 --- /dev/null +++ b/docs/new_feture/dental-module/preset-content.md @@ -0,0 +1,245 @@ +# محتوای بستهٔ پیش‌فرض دندانپزشکی + +> **وضعیت: پیش‌نویس.** +> این فهرست توسط دندانپزشک تأیید نشده است. +> تسک `DM1-15` مسدودکننده است و بدون آن فاز ۱ بسته نمی‌شود. + +دلیل سختگیری: این فهرست در همهٔ کلینیک‌های نصب‌کننده کپی می‌شود. +اصلاح یک نام غلط بعد از نصب در پنجاه کلینیک، ممکن نیست. + +قیمت همهٔ خدمات صفر است و مدیر باید تعرفهٔ خودش را وارد کند. + +--- + +## ۱. گروه‌های خدمات + +سیزده گروه، همه در سطح ریشه. + +| کلید قالب | نام | ترتیب | +|---|---|---| +| `dx` | تشخیص و معاینه | ۱ | +| `radiology` | رادیولوژی | ۲ | +| `preventive` | پیشگیری | ۳ | +| `restorative` | ترمیمی | ۴ | +| `endodontics` | درمان ریشه | ۵ | +| `periodontics` | جراحی لثه و پریو | ۶ | +| `oral_surgery` | جراحی دهان و فک | ۷ | +| `fixed_prostho` | پروتز ثابت | ۸ | +| `removable_prostho` | پروتز متحرک | ۹ | +| `implant` | ایمپلنت | ۱۰ | +| `orthodontics` | ارتودنسی | ۱۱ | +| `pediatric` | دندانپزشکی کودکان | ۱۲ | +| `cosmetic` | زیبایی | ۱۳ | + +درخت تک‌سطحی است. +دلیل: عمق بیشتر بدون نیاز واقعی، فقط پیمایش را سخت می‌کند و `CatalogCategory` تا عمق ۴ را همیشه اجازه می‌دهد اگر بعداً لازم شد. + +--- + +## ۲. خدمات + +ستون‌ها: + +- **هدف** مقدار `target_scope` +- **مبنا** مقدار `pricing_basis` +- **دقیقه** مدت پیش‌فرض نوبت +- **جلسه** تعداد جلسهٔ پیش‌فرض +- **اثر چارت** مقدار `chart_effect` که پروجکتور فاز ۲ استفاده می‌کند + +### تشخیص و معاینه + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | اثر چارت | +|---|---|---|---|---|---|---| +| `dx_exam` | معاینه و مشاوره | `none` | `flat` | ۱۵ | ۱ | — | +| `dx_emergency` | ویزیت اورژانس | `none` | `flat` | ۲۰ | ۱ | — | +| `dx_full_chart` | معاینهٔ کامل و چارت‌نگاری | `mouth` | `flat` | ۳۰ | ۱ | — | +| `dx_perio_chart` | چارت پریودنتال | `mouth` | `flat` | ۳۰ | ۱ | — | + +### رادیولوژی + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | اثر چارت | +|---|---|---|---|---|---|---| +| `rad_pa` | رادیوگرافی پری‌اپیکال | `tooth` | `per_tooth` | ۱۰ | ۱ | — | +| `rad_bw` | رادیوگرافی بایت‌وینگ | `quadrant` | `per_quadrant` | ۱۰ | ۱ | — | +| `rad_opg` | رادیوگرافی پانورامیک | `mouth` | `flat` | ۱۵ | ۱ | — | +| `rad_cbct` | سی‌بی‌سی‌تی | `mouth` | `flat` | ۲۰ | ۱ | — | + +### پیشگیری + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | اثر چارت | +|---|---|---|---|---|---|---| +| `prev_scaling` | جرم‌گیری | `mouth` | `flat` | ۳۰ | ۱ | — | +| `prev_scaling_arch` | جرم‌گیری یک فک | `arch` | `per_arch` | ۲۰ | ۱ | — | +| `prev_polish` | پالیش و بروساژ | `mouth` | `flat` | ۲۰ | ۱ | — | +| `prev_fluoride` | فلوراید تراپی | `mouth` | `flat` | ۱۵ | ۱ | — | +| `prev_fissure_sealant` | فیشورسیلانت | `tooth` | `per_tooth` | ۱۵ | ۱ | `filled` | + +### ترمیمی + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | اثر چارت | +|---|---|---|---|---|---|---| +| `rest_composite_1` | ترمیم کامپوزیت یک سطحی | `tooth_surface` | `per_surface` | ۳۰ | ۱ | `filled` | +| `rest_composite_2` | ترمیم کامپوزیت دو سطحی | `tooth_surface` | `per_surface` | ۴۵ | ۱ | `filled` | +| `rest_composite_3` | ترمیم کامپوزیت سه سطحی | `tooth_surface` | `per_surface` | ۶۰ | ۱ | `filled` | +| `rest_amalgam` | ترمیم آمالگام | `tooth_surface` | `per_surface` | ۳۰ | ۱ | `filled` | +| `rest_buildup` | بازسازی تاج | `tooth` | `per_tooth` | ۴۵ | ۱ | `filled` | +| `rest_post_core` | پست و کور | `tooth` | `per_tooth` | ۶۰ | ۱ | `filled` | + +### درمان ریشه + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | اثر چارت | +|---|---|---|---|---|---|---| +| `endo_single` | درمان ریشه تک‌کاناله | `tooth` | `per_canal` | ۶۰ | ۱ | `root_canal` | +| `endo_multi` | درمان ریشه چندکاناله | `tooth` | `per_canal` | ۹۰ | ۲ | `root_canal` | +| `endo_retreat` | درمان مجدد ریشه | `tooth` | `per_canal` | ۹۰ | ۲ | `root_canal` | +| `endo_pulpotomy` | پالپوتومی | `tooth` | `per_tooth` | ۴۵ | ۱ | `root_canal` | +| `endo_apicoectomy` | آپیکواکتومی | `tooth` | `per_tooth` | ۹۰ | ۱ | `root_canal` | + +### جراحی لثه و پریو + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | اثر چارت | +|---|---|---|---|---|---|---| +| `perio_srp` | جرم‌گیری عمقی و تسطیح ریشه | `quadrant` | `per_quadrant` | ۴۵ | ۱ | — | +| `perio_flap` | جراحی فلپ | `quadrant` | `per_quadrant` | ۹۰ | ۱ | — | +| `perio_gingivectomy` | ژنژیوکتومی | `quadrant` | `per_quadrant` | ۶۰ | ۱ | — | +| `perio_crown_lengthening` | افزایش طول تاج | `tooth` | `per_tooth` | ۶۰ | ۱ | — | +| `perio_graft` | پیوند لثه | `quadrant` | `per_quadrant` | ۹۰ | ۱ | — | + +### جراحی دهان و فک + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | اثر چارت | +|---|---|---|---|---|---|---| +| `surg_extraction` | کشیدن دندان ساده | `tooth` | `per_tooth` | ۳۰ | ۱ | `extracted` | +| `surg_extraction_surgical` | کشیدن دندان جراحی | `tooth` | `per_tooth` | ۶۰ | ۱ | `extracted` | +| `surg_wisdom` | جراحی دندان عقل نهفته | `tooth` | `per_tooth` | ۹۰ | ۱ | `extracted` | +| `surg_root_remnant` | خارج کردن ریشهٔ باقی‌مانده | `tooth` | `per_tooth` | ۴۵ | ۱ | `extracted` | +| `surg_biopsy` | نمونه‌برداری | `mouth` | `flat` | ۴۵ | ۱ | — | + +### پروتز ثابت + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | لابراتوار | اثر چارت | +|---|---|---|---|---|---|---|---| +| `fixed_pfm_crown` | روکش پرسلن روی فلز | `tooth` | `per_unit` | ۶۰ | ۲ | بله | `crown` | +| `fixed_zirconia_crown` | روکش زیرکونیا | `tooth` | `per_unit` | ۶۰ | ۲ | بله | `crown` | +| `fixed_bridge_unit` | هر واحد بریج | `tooth` | `per_unit` | ۶۰ | ۲ | بله | `crown` | +| `fixed_inlay_onlay` | اینله و آنله | `tooth` | `per_unit` | ۶۰ | ۲ | بله | `filled` | +| `fixed_temp_crown` | روکش موقت | `tooth` | `per_unit` | ۳۰ | ۱ | خیر | `crown` | + +### پروتز متحرک + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | لابراتوار | +|---|---|---|---|---|---|---| +| `remov_complete_denture` | دست دندان کامل | `arch` | `per_arch` | ۶۰ | ۵ | بله | +| `remov_partial_acrylic` | پارسیل آکریلی | `arch` | `per_arch` | ۶۰ | ۴ | بله | +| `remov_partial_frame` | پارسیل فریم فلزی | `arch` | `per_arch` | ۶۰ | ۵ | بله | +| `remov_reline` | ریلاین | `arch` | `per_arch` | ۳۰ | ۱ | بله | +| `remov_repair` | تعمیر پروتز | `arch` | `per_arch` | ۳۰ | ۱ | بله | + +### ایمپلنت + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | لابراتوار | اثر چارت | +|---|---|---|---|---|---|---|---| +| `impl_fixture` | کاشت فیکسچر | `tooth` | `per_unit` | ۹۰ | ۱ | خیر | `implant` | +| `impl_abutment` | اباتمنت | `tooth` | `per_unit` | ۴۵ | ۱ | بله | `implant` | +| `impl_crown` | روکش روی ایمپلنت | `tooth` | `per_unit` | ۶۰ | ۲ | بله | `implant` | +| `impl_bone_graft` | پیوند استخوان | `tooth` | `per_unit` | ۹۰ | ۱ | خیر | — | +| `impl_sinus_lift` | سینوس لیفت | `quadrant` | `per_quadrant` | ۱۲۰ | ۱ | خیر | — | + +### ارتودنسی + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | +|---|---|---|---|---|---| +| `ortho_consult` | مشاورهٔ ارتودنسی | `none` | `flat` | ۳۰ | ۱ | +| `ortho_fixed` | ارتودنسی ثابت دو فک | `mouth` | `flat` | ۶۰ | ۱۸ | +| `ortho_fixed_single_arch` | ارتودنسی ثابت یک فک | `arch` | `per_arch` | ۶۰ | ۱۲ | +| `ortho_adjust` | ویزیت تنظیم | `mouth` | `per_session` | ۲۰ | ۱ | +| `ortho_retainer` | پلاک نگهدارنده | `arch` | `per_arch` | ۳۰ | ۱ | + +### دندانپزشکی کودکان + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | نوع دندان | اثر چارت | +|---|---|---|---|---|---|---|---| +| `ped_exam` | معاینهٔ کودک | `none` | `flat` | ۲۰ | ۱ | `any` | — | +| `ped_filling` | ترمیم دندان شیری | `tooth_surface` | `per_surface` | ۳۰ | ۱ | `primary_only` | `filled` | +| `ped_pulpotomy` | پالپوتومی شیری | `tooth` | `per_tooth` | ۴۵ | ۱ | `primary_only` | `root_canal` | +| `ped_ssc` | روکش استیل زنگ‌نزن | `tooth` | `per_unit` | ۴۵ | ۱ | `primary_only` | `crown` | +| `ped_extraction` | کشیدن دندان شیری | `tooth` | `per_tooth` | ۲۰ | ۱ | `primary_only` | `extracted` | +| `ped_space_maintainer` | فضانگهدار | `quadrant` | `per_quadrant` | ۳۰ | ۱ | `primary_only` | — | + +### زیبایی + +| کلید | نام | هدف | مبنا | دقیقه | جلسه | لابراتوار | اثر چارت | +|---|---|---|---|---|---|---|---| +| `cosm_bleaching_office` | بلیچینگ مطبی | `mouth` | `flat` | ۶۰ | ۱ | خیر | — | +| `cosm_bleaching_home` | بلیچینگ خانگی | `mouth` | `flat` | ۳۰ | ۱ | بله | — | +| `cosm_veneer_composite` | ونیر کامپوزیت | `tooth` | `per_unit` | ۶۰ | ۱ | خیر | `filled` | +| `cosm_veneer_porcelain` | لمینت سرامیکی | `tooth` | `per_unit` | ۶۰ | ۲ | بله | `crown` | +| `cosm_gum_contouring` | اصلاح طرح لبخند لثه | `arch` | `per_arch` | ۶۰ | ۱ | خیر | — | + +جمع: هفتاد و یک خدمت. + +--- + +## ۳. نوع منبع + +| کلید | کد | نام | +|---|---|---| +| `unit` | `dental_unit` | یونیت دندانپزشکی | +| `sterilizer` | `autoclave` | اتوکلاو | + +نوع منبع ساخته می‌شود، ولی هیچ منبعی ساخته نمی‌شود. +تعداد یونیت را فقط خود مدیر می‌داند. + +نوع `autoclave` فقط در فاز ۵ استفاده می‌شود، ولی چون تعریف نوع منبع ارزان است، همان اول ساخته می‌شود تا مدیر بتواند دستگاهش را ثبت کند. + +--- + +## ۴. پروتکل‌ها + +فقط برای خدماتی که واقعاً چندجلسه‌ای‌اند. + +| کلید | خدمت | جلسه | فاصله روز | +|---|---|---|---| +| `proto_endo_multi` | `endo_multi` | ۲ | ۷ | +| `proto_endo_retreat` | `endo_retreat` | ۲ | ۷ | +| `proto_fixed_crown` | `fixed_pfm_crown` | ۲ | ۱۰ | +| `proto_zirconia` | `fixed_zirconia_crown` | ۲ | ۱۰ | +| `proto_denture` | `remov_complete_denture` | ۵ | ۷ | +| `proto_partial_frame` | `remov_partial_frame` | ۵ | ۷ | +| `proto_impl_crown` | `impl_crown` | ۲ | ۱۴ | +| `proto_ortho_fixed` | `ortho_fixed` | ۱۸ | ۲۸ | + +پزشک سرپرست پروتکل هنگام نصب مشخص نمی‌شود. +اگر محیط فقط یک پزشک دارد همان انتخاب می‌شود، وگرنه خالی می‌ماند و مدیر باید تکمیل کند. +دلیل: انتخاب خودکار پزشک اشتباه، مسئولیت بالینی را به کسی نسبت می‌دهد که قبول نکرده. + +--- + +## ۵. مواد مصرفی — فاز ۵ + +| کلید | نام | واحد | +|---|---|---| +| `cons_composite` | کامپوزیت | سرنگ | +| `cons_bond` | باندینگ | میلی‌لیتر | +| `cons_anesthetic` | کارپول بی‌حسی | عدد | +| `cons_needle` | سوزن تزریق | عدد | +| `cons_rotary_file` | فایل روتاری | عدد | +| `cons_gutta` | گوتاپرکا | عدد | +| `cons_glove` | دستکش | جفت | +| `cons_mask` | ماسک | عدد | +| `cons_suction_tip` | ساکشن یک‌بار مصرف | عدد | +| `cons_impression` | ماده قالب‌گیری | گرم | + +--- + +## ۶. چک‌لیست بازبینی تخصصی + +دندانپزشک بازبین باید این‌ها را جواب بدهد: + +۱. نام هر خدمت با زبان رایج مطب می‌خواند یا اصطلاح کتابی است؟ +۲. مدت پیش‌فرض هر خدمت واقع‌بینانه است؟ +۳. مبنای قیمت هر خدمت درست است؟ مثلاً درمان ریشه به‌ازای کانال قیمت می‌خورد یا به‌ازای دندان؟ +۴. کدام خدمت جا افتاده که در هر مطب هست؟ +۵. کدام خدمت اضافه است و در مطب عمومی استفاده نمی‌شود؟ +۶. اثر چارت هر خدمت درست است؟ +۷. تعداد جلسه و فاصلهٔ پروتکل‌ها منطقی است؟