- Removed the "دکتر" prefix from doctor names in various components and API responses to ensure consistency and clarity. - Updated the AppointmentDetailPage, CommentsPage, DashboardPage, RatingsPage, SecretariesPage, and other relevant files to reflect the changes in doctor name formatting. - Adjusted API documentation to align with the new naming conventions. - Implemented validation to prevent the creation of clinics without a name and restricted users to a single clinic. - Added tests to verify that doctor names are stored without titles and that clinic creation adheres to the new validation rules.
7.7 KiB
Doctor Profile Claim API (تصاحب پروفایل پزشک ایمپورتشده)
Controller:
App\Doctor\Controller\DoctorClaimController— منطق درApp\Doctor\Service\DoctorClaimServiceمصرفکننده: سایت عمومی Nobat724 (همهٔ دامنهها) + پنل ادمین
پزشکِ ایمپورتشده از نظام پزشکی (owner_status = unclaimed) توسط پزشک واقعی تصاحب میشود.
احراز هویت سمت سرور با API.ir انجام میشود (شاهکار: تطبیق موبایل↔کدملی؛ PersonInfo: تطبیق
کدملی+تاریخ تولد و نام). هیچ درخواستی از فرانت به API.ir نمیرود و توکن API.ir هرگز به کلاینت
نمیرسد. claim پس از تطبیق موفق خودکار نهایی میشود (بدون approve ادمین — تصمیم مستند در
.claude/prompt/irimc-import-complete.md §۲.۲).
چرخهٔ وضعیت
unclaimed ──claim/transfer شروع──▶ pending_transfer ──موفق──▶ claimed
▲ │شکست تطبیق/خطای استعلام
└───────────────────────────────────┘ (برگشت، قابل تلاش مجدد)
هر تلاش یک رکورد ممیزی در doctor_claim_requests میسازد — کد ملی فقط hash sha256 و
موبایل فقط mask شده ذخیره میشود؛ هیچ دادهٔ هویتی خام در DB یا لاگ نمیماند.
GET /api/v1/doctor/{uuid}/claim-info
Permission: عمومی (بدون JWT) — فقط برای رندر دکمهٔ «آیا شما این پزشک هستید؟»
Response 200
{ "success": true, "data": { "claimable": true, "owner_status": "unclaimed" } }
| کد | حالت |
|---|---|
404 |
پزشک یافت نشد |
POST /api/v1/doctor/{uuid}/claim
Permission: IS_AUTHENTICATED_FULLY — کاربر با OTP لاگین شده (موبایلش تأییدشده است)
Rate limit: limiter doctor_claim — ۵ تلاش در ساعت بهازای هر (کاربر، پزشک)
Captcha: ALTCHA — بدنه باید payload کپچا بفرستد (CaptchaGuard::assertValid)؛ در dev با ALTCHA_ENABLED=false بیاثر است، در prod اجباری. خطا → ERR_CAPTCHA_001 (۴۲۲).
Request
{
"national_code": "0010007700",
"birth_date": "1371/1/1",
"first_name": "فرخنده",
"last_name": "حسینی",
"mobile": "09121234567",
"altcha": "<payload کپچا>"
}
| فیلد | الزامی | قاعده |
|---|---|---|
national_code |
✅ | ۱۰ رقم (ارقام فارسی پذیرفته و نرمال میشوند) |
birth_date |
✅ | شمسی Y/m/d |
first_name / last_name |
✅ | با هویت ثبت احوال و نام پروفایل تطبیق داده میشود (نرمالسازی ي/ی، ك/ک، نیمفاصله — PersianText) |
mobile |
❌ | اگر داده شود، باید با موبایل حساب کاربری یکی باشد وگرنه 422 ERR_CONFLICT_001؛ اگر خالی باشد از موبایل کاربر لاگینشده استفاده میشود |
altcha |
prod | payload کپچای ALTCHA |
مراحل سرور (اتمیک/ضد race)
- قفل
PESSIMISTIC_WRITEروی ردیف پزشک → اگرunclaimedنبود409؛ اگر کاربر از قبل پزشکی دارد409؛ اگر کد ملی متعلق به کاربر دیگری است409— سپسpending_transfer+ رکورد ممیزی (تراکنش کوتاه، بدون فراخوان خارجی داخل قفل). - شاهکار (
ApiIrService::shahkarMatch) — تطبیق موبایل کاربر با کد ملی. اگر API.ir پیکربندی نشده باشد، این گام skip و مبنا موبایلِ OTP-تأییدشده است. PersonInfo— تطبیق کدملی+تاریخ تولد؛alive=false→ رد.- تطبیق نام: ورودی کاربر ↔ هویت تأییدشده ↔ نام پروفایل (بدون پیشوند «دکتر»).
- نهاییسازی اتمیک:
user_id→ کاربر واقعی،ROLE_DOCTOR،national_code_verified=true،owner_status=claimed، حذف امنِ کاربر جانشین (فقط باROLE_UNCLAIMED_DOCTORو بدون پزشک دیگر). - پیامک خوشآمد (تمپلیت
welcome، async).
شکست در گامهای ۲-۴ پروفایل را به
unclaimedبرمیگرداند تا پزشک واقعی بتواند دوباره تلاش کند. نوبتدهی (active_doctor_appointment) خاموش میماند تا مالک جدید برنامهٔ کاری تعریف کند.
Response 200
{
"success": true,
"data": {
"status": "claimed",
"claim": { "uuid": "…" },
"doctor": { "uuid": "…", "name": "فرخنده حسینی" }
}
}
Errors
| کد | HTTP | حالت |
|---|---|---|
ERR_AUTH_001 |
401 | بدون لاگین |
ERR_NOT_FOUND_001 |
404 | پزشک یافت نشد |
ERR_CONFLICT_001 |
409 | پروفایل قابل تصاحب نیست / کاربر پزشک دیگری دارد / درخواست همزمان دیگری در جریان است |
ERR_PROFILE_001 |
409 | کد ملی قبلاً برای کاربر دیگری ثبت شده است |
ERR_VALIDATION_001/002 |
422 | کد ملی/تاریخ/نام نامعتبر |
ERR_IDENTITY_001 |
422 | عدم تطبیق هویت (شاهکار/ثبت احوال/نام) — پیام عمومی، ضد enumeration |
ERR_RATE_LIMIT_001 |
429 | عبور از سقف تلاش |
ERR_EXTERNAL_001 |
502 | خطا/تایماوت API.ir |
ERR_EXTERNAL_002 |
503 | API.ir پیکربندی نشده (env) |
POST /api/v1/admin/doctors/{uuid}/transfer
انتقال دستی (ابزار پشتیبانی ادمین) — مستند کامل در docs/api/doctor-import.md.
Permission: ROLE_ADMIN. بدنه { "mobile": "09…" }. همان نهاییسازی claim را اجرا میکند
و رکورد ممیزی با verification_method="admin_manual" میسازد.
GET /api/v1/admin/doctor-claims
Permission: ROLE_ADMIN — لیست ممیزی درخواستهای claim برای پشتیبانی عملیاتی.
| Query | پیشفرض | توضیح |
|---|---|---|
status |
همه | pending | completed | failed |
page / limit |
1 / 20 (سقف 50) | صفحهبندی استاندارد |
Response 200 (paginated)
{
"success": true,
"data": [
{
"uuid": "…",
"status": "completed",
"doctor": { "uuid": "…", "name": "فرخنده حسینی" },
"mobile_masked": "0912***4567",
"verification_method": "apiir_personinfo+shahkar",
"failure_reason": null,
"created_at": 1783750000,
"completed_at": 1783750040
}
],
"meta": { "totalRecords": 1, "totalPages": 1, "currentPage": 1 }
}
failure_reason فارسی و انسانیخوان است تا ادمین بدون خواندن لاگ سرور علت شکست را ببیند.
env های مرتبط
| متغیر | نقش |
|---|---|
APIIR_* (baseUrl/token موجود ApiIrService) |
استعلام شاهکار و PersonInfo |
CRAWLER_SERVICE_TOKEN |
فقط برای لاگین سرویسی کرالر (doctor-import.md) — ربطی به claim ندارد |
تستها
tests/Doctor/DoctorClaimTest.php (۱۱ سناریو، API.ir همیشه mock — هیچ تستی به سرویس واقعی
درخواست نمیزند) و tests/Shared/PersianTextTest.php (نرمالسازی نام).