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.
This commit is contained in:
+32
-3
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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:** خدمات دندانپزشکی مثل هر خدمت دیگری در سایت دیده میشوند.
|
||||
هیچ تغییر اختصاصی لازم نیست مگر اینکه بخواهیم انتخاب دندان در رزرو آنلاین باشد، که خارج از این سند است.
|
||||
@@ -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` مسدودکننده است.
|
||||
|
||||
**نصب خودکار روی محیطی که کاربر نمیخواست.**
|
||||
مهار: فقط وقتی کاتالوگ خالی است، و ردیفها با قیمت صفر و قابل غیرفعال کردن.
|
||||
|
||||
**تعریف «کاتالوگ خالی» ناپایدار.**
|
||||
اگر کلینیکی یک دستهٔ آزمایشی ساخته باشد، نصب خودکار انجام نمیشود و کاربر گیج میشود.
|
||||
مهار: پنل همیشه وضعیت نصب و دکمه را نشان میدهد، پس مسیر دوم همیشه در دسترس است.
|
||||
@@ -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 از پیشفرض ساخته میشوند.
|
||||
|
||||
**سمت چپ و راست جابهجا.**
|
||||
خطای رایج در چارت دندان و در سند پزشکی خطرناک است.
|
||||
مهار: قرارداد جهت در کامنت بالای کامپوننت، و یک تست که دندان ۱۱ را در جای درست ادعا میکند.
|
||||
@@ -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<string> کلید شاخصهایی که این نقش میبیند */
|
||||
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: { <key>: { 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.
|
||||
|
||||
**دو عدد متفاوت برای یک شاخص.**
|
||||
مهار: هیچ فرمولی در فرانت نوشته نمیشود.
|
||||
@@ -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 | مسیرها با کد یکی است |
|
||||
|
||||
---
|
||||
|
||||
## ۱۱. تصمیمهای باز
|
||||
|
||||
اینها قبل از شروع فاز ۴ باید جواب بگیرند:
|
||||
|
||||
۱. مهلت انقضای برآورد چند روز باشد؟ پیشنهاد ۹۰ روز.
|
||||
۲. درمان مجدد هزینهدار است یا صفر؟ روی شاخص تولید اثر مستقیم دارد.
|
||||
۳. قیمت ردیف برآورد از تعرفهٔ لحظهٔ ارائه میآید یا قابل ویرایش دستی است؟ پیشنهاد: پیشفرض از تعرفه، قابل ویرایش با ثبت لاگ.
|
||||
۴. آیا یک بیمار میتواند همزمان دو برآورد ارائهشده داشته باشد؟ پیشنهاد: بله، ولی داشبورد باید هشدار بدهد.
|
||||
@@ -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 | فیلتر ضمیمه بر اساس دندان در تب چارت | کلیک روی دندان، تصاویرش را نشان میدهد |
|
||||
|
||||
---
|
||||
|
||||
## ۶. رضایت آگاهانه — خارج از این سند
|
||||
|
||||
فرم رضایت آگاهانه در همهٔ حوزهها لازم است، نه فقط دندانپزشکی.
|
||||
ساختنش داخل ماژول دندانپزشکی یعنی حوزهٔ بعدی باید دوباره بسازدش.
|
||||
پیشنهاد: سند جدا، در سطح پرونده بیمار.
|
||||
@@ -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` | ماده قالبگیری | گرم |
|
||||
|
||||
---
|
||||
|
||||
## ۶. چکلیست بازبینی تخصصی
|
||||
|
||||
دندانپزشک بازبین باید اینها را جواب بدهد:
|
||||
|
||||
۱. نام هر خدمت با زبان رایج مطب میخواند یا اصطلاح کتابی است؟
|
||||
۲. مدت پیشفرض هر خدمت واقعبینانه است؟
|
||||
۳. مبنای قیمت هر خدمت درست است؟ مثلاً درمان ریشه بهازای کانال قیمت میخورد یا بهازای دندان؟
|
||||
۴. کدام خدمت جا افتاده که در هر مطب هست؟
|
||||
۵. کدام خدمت اضافه است و در مطب عمومی استفاده نمیشود؟
|
||||
۶. اثر چارت هر خدمت درست است؟
|
||||
۷. تعداد جلسه و فاصلهٔ پروتکلها منطقی است؟
|
||||
Reference in New Issue
Block a user