# Story 8.6: Consultation Reminder (24 Hours) ## Epic Reference **Epic 8:** Email Notification System ## Dependencies - **Story 8.1:** Email infrastructure setup (base template, queue config, SMTP) - **Story 8.4:** BookingApprovedEmail pattern and CalendarService integration - **Consultation Model:** Must have `status`, `scheduled_date`, `scheduled_time`, `consultation_type`, `payment_status` fields - **User Model:** Must have `preferred_language` field ## User Story As a **client**, I want **to receive a reminder 24 hours before my consultation**, So that **I don't forget my appointment**. ## Acceptance Criteria ### Trigger - [ ] Scheduled artisan command runs hourly - [ ] Find consultations approximately 24 hours away (within 30-minute window) - [ ] Only for approved consultations (`status = 'approved'`) - [ ] Skip cancelled/no-show/completed consultations - [ ] Track sent reminders to prevent duplicates ### Content - [ ] Subject: "Reminder: Your consultation is tomorrow" / "تذكير: استشارتك غدًا" - [ ] Consultation date and time (formatted per locale) - [ ] Consultation type (free/paid) - [ ] Payment reminder: Show if `consultation_type = 'paid'` AND `payment_status != 'received'` - [ ] Calendar file download link (using route to CalendarService) - [ ] Office contact information for questions ### Language - [ ] Email rendered in client's `preferred_language` (ar/en) - [ ] Date/time formatted according to locale ## Technical Notes ### Database Migration Add tracking column to prevent duplicate reminders: ```php // database/migrations/xxxx_add_reminder_sent_columns_to_consultations_table.php Schema::table('consultations', function (Blueprint $table) { $table->timestamp('reminder_24h_sent_at')->nullable()->after('status'); $table->timestamp('reminder_2h_sent_at')->nullable()->after('reminder_24h_sent_at'); }); ``` ### Artisan Command ```php // app/Console/Commands/Send24HourReminders.php namespace App\Console\Commands; use App\Models\Consultation; use App\Notifications\ConsultationReminder24h; use Carbon\Carbon; use Illuminate\Console\Command; class Send24HourReminders extends Command { protected $signature = 'reminders:send-24h'; protected $description = 'Send 24-hour consultation reminders'; public function handle(): int { $targetTime = now()->addHours(24); $windowStart = $targetTime->copy()->subMinutes(30); $windowEnd = $targetTime->copy()->addMinutes(30); $consultations = Consultation::query() ->where('status', 'approved') ->whereNull('reminder_24h_sent_at') ->whereDate('scheduled_date', $targetTime->toDateString()) ->get() ->filter(function ($consultation) use ($windowStart, $windowEnd) { $consultationDateTime = Carbon::parse( $consultation->scheduled_date->format('Y-m-d') . ' ' . $consultation->scheduled_time ); return $consultationDateTime->between($windowStart, $windowEnd); }); $count = 0; foreach ($consultations as $consultation) { $consultation->user->notify(new ConsultationReminder24h($consultation)); $consultation->update(['reminder_24h_sent_at' => now()]); $count++; } $this->info("Sent {$count} reminder(s)."); return Command::SUCCESS; } } ``` ### Notification Class ```php // app/Notifications/ConsultationReminder24h.php namespace App\Notifications; use App\Models\Consultation; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; class ConsultationReminder24h extends Notification implements ShouldQueue { use Queueable; public function __construct( public Consultation $consultation ) {} public function via(object $notifiable): array { return ['mail']; } public function toMail(object $notifiable): MailMessage { $locale = $notifiable->preferred_language ?? 'ar'; $consultation = $this->consultation; $subject = $locale === 'ar' ? 'تذكير: استشارتك غدًا' : 'Reminder: Your consultation is tomorrow'; $message = (new MailMessage) ->subject($subject) ->markdown("emails.reminders.consultation-24h.{$locale}", [ 'consultation' => $consultation, 'user' => $notifiable, 'showPaymentReminder' => $this->shouldShowPaymentReminder(), 'calendarUrl' => route('consultations.calendar', $consultation), ]); return $message; } private function shouldShowPaymentReminder(): bool { return $this->consultation->consultation_type === 'paid' && $this->consultation->payment_status !== 'received'; } } ``` ### Email Templates **Arabic Template:** `resources/views/emails/reminders/consultation-24h/ar.blade.php` **English Template:** `resources/views/emails/reminders/consultation-24h/en.blade.php` Template content should include: - Greeting with client name - Reminder message ("Your consultation is scheduled for tomorrow") - Date/time (formatted: `scheduled_date->translatedFormat()`) - Consultation type badge - Payment reminder section (conditional) - "Add to Calendar" button linking to `$calendarUrl` - Office contact information - Branded footer (from Story 8.1 base template) ### Schedule Registration ```php // routes/console.php or bootstrap/app.php Schedule::command('reminders:send-24h')->hourly(); ``` ## Edge Cases & Error Handling | Scenario | Handling | |----------|----------| | Notification fails to send | Queue will retry; failed jobs logged to `failed_jobs` table | | Consultation rescheduled after reminder | New datetime won't trigger duplicate (24h check resets) | | Consultation cancelled after reminder sent | No action needed - reminder already sent | | User has no email | Notification skipped (Laravel handles gracefully) | | Timezone considerations | All times stored/compared in app timezone (configured in `config/app.php`) | ## Test Scenarios ### Unit Tests (`tests/Unit/Commands/Send24HourRemindersTest.php`) ```php test('command finds consultations approximately 24 hours away', function () { // Create consultation 24 hours from now // Run command // Assert notification sent }); test('command skips consultations with reminder already sent', function () { // Create consultation with reminder_24h_sent_at set // Run command // Assert no notification sent }); test('command skips non-approved consultations', function () { // Create cancelled, no-show, pending consultations // Run command // Assert no notifications sent }); ``` ### Feature Tests (`tests/Feature/Notifications/ConsultationReminder24hTest.php`) ```php test('reminder email contains correct consultation details', function () { // Create consultation // Send notification // Assert email contains date, time, type }); test('payment reminder shown for unpaid paid consultations', function () { // Create paid consultation with payment_status = 'pending' // Assert email contains payment reminder section }); test('payment reminder hidden when payment received', function () { // Create paid consultation with payment_status = 'received' // Assert email does NOT contain payment reminder }); test('email uses client preferred language', function () { // Create user with preferred_language = 'en' // Assert email template is English version }); test('calendar download link is included', function () { // Assert email contains route('consultations.calendar', $consultation) }); ``` ## Files to Create/Modify | File | Action | |------|--------| | `database/migrations/xxxx_add_reminder_sent_columns_to_consultations_table.php` | CREATE | | `app/Console/Commands/Send24HourReminders.php` | CREATE | | `app/Notifications/ConsultationReminder24h.php` | CREATE | | `resources/views/emails/reminders/consultation-24h/ar.blade.php` | CREATE | | `resources/views/emails/reminders/consultation-24h/en.blade.php` | CREATE | | `routes/console.php` | MODIFY (add schedule) | | `tests/Unit/Commands/Send24HourRemindersTest.php` | CREATE | | `tests/Feature/Notifications/ConsultationReminder24hTest.php` | CREATE | ## Definition of Done - [ ] Migration adds `reminder_24h_sent_at` column to consultations table - [ ] Artisan command `reminders:send-24h` created and works - [ ] Command scheduled to run hourly - [ ] Notification class implements `ShouldQueue` - [ ] Reminders only sent for approved consultations within 24h window - [ ] No duplicate reminders (tracking column updated) - [ ] Payment reminder shown only when `paid` AND `payment_status != 'received'` - [ ] Calendar download link included - [ ] Bilingual email templates (Arabic/English) - [ ] All unit and feature tests pass - [ ] Code formatted with `vendor/bin/pint` ## References - **PRD Section 5.4:** Email Notifications - "Consultation reminder (24 hours before)" - **PRD Section 8.2:** Email Templates - Template requirements and branding - **Story 8.1:** Base email template and queue configuration - **Story 8.4:** Pattern for calendar file attachment/linking ## Estimation **Complexity:** Medium | **Effort:** 3 hours