From 09c393a574c275a70c6ab8b32dce82d868c8e6ea Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Sat, 13 Jun 2026 18:47:26 +0330 Subject: [PATCH] feat: update primaryRole handling in authStore and adjust context role for clinics --- .claude/prompt/fix-switch-context-role.md | 168 ++++++++++++++++++ assets/admin/pages/DoctorDetailPage.tsx | 4 +- assets/admin/stores/authStore.ts | 7 +- config/packages/lexik_jwt_authentication.yaml | 2 +- docs/api/auth.md | 12 +- src/Auth/Controller/AuthController.php | 2 +- 6 files changed, 186 insertions(+), 9 deletions(-) create mode 100644 .claude/prompt/fix-switch-context-role.md diff --git a/.claude/prompt/fix-switch-context-role.md b/.claude/prompt/fix-switch-context-role.md new file mode 100644 index 00000000..0a0e76e5 --- /dev/null +++ b/.claude/prompt/fix-switch-context-role.md @@ -0,0 +1,168 @@ +# رفع باگ تغییر محیط کاری — primaryRole آپدیت نمی‌شود + +## زمینه + +دکتری که عضو چند کلینیک است، در صفحه «انتخاب محیط کاری» می‌تواند بین مطب شخصی و کلینیک‌های مختلف سوئیچ کند. اما بعد از سوئیچ، `primaryRole` در authStore تغییر نمی‌کند — هنوز `'doctor'` می‌ماند. نتیجه: وقتی به `/admin/profile` می‌رود، کد `uuid = dbUuid` (که حالا uuid کلینیک است) را به `GET /api/v1/doctor/{clinicUuid}` می‌فرستد → 404. + +## مشکل / هدف + +وقتی `switchContext` صدا زده می‌شود: +- `dbUuid` در store تغییر می‌کند (به uuid کلینیک) +- اما `primaryRole` تغییر **نمی‌کند** (هنوز `'doctor'` می‌ماند) +- `context.role` در store ذخیره می‌شود اما از آن برای آپدیت `primaryRole` استفاده نمی‌شود + +## فایل‌های مرتبط + +| فایل | نقش | +|------|-----| +| `src/Auth/Controller/AuthController.php` | `switchContext` endpoint — خط 571 | +| `assets/admin/stores/authStore.ts` | `switchContext` action — خط 105 | +| `assets/admin/pages/DoctorDetailPage.tsx` | `uuid = isOwnProfile ? dbUuid : paramUuid` — خط 2162 | +| `assets/admin/pages/SelectContextPage.tsx` | `handleSelect` — خط 23 | + +## وضعیت فعلی + +### Backend — `switchContext` response (خط 595-599) + +```php +return $this->success([ + 'db_uuid' => $dbUuid, + 'db_key' => $this->buildDbKey($dbUuid), + 'context' => $matched, +]); +``` + +`$matched` از `buildAvailableContexts()` می‌آید و دارای فیلد `role` است: + +```php +// برای context کلینیک‌هایی که دکتر در آن‌ها عضو است: +$contexts[] = [ + 'type' => 'clinic', + 'db_uuid' => $clinic->getUuid(), + 'name' => $clinic->getName() ?? '', + 'role' => 'doctor', // ← نه 'clinic'، چون دکتر است نه صاحب کلینیک +]; + +// برای context مطب شخصی: +$contexts[] = [ + 'type' => 'doctor', + 'db_uuid' => $doctor->getUuid(), + 'name' => 'مطب شخصی ...', + 'role' => 'doctor', +]; +``` + +پس `context.role` موجود است اما backend آن را به عنوان `primary_role` جداگانه برنمی‌گرداند. + +### Frontend — `switchContext` در authStore (خط 105-117) + +```ts +switchContext: async (dbUuid: string) => { + const res = await apiFetch('/api/v1/auth/switch-context', { + method: 'POST', + body: JSON.stringify({ db_uuid: dbUuid }), + }); + if (res.success) { + set({ + dbUuid: res.data.db_uuid, + dbKey: res.data.db_key, + context: res.data.context, + // ← primaryRole آپدیت نمی‌شود! + }); + } +}, +``` + +### Frontend — DoctorDetailPage (خط 2162) + +```tsx +const uuid = isOwnProfile ? (dbUuid ?? undefined) : paramUuid; +// وقتی context کلینیک است، dbUuid = uuid کلینیک است +// اما primaryRole هنوز 'doctor' است +// پس GET /api/v1/doctor/{clinicUuid} → 404 +``` + +## وظایف + +### ۱. Frontend — آپدیت `primaryRole` در `switchContext` + +در `assets/admin/stores/authStore.ts`، خط 110-115، `primaryRole` را از `res.data.context.role` بخوان و set کن: + +```ts +switchContext: async (dbUuid: string) => { + const res = await apiFetch('/api/v1/auth/switch-context', { + method: 'POST', + body: JSON.stringify({ db_uuid: dbUuid }), + }); + if (res.success) { + set({ + dbUuid: res.data.db_uuid, + dbKey: res.data.db_key, + context: res.data.context, + primaryRole: res.data.context?.role ?? null, // ← اضافه شود + }); + } +}, +``` + +### ۲. Backend — بررسی `role` در context کلینیک‌های عضو دکتر + +در `buildAvailableContexts()` (خط 626-633)، وقتی دکتر به context یک کلینیک سوئیچ می‌کند، role باید `'clinic'` باشد نه `'doctor'` — چون در آن محیط باید به عنوان نماینده کلینیک عمل کند: + +```php +foreach ($this->clinicRepo->findByDoctor($doctor) as $clinic) { + $contexts[] = [ + 'type' => 'clinic', + 'db_uuid' => $clinic->getUuid(), + 'name' => $clinic->getName() ?? '', + 'role' => 'clinic', // ← از 'doctor' به 'clinic' تغییر دهید + ]; +} +``` + +**نکته مهم:** این تغییر باعث می‌شود وقتی دکتر در context کلینیک است، `primaryRole === 'clinic'` شود. در `DoctorDetailPage` این رفتار درست است — read-only view کلینیک فعال می‌شود و از `GET /api/v1/clinic/my-doctor/{uuid}` استفاده می‌کند. + +### ۳. رفع مشکل `/admin/profile` برای context کلینیک + +در `DoctorDetailPage.tsx` خط 2162: + +```tsx +const uuid = isOwnProfile ? (dbUuid ?? undefined) : paramUuid; +``` + +وقتی `isOwnProfile = true` و `primaryRole === 'clinic'`، صفحه profile پزشک منطقی نیست — دکتر در حال مشاهده به عنوان نماینده کلینیک است. دو گزینه: + +**گزینه A (توصیه‌شده):** در `DoctorProfilePage.tsx` و route `/admin/profile`، وقتی `primaryRole === 'clinic'`، به dashboard هدایت کن: + +```tsx +// assets/admin/pages/DoctorProfilePage.tsx +export default function DoctorProfilePage() { + const primaryRole = useAuthStore(s => s.primaryRole); + const navigate = useNavigate(); + + useEffect(() => { + if (primaryRole === 'clinic') navigate('/admin/dashboard', { replace: true }); + }, [primaryRole, navigate]); + + return ; +} +``` + +**گزینه B:** در `App.tsx` route `profile` را فقط به role `doctor` محدود کن: + +```tsx +// App.tsx — خط 142 +} /> +// این از قبل هست — مشکل این است که primaryRole درست آپدیت نمی‌شود +``` + +گزینه B کافی است اگر وظیفه ۱ و ۲ درست اجرا شوند — بعد از switch، `primaryRole === 'clinic'` می‌شود و `RoleRoute` کاربر را به dashboard redirect می‌کند. + +## نکات مهم + +- **migration لازم نیست** — هیچ entity تغییر نمی‌کند +- **`context.role` از قبل در response موجود است** — backend فقط نیاز به بررسی مقدار دارد، endpoint تغییر نمی‌کند +- بعد از اصلاح، context کلینیک‌های عضو دکتر `role: 'clinic'` خواهند داشت — این با guard های `primaryRole === 'clinic'` در DoctorDetailPage سازگار است +- `cache:clear` بعد از تغییر backend لازم است +- `yarn dev` بعد از تغییر frontend لازم است +- مستندات: `docs/api/auth.md` — توضیح رفتار `context.role` در `switch-context` response به‌روزرسانی شود diff --git a/assets/admin/pages/DoctorDetailPage.tsx b/assets/admin/pages/DoctorDetailPage.tsx index 14583b32..4cef9a10 100644 --- a/assets/admin/pages/DoctorDetailPage.tsx +++ b/assets/admin/pages/DoctorDetailPage.tsx @@ -1783,11 +1783,11 @@ function ScheduleSection({ doctorUuid }: { doctorUuid: string }) { const locationsQ = useQuery({ queryKey: ['available-locations', doctorUuid], - queryFn: () => api.get>(`/api/v1/appointment-settings/available-locations/${doctorUuid}`), + queryFn: () => api.get>(`/api/v1/appointment-settings/available-locations/${doctorUuid}`), enabled: !!doctorUuid, staleTime: 30_000, }); - const availableLocations: AddressData[] = locationsQ.data?.data ?? []; + const availableLocations: AddressData[] = locationsQ.data?.data?.data ?? locationsQ.data?.data ?? []; return (
diff --git a/assets/admin/stores/authStore.ts b/assets/admin/stores/authStore.ts index f72aa931..36fbdb8b 100644 --- a/assets/admin/stores/authStore.ts +++ b/assets/admin/stores/authStore.ts @@ -109,9 +109,10 @@ export const useAuthStore = create()( }); if (res.success) { set({ - dbUuid: res.data.db_uuid, - dbKey: res.data.db_key, - context: res.data.context, + dbUuid: res.data.db_uuid, + dbKey: res.data.db_key, + context: res.data.context, + primaryRole: res.data.context?.role ?? null, }); } }, diff --git a/config/packages/lexik_jwt_authentication.yaml b/config/packages/lexik_jwt_authentication.yaml index dd7a3aa5..11fddbfd 100644 --- a/config/packages/lexik_jwt_authentication.yaml +++ b/config/packages/lexik_jwt_authentication.yaml @@ -2,4 +2,4 @@ lexik_jwt_authentication: secret_key: '%env(resolve:JWT_SECRET_KEY)%' public_key: '%env(resolve:JWT_PUBLIC_KEY)%' pass_phrase: '%env(JWT_PASSPHRASE)%' - token_ttl: 3600 + token_ttl: 604800 diff --git a/docs/api/auth.md b/docs/api/auth.md index 052f5808..966e9dc8 100644 --- a/docs/api/auth.md +++ b/docs/api/auth.md @@ -265,7 +265,7 @@ Authorization: Bearer "type": "clinic", "db_uuid": "clinic-uuid-...", "name": "کلینیک سلامت", - "role": "doctor" + "role": "clinic" } ] } @@ -287,6 +287,14 @@ Authorization: Bearer - `ROLE_SECRETARY` → `"secretary"` - بقیه → `"user"` +**قانون `context.role`** — نقشی که در آن محیط کاری فعال است: +- context مطب شخصی دکتر: `"doctor"` +- context کلینیک که دکتر **عضو** آن است: `"clinic"` (read-only — دسترسی به اطلاعات کلینیک) +- context کلینیک که دکتر **صاحب** آن است: `"clinic"` +- context منشی: `"secretary"` + +> **نکته frontend:** پس از `switchContext`، `primaryRole` در store باید از `context.role` آپدیت شود. + **قانون `db_uuid`**: - اگر یک context وجود دارد: خودکار فعال می‌شود - اگر چند context وجود دارد و کاربر هنوز انتخاب نکرده: `null` — frontend باید صفحه انتخاب نشان دهد @@ -327,7 +335,7 @@ Authorization: Bearer "type": "clinic", "db_uuid": "clinic-uuid-...", "name": "کلینیک سلامت", - "role": "doctor" + "role": "clinic" } } } diff --git a/src/Auth/Controller/AuthController.php b/src/Auth/Controller/AuthController.php index 4e41fb60..c029865c 100644 --- a/src/Auth/Controller/AuthController.php +++ b/src/Auth/Controller/AuthController.php @@ -628,7 +628,7 @@ class AuthController extends BaseController 'type' => 'clinic', 'db_uuid' => $clinic->getUuid(), 'name' => $clinic->getName() ?? '', - 'role' => 'doctor', + 'role' => 'clinic', ]; } }