libra/docs/stories/story-8.6-consultation-remi...

267 lines
9.2 KiB
Markdown

# 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