reviewed epic 8 stories
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
## Epic Reference
|
||||
**Epic 8:** Email Notification System
|
||||
|
||||
## Dependencies
|
||||
- **Story 8.1:** Email infrastructure setup (base template, queue config, SMTP)
|
||||
- **Story 3.6:** CalendarService for .ics file generation
|
||||
|
||||
## User Story
|
||||
As a **client**,
|
||||
I want **to receive notification when my booking is approved**,
|
||||
@@ -31,7 +35,37 @@ So that **I can confirm the appointment and add it to my calendar**.
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### Required Consultation Model Fields
|
||||
This story assumes the following fields exist on the `Consultation` model (from Epic 3):
|
||||
- `id` - Unique identifier (booking reference)
|
||||
- `user_id` - Foreign key to User
|
||||
- `scheduled_date` - Date of consultation
|
||||
- `scheduled_time` - Time of consultation
|
||||
- `duration` - Duration in minutes (default: 45)
|
||||
- `status` - Consultation status ('pending', 'approved', 'rejected', etc.)
|
||||
- `type` - 'free' or 'paid'
|
||||
- `payment_amount` - Amount for paid consultations (nullable)
|
||||
|
||||
### Views to Create
|
||||
- `resources/views/emails/booking/approved/ar.blade.php` - Arabic template
|
||||
- `resources/views/emails/booking/approved/en.blade.php` - English template
|
||||
|
||||
### Mailable Class
|
||||
Create `app/Mail/BookingApprovedEmail.php`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Consultation;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Attachment;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class BookingApprovedEmail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
@@ -42,6 +76,16 @@ class BookingApprovedEmail extends Mailable
|
||||
public ?string $paymentInstructions = null
|
||||
) {}
|
||||
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
$locale = $this->consultation->user->preferred_language ?? 'ar';
|
||||
$subject = $locale === 'ar'
|
||||
? 'تمت الموافقة على استشارتك'
|
||||
: 'Your Consultation Has Been Approved';
|
||||
|
||||
return new Envelope(subject: $subject);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
$locale = $this->consultation->user->preferred_language ?? 'ar';
|
||||
@@ -50,6 +94,7 @@ class BookingApprovedEmail extends Mailable
|
||||
markdown: "emails.booking.approved.{$locale}",
|
||||
with: [
|
||||
'consultation' => $this->consultation,
|
||||
'user' => $this->consultation->user,
|
||||
'paymentInstructions' => $this->paymentInstructions,
|
||||
],
|
||||
);
|
||||
@@ -65,13 +110,147 @@ class BookingApprovedEmail extends Mailable
|
||||
}
|
||||
```
|
||||
|
||||
### Trigger Mechanism
|
||||
Add observer or listener to send email when consultation status changes to 'approved':
|
||||
|
||||
```php
|
||||
// Option 1: In Consultation model boot method or observer
|
||||
use App\Mail\BookingApprovedEmail;
|
||||
use App\Services\CalendarService;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
// In ConsultationObserver or model event
|
||||
public function updated(Consultation $consultation): void
|
||||
{
|
||||
if ($consultation->wasChanged('status') && $consultation->status === 'approved') {
|
||||
$icsContent = app(CalendarService::class)->generateIcs($consultation);
|
||||
|
||||
$paymentInstructions = null;
|
||||
if ($consultation->type === 'paid') {
|
||||
$paymentInstructions = $this->getPaymentInstructions($consultation);
|
||||
}
|
||||
|
||||
Mail::to($consultation->user)
|
||||
->queue(new BookingApprovedEmail($consultation, $icsContent, $paymentInstructions));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Payment Instructions
|
||||
For paid consultations, include payment details:
|
||||
- Amount to pay
|
||||
- Payment methods accepted
|
||||
- Payment deadline (before consultation)
|
||||
- Bank transfer details or payment link
|
||||
|
||||
## Testing Guidance
|
||||
|
||||
### Test Approach
|
||||
- Unit tests for Mailable class
|
||||
- Feature tests for trigger mechanism (observer)
|
||||
- Integration tests for email queue
|
||||
|
||||
### Key Test Scenarios
|
||||
|
||||
```php
|
||||
use App\Mail\BookingApprovedEmail;
|
||||
use App\Models\Consultation;
|
||||
use App\Models\User;
|
||||
use App\Services\CalendarService;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
it('queues email when consultation is approved', function () {
|
||||
Mail::fake();
|
||||
|
||||
$consultation = Consultation::factory()->create(['status' => 'pending']);
|
||||
$consultation->update(['status' => 'approved']);
|
||||
|
||||
Mail::assertQueued(BookingApprovedEmail::class, function ($mail) use ($consultation) {
|
||||
return $mail->consultation->id === $consultation->id;
|
||||
});
|
||||
});
|
||||
|
||||
it('does not send email when status changes to non-approved', function () {
|
||||
Mail::fake();
|
||||
|
||||
$consultation = Consultation::factory()->create(['status' => 'pending']);
|
||||
$consultation->update(['status' => 'rejected']);
|
||||
|
||||
Mail::assertNotQueued(BookingApprovedEmail::class);
|
||||
});
|
||||
|
||||
it('includes ics attachment', function () {
|
||||
$consultation = Consultation::factory()->approved()->create();
|
||||
$icsContent = app(CalendarService::class)->generateIcs($consultation);
|
||||
|
||||
$mailable = new BookingApprovedEmail($consultation, $icsContent);
|
||||
|
||||
expect($mailable->attachments())->toHaveCount(1);
|
||||
expect($mailable->attachments()[0]->as)->toBe('consultation.ics');
|
||||
});
|
||||
|
||||
it('uses Arabic template for Arabic-preferring users', function () {
|
||||
$user = User::factory()->create(['preferred_language' => 'ar']);
|
||||
$consultation = Consultation::factory()->approved()->for($user)->create();
|
||||
$icsContent = app(CalendarService::class)->generateIcs($consultation);
|
||||
|
||||
$mailable = new BookingApprovedEmail($consultation, $icsContent);
|
||||
|
||||
expect($mailable->content()->markdown)->toBe('emails.booking.approved.ar');
|
||||
});
|
||||
|
||||
it('uses English template for English-preferring users', function () {
|
||||
$user = User::factory()->create(['preferred_language' => 'en']);
|
||||
$consultation = Consultation::factory()->approved()->for($user)->create();
|
||||
$icsContent = app(CalendarService::class)->generateIcs($consultation);
|
||||
|
||||
$mailable = new BookingApprovedEmail($consultation, $icsContent);
|
||||
|
||||
expect($mailable->content()->markdown)->toBe('emails.booking.approved.en');
|
||||
});
|
||||
|
||||
it('includes payment instructions for paid consultations', function () {
|
||||
$consultation = Consultation::factory()->approved()->create([
|
||||
'type' => 'paid',
|
||||
'payment_amount' => 150.00,
|
||||
]);
|
||||
$icsContent = app(CalendarService::class)->generateIcs($consultation);
|
||||
$paymentInstructions = 'Please pay 150 ILS before your consultation.';
|
||||
|
||||
$mailable = new BookingApprovedEmail($consultation, $icsContent, $paymentInstructions);
|
||||
|
||||
expect($mailable->paymentInstructions)->toBe($paymentInstructions);
|
||||
});
|
||||
|
||||
it('excludes payment instructions for free consultations', function () {
|
||||
$consultation = Consultation::factory()->approved()->create(['type' => 'free']);
|
||||
$icsContent = app(CalendarService::class)->generateIcs($consultation);
|
||||
|
||||
$mailable = new BookingApprovedEmail($consultation, $icsContent);
|
||||
|
||||
expect($mailable->paymentInstructions)->toBeNull();
|
||||
});
|
||||
```
|
||||
|
||||
### Edge Cases to Test
|
||||
- User with null `preferred_language` defaults to 'ar'
|
||||
- Consultation without payment_amount for paid type (handle gracefully)
|
||||
- Email render test with `$mailable->render()`
|
||||
|
||||
## References
|
||||
- `docs/stories/story-8.1-email-infrastructure-setup.md` - Base email template and queue config
|
||||
- `docs/stories/story-3.6-calendar-file-generation.md` - CalendarService for .ics generation
|
||||
- `docs/epics/epic-8-email-notifications.md#story-84-booking-approved-email` - Epic acceptance criteria
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Email sent on approval
|
||||
- [ ] All details included
|
||||
- [ ] All details included (date, time, duration, type)
|
||||
- [ ] Payment info for paid consultations
|
||||
- [ ] .ics file attached
|
||||
- [ ] Bilingual templates
|
||||
- [ ] Tests pass
|
||||
- [ ] Bilingual templates (Arabic/English)
|
||||
- [ ] Observer/listener triggers on status change
|
||||
- [ ] Tests pass (all scenarios above)
|
||||
- [ ] Code formatted with Pint
|
||||
|
||||
## Estimation
|
||||
**Complexity:** Medium | **Effort:** 3 hours
|
||||
|
||||
Reference in New Issue
Block a user