Files
clinicpro/docs/architecture/deviations.md
T
hamedandClaude Opus 5 e5b74ebab4 docs: settle every remaining row, and add the third occupancy mode
The last structural gap from task 05 was the third occupancy mode. It is
passive: the resource is genuinely held — nobody else can take that room while
the patient waits for the anaesthetic — but the time is not work done. It
blocks exactly like exclusive; the difference is in the report, where without
it a room that spends half its day waiting reads as fully utilised. The mode is
validated, offered in the segment editor and carried through to the plan.

Everything else that was still marked as a deviation is now recorded in
docs/architecture/deviations.md, one row each, in the form "what the plan said
/ what was built / why". That includes the ones I would defend (five plan
services collapsed into one builder that only build() calls; a Skill foreign
key instead of a JSON array, because a deleted skill in JSON fails silently)
and the ones that are simply facts about the product (service_option does not
exist here, so a column for it would sit empty until someone read it as a bug).

The i18n section says plainly that the product is single-language and describes
the order to migrate in if that changes — a translation layer with one language
is an indirection, not an abstraction.

All sixteen checklists now read zero pending and zero unresolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:40:55 +03:30

90 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# انحراف‌ها از نقشهٔ تسک‌ها
هر ردیفی که در چک‌لیست‌ها ⚠️ مانده، اینجا با سه چیز ثبت است: **نقشه چه گفت**، **چه شد**،
و **چرا**. هیچ‌کدام «انجام نشده» نیست؛ همه تصمیم‌اند.
قاعدهٔ خواندن: اگر روزی یکی از این تصمیم‌ها اشتباه از آب درآمد، همین‌جا عوضش کن و دلیل
تازه را بنویس. سندی که فقط توجیه جمع کند به درد نمی‌خورد.
---
## چیزهایی که **نباید** به نقشه برگردند
هیچ‌کدام. سه موردی که قبلاً اینجا بودند (idempotency با `catch`، تراکنش سراسری لغو، و
مانده تجمعی در UI) به نقشه برگردانده شدند — هر کدام با بستنِ باگی که دعوتش می‌کردند.
جزئیات در کامیت `refactor: take the three risky rows back to the plan`.
---
## تسک ۰۴ — کاتالوگ سرویس
| ردیف | نقشه | چه شد | چرا |
|---|---|---|---|
| ۴.۳ | جدول آیتم‌ها با نام/زمان/قیمت | جدول در **پیش‌نمایش انتخاب** است | همان ستون‌ها، ولی کنار عددی که کاربر دربارهٔ آن تصمیم می‌گیرد؛ جدول دوم یعنی دو جای خواندن یک چیز |
| ۴.۶ | قیمت با `PriceInput` | فقط نمایش با `formatRial` | این تب قیمت را **ویرایش نمی‌کند**؛ ویرایشش در `ServiceItemFormModal` است که از قبل `PriceInput` دارد |
| ۴.۱۰ | فرم با RHF + Zod | ذخیرهٔ inline per تغییر | RHF برای فرمی است که دکمهٔ submit دارد. این تب ندارد — هر تغییر مستقل و فوری ذخیره می‌شود |
| ۴.۱۲ | خطاها زیر همان گروه | همهٔ خطاها زیر پیش‌نمایش | قرارداد پروژه «همهٔ خطاها با هم» است؛ نگاشت `group_uuid` به کارت، خطا را از کنار عددِ نتیجه دور می‌کرد |
## تسک ۰۵ — برنامهٔ نوبت
| ردیف | نقشه | چه شد | چرا |
|---|---|---|---|
| ۱.۳ | پنج سرویس جدا | یک `AppointmentPlanBuilder` | پنج کلاس وقتی معنا دارد که هرکدام مصرف‌کنندهٔ مستقل داشته باشند؛ اینجا هر پنج فقط از `build()` صدا زده می‌شوند |
| ۱.۴ | `build()` تابع خالص | از دیتابیس می‌خواند | تا تسک ۰۸ خالص بود؛ تسک ۰۹ قوانین را وصل کرد و قانون در دیتابیس است. خلوص را فقط با کپی‌کردن قوانین در حافظه می‌شد نگه داشت |
| ۱.۸ | اشغال جدا از offset نمایشی | `setup/cleanup` روی `PlannedRequirement` | اثر عملی یکی است و تسک ۰۷ همان را می‌خواند؛ دو offset جدا یعنی دو عدد که باید هم‌زمان درست بمانند |
| ۲.۲ | `service_item_id` یا `service_option_id` | فقط `ServiceItem` | مفهوم `service_option` در این محصول وجود ندارد. ستونی برای چیزی که نیست، تهی می‌ماند و بعداً کسی فکر می‌کند باگ است |
| ۲.۳ | `fixed_minutes` یا `duration_share` | `duration_source``fixed`\|`items` | «مدت از آیتم‌ها» همان نیاز واقعی است؛ سهم درصدی هیچ مصرف‌کننده‌ای نداشت |
| ۲.۵ | `required_skills` به‌صورت JSON | یک `Skill` با FK | چند مهارت هم‌زمان نیاز واقعی نداشت، و FK اعتبار ارجاعی می‌دهد که JSON نمی‌دهد — مهارتِ حذف‌شده در JSON بی‌صدا می‌ماند |
## تسک ۰۹ — موتور قوانین
رجیستری‌ها، شش موتور و `specificity` ذخیره‌شده **ساخته شدند** (کامیت
`refactor(policy): build the registries and six engines`). آنچه مانده:
| ردیف | نقشه | چه شد | چرا |
|---|---|---|---|
| ۱.۱۱ | `forbiddenRanges()` — کوئری نه حلقه | `spacing` در لحظهٔ رزرو موقت | هزینه‌اش یک اسلات است که نمایش داده و بعد رد می‌شود؛ سودش این است که جستجوی وقت per کاندید یک کوئری تاریخچهٔ بیمار نزند |
| ۲.۳ | تسک ۰۶ → `Spacing` | `BookingPolicyGuard` | همان نتیجه، در همان نقطه‌ای که تصمیم واقعی گرفته می‌شود |
| ۲.۴ | `Eligibility` در `confirm` | در `hold` | رد کردن **بعد از** گرفتن صندلی هم وقت بیمار را تلف می‌کند هم صندلی را |
| ۲.۶ | هیچ امضایی عوض نشد | سه سرویس یک وابستگی سازنده گرفتند | امضای هیچ متد عمومی‌ای عوض نشد؛ سازنده تنها راه رسیدن قانون به آن سه است |
| نام فیلدها | `patient.age` نقطه‌دار | `patient_age` | شرط‌های ذخیره‌شده روی قانون‌های **زندهٔ کلینیک‌ها** به نام‌های فعلی اشاره می‌کنند؛ تغییرشان مهاجرت داده است و نگاشت یک‌به‌یک هم ندارد |
## تسک ۱۰ — آزمایشگاه قانون
| ردیف | نقشه | چه شد | چرا |
|---|---|---|---|
| ۱.۶ | فیلتر از داخل `condition` | از **دامنهٔ** قانون | شرط‌ها فیلد id ندارند که به کوئری ترجمه شوند؛ دامنه دقیقاً همان چیزی است که قابل ترجمه است |
## تسک ۱۱ — دفتر اعتبار
| ردیف | نقشه | چه شد | چرا |
|---|---|---|---|
| ۴.۴ | مانده هرگز منفی نمی‌شود | با مصرف پشت‌سرهم تست شد | تست هم‌زمانی واقعی حالا هست (`testAConcurrentConsumeIsAbsorbedWithoutBurningTheRequest`) |
## تسک ۱۲ — دورهٔ درمان
| ردیف | نقشه | چه شد | چرا |
|---|---|---|---|
| ۱.۱۲ | سخت‌گیرانه‌تر برنده در هر دو جهت | فقط `max(min)` | قانون `spacing` اثر «حداکثر» ندارد، پس `min(max)` چیزی برای انتخاب کردن ندارد |
## تسک ۱۳ — لغو و لیست انتظار
| ردیف | نقشه | چه شد | چرا |
|---|---|---|---|
| ۳.۱ | `Service` + `Matcher` جدا | یک `WaitlistNotifier` | تطبیق یک کوئری در repository است؛ کلاس جدا فقط یک لایهٔ اسم‌گذاری می‌شد |
## تسک ۱۴ — رویدادها
| ردیف | نقشه | چه شد | چرا |
|---|---|---|---|
| ۱.۱ | کلاس پایه + چهارده زیرکلاس | فهرست بستهٔ نام + یک entity | زیرکلاسِ خالی که فقط نام را در تایپ نگه می‌دارد، همان کاری را می‌کند که `const` — با چهارده فایل بیشتر |
## چندزبانگی (i18n)
پروژه فایل i18n ندارد و همهٔ رشته‌ها inline اند. محصول **تک‌زبانه** است و لایهٔ ترجمه
بدون زبان دوم فقط یک واسطهٔ اضافه است.
**روزی که زبان دوم لازم شد**، ترتیبش این است: اول `assets/admin/lib/i18n.ts` با یک
`t()` ساده و کلیدهای تخت؛ بعد صفحه‌به‌صفحه مهاجرت، نه یک‌باره. مهاجرت هم‌زمانِ هزاران
رشته یعنی رگرسیون متنی که هیچ تستی نمی‌گیردش.