# پیامک اعلان نوبت جدید به «شمارهٔ اعلان» دکتر (فقط نوبت‌های پرداخت‌شدهٔ سایت) ## پروژه `clinicpro` (backend / Payment + Sms) ## زمینه هر دکتر در `/admin/profile` یک «شمارهٔ اعلان نوبت» (`Doctor::notificationMobile`) ست می‌کند («پیامک نوبت جدید به این شماره ارسال می‌شود»). این فیلد و تگ `SmsLog::TAG_NOTIFICATION_MOBILE` وجود دارند، ولی تگ فعلی فقط برای **تأیید ثبت شماره** (کد تأیید در `NotificationMobileController`) استفاده می‌شود — به رویداد «نوبت جدید» وصل نیست. الان هنگام موفقیت پرداخت نوبت، فقط به **بیمار** پیامک می‌رود (`PaymentManager::handleAppointmentConfirmation` با تگ `TAG_PAYMENT`). هدف: علاوه بر آن، به **شمارهٔ اعلانِ دکتر** هم یک پیامک با قالب مشخص (شامل نام بیمار و ساعت نوبت) ارسال شود. **فقط نوبت‌های پرداخت‌شده از سایت** باید این پیامک را بدهند — نه نوبت‌هایی که منشی ثبت می‌کند. چون `handleAppointmentConfirmation` **فقط** در مسیر موفقیت پرداخت (`PaymentManager::processCallback` → `runPostAction`) اجرا می‌شود و نوبت‌های ثبت‌شده توسط منشی از این مسیر عبور نمی‌کنند، افزودن کد در همین‌جا به‌طور طبیعی شرط «فقط سایت/پرداخت» را رعایت می‌کند. ## مشکل / هدف هنگام تأیید پرداختِ یک نوبت، اگر دکترِ آن نوبت `notificationMobile` ست کرده باشد، یک پیامک با تگ/قالب اختصاصی (`TAG_DOCTOR_APPOINTMENT`) شامل `{patient}` و `{date}` و `{time}` به آن شماره ارسال شود. ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `clinicpro/src/Sms/Entity/SmsLog.php` | افزودن تگ `TAG_DOCTOR_APPOINTMENT` + به آرایهٔ `TAGS` | | `clinicpro/src/Sms/Entity/SmsMessageTemplate.php` | افزودن قالب پیش‌فرض این تگ به `DEFAULTS` | | `clinicpro/src/Payment/Service/PaymentManager.php` | ارسال پیامک به `notificationMobile` دکتر در `handleAppointmentConfirmation` | | `clinicpro/docs/api/sms.md` | مستندسازی تگ/قالب جدید | ## وضعیت فعلی ### `PaymentManager::handleAppointmentConfirmation` (فقط پیامک بیمار) ```php private function handleAppointmentConfirmation(Payment $payment): void { $appointment = $payment->getAppointment(); if ($appointment === null || !$appointment->canTransitionTo(Appointment::STATUS_CONFIRMED)) { return; } $appointment->transitionTo(Appointment::STATUS_CONFIRMED); $this->em->persist($appointment); $doctor = $appointment->getDoctor(); $this->commissionService->processAppointment( $payment, $doctor->getRepresentationId(), $appointment->getBookingRepresentationId(), $doctor->getId(), ); $mobile = $appointment->getPatientMobile(); if ($mobile) { $when = $this->jalali->formatDateTime($appointment->getSlotStart()); $message = $this->smsText->resolve(SmsLog::TAG_PAYMENT, [ 'doctor' => $doctor->getName(), 'date' => $when, ]); $this->smsService->dispatchAsync($mobile, $message, tag: SmsLog::TAG_PAYMENT); } } ``` ### مقادیر در دسترس - `Doctor::getNotificationMobile(): ?string` (فیلد `notification_mobile`, nullable) - `Doctor::getName()` - `Appointment::getPatientName(): ?string`، `getPatientMobile()`، `getSlotStart(): int` (unix) - `JalaliDateService::formatDateTime(int $ts, bool $withTime = true): string` — `false` = فقط تاریخ شمسی. متد جدا برای «ساعت» ندارد؛ ساعت را با `date('H:i', $slotStart)` بگیر. ### الگوی تگ‌ها (SmsLog) ```php public const TAG_NOTIFICATION_MOBILE = 'notification_mobile'; public const TAGS = [ self::TAG_GLOBAL, self::TAG_OTP, self::TAG_PAYMENT, /* ... */ self::TAG_SECRETARY ]; ``` ### الگوی قالب‌ها (SmsMessageTemplate::DEFAULTS) ```php SmsLog::TAG_PAYMENT => [ 'title' => 'تأیید پرداخت و نوبت', 'body' => 'نوبت شما با {doctor} در تاریخ {date} ثبت و تأیید شد.', 'variables' => ['doctor', 'date'], ], ``` ## وظایف ### ۱. تگ جدید در `SmsLog` ```php public const TAG_DOCTOR_APPOINTMENT = 'doctor_appointment'; ``` و آن را به آرایهٔ `TAGS` اضافه کن (تا در پنل مدیریت قالب‌ها و seed شناخته شود). ### ۲. قالب پیش‌فرض در `SmsMessageTemplate::DEFAULTS` ```php SmsLog::TAG_DOCTOR_APPOINTMENT => [ 'title' => 'نوبت جدید (اعلان به پزشک)', 'body' => "نوبت جدید ثبت شد.\nبیمار: {patient}\nتاریخ: {date} ساعت {time}", 'variables' => ['patient', 'date', 'time'], ], ``` ### ۳. ارسال پیامک به شمارهٔ اعلان دکتر در `handleAppointmentConfirmation` بعد از بلوک پیامک بیمار (داخل همان متد)، اضافه کن: ```php $notify = $doctor->getNotificationMobile(); if ($notify) { $docMessage = $this->smsText->resolve(SmsLog::TAG_DOCTOR_APPOINTMENT, [ 'patient' => $appointment->getPatientName() ?? '—', 'date' => $this->jalali->formatDateTime($appointment->getSlotStart(), false), 'time' => date('H:i', $appointment->getSlotStart()), ]); $this->smsService->dispatchAsync($notify, $docMessage, tag: SmsLog::TAG_DOCTOR_APPOINTMENT); } ``` ### ۴. seed قالب جدید بعد از افزودن به `DEFAULTS`، دستور موجود را اجرا کن تا رکورد قالب برای تگ جدید ساخته شود (فقط تگ‌های بدون رکورد را می‌سازد): ```bash ddev exec php bin/console app:seed-sms-message-templates ``` ### ۵. مستندسازی `docs/api/sms.md`: تگ `doctor_appointment`، قالب پیش‌فرض، placeholderها (`patient`/`date`/`time`)، و اینکه فقط برای نوبت‌های **پرداخت‌شدهٔ سایت** (در مسیر verify پرداخت) ارسال می‌شود و به `Doctor.notificationMobile` می‌رود. ## نکات مهم - **فقط مسیر پرداخت:** کد داخل `handleAppointmentConfirmation` است که تنها از `runPostAction` بعد از verify موفق صدا زده می‌شود؛ نوبت‌های منشی (بدون پرداخت) این‌جا نمی‌آیند — پس شرط «نه نوبت منشی» خودکار برقرار است. کد را جای دیگری (مثل ساخت نوبت) نگذار. - اگر `notificationMobile` خالی بود، هیچ پیامکی نرود (شرط `if ($notify)`). - `dispatchAsync` مثل پیامک بیمار استفاده شود (صف async، شکست پیامک نباید جریان پرداخت را بشکند — الگوی موجود). - ارسال پیامک دکتر **مستقل از** پیامک بیمار است (حتی اگر `patientMobile` خالی باشد، پیامک دکتر باید برود). - `title`/`body` قالب فارسی و قابل ویرایش از پنل قالب‌هاست؛ فقط مقدار پیش‌فرض را در `DEFAULTS` بگذار. - `Doctor` تغییر Entity ندارد (فیلد موجود است) → migration لازم نیست. - هزینهٔ پیامک: این پیامک هم مثل بقیه از کیف‌پول/سهمیهٔ پیامک همان entity کسر می‌شود (اگر `SmsService`/wallet چنین منطقی دارد، خودکار اعمال می‌شود؛ چیزی اضافه نکن مگر لازم شود).