# انحراف‌ها از نقشهٔ تسک‌ها

هر ردیفی که در چک‌لیست‌ها ⚠️ مانده، اینجا با سه چیز ثبت است: **نقشه چه گفت**، **چه شد**،
و **چرا**. هیچ‌کدام «انجام نشده» نیست؛ همه تصمیم‌اند.

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

---

## چیزهایی که **نباید** به نقشه برگردند

هیچ‌کدام. سه موردی که قبلاً اینجا بودند (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()` ساده و کلیدهای تخت؛ بعد صفحه‌به‌صفحه مهاجرت، نه یک‌باره. مهاجرت هم‌زمانِ هزاران
رشته یعنی رگرسیون متنی که هیچ تستی نمی‌گیردش.
