Files
clinicpro/.claude/prompt/doctor-appointment-notification-sms.md

7.8 KiB
Raw Permalink Blame History

پیامک اعلان نوبت جدید به «شمارهٔ اعلان» دکتر (فقط نوبت‌های پرداخت‌شدهٔ سایت)

پروژه

clinicpro (backend / Payment + Sms)

زمینه

هر دکتر در /admin/profile یک «شمارهٔ اعلان نوبت» (Doctor::notificationMobile) ست می‌کند («پیامک نوبت جدید به این شماره ارسال می‌شود»). این فیلد و تگ SmsLog::TAG_NOTIFICATION_MOBILE وجود دارند، ولی تگ فعلی فقط برای تأیید ثبت شماره (کد تأیید در NotificationMobileController) استفاده می‌شود — به رویداد «نوبت جدید» وصل نیست.

الان هنگام موفقیت پرداخت نوبت، فقط به بیمار پیامک می‌رود (PaymentManager::handleAppointmentConfirmation با تگ TAG_PAYMENT). هدف: علاوه بر آن، به شمارهٔ اعلانِ دکتر هم یک پیامک با قالب مشخص (شامل نام بیمار و ساعت نوبت) ارسال شود.

فقط نوبت‌های پرداخت‌شده از سایت باید این پیامک را بدهند — نه نوبت‌هایی که منشی ثبت می‌کند. چون handleAppointmentConfirmation فقط در مسیر موفقیت پرداخت (PaymentManager::processCallbackrunPostAction) اجرا می‌شود و نوبت‌های ثبت‌شده توسط منشی از این مسیر عبور نمی‌کنند، افزودن کد در همین‌جا به‌طور طبیعی شرط «فقط سایت/پرداخت» را رعایت می‌کند.

مشکل / هدف

هنگام تأیید پرداختِ یک نوبت، اگر دکترِ آن نوبت 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 (فقط پیامک بیمار)

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): stringfalse = فقط تاریخ شمسی. متد جدا برای «ساعت» ندارد؛ ساعت را با date('H:i', $slotStart) بگیر.

الگوی تگ‌ها (SmsLog)

public const TAG_NOTIFICATION_MOBILE = 'notification_mobile';
public const TAGS = [ self::TAG_GLOBAL, self::TAG_OTP, self::TAG_PAYMENT, /* ... */ self::TAG_SECRETARY ];

الگوی قالب‌ها (SmsMessageTemplate::DEFAULTS)

SmsLog::TAG_PAYMENT => [
    'title'     => 'تأیید پرداخت و نوبت',
    'body'      => 'نوبت شما با {doctor} در تاریخ {date} ثبت و تأیید شد.',
    'variables' => ['doctor', 'date'],
],

وظایف

۱. تگ جدید در SmsLog

public const TAG_DOCTOR_APPOINTMENT = 'doctor_appointment';

و آن را به آرایهٔ TAGS اضافه کن (تا در پنل مدیریت قالب‌ها و seed شناخته شود).

۲. قالب پیش‌فرض در SmsMessageTemplate::DEFAULTS

SmsLog::TAG_DOCTOR_APPOINTMENT => [
    'title'     => 'نوبت جدید (اعلان به پزشک)',
    'body'      => "نوبت جدید ثبت شد.\nبیمار: {patient}\nتاریخ: {date} ساعت {time}",
    'variables' => ['patient', 'date', 'time'],
],

۳. ارسال پیامک به شمارهٔ اعلان دکتر در handleAppointmentConfirmation

بعد از بلوک پیامک بیمار (داخل همان متد)، اضافه کن:

$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، دستور موجود را اجرا کن تا رکورد قالب برای تگ جدید ساخته شود (فقط تگ‌های بدون رکورد را می‌سازد):

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 چنین منطقی دارد، خودکار اعمال می‌شود؛ چیزی اضافه نکن مگر لازم شود).