257 lines
8.1 KiB
Markdown
257 lines
8.1 KiB
Markdown
# Story 8.4: Booking Approved Email
|
|
|
|
## 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**,
|
|
So that **I can confirm the appointment and add it to my calendar**.
|
|
|
|
## Acceptance Criteria
|
|
|
|
### Trigger
|
|
- [ ] Sent on booking approval by admin
|
|
|
|
### Content
|
|
- [ ] "Your consultation has been approved"
|
|
- [ ] Confirmed date and time
|
|
- [ ] Duration (45 minutes)
|
|
- [ ] Consultation type (free/paid)
|
|
- [ ] If paid: amount and payment instructions
|
|
- [ ] .ics calendar file attached
|
|
- [ ] "Add to Calendar" button
|
|
- [ ] Location/contact information
|
|
|
|
### Language
|
|
- [ ] Email in client's preferred language
|
|
|
|
### Attachment
|
|
- [ ] Valid .ics calendar file
|
|
|
|
## 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;
|
|
|
|
public function __construct(
|
|
public Consultation $consultation,
|
|
public string $icsContent,
|
|
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';
|
|
|
|
return new Content(
|
|
markdown: "emails.booking.approved.{$locale}",
|
|
with: [
|
|
'consultation' => $this->consultation,
|
|
'user' => $this->consultation->user,
|
|
'paymentInstructions' => $this->paymentInstructions,
|
|
],
|
|
);
|
|
}
|
|
|
|
public function attachments(): array
|
|
{
|
|
return [
|
|
Attachment::fromData(fn() => $this->icsContent, 'consultation.ics')
|
|
->withMime('text/calendar'),
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
### 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 (date, time, duration, type)
|
|
- [ ] Payment info for paid consultations
|
|
- [ ] .ics file attached
|
|
- [ ] 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
|