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

12 KiB
Raw Blame History

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:

{
  "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 موفق:

{ "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)
    6. status را روی approved و updatedAt را update کنید
    7. یک 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">: کل بلوک زیر را حذف کن:

<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>

و به جای آن این بلوک را قرار بده:

<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 وصل کن:

<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:

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 به‌روزرسانی کن