reviewed epic 3
This commit is contained in:
@@ -52,6 +52,32 @@ So that **I don't forget my appointment and can prepare accordingly**.
|
||||
- [ ] Logging for debugging
|
||||
- [ ] Tests for reminder logic
|
||||
|
||||
## Prerequisites & Assumptions
|
||||
|
||||
### Consultation Model (from Story 3.4/3.5)
|
||||
The `Consultation` model must exist with the following structure:
|
||||
- **Fields:**
|
||||
- `status` - enum: `pending`, `approved`, `completed`, `cancelled`, `no_show`
|
||||
- `scheduled_date` - date (cast to Carbon)
|
||||
- `scheduled_time` - time string (H:i:s)
|
||||
- `type` - enum: `free`, `paid`
|
||||
- `payment_status` - enum: `pending`, `received`, `not_applicable`
|
||||
- `payment_amount` - decimal, nullable
|
||||
- `user_id` - foreign key to users table
|
||||
- `reminder_24h_sent_at` - timestamp, nullable (added by this story)
|
||||
- `reminder_2h_sent_at` - timestamp, nullable (added by this story)
|
||||
- **Relationships:**
|
||||
- `user()` - BelongsTo User
|
||||
- **Factory States:**
|
||||
- `approved()` - sets status to 'approved'
|
||||
|
||||
### User Model Requirements
|
||||
- `preferred_language` field must exist (values: `ar`, `en`)
|
||||
- This field should be added in Epic 1 or early user stories
|
||||
|
||||
### Route Requirements
|
||||
- `client.consultations.calendar` route from Story 3.6 must exist for calendar file download link
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### Reminder Commands
|
||||
@@ -344,6 +370,96 @@ it('does not send reminder for cancelled consultation', function () {
|
||||
|
||||
Notification::assertNotSentTo($consultation->user, ConsultationReminder24h::class);
|
||||
});
|
||||
|
||||
it('does not send reminder for no-show consultation', function () {
|
||||
Notification::fake();
|
||||
|
||||
$consultation = Consultation::factory()->create([
|
||||
'status' => 'no_show',
|
||||
'scheduled_date' => now()->addHours(24)->toDateString(),
|
||||
'scheduled_time' => now()->addHours(24)->format('H:i:s'),
|
||||
]);
|
||||
|
||||
$this->artisan('reminders:send-24h')
|
||||
->assertSuccessful();
|
||||
|
||||
Notification::assertNotSentTo($consultation->user, ConsultationReminder24h::class);
|
||||
});
|
||||
|
||||
it('does not send duplicate 24h reminders', function () {
|
||||
Notification::fake();
|
||||
|
||||
$consultation = Consultation::factory()->approved()->create([
|
||||
'scheduled_date' => now()->addHours(24)->toDateString(),
|
||||
'scheduled_time' => now()->addHours(24)->format('H:i:s'),
|
||||
'reminder_24h_sent_at' => now()->subHour(), // Already sent
|
||||
]);
|
||||
|
||||
$this->artisan('reminders:send-24h')
|
||||
->assertSuccessful();
|
||||
|
||||
Notification::assertNotSentTo($consultation->user, ConsultationReminder24h::class);
|
||||
});
|
||||
|
||||
it('sends 2h reminder for upcoming consultation', function () {
|
||||
Notification::fake();
|
||||
|
||||
$consultation = Consultation::factory()->approved()->create([
|
||||
'scheduled_date' => now()->addHours(2)->toDateString(),
|
||||
'scheduled_time' => now()->addHours(2)->format('H:i:s'),
|
||||
'reminder_2h_sent_at' => null,
|
||||
]);
|
||||
|
||||
$this->artisan('reminders:send-2h')
|
||||
->assertSuccessful();
|
||||
|
||||
Notification::assertSentTo(
|
||||
$consultation->user,
|
||||
ConsultationReminder2h::class
|
||||
);
|
||||
|
||||
expect($consultation->fresh()->reminder_2h_sent_at)->not->toBeNull();
|
||||
});
|
||||
|
||||
it('includes payment reminder for unpaid consultations', function () {
|
||||
Notification::fake();
|
||||
|
||||
$consultation = Consultation::factory()->approved()->create([
|
||||
'scheduled_date' => now()->addHours(24)->toDateString(),
|
||||
'scheduled_time' => now()->addHours(24)->format('H:i:s'),
|
||||
'type' => 'paid',
|
||||
'payment_status' => 'pending',
|
||||
'payment_amount' => 200.00,
|
||||
]);
|
||||
|
||||
$this->artisan('reminders:send-24h')
|
||||
->assertSuccessful();
|
||||
|
||||
Notification::assertSentTo(
|
||||
$consultation->user,
|
||||
ConsultationReminder24h::class,
|
||||
function ($notification) {
|
||||
return $notification->consultation->type === 'paid'
|
||||
&& $notification->consultation->payment_status === 'pending';
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('respects user language preference for reminders', function () {
|
||||
Notification::fake();
|
||||
|
||||
$user = User::factory()->create(['preferred_language' => 'en']);
|
||||
$consultation = Consultation::factory()->approved()->create([
|
||||
'user_id' => $user->id,
|
||||
'scheduled_date' => now()->addHours(24)->toDateString(),
|
||||
'scheduled_time' => now()->addHours(24)->format('H:i:s'),
|
||||
]);
|
||||
|
||||
$this->artisan('reminders:send-24h')
|
||||
->assertSuccessful();
|
||||
|
||||
Notification::assertSentTo($user, ConsultationReminder24h::class);
|
||||
});
|
||||
```
|
||||
|
||||
## Definition of Done
|
||||
@@ -363,8 +479,11 @@ it('does not send reminder for cancelled consultation', function () {
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Story 3.5:** Booking approval (creates approved consultations)
|
||||
- **Epic 8:** Email infrastructure
|
||||
- **Story 3.4:** Consultation model and booking submission (`app/Models/Consultation.php`, `database/factories/ConsultationFactory.php`)
|
||||
- **Story 3.5:** Booking approval workflow (creates approved consultations)
|
||||
- **Story 3.6:** Calendar file generation (provides `client.consultations.calendar` route)
|
||||
- **Epic 1:** User model with `preferred_language` field (`app/Models/User.php`)
|
||||
- **Epic 8:** Email infrastructure (mail configuration, queue setup)
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
|
||||
Reference in New Issue
Block a user