feat: implement service mode completion for nobat724_front

- Add task for completing service mode in clinicpro with detailed objectives and acceptance criteria.
- Create architecture documentation for task 00b, outlining involved components and necessary changes.
- Develop checklist for task 00b to ensure all requirements are met.
- Document implementation notes for task 00b, emphasizing API contract checks and design system adherence.
- Update task documentation for task 00b, specifying goals and current issues with service mode.
This commit is contained in:
hamed
2026-07-30 11:56:08 +03:30
parent 021d0eb6b2
commit 158dcb58aa
12 changed files with 1846 additions and 0 deletions
@@ -0,0 +1,85 @@
# تعریف «تمام‌شده» و قالب چک‌لیست
هر تسک یک `checklist.md` دارد. **پیش از اعلام پایان تسک، همهٔ ردیف‌ها بازبینی می‌شوند و
هیچ ردیفی در `🔄` یا `⏳` نمی‌ماند.**
---
## نمادها
| نماد | معنی | اجازهٔ باقی‌ماندن در پایان تسک |
|---|---|---|
| ✅ | انجام‌شده و تأییدشده | بله |
| 🔄 | در حال انجام | **نه** — یا ✅ شود یا با دلیل صریح به ⏳ منتقل شود |
| ⏳ | انجام‌نشده | **نه** — یا ✅ شود یا با دلیل مکتوب و تسک مقصد به تعویق برود |
| ⚠️ | نیازمند بررسی یا تست | **نه** — باید تعیین تکلیف شود |
`⏳` تنها وقتی در پایان مجاز است که کنارش نوشته شده باشد: **چرا** به تعویق افتاد و
**کدام تسک** آن را برمی‌دارد. `⏳ بدون دلیل = تسک تمام نشده.`
---
## قالب `checklist.md`
```markdown
# چک‌لیست — تسک XX
وضعیت کلی: ⏳ شروع نشده | 🔄 در حال انجام | ✅ تمام‌شده
آخرین بازبینی: —
## ۰. خط سرخ‌ها (رجوع: _shared/red-lines.md)
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | منطق اسلاتی دست‌کاری نشد · `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | هیچ متد موجود `SlotCalculatorService` ویرایش نشد | ⏳ | |
| ۰.۳ | قرارداد `appointment-slots` و `month-availability` دست‌نخورده | ⏳ | |
## ۱. بک‌اند
## ۲. دیتابیس و مهاجرت
## ۳. UI (رجوع: _shared/ui-conventions.md)
## ۴. تست
## ۵. مستندات
## ۶. بازبینی پایانی
```
---
## بخش ۶ — بازبینی پایانی، یکسان در همهٔ تسک‌ها
```markdown
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | همهٔ ردیف‌های بالا وضعیت نهایی دارند (هیچ 🔄 و ⏳ بی‌دلیل) | ⏳ | |
| ۶.۲ | `ddev exec php bin/phpunit` کامل سبز | ⏳ | |
| ۶.۳ | `ddev exec php bin/phpunit --group=slot-mode-frozen` سبز | ⏳ | |
| ۶.۴ | `ddev exec php vendor/bin/phpstan analyse` بدون خطای جدید | ⏳ | |
| ۶.۵ | `npx tsc --noEmit` بدون خطا | ⏳ | |
| ۶.۶ | `yarn test` سبز | ⏳ | |
| ۶.۷ | `TenantSchemaCoverageTest` و `TenantLookupInventoryTest` سبز | ⏳ | |
| ۶.۸ | `docs/api/*` به‌روز شد (قاعدهٔ ثابت پروژه) | ⏳ | |
| ۶.۹ | چک‌لیست UI کامل شد (اگر تسک صفحه/کامپوننت دارد) | ⏳ | |
| ۶.۱۰ | مصرف‌کنندگان دیگر دستی بررسی شدند: `nobat724_front` · `clinic-pro-tauri` | ⏳ | |
| ۶.۱۱ | تغییرات commit شد، سپس `graphify update .` اجرا شد | ⏳ | |
| ۶.۱۲ | موارد به‌تعویق‌افتاده با دلیل و تسک مقصد ثبت شدند | ⏳ | |
```
ردیف ۶.۱۰ در build هیچ‌کدام از آن دو ریپو خطا نمی‌دهد — بررسی فقط دستی ممکن است.
ردیف ۶.۱۱ ترتیبش مهم است: اول commit، بعد `graphify update`.
---
## قواعد ثابت پروژه که در هر تسک اعمال می‌شوند
از `CLAUDE.md` و `docs/architecture/tenancy.md`:
1. entity جدید یا `TenantOwnedTrait` می‌گیرد یا با دلیل در `GlobalTables` ثبت می‌شود
2. `entity_type, entity_id` ستون‌های **اول** هر ایندکس ترکیبیِ لیست
3. هر uuid از request با `TenantOwnershipChecker` سنجیده می‌شود
4. timestamp ها `int` یونیکس، نه `DateTime` · نمایش شمسی فقط در UI
5. کنترلر نازک · `extends BaseController` · `success()/paginated()/error()`
6. منطق در Service، کوئری در Repository، وابستگی با constructor injection
7. لیست‌های ادمین با `getArrayResult()`
8. API جدید فقط وقتی هیچ endpoint موجودی — حتی با توسعه — کافی نباشد؛ **دلیلش نوشته شود**
9. تست موفق + خطا + مرزی · بدون اجرای موفق تست، تسک تمام نیست
10. کد و کامیت و مستندات انگلیسی · رشته‌های UI فارسی از i18n
11. SOLID · کلاس/کامپوننت چندمسئولیتی ننویس
@@ -0,0 +1,96 @@
# خط سرخ‌ها — قواعدی که هیچ تسکی نمی‌تواند نقض کند
این فایل بالای هر تسک حاکم است. اگر تسکی با این‌ها تناقض داشت، **این فایل برنده است** و
تسک باید اصلاح شود، نه این فایل.
---
## ۱. ⛔ نوبت‌دهی اسلاتی به هیچ عنوان دست‌کاری نمی‌شود
`booking_mode = 'slot'` منطق تولیدیِ زنده است. در **هیچ تسکی از این فاز** نه رفتارش،
نه امضایش، نه خروجی‌اش تغییر نمی‌کند.
### فایل‌ها و مسیرهای قفل‌شده
| فایل / مسیر | چه چیزی قفل است |
|---|---|
| `src/Appointment/Service/SlotCalculatorService.php` | متدهای `getAvailableSlots`، `getAllSlotsWithAvailability`، `hasAnyAvailability`، `findNextAvailableStart`، `buildSessionSlots`، `buildAllSessions`، `filterBookedSlots`، `isWithinBookingWindow`**هیچ‌کدام** ویرایش نمی‌شوند |
| `GET /api/v1/appointment-slots` | قرارداد request/response |
| `GET /api/v1/appointment-settings/month-availability/{doctorUuid}` | قرارداد |
| `Appointment::active_slot_key` و `refreshActiveSlotKey()` | مکانیزم یکتایی موجود |
| `AppointmentRepository::isSlotTaken` | امضا و معنا |
| `WeeklySchedule::MODE_SLOT` و `DEFAULT_META['booking_mode']` | مقدار پیش‌فرض `slot` می‌ماند |
### چه چیزی مجاز است
- **افزودن** متد جدید به `SlotCalculatorService` — بدون تغییر متدهای موجود
- **افزودن** کلاس/سرویس موازی (مثل `AvailabilityEngine` تسک ۰۶)
- **افزودن** کلید جدید به `WeeklySchedule.meta` — با حفظ پیش‌فرض‌های موجود
- **افزودن** ستون تهی‌پذیر به `appointments`
### چه چیزی ممنوع است
- تغییر امضای هر متد موجود در `SlotCalculatorService`
- تغییر شکل خروجی `appointment-slots` (حتی افزودن فیلد، اگر ترتیب/نوع فیلدهای موجود عوض شود)
- «یکدست‌سازی» یا refactor مسیر اسلاتی
- حذف `active_slot_key` یا `is_reserve`
- اعمال قوانین جدید (تسک ۰۹) روی حالت `slot` — حتی اگر منطقی به نظر برسد
### اجبار خودکار
هر تسک باید این تست را سبز نگه دارد:
```bash
ddev exec php bin/phpunit --group=slot-mode-frozen
```
و در `tests/Appointment/SlotModeFrozenTest.php`:
```php
/** @group slot-mode-frozen */
public function testSlotModeContractUnchanged(): void
{
// snapshot خروجی appointment-slots برای یک برنامهٔ ثابت
// هر تغییری در شکل پاسخ، این تست را قرمز می‌کند
self::assertJsonStringEqualsJsonFile(
__DIR__ . '/fixtures/slot-mode-contract.json',
$this->client->getResponse()->getContent()
);
}
```
fixture در تسک ۰۰ ساخته می‌شود و **هیچ تسکی اجازهٔ به‌روزرسانی‌اش را ندارد**.
---
## ۲. ✅ نوبت‌دهی سرویسی در همین فاز کامل می‌شود
`booking_mode = 'service'` نیمه‌کاره است: در مسیر رزرو (سایت و درawer پنل) کار می‌کند، ولی
در ویرایش نوبت، نوبت رزرو، و بخشی از پنل بیمار غایب است.
تکمیلش **پیش‌نیاز** بقیهٔ فاز است، نه موازی با آن:
```
تسک ۰۰ تکمیل نوبت‌دهی سرویسی در clinicpro
تسک ۰۰ب سازگارسازی nobat724_front با وضعیت فعلی
تسک ۰۱ به بعد (موتور چندمنبعی)
```
دلیل ترتیب: اگر حالت `resource` (تسک ۰۶) روی حالت `service` نیمه‌کاره ساخته شود، هر باگ
موجود سرویسی به موتور جدید ارث می‌رسد و تشخیص منبعش غیرممکن می‌شود.
---
## ۳. 🎨 هر صفحه یا بخش جدید، عیناً با دیزاین‌سیستم موجود
هیچ طراحی جدید، هیچ کامپوننت موازی، هیچ رنگ hard-code.
جزئیات کامل و چک‌لیست: [ui-conventions.md](ui-conventions.md)
---
## ۴. ☑️ هیچ تسکی بدون تکمیل چک‌لیستش تمام نیست
هر تسک یک `checklist.md` دارد. پیش از اعلام پایان، **همهٔ** ردیف‌ها باید وضعیت نهایی
داشته باشند و هیچ ردیفی در `🔄` یا `⏳` نماند.
قالب و قواعد: [definition-of-done.md](definition-of-done.md)
@@ -0,0 +1,126 @@
# قواعد UI — هر صفحه و بخش جدید عیناً مطابق سیستم موجود
**الزامی برای همهٔ تسک‌ها.** هیچ طراحی جدید، هیچ تم جدید، هیچ کامپوننت موازی.
منبع حقیقت: کدِ موجود، نه سلیقه و نه `docs/admin-ui/ui-design-spec.md` (که draft قدیمی
با پالت بنفش است و با کد شیپ‌شده نمی‌خواند).
---
## پنل ادمین `clinicpro` — React 19 + Webpack Encore + Tailwind v4
### توکن‌ها — هرگز مقدار hard-code
منبع: `assets/admin/styles.css`، بلوک `:root`.
```tsx
// ❌
<div style={{ background: '#5559CE', borderRadius: 14 }}>
// ✅
<div className="bg-[var(--primary)] rounded-[var(--r)]">
```
| گروه | توکن |
|---|---|
| برند | `--primary` `#5559CE` · `--primary-600/700` · `--primary-soft/soft2` · `--on-primary` |
| اکسنت | `--accent` `#f0682a` · `--accent-600` · `--accent-bg` |
| سطوح | `--bg` `--bg-2` `--surface` `--surface-2/3` `--border` `--border-2` |
| متن | `--text` `--text-2` `--text-3` |
| وضعیت | `--success/-bg` `--warning/-bg` `--danger/-bg` `--info/-bg` `--violet/-bg` |
| کارت آمار | `--stat-{amber,violet,green,pink}-{bg,fg}` |
| شعاع | `--r-xs:7` `--r-sm:8` `--r:14` `--r-lg:18` `--r-xl:24` `--r-pill:999` |
| سایه | `--shadow-sm` `--shadow` `--shadow-lg` |
| چیدمان | `--sidebar-w:243` `--collapsed-w:90` `--topbar-h:64` `--gap:20` `--card-pad:22` `--row-h:56` |
| حرکت | `--ease: cubic-bezier(.22,.61,.36,1)` |
دارک‌مود با `[data-theme="dark"]` و حالت فشرده با `[data-density="compact"]` خودکار
اعمال می‌شوند — **اگر** از توکن استفاده کرده باشی. مقدار hard-code در دارک‌مود می‌شکند.
### کامپوننت‌ها — اول جست‌وجو، بعد ساخت
`assets/admin/components/ui/` این‌ها را دارد. ساختن نسخهٔ موازی از هر کدام **رد** می‌شود:
```
DataTable (مرتب‌سازی، جستجو، skeleton، empty state، bulk) · Modal · ConfirmDialog
PageHeader (عنوان + breadcrumb + action + backTo) · BackButton · StatCard · StatusBadge
Pagination · SearchableSelect · AppointmentStatusDropdown
PersianDateInput / PersianDatePicker / PersianCalendar
MobileInput · PriceInput · Portal · FeatureGate · Altcha
```
### پنج قاعدهٔ غیرقابل‌مذاکره
1. **`SearchableSelect`، هرگز `<select>` بومی.** بی‌استثنا.
2. **دکمهٔ بازگشت در هر زیرصفحه.** صفحه‌ای که از دل صفحهٔ دیگر باز می‌شود:
- با `PageHeader` → فقط `backTo="/admin/…"` بده
- بی `PageHeader``<BackButton fallback="/admin/…" />` بالای هدر
- دکمهٔ دست‌ساز نساز. رفتار در `hooks/useGoBack.ts` متمرکز است.
3. **وضعیت لیست در URL، نه در `useState`.** جستجو، فیلتر، شمارهٔ صفحه، نما — همه با
`hooks/useUrlState.ts`:
```tsx
const [urlState, setUrlState] = useUrlState({ page: '1', search: '', status: '' });
const setSearch = (v: string) => setUrlState({ search: v, page: '1' });
```
دلیلش «بازگشت» است: `navigate(-1)` همان URL را برمی‌گرداند. فیلد جستجوی debounce‌شده
local می‌ماند، فقط مقدار نهایی به URL می‌رود.
4. **داده فقط با TanStack Query.** `useQuery`/`useMutation`، کلید `['resource', page, filters]
همهٔ HTTP از `lib/api.ts`. استخراج: paginated → `data?.data` + `data?.meta?.totalRecords
single → `data?.data`؛ Category → `data?.data?.data ?? []`.
5. **فرم با React Hook Form + Zod.** `z.object({...})` و تایپ با `z.infer`.
### فارسی، RTL، شمسی
- همهٔ رشته‌های UI فارسی — از فایل i18n، نه inline
- تاریخ‌ها شمسی با `formatDate`/`formatDateTime` از `lib/utils.ts` (پشت‌صحنه `jalaali-js`)
- مبالغ با `formatRial`/`formatNumber`
- جهت RTL — `ms-*`/`me-*` به‌جای `ml-*`/`mr-*`
- فونت `Vazirmatn` از `@fontsource/vazirmatn`
- آیکن‌ها Heroicons v2 · نمودار Recharts · تُست `sonner`
### نام‌گذاری و مسیر
- صفحه: `assets/admin/pages/XxxPage.tsx` · کامپوننت PascalCase · هوک `useXxx.ts`
- کامپوننت عمومی → `components/ui/` · کامپوزیت مخصوص فیچر → `components/*.tsx`
- تایپ‌های مشترک → `types/index.ts`
- مسیر در `App.tsx` (React Router v7)، زیر `/admin/*`
---
## سایت عمومی `nobat724_front` — Next.js 15 App Router + MUI v5 + Tailwind
- تم MUI در `mui/index.js` با `direction: rtl` و فونت Vazir — تم جدید نساز
- Tailwind با `darkMode: "class"`؛ صفحات عمومی `data-theme`، پنل `class`
(`next-themes` در `app/Providers.js`)
- **فونت فقط Vazir** — در `app/globals.css` با `@font-face`. فونت دیگر اضافه نکن.
- هر صفحه باید `generateMetadata` صادر کند · همیشه `await params`
- شهر از subdomain: server-side `lib/getStateInfo.js` · client-side `useProvince()`
- فراخوانی API: `services/response.js` → `request.*`؛ برای auth `{ requireAuth: true }`
- داده server-side: `lib/req.js` → `fetchReq(url)`
- slug پزشک/کلینیک = `uuid`
- JSON-LD مستقیم در JSX صفحات doctor/clinic/blog
- تاریخ شمسی با `jalali-moment` / `dayjs`
- کامپوننت‌های موجود `components/appointment/*` را توسعه بده، مسیر موازی نساز
---
## چک‌لیست UI — در `checklist.md` هر تسکی که صفحه یا کامپوننت می‌سازد
هر ردیف باید یکی از ✅ / 🔄 / ⏳ / ⚠️ بگیرد:
```
□ هیچ رنگ/شعاع/سایهٔ hard-code نیست — همه از توکن‌های styles.css
□ دارک‌مود بررسی شد (data-theme="dark") و چیزی نمی‌شکند
□ حالت فشرده بررسی شد (data-density="compact")
□ همهٔ select ها SearchableSelect اند، هیچ <select> بومی نیست
□ زیرصفحه‌ها backTo یا <BackButton /> دارند
□ وضعیت لیست (جستجو/فیلتر/صفحه) در URL است با useUrlState
□ لیست‌ها از DataTable استفاده می‌کنند با skeleton و empty state فارسی
□ هیچ کامپوننت موازیِ چیزی که در components/ui/ هست ساخته نشد
□ همهٔ رشته‌ها فارسی و از i18n
□ تاریخ‌ها شمسی با formatDate · مبالغ با formatRial
□ RTL بررسی شد (ms/me نه ml/mr)
□ موبایل بررسی شد (بدون اسکرول افقی)
□ فرم‌ها با React Hook Form + Zod
□ داده با TanStack Query و استخراج envelope درست
□ خطاها با پیام فارسی از ErrorCodes نمایش داده می‌شوند
```