feat: add doctor_uuid support in authStore and update DoctorDetailPage to prioritize doctorUuid
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
# رفع باگ پروفایل دکتر در context کلینیک — doctor_uuid با clinic_uuid اشتباه گرفته میشود
|
||||
|
||||
## زمینه
|
||||
|
||||
دکتری که عضو چند کلینیک است، در صفحه «انتخاب محیط کاری» میتواند به context کلینیک سوئیچ کند. بعد از سوئیچ، `dbUuid` در authStore برابر **uuid کلینیک** میشود. وقتی دکتر به `/admin/profile` میرود، `DoctorProfilePage` از `dbUuid` به عنوان uuid دکتر استفاده میکند و همه API callها با uuid اشتباه صدا زده میشوند:
|
||||
|
||||
- `GET /api/v1/doctor/c9c6a393-...` (uuid کلینیک) → 404
|
||||
- `GET /api/v1/appointment-slots?doctor_uuid=c9c6a393-...` → 404
|
||||
|
||||
کاربر تست: `09123456783` — دکتر `8b066a5e-3742-4bf7-8087-0b1f8d98de08` (uuid دکتر) که عضو کلینیک `c9c6a393-9182-4388-8cdc-6423026eeb54` است.
|
||||
|
||||
## مشکل / هدف
|
||||
|
||||
`dbUuid` دو معنی متفاوت دارد:
|
||||
- در context مطب شخصی: uuid دکتر ✓
|
||||
- در context کلینیک: uuid کلینیک ✗ (اما `DoctorProfilePage` آن را uuid دکتر فرض میکند)
|
||||
|
||||
راهحل: `doctor_uuid` را به عنوان فیلد جداگانه در `userinfo` و `switch-context` response اضافه کن و در store ذخیره کن. `DoctorProfilePage` باید از `doctorUuid` استفاده کند نه `dbUuid`.
|
||||
|
||||
## فایلهای مرتبط
|
||||
|
||||
| فایل | نقش |
|
||||
|------|-----|
|
||||
| `src/Auth/Controller/AuthController.php` | `userInfo()` خط 516، `switchContext()` خط 571 |
|
||||
| `assets/admin/stores/authStore.ts` | `fetchMe()` و `switchContext()` — خط 86 و 105 |
|
||||
| `assets/admin/pages/DoctorDetailPage.tsx` | `uuid = isOwnProfile ? (dbUuid ?? undefined) : paramUuid` — خط 2162 |
|
||||
| `assets/admin/pages/DoctorProfilePage.tsx` | wrapper که `isOwnProfile` را به DoctorDetailPage پاس میدهد |
|
||||
|
||||
## وضعیت فعلی
|
||||
|
||||
### Backend — `userInfo()` response (خط 536-548)
|
||||
|
||||
```php
|
||||
return $this->success([
|
||||
'uuid' => $user->getUuid(),
|
||||
'primary_role' => $primaryRole,
|
||||
'db_uuid' => $dbUuid, // ← وقتی context کلینیک است، این uuid کلینیک است
|
||||
'context' => $context,
|
||||
'available_contexts' => $availableContexts,
|
||||
// ← doctor_uuid وجود ندارد
|
||||
]);
|
||||
```
|
||||
|
||||
### Backend — `buildAvailableContexts()` (خط 614-661)
|
||||
|
||||
```php
|
||||
if ($doctor = $this->doctorRepo->findByUser($user)) {
|
||||
$contexts[] = [
|
||||
'type' => 'doctor',
|
||||
'db_uuid' => $doctor->getUuid(), // ← uuid دکتر در این context
|
||||
'role' => 'doctor',
|
||||
];
|
||||
foreach ($this->clinicRepo->findByDoctor($doctor) as $clinic) {
|
||||
$contexts[] = [
|
||||
'type' => 'clinic',
|
||||
'db_uuid' => $clinic->getUuid(), // ← uuid کلینیک در این context
|
||||
'role' => 'clinic',
|
||||
// ← doctor_uuid ندارد
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend — `authStore.ts` (خط 19، 94-95)
|
||||
|
||||
```ts
|
||||
interface AuthState {
|
||||
dbUuid: string | null; // ← فقط یک uuid — در context کلینیک، uuid کلینیک است
|
||||
// ← doctorUuid وجود ندارد
|
||||
}
|
||||
|
||||
// fetchMe:
|
||||
set({
|
||||
dbUuid: d.db_uuid ?? null, // ← uuid کلینیک یا دکتر بسته به context
|
||||
// ← doctorUuid set نمیشود
|
||||
});
|
||||
```
|
||||
|
||||
### Frontend — `DoctorDetailPage.tsx` (خط 2162)
|
||||
|
||||
```tsx
|
||||
const uuid = isOwnProfile ? (dbUuid ?? undefined) : paramUuid;
|
||||
// ← وقتی context کلینیک است: uuid = uuid کلینیک → همه API ها با uuid اشتباه صدا زده میشوند
|
||||
```
|
||||
|
||||
## وظایف
|
||||
|
||||
### ۱. Backend — اضافه کردن `doctor_uuid` به response های auth
|
||||
|
||||
در `src/Auth/Controller/AuthController.php`:
|
||||
|
||||
**الف) `userInfo()` (خط 536):** فیلد `doctor_uuid` را اضافه کن:
|
||||
|
||||
```php
|
||||
$doctor = $this->doctorRepo->findByUser($user);
|
||||
|
||||
return $this->success([
|
||||
'id' => $user->getId(),
|
||||
'uuid' => $user->getUuid(),
|
||||
'mobile_number' => $user->getMobileNumber(),
|
||||
'realName' => $user->getRealName(),
|
||||
'status' => $user->getStatus(),
|
||||
'roles' => $user->getRoles(),
|
||||
'primary_role' => $primaryRole,
|
||||
'db_uuid' => $dbUuid,
|
||||
'db_key' => $dbKey,
|
||||
'doctor_uuid' => $doctor?->getUuid(), // ← اضافه شود
|
||||
'context' => $context,
|
||||
'available_contexts' => $availableContexts,
|
||||
]);
|
||||
```
|
||||
|
||||
**ب) `buildAvailableContexts()` (خط 626):** `doctor_uuid` را به context های کلینیک اضافه کن:
|
||||
|
||||
```php
|
||||
foreach ($this->clinicRepo->findByDoctor($doctor) as $clinic) {
|
||||
$contexts[] = [
|
||||
'type' => 'clinic',
|
||||
'db_uuid' => $clinic->getUuid(),
|
||||
'name' => $clinic->getName() ?? '',
|
||||
'role' => 'clinic',
|
||||
'doctor_uuid' => $doctor->getUuid(), // ← اضافه شود
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
**ج) `switchContext()` response (خط 595):** بدون تغییر — `context` کامل برمیگردد و شامل `doctor_uuid` خواهد بود.
|
||||
|
||||
### ۲. Frontend — اضافه کردن `doctorUuid` به authStore
|
||||
|
||||
در `assets/admin/stores/authStore.ts`:
|
||||
|
||||
**الف) `ContextItem` interface (خط 4):**
|
||||
|
||||
```ts
|
||||
export interface ContextItem {
|
||||
type: 'doctor' | 'clinic';
|
||||
db_uuid: string;
|
||||
name: string;
|
||||
role: 'admin' | 'clinic' | 'doctor' | 'secretary' | 'user';
|
||||
doctor_uuid?: string; // ← اضافه شود — فقط در context های کلینیک
|
||||
permissions?: Record<string, any>;
|
||||
}
|
||||
```
|
||||
|
||||
**ب) `AuthState` interface (خط 12):**
|
||||
|
||||
```ts
|
||||
interface AuthState {
|
||||
// ...
|
||||
dbUuid: string | null;
|
||||
doctorUuid: string | null; // ← اضافه شود — همیشه uuid دکتر، بدون توجه به context
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**ج) مقدار اولیه (خط 62):**
|
||||
|
||||
```ts
|
||||
dbUuid: null,
|
||||
doctorUuid: null, // ← اضافه شود
|
||||
```
|
||||
|
||||
**د) `logout` (خط 72):**
|
||||
|
||||
```ts
|
||||
doctorUuid: null, // ← اضافه شود
|
||||
```
|
||||
|
||||
**ه) `fetchMe` (خط 91):**
|
||||
|
||||
```ts
|
||||
set({
|
||||
dbUuid: d.db_uuid ?? null,
|
||||
doctorUuid: d.doctor_uuid ?? null, // ← اضافه شود
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
**و) `switchContext` (خط 111):**
|
||||
|
||||
```ts
|
||||
set({
|
||||
dbUuid: res.data.db_uuid,
|
||||
doctorUuid: res.data.context?.doctor_uuid ?? get().doctorUuid, // ← context کلینیک doctor_uuid دارد، بقیه همان قبلی
|
||||
dbKey: res.data.db_key,
|
||||
context: res.data.context,
|
||||
primaryRole: res.data.context?.role ?? null,
|
||||
});
|
||||
```
|
||||
|
||||
**ز) `partialize` (خط 121):**
|
||||
|
||||
```ts
|
||||
partialize: (s) => ({
|
||||
// ...
|
||||
doctorUuid: s.doctorUuid, // ← اضافه شود
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
### ۳. Frontend — استفاده از `doctorUuid` در `DoctorDetailPage`
|
||||
|
||||
در `assets/admin/pages/DoctorDetailPage.tsx` خط 2160-2162:
|
||||
|
||||
```tsx
|
||||
const primaryRole = useAuthStore(s => s.primaryRole);
|
||||
const dbUuid = useAuthStore(s => s.dbUuid);
|
||||
const doctorUuid = useAuthStore(s => s.doctorUuid); // ← اضافه شود
|
||||
|
||||
// تغییر خط 2162:
|
||||
const uuid = isOwnProfile
|
||||
? (doctorUuid ?? dbUuid ?? undefined) // ← doctorUuid اولویت دارد
|
||||
: paramUuid;
|
||||
```
|
||||
|
||||
این تضمین میکند که حتی در context کلینیک، uuid دکتر برای همه API callها استفاده شود.
|
||||
|
||||
## نکات مهم
|
||||
|
||||
- **migration لازم نیست** — هیچ entity تغییر نمیکند
|
||||
- `doctorUuid` فقط برای کاربرانی که role دکتر دارند پر میشود — برای admin و clinic-owner خالص `null` است
|
||||
- `get().doctorUuid` در `switchContext` — اگر context جدید `doctor_uuid` نداشت (مثلاً مطب شخصی که `db_uuid` خودش uuid دکتر است)، مقدار قبلی حفظ شود
|
||||
- وقتی context مطب شخصی دکتر است (`type: 'doctor'`)، `db_uuid` خودش uuid دکتر است — `doctor_uuid` جداگانه نیاز نیست اما خوب است همان مقدار ست شود
|
||||
- بعد از تغییر backend: `cache:clear`
|
||||
- بعد از تغییر frontend: `yarn dev` و TypeScript check
|
||||
- مستندات: `docs/api/auth.md` را با فیلد `doctor_uuid` در response های `userinfo` و `switch-context` بهروزرسانی کن
|
||||
- کاربر تست: `09123456783` — doctor_uuid: `8b066a5e-3742-4bf7-8087-0b1f8d98de08`
|
||||
@@ -2159,7 +2159,8 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
const qc = useQueryClient();
|
||||
const primaryRole = useAuthStore(s => s.primaryRole);
|
||||
const dbUuid = useAuthStore(s => s.dbUuid);
|
||||
const uuid = isOwnProfile ? (dbUuid ?? undefined) : paramUuid;
|
||||
const doctorUuid = useAuthStore(s => s.doctorUuid);
|
||||
const uuid = isOwnProfile ? (doctorUuid ?? dbUuid ?? undefined) : paramUuid;
|
||||
|
||||
const [editOpen, setEditOpen] = useState(searchParams.get('edit') === '1');
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface ContextItem {
|
||||
db_uuid: string;
|
||||
name: string;
|
||||
role: 'admin' | 'clinic' | 'doctor' | 'secretary' | 'user';
|
||||
doctor_uuid?: string;
|
||||
permissions?: Record<string, any>;
|
||||
}
|
||||
|
||||
@@ -18,6 +19,7 @@ interface AuthState {
|
||||
primaryRole: 'admin' | 'clinic' | 'doctor' | 'secretary' | 'user' | null;
|
||||
dbUuid: string | null;
|
||||
dbKey: string | null;
|
||||
doctorUuid: string | null;
|
||||
context: ContextItem | null;
|
||||
availableContexts: ContextItem[];
|
||||
|
||||
@@ -61,6 +63,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
primaryRole: null,
|
||||
dbUuid: null,
|
||||
dbKey: null,
|
||||
doctorUuid: null,
|
||||
context: null,
|
||||
availableContexts: [],
|
||||
|
||||
@@ -79,6 +82,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
primaryRole: null,
|
||||
dbUuid: null,
|
||||
dbKey: null,
|
||||
doctorUuid: null,
|
||||
context: null,
|
||||
availableContexts: [],
|
||||
}),
|
||||
@@ -94,6 +98,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
primaryRole: d.primary_role ?? null,
|
||||
dbUuid: d.db_uuid ?? null,
|
||||
dbKey: d.db_key ?? null,
|
||||
doctorUuid: d.doctor_uuid ?? null,
|
||||
context: d.context ?? null,
|
||||
availableContexts: d.available_contexts ?? [],
|
||||
});
|
||||
@@ -111,6 +116,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
set({
|
||||
dbUuid: res.data.db_uuid,
|
||||
dbKey: res.data.db_key,
|
||||
doctorUuid: res.data.context?.doctor_uuid ?? get().doctorUuid,
|
||||
context: res.data.context,
|
||||
primaryRole: res.data.context?.role ?? null,
|
||||
});
|
||||
@@ -128,6 +134,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
primaryRole: s.primaryRole,
|
||||
dbUuid: s.dbUuid,
|
||||
dbKey: s.dbKey,
|
||||
doctorUuid: s.doctorUuid,
|
||||
context: s.context,
|
||||
availableContexts: s.availableContexts,
|
||||
}),
|
||||
|
||||
+6
-2
@@ -248,6 +248,7 @@ Authorization: Bearer <token>
|
||||
"primary_role": "doctor",
|
||||
"db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
|
||||
"db_key": "hmac-sha256-hash...",
|
||||
"doctor_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
|
||||
"context": {
|
||||
"type": "doctor",
|
||||
"db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
|
||||
@@ -265,7 +266,8 @@ Authorization: Bearer <token>
|
||||
"type": "clinic",
|
||||
"db_uuid": "clinic-uuid-...",
|
||||
"name": "کلینیک سلامت",
|
||||
"role": "clinic"
|
||||
"role": "clinic",
|
||||
"doctor_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -277,6 +279,7 @@ Authorization: Bearer <token>
|
||||
| `primary_role` | string | نقش اصلی: `admin` \| `clinic` \| `doctor` \| `secretary` \| `user` |
|
||||
| `db_uuid` | string\|null | UUID موجودیت فعال (null = هنوز context انتخاب نشده) |
|
||||
| `db_key` | string\|null | `HMAC-SHA256(db_uuid, APP_SECRET)` برای اعتبارسنجی |
|
||||
| `doctor_uuid` | string\|null | UUID دکتر — ثابت است حتی در context کلینیک که `db_uuid` برابر UUID کلینیک است. برای کاربران غیر دکتر: `null` |
|
||||
| `context` | object\|null | context فعال انتخابشده |
|
||||
| `available_contexts` | array | همه محیطهای کاری قابل انتخاب |
|
||||
|
||||
@@ -335,7 +338,8 @@ Authorization: Bearer <token>
|
||||
"type": "clinic",
|
||||
"db_uuid": "clinic-uuid-...",
|
||||
"name": "کلینیک سلامت",
|
||||
"role": "clinic"
|
||||
"role": "clinic",
|
||||
"doctor_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,6 +532,7 @@ class AuthController extends BaseController
|
||||
$dbUuid = $activeCtx?->getDbUuid();
|
||||
$dbKey = $dbUuid !== null ? $this->buildDbKey($dbUuid) : null;
|
||||
$context = $dbUuid !== null ? $this->findContextByDbUuid($dbUuid, $availableContexts) : null;
|
||||
$doctor = $this->doctorRepo->findByUser($user);
|
||||
|
||||
return $this->success([
|
||||
'id' => $user->getId(),
|
||||
@@ -543,6 +544,7 @@ class AuthController extends BaseController
|
||||
'primary_role' => $primaryRole,
|
||||
'db_uuid' => $dbUuid,
|
||||
'db_key' => $dbKey,
|
||||
'doctor_uuid' => $doctor?->getUuid(),
|
||||
'context' => $context,
|
||||
'available_contexts' => $availableContexts,
|
||||
]);
|
||||
@@ -625,10 +627,11 @@ class AuthController extends BaseController
|
||||
];
|
||||
foreach ($this->clinicRepo->findByDoctor($doctor) as $clinic) {
|
||||
$contexts[] = [
|
||||
'type' => 'clinic',
|
||||
'db_uuid' => $clinic->getUuid(),
|
||||
'name' => $clinic->getName() ?? '',
|
||||
'role' => 'clinic',
|
||||
'type' => 'clinic',
|
||||
'db_uuid' => $clinic->getUuid(),
|
||||
'name' => $clinic->getName() ?? '',
|
||||
'role' => 'clinic',
|
||||
'doctor_uuid' => $doctor->getUuid(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user