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>
90 lines
7.6 KiB
Markdown
90 lines
7.6 KiB
Markdown
# انحرافها از نقشهٔ تسکها
|
||
|
||
هر ردیفی که در چکلیستها ⚠️ مانده، اینجا با سه چیز ثبت است: **نقشه چه گفت**، **چه شد**،
|
||
و **چرا**. هیچکدام «انجام نشده» نیست؛ همه تصمیماند.
|
||
|
||
قاعدهٔ خواندن: اگر روزی یکی از این تصمیمها اشتباه از آب درآمد، همینجا عوضش کن و دلیل
|
||
تازه را بنویس. سندی که فقط توجیه جمع کند به درد نمیخورد.
|
||
|
||
---
|
||
|
||
## چیزهایی که **نباید** به نقشه برگردند
|
||
|
||
هیچکدام. سه موردی که قبلاً اینجا بودند (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()` ساده و کلیدهای تخت؛ بعد صفحهبهصفحه مهاجرت، نه یکباره. مهاجرت همزمانِ هزاران
|
||
رشته یعنی رگرسیون متنی که هیچ تستی نمیگیردش.
|