# رفع باگ دسترسی کاملِ پزشکِ مهمان در محیط کلینیک

## پروژه

`clinicpro` (Admin React SPA + گارد Backend). ادامه/تکمیلِ پرامپت قبلی `fix-access-scoping-doctor-clinic-representation.md`.

## زمینه

در پرامپت قبلی، نقشِ context پزشکی که به کلینیک دعوت می‌شود از `clinic` به `doctor` + `scope: clinic` تغییر کرد (در `buildAvailableContexts`). این درست بود ولی **ناقص**: هیچ‌جای فرانت و بک‌اند به فیلد `scope` توجه نمی‌کند. در نتیجه وقتی پزشکِ مهمان به محیط کاریِ آن کلینیک سوییچ می‌کند (`db_uuid = clinicUuid`)، همان منوی کاملِ نقش `doctor` را می‌بیند — شامل **پرسنل، منشی‌ها، سرویس‌ها، اشتراک، پرونده بیماران** — و عملاً به امکانات مدیریتیِ محیط کلینیک دسترسی دارد. این یعنی باگ «پزشکِ اضافه‌شده به کلینیک، دسترسی به همه‌ی امکانات کلینیک دارد» هنوز باقی است.

رفتار درست: پزشکِ مهمان در محیط کلینیک فقط باید **نوبت‌های خودش در آن کلینیک** را ببیند (read/منظوره‌ی محدود)، نه ابزارهای مدیریت کلینیک. در محیط «مطب شخصی» خودش (context با `scope` خالی) همه‌ی ابزارها در دسترس می‌ماند.

## مشکل / هدف

`scope: 'clinic'` که روی context پزشکِ مهمان ست شده، باید واقعاً دسترسی را محدود کند — هم در منوی Sidebar، هم در RoleRouteهای SPA، و هم با گارد در endpointهای مدیریتی (defense in depth).

## فایل‌های مرتبط

| فایل | نقش |
|------|-----|
| `src/Auth/Controller/AuthController.php` | `buildAvailableContexts` (از قبل `scope: 'clinic'` می‌دهد) و `userinfo`/`switchContext` که `context` را برمی‌گرداند |
| `assets/admin/stores/authStore.ts` | نگه‌داری `context` فعال + `primaryRole`؛ باید `scope` در دسترس باشد |
| `assets/admin/components/layout/Sidebar.tsx` | منوی نقش `doctor` — باید در `scope: clinic` محدود شود |
| `assets/admin/App.tsx` | `RoleRoute` — صفحات مدیریتیِ پزشک باید در `scope: clinic` مسدود شوند |
| `src/Staff/Controller/StaffController.php` | `resolveEntity` پزشک = scope شخصی؛ گارد لازم برای حالت مهمان |
| `src/Secretary/Controller/SecretaryController.php` | همان منطق برای منشی‌ها |
| `src/ClinicService/Controller/*` (سرویس‌ها) | همان منطق |
| `docs/api/auth.md` | مستندسازی معنای `scope` |

## وضعیت فعلی (کد واقعی)

`buildAvailableContexts` — context مهمان دارای `scope` ولی بدون اثر:
```php
foreach ($this->clinicRepo->findByDoctor($doctor) as $clinic) {
    $isOwner = $clinic->getUser()->getId() === $user->getId();
    $contexts[] = [
        'type'        => 'clinic',
        'db_uuid'     => $clinic->getUuid(),
        'name'        => $clinic->getName() ?? '',
        'role'        => $isOwner ? 'clinic' : 'doctor',
        'scope'       => $isOwner ? null : 'clinic',   // ← ست می‌شود ولی هیچ‌جا مصرف نمی‌شود
        'doctor_uuid' => $doctor->getUuid(),
    ];
}
```

`Sidebar.tsx` — منوی پزشک، بدون توجه به scope (همه‌ی آیتم‌های مدیریتی نمایش داده می‌شوند):
```tsx
if (primaryRole === "doctor") {
    return [
        { label: "عمومی",  items: [{ to: "/admin/dashboard", ... }] },
        { label: "پروفایل", items: [{ to: "/admin/profile", ... }] },
        { label: "مدیریت", items: [
            { to: "/admin/appointments",   label: "نوبت‌های من" },
            { to: "/admin/my-patients",    label: "پرونده بیماران" },
            { to: "/admin/staff",          label: "پرسنل" },        // ← نباید برای مهمان باشد
            { to: "/admin/my-secretaries", label: "منشی ها" },      // ← نباید
            { to: "/admin/clinic-services",label: "سرویس‌ها" },      // ← نباید
            ...
        ]},
    ];
}
```

`authStore.ts` — context فعال نگه‌داری می‌شود (شامل `role` و باید `scope`):
```ts
primaryRole: res.data.context?.role ?? null,
context:     res.data.context,
```
(نوع `ContextItem` احتمالاً `scope?` ندارد — باید اضافه شود.)

## وظایف

### ۱. در دسترس‌بودن `scope` در authStore

- در `assets/admin/stores/authStore.ts`، type `ContextItem` فیلد `scope?: string | null` را داشته باشد و از `userinfo`/`switchContext` پر شود.
- یک selector/مقدار مشتق در store یا کامپوننت‌ها: `isClinicScopedDoctor = primaryRole === 'doctor' && context?.scope === 'clinic'`.

### ۲. محدودکردن منوی Sidebar برای پزشکِ مهمان

در `Sidebar.tsx`، شاخه‌ی `primaryRole === "doctor"`:
- اگر `context?.scope === 'clinic'` بود، فقط آیتم‌های مجاز را برگردان:
  - داشبورد (`/admin/dashboard`)
  - نوبت‌ها (`/admin/appointments`) — نوبت‌های پزشک در آن کلینیک
- آیتم‌های `staff`, `my-secretaries`, `clinic-services`, `subscription`, `my-patients`, `profile` (مدیریت/داده‌ی شخصیِ مطب) را در این حالت **نشان نده**.
- در محیط «مطب شخصی» (`scope` خالی) منوی کامل فعلی بماند.

### ۳. گارد RoleRoute در SPA برای صفحات مدیریتیِ مهمان

در `App.tsx`:
- یک گارد سبک اضافه کن (مثلاً گسترش `RoleRoute` یا یک wrapper `ClinicScopeBlock`) که اگر کاربر `doctor` با `context.scope === 'clinic'` است، مسیرهای `staff`, `my-secretaries`, `clinic-services`, `subscription`, `my-patients`, `profile` را به `/admin/dashboard` ریدایرکت کند.
- مسیرهای `dashboard` و `appointments` باز بمانند.

### ۴. گارد Backend (defense in depth)

حتی با محدودیت UI، endpointهای مدیریتی نباید به پزشکِ مهمان اجازه‌ی عمل روی کلینیک بدهند. الگوی موجود این است که `StaffController::resolveEntity` برای `ROLE_DOCTOR` همیشه scope **شخصیِ** پزشک را برمی‌گرداند (`['doctor', doctorId]`) — یعنی پزشک فقط پرسنل/سرویسِ مطب خودش را می‌بیند، نه کلینیک. این را **تأیید کن** و مطمئن شو هیچ endpoint مدیریتی، `db_uuid`/clinicUuid را از کلاینت گرفته و بدون چک مالکیت (`clinic.getUser() === currentUser`) روی کلینیک عمل نمی‌کند:
- `StaffController`, `SecretaryController`, `ClinicService` controllers, و `ClinicController` (update/address/logo) را مرور کن؛ هر مسیری که کلینیک را با `findByUuid($clinicUuid)` می‌گیرد باید مالکیت یا `ROLE_ADMIN` را چک کند (الگوی `$clinic->getUser()->getId() !== $user->getId() && !hasRole('ROLE_ADMIN')` → 403). اگر جایی این چک نیست، اضافه کن.
- نتیجه‌ی مورد انتظار: پزشکِ مهمان (نه مالک، نه ادمین) روی این endpointها `403` بگیرد.

### ۵. مستندسازی

- `docs/api/auth.md`: در توضیح `context`/`available_contexts`، معنای `scope: 'clinic'` را شرح بده: «پزشکِ مهمان در محیط کلینیک؛ فقط نوبت‌های خودش، بدون دسترسی مدیریتی به کلینیک». 

## نکات مهم

- **منبع حقیقتِ scope = context فعال**، نه `dbUuid` تنها. تمایز «مالک کلینیک» (`role: clinic`)، «پزشک در مطب شخصی» (`role: doctor`, بدون scope)، و «پزشک مهمان در کلینیک» (`role: doctor`, `scope: clinic`).
- در محیط مطب شخصی هیچ محدودیتی اضافه نشود؛ فقط `scope: clinic` محدود می‌شود.
- گارد UI برای UX است؛ **گارد واقعی Backend است** — هر دو لازم‌اند (وظیفه ۴ نباید رها شود).
- ادمین (`ROLE_ADMIN`) همیشه دسترسی کامل دارد؛ مالک کلینیک (`role: clinic`) هم بدون تغییر.
- بعد از تغییر backend، `cache:clear` و به‌روزرسانی `docs/api/auth.md`. تغییر فرانت با `tsc --noEmit` + `yarn dev` تست شود.
- **تست‌ها** (با `lexik:jwt:generate-token`):
  - پزشکِ مهمان → `userinfo` آن کلینیک `role:doctor, scope:clinic`؛ بعد از switch، Sidebar فقط «داشبورد» و «نوبت‌ها»؛ مسیرهای `/admin/staff` و `/admin/clinic-services` ریدایرکت به داشبورد.
  - همان پزشک روی endpoint مدیریتی کلینیک (مثلاً ساخت پرسنل برای آن کلینیک) → `403`.
  - مالک واقعی کلینیک → دسترسی کامل بدون تغییر.
  - پزشک در مطب شخصی → منوی کامل و دسترسی به staff/services خودش بدون تغییر.
