# پروفایل unclaimed پزشک — حذف امتیاز پیش‌فرض جعلی از پاسخ API ## پروژه `clinicpro` (backend) — **cross-repo**. پرامپت همتای frontend: `nobat724_front/.claude/prompt/unclaimed-doctor-public-page.md` این تغییر backend باید **اول** انجام شود؛ سایت عمومی و پنل ادمین و tauri همگی همین پاسخ را مصرف می‌کنند. ## زمینه پزشکانِ ایمپورت‌شده از نظام پزشکی (IRIMC) با `owner_status = 'unclaimed'` ساخته می‌شوند و هیچ‌گاه فیلد امتیاز ست نمی‌شود؛ پس مقادیر پیش‌فرضِ انتیتی (`doctor_rate = 3.5`, `doctor_rate_percentage = 60`) به‌عنوان امتیاز واقعی در پاسخ عمومی برمی‌گردند. سایت این‌ها را به‌صورت «امتیاز: ۳.۵» و «۶۰٪ رضایت» و در JSON-LD به‌صورت `AggregateRating` نشان می‌دهد — در حالی که تعداد نظرات واقعی صفر است. ## مشکل / هدف - **بحرانی:** داده‌ی امتیاز جعلی برای پروفایل بدون صاحب. هم گمراه‌کننده، هم نقض دستورالعمل structured data گوگل (ریویوی جعلی) و ریسک جریمه. - هدف: تا وقتی پروفایل `claimed` نشده، فیلدهای امتیاز در پاسخ عمومی **null** برگردند (نه مقدار پیش‌فرض). بعد از claim، رفتار عادی برگردد (بدون تغییر، چون آن‌موقع `owner_status='claimed'`). ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `src/Doctor/Entity/Doctor.php` | `toDetailArray()` (L530–576) و `toListArray()` (L506–528) — سریال‌سازی `satisfaction`/`point`/`owner_status` | | `src/Doctor/Controller/DoctorController.php` | `show()` L149–173 (public `GET /api/v1/doctor/{uuid}`)، `list()` L244–260 (public `GET /api/v1/doctors`) — بدون تغییر، فقط مصرف‌کننده | | `docs/api/doctor.md` | باید به‌روز شود | ## وضعیت فعلی فیلدهای انتیتی (`src/Doctor/Entity/Doctor.php`): ```php #[ORM\Column(name: 'doctor_rate', type: 'float')] private float $doctorRate = 3.5; // L67–68 #[ORM\Column(name: 'doctor_rate_percentage', type: 'float')] private float $doctorRatePercentage = 60.0; // L70–71 // owner_status enum: claimed | unclaimed | pending_transfer (L83) #[ORM\Column(name: 'owner_status', type: 'string', length: 20)] private string $ownerStatus = 'claimed'; // L84–85 ``` سریال‌سازی فعلی (هر دو آرایه امتیاز را همیشه برمی‌گردانند): ```php // toDetailArray() L557–559 'satisfaction' => (string) $this->doctorRatePercentage, 'point' => (string) $this->doctorRate, 'owner_status' => $this->ownerStatus, // toListArray() L521–526 — همان دو کلید + owner_status ``` ## وظایف ### ۱. helper برای «آیا امتیاز واقعی نمایش داده شود؟» در `Doctor.php` یک متد کوچک اضافه کن (منبع واحد منطق): ```php /** امتیاز فقط برای پروفایل claimed معتبر است؛ unclaimed/pending_transfer مقدار پیش‌فرض جعلی دارد. */ public function hasPublicRating(): bool { return $this->ownerStatus === 'claimed'; } ``` ### ۲. null کردن امتیاز در `toDetailArray()` و `toListArray()` در هر دو متد، به‌جای مقدار مستقیم: ```php 'satisfaction' => $this->hasPublicRating() ? (string) $this->doctorRatePercentage : null, 'point' => $this->hasPublicRating() ? (string) $this->doctorRate : null, ``` `owner_status` بدون تغییر بماند (frontend به آن نیاز دارد). کلیدها **حذف نشوند**، فقط مقدارشان `null` شود تا شکل پاسخ نشکند. ### ۳. مستندسازی `docs/api/doctor.md` را به‌روز کن: - در توضیح فیلدهای پاسخ single (L83–118) و list (L196–222) ذکر کن: `point` و `satisfaction` برای `owner_status !== 'claimed'` مقدار `null` دارند. - یک خط در header note اضافه کن که امتیاز پیش‌فرض فقط برای پروفایل claimed منتشر می‌شود. ## نکات مهم - **پاسخ double-nested است**: `show()` از `$this->success(['data' => array_merge($doctor->toDetailArray($schedule), [...])])` استفاده می‌کند → frontend با `data.data.data` می‌خواند. شکل را تغییر نده، فقط مقدار دو کلید. - **بعد از claim خودکار درست می‌شود**: `DoctorClaimService::finalize()` وضعیت را به `'claimed'` می‌برد → `hasPublicRating()` صحیح می‌شود → امتیاز واقعی برمی‌گردد. نیازی به منطق اضافه برای «برگشت به حالت عادی» نیست. - `pending_transfer` هم مثل `unclaimed` امتیاز نداشته باشد (فقط `claimed` امتیاز دارد). - تست موجود اگر روی `toDetailArray`/`toListArray` هست را چک کن (مقدار `point`/`satisfaction` ممکن است اکنون `null` شود). `ddev exec php bin/phpunit`. - migration لازم **نیست** (فقط منطق سریال‌سازی، نه تغییر schema). - بعد از تغییر: `ddev exec php vendor/bin/phpstan analyse` و `graphify update .`.