Files
clinicpro/.claude/prompt/pre-registration.md
T

213 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Pre-Registration Feature
## هدف
پیاده‌سازی سیستم پیش ثبت‌نام برای دکتر و کلینیک:
- روی صفحه اصلی (`/`) دو دکمه باشد:
- **ورود به پنل** → `/admin` (برای کسانی که قبلاً ثبت‌نام شده‌اند)
- **ثبت نام** → فرم پیش ثبت‌نام (فقط برای دکتر و کلینیک)
- دکتر/کلینیک فرم را پر می‌کند
- ادمین در پنل درخواست‌ها را می‌بیند و تأیید یا رد می‌کند
- پس از تأیید: کاربر و entity مربوطه ساخته می‌شود و نام‌کاربری+پسورد از طریق SMS ارسال می‌شود
---
## قابلیت‌ها
### قابلیت ۱ — Entity و Migration: PreRegistration
ایجاد Entity جدید `src/Auth/Entity/PreRegistration.php` با فیلدهای:
- `id` (int, PK)
- `uuid` (string, unique)
- `type` (string: `independent_doctor` | `doctor_with_clinic` | `clinic_manager`) — نوع حساب (توضیح در پایین)
- `name` (string, 255) — نام کامل
- `mobile` (string, 20) — شماره موبایل
- `info` (text, nullable) — توضیحات / اطلاعات اضافه (تخصص، سابقه، ...)
- `status` (string: `pending` | `approved` | `rejected`) — default: `pending`
- `adminNote` (text, nullable) — یادداشت ادمین هنگام رد کردن
- `createdAt` (int) — Unix timestamp
- `updatedAt` (int) — Unix timestamp
بعد از ایجاد Entity: حتماً `doctrine:migrations:diff` و `doctrine:migrations:migrate` اجرا شود.
---
### قابلیت ۲ — Backend: Public Pre-Registration Endpoint
**`POST /api/v1/pre-registration`** — endpoint عمومی (بدون auth)
فایل: `src/Auth/Controller/PreRegistrationController.php` (class جدید، extends BaseController)
Request body:
```json
{
"type": "independent_doctor", // independent_doctor | doctor_with_clinic | clinic_manager — الزامی
"name": "دکتر احمدی", // الزامی
"mobile": "09121234567", // الزامی
"info": "متخصص داخلی، ۱۰ سال سابقه" // اختیاری
}
```
مفهوم `type` و نقش‌های دریافتی پس از تأیید:
| مقدار | متن نمایشی در فرم | نقش‌ها | entity ساخته می‌شود |
|-------|-------------------|--------|---------------------|
| `independent_doctor` | «دکتر هستم» | `ROLE_DOCTOR` | Doctor |
| `doctor_with_clinic` | «دکتر هستم و کلینیک دارم و با چند دکتر همکاری می‌کنم» | `ROLE_DOCTOR` + `ROLE_CLINIC` | Doctor + Clinic |
| `clinic_manager` | «تنها مدیر کلینیک هستم» | `ROLE_CLINIC` | Clinic |
Validation:
- `type` باید یکی از `independent_doctor`، `doctor_with_clinic`، یا `clinic_manager` باشد
- `mobile` باید ۱۰ تا ۱۵ کاراکتر باشد
- اگر همین موبایل با status=pending در جدول وجود داشت → خطا: `DUPLICATE_REQUEST` با پیام "درخواست ثبت‌نام شما در حال بررسی است"
Response موفق:
```json
{ "success": true, "data": { "uuid": "...", "status": "pending" } }
```
امنیت: این endpoint باید در `security.yaml` به عنوان عمومی (public) تعریف شود.
---
### قابلیت ۳ — Backend: Admin Pre-Registration Management Endpoints
در فایل `src/Auth/Controller/PreRegistrationController.php` متدهای زیر اضافه شوند:
**`GET /api/v1/admin/pre-registrations`** — لیست درخواست‌ها
- `#[IsGranted('ROLE_ADMIN')]`
- Query params: `page`, `limit`, `status` (pending|approved|rejected|all — default: pending)
- DQL array hydration، برگرداندن: uuid, type, name, mobile, info, status, admin_note, created_at
- Return: `$this->paginated(...)`
**`POST /api/v1/admin/pre-registrations/{uuid}/approve`** — تأیید درخواست
- `#[IsGranted('ROLE_ADMIN')]`
- اقدامات هنگام تأیید:
1. بررسی اینکه status=pending باشد (در غیر این صورت خطا: `ALREADY_PROCESSED`)
2. یک پسورد تصادفی ۸ کاراکتری بسازید (حروف+عدد)
3. اگر user با این mobile وجود ندارد → User جدید بسازید با این mobile و پسورد
4. اگر user وجود دارد → پسورد را reset کنید
5. بر اساس `type` entity و نقش‌ها را بساز:
- `independent_doctor`:
- Doctor entity بساز (firstName/lastName از split نام؛ اگر یک کلمه بود همه firstName)
- به user بده: `ROLE_DOCTOR`
- `doctor_with_clinic`:
- Doctor entity بساز (مانند بالا)
- Clinic entity بساز با `name`=نام درخواست
- به user بده: `ROLE_DOCTOR` + `ROLE_CLINIC`
- `clinic_manager`:
- Clinic entity بساز با `name`=نام درخواست
- به user بده: `ROLE_CLINIC`
در همه حالت‌ها قبل از ساخت entity بررسی کن که قبلاً وجود نداشته باشد (جلوگیری از duplicate)
7. `status` را روی `approved` و `updatedAt` را update کنید
8. یک SMS بفرستید از طریق سرویس SMS موجود در پروژه (کلاس `App\Sms\`) با متن: "به کلینیک پرو خوش آمدید! شماره‌کاربری: {mobile} | رمز عبور: {password} | لینک ورود: https://clinic-pro.ddev.site/admin"
- Response: `$this->success(['message' => 'تأیید شد و اطلاعات ورود ارسال گردید'])`
**`POST /api/v1/admin/pre-registrations/{uuid}/reject`** — رد درخواست
- `#[IsGranted('ROLE_ADMIN')]`
- Request body: `{ "note": "توضیح دلیل رد" }` — اختیاری
- `status` را `rejected` کن، `adminNote` را ذخیره کن
- Response: `$this->success(['message' => 'درخواست رد شد'])`
---
### قابلیت ۴ — Frontend Homepage: دو دکمه در header و hero
فایل: `templates/public/home.html.twig`
**۴الف — جایگزینی کامل `<div class="nav-right">`:**
کل بلوک زیر را حذف کن:
```html
<div class="nav-right">
<button class="nav-ico search" ...>...</button>
<button class="nav-burger nav-toggle" id="navToggle" ...>...</button>
<a href="#download" class="nav-burger" ...>...</a>
</div>
```
و به جای آن این بلوک را قرار بده:
```html
<div class="nav-right">
<button class="nav-burger nav-toggle" id="navToggle" aria-label="منو">
<svg viewBox="0 0 24 24" fill="none"><path d="M4 7h16M4 12h16M4 17h16" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
</button>
<button class="btn btn-ghost" id="openRegModal" style="font-size:13px;padding:8px 20px">ثبت نام</button>
<a href="/admin" class="btn btn-blue" style="font-size:13px;padding:8px 20px;text-decoration:none">ورود به پنل</a>
</div>
```
توجه: دکمه hamburger (navToggle) را نگه دار چون JS موبایل به آن وابسته است. دکمه search و لینک download را حذف کن.
**۴ب — دکمه ثبت نام در hero:**
در `<div class="hero-actions reveal d2">` دکمه "دانلود رایگان" را به "ثبت نام" تغییر بده.
توجه: از `class="btn btn-coral"` با `id="openRegModal2"` استفاده کن (id متفاوت از header) و در JS هر دو را به modal وصل کن:
```html
<button class="btn btn-coral" id="openRegModal2">ثبت نام دکتر / کلینیک</button>
<a href="#contact" class="btn btn-blue">تماس با ما</a>
```
**۴ج — Modal پیش ثبت‌نام:**
یک modal HTML در انتهای `<main>` اضافه کن (قبل از `</main>`) با:
- overlay با id=`regOverlay`
- modal box با فرم شامل:
- **انتخاب نوع حساب** — سه کارت قابل کلیک (radio-style) با آیکون و توضیح، به صورت عمودی (stacked):
- `independent_doctor` → عنوان: «دکتر هستم» — زیرعنوان: «مطب شخصی دارم، به تنهایی کار می‌کنم»
- `doctor_with_clinic` → عنوان: «دکتر هستم و کلینیک دارم» — زیرعنوان: «با چند دکتر در یک کلینیک همکاری می‌کنم»
- `clinic_manager` → عنوان: «مدیر کلینیک هستم» — زیرعنوان: «مدیریت کلینیک را دارم، خودم دکتر نیستم»
کارت انتخاب‌شده با border رنگی و پس‌زمینه روشن هایلایت شود
- input: نام کامل
- input: شماره موبایل (dir=ltr)
- textarea: توضیحات اختیاری (تخصص، آدرس، ...)
- دکمه ارسال (disable تا زمانی که type انتخاب نشده)
- CSS inline برای modal (همانند استایل‌های موجود در صفحه: رنگ‌ها `--blue`, `--coral`, border-radius مشابه)
- JS inline:
- باز/بسته شدن modal با کلیک روی هر دو دکمه (`openRegModal` در header و `openRegModal2` در hero) و کلیک روی overlay
- ارسال فرم با `fetch('/api/v1/pre-registration', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({...}) })`
- نمایش پیام موفقیت یا خطا
- بعد از موفقیت: بستن modal و نمایش پیام "درخواست شما ثبت شد. پس از بررسی، اطلاعات ورود از طریق SMS ارسال می‌شود."
---
### قابلیت ۵ — Frontend Admin Panel: صفحه مدیریت درخواست‌ها
**۵الف — ایجاد `assets/admin/pages/PreRegistrationsPage.tsx`:**
- `useQuery` با `queryKey: ['pre-registrations', page, statusFilter]`
- API: `GET /api/v1/admin/pre-registrations?page=...&status=...`
- جدول با ستون‌ها: نام، موبایل، نوع (badge با رنگ‌بندی: «دکتر» آبی برای `independent_doctor` / «دکتر + کلینیک» بنفش برای `doctor_with_clinic` / «مدیر کلینیک» نارنجی برای `clinic_manager`)، وضعیت badge، تاریخ درخواست، ستون اقدامات
- Filter tabs: همه | در انتظار | تأیید شده | رد شده
- برای هر ردیف با status=pending: دکمه‌های تأیید (سبز) و رد (قرمز)
- کلیک تأیید → mutation به `POST /api/v1/admin/pre-registrations/{uuid}/approve` → toast موفقیت
- کلیک رد → یک prompt کوچک (یا یک inline textarea در row) برای وارد کردن دلیل → mutation به `POST /api/v1/admin/pre-registrations/{uuid}/reject` → toast
- بعد از هر mutation: `invalidateQueries(['pre-registrations'])`
**۵ب — اضافه کردن به Sidebar.tsx:**
در بخش admin داخل `buildSections`، در section «مدیریت» یک آیتم اضافه کن:
```
{ to: '/admin/pre-registrations', icon: ClipboardDocumentCheckIcon, label: 'درخواست‌های ثبت‌نام' }
```
(import `ClipboardDocumentCheckIcon` از `@heroicons/react/24/outline`)
**۵ج — اضافه کردن route به App.tsx:**
```tsx
import PreRegistrationsPage from './pages/PreRegistrationsPage';
// ...
<Route path="pre-registrations" element={<RoleRoute roles={['admin']}><PreRegistrationsPage /></RoleRoute>} />
```
---
## نکات اجرایی
### امنیت
- endpoint عمومی `POST /api/v1/pre-registration` را در `config/packages/security.yaml` داخل `public_endpoints` firewall اضافه کن
- endpoint های admin با `#[IsGranted('ROLE_ADMIN')]` محافظت شوند
### SMS
- کلاس SMS موجود را در پروژه پیدا کن (`src/Sms/`) و از آن استفاده کن
- اگر ارسال SMS با خطا مواجه شد، عملیات approve کامل شود (rollback نشود) — فقط یک log warning بزن
### پسورد تصادفی
از `bin2hex(random_bytes(4))` برای تولید پسورد ۸ کاراکتری hex استفاده کن.
### مستندسازی
بعد از اتمام هر قابلیت:
- `docs/api/auth.md` را برای endpoint های جدید pre-registration به‌روزرسانی کن
- `docs/api/admin.md` را برای endpoint های admin به‌روزرسانی کن