# Story 8.8: Timeline Update Notification ## Epic Reference **Epic 8:** Email Notification System ## Story Context This story implements email notifications when admin adds updates to a client's case timeline (from Epic 4). When admin creates a `TimelineUpdate` record, the associated client automatically receives an email with the update details, keeping them informed of their case progress without needing to manually check the portal. ## User Story As a **client**, I want **to be notified via email when my case timeline is updated**, So that **I stay informed about my case progress without having to repeatedly check the portal**. ## Dependencies - **Requires**: Story 8.1 (Email Infrastructure - `BaseMailable` class and templates) - **Requires**: Epic 4 Stories 4.1-4.2 (Timeline and TimelineUpdate models must exist) - **Blocks**: None ## Data Model Reference From Epic 4, the relevant models are: ``` Timeline ├── id ├── user_id (FK → users.id, the client) ├── case_name (string, required) ├── case_reference (string, optional, unique if provided) ├── status (enum: 'active', 'archived') ├── created_at └── updated_at TimelineUpdate ├── id ├── timeline_id (FK → timelines.id) ├── admin_id (FK → users.id, the admin who created it) ├── update_text (text, the update content) ├── created_at └── updated_at Relationships: - TimelineUpdate belongsTo Timeline - Timeline belongsTo User (client) - Timeline hasMany TimelineUpdate - Access client: $timelineUpdate->timeline->user ``` ## Acceptance Criteria ### Trigger - [ ] Email sent automatically when `TimelineUpdate` is created - [ ] Uses model observer pattern for clean separation - [ ] Email queued for performance (not sent synchronously) - [ ] Only triggered for active timelines (not archived) ### Content - [ ] Subject: "Update on your case: [Case Name]" / "تحديث على قضيتك: [اسم القضية]" - [ ] Case reference number (if exists) - [ ] Full update content text - [ ] Date of update (formatted for locale) - [ ] "View Timeline" button linking to client dashboard timeline view ### Language - [ ] Email template selected based on client's `preferred_language` - [ ] Default to Arabic ('ar') if no preference set - [ ] Date formatting appropriate for locale ### Design - [ ] Uses base email template from Story 8.1 (Libra branding) - [ ] Professional, informative tone - [ ] Clear visual hierarchy: case name → update content → action button ## Technical Implementation ### Files to Create/Modify | File | Action | Description | |------|--------|-------------| | `app/Mail/TimelineUpdateEmail.php` | Create | Mailable extending BaseMailable | | `resources/views/emails/timeline/update-ar.blade.php` | Create | Arabic email template | | `resources/views/emails/timeline/update-en.blade.php` | Create | English email template | | `app/Observers/TimelineUpdateObserver.php` | Create | Observer to trigger email | | `app/Providers/AppServiceProvider.php` | Modify | Register the observer | ### Mailable Implementation ```php locale = $this->update->timeline->user->preferred_language ?? 'ar'; } public function envelope(): Envelope { $caseName = $this->update->timeline->case_name; $subject = $this->locale === 'ar' ? "تحديث على قضيتك: {$caseName}" : "Update on your case: {$caseName}"; return new Envelope( subject: $subject, ); } public function content(): Content { return new Content( markdown: "emails.timeline.update-{$this->locale}", with: [ 'update' => $this->update, 'timeline' => $this->update->timeline, 'client' => $this->update->timeline->user, 'viewUrl' => route('client.timelines.show', $this->update->timeline), ], ); } } ``` ### Observer Implementation ```php timeline->status !== 'active') { return; } $client = $update->timeline->user; Mail::to($client->email)->queue( new TimelineUpdateEmail($update) ); } } ``` ### Register Observer In `AppServiceProvider::boot()`: ```php use App\Models\TimelineUpdate; use App\Observers\TimelineUpdateObserver; public function boot(): void { TimelineUpdate::observe(TimelineUpdateObserver::class); } ``` ### Arabic Template Structure (`update-ar.blade.php`) ```blade # تحديث على قضيتك **اسم القضية:** {{ $timeline->case_name }} @if($timeline->case_reference) **رقم المرجع:** {{ $timeline->case_reference }} @endif --- ## التحديث {{ $update->update_text }} **تاريخ التحديث:** {{ $update->created_at->locale('ar')->isoFormat('LL') }} عرض الجدول الزمني مع تحياتنا,
{{ config('app.name') }}
``` ### English Template Structure (`update-en.blade.php`) ```blade # Update on Your Case **Case Name:** {{ $timeline->case_name }} @if($timeline->case_reference) **Reference:** {{ $timeline->case_reference }} @endif --- ## Update {{ $update->update_text }} **Date:** {{ $update->created_at->format('F j, Y') }} View Timeline Best regards,
{{ config('app.name') }}
``` ## Edge Cases & Error Handling | Scenario | Handling | |----------|----------| | Archived timeline gets update | No email sent (observer checks status) | | Client has no `preferred_language` | Default to Arabic ('ar') | | Client email is null/invalid | Mail will fail gracefully, logged to failed_jobs | | Timeline has no case_reference | Template conditionally hides reference line | | Multiple rapid updates | Each triggers separate email (acceptable per requirements) | ## Testing Requirements ### Test File Create `tests/Feature/Mail/TimelineUpdateEmailTest.php` ### Test Scenarios ```php create(['preferred_language' => 'en']); $timeline = Timeline::factory()->for($client)->create(['status' => 'active']); TimelineUpdate::factory()->for($timeline)->create(); Mail::assertQueued(TimelineUpdateEmail::class); }); test('email is not sent for archived timeline updates', function () { Mail::fake(); $client = User::factory()->create(); $timeline = Timeline::factory()->for($client)->create(['status' => 'archived']); TimelineUpdate::factory()->for($timeline)->create(); Mail::assertNothingQueued(); }); test('email uses arabic template when client prefers arabic', function () { $client = User::factory()->create(['preferred_language' => 'ar']); $timeline = Timeline::factory()->for($client)->create(); $update = TimelineUpdate::factory()->for($timeline)->create(); $mailable = new TimelineUpdateEmail($update); expect($mailable->locale)->toBe('ar'); }); test('email uses english template when client prefers english', function () { $client = User::factory()->create(['preferred_language' => 'en']); $timeline = Timeline::factory()->for($client)->create(); $update = TimelineUpdate::factory()->for($timeline)->create(); $mailable = new TimelineUpdateEmail($update); expect($mailable->locale)->toBe('en'); }); test('email defaults to arabic when no language preference', function () { $client = User::factory()->create(['preferred_language' => null]); $timeline = Timeline::factory()->for($client)->create(); $update = TimelineUpdate::factory()->for($timeline)->create(); $mailable = new TimelineUpdateEmail($update); expect($mailable->locale)->toBe('ar'); }); test('email contains case name in subject', function () { $client = User::factory()->create(['preferred_language' => 'en']); $timeline = Timeline::factory()->for($client)->create(['case_name' => 'Smith vs Jones']); $update = TimelineUpdate::factory()->for($timeline)->create(); $mailable = new TimelineUpdateEmail($update); expect($mailable->envelope()->subject)->toContain('Smith vs Jones'); }); test('email contains update content', function () { $client = User::factory()->create(['preferred_language' => 'en']); $timeline = Timeline::factory()->for($client)->create(); $update = TimelineUpdate::factory()->for($timeline)->create([ 'update_text' => 'Court date scheduled for next month.', ]); $mailable = new TimelineUpdateEmail($update); $mailable->assertSeeInHtml('Court date scheduled for next month.'); }); test('email contains view timeline link', function () { $client = User::factory()->create(['preferred_language' => 'en']); $timeline = Timeline::factory()->for($client)->create(); $update = TimelineUpdate::factory()->for($timeline)->create(); $mailable = new TimelineUpdateEmail($update); $mailable->assertSeeInHtml(route('client.timelines.show', $timeline)); }); ``` ## Definition of Done - [ ] `TimelineUpdateEmail` mailable created extending `BaseMailable` - [ ] Arabic and English templates created with proper formatting - [ ] Observer registered and triggers on `TimelineUpdate` creation - [ ] Email only sent for active timelines (not archived) - [ ] Email queued (not sent synchronously) - [ ] Subject includes case name in appropriate language - [ ] Email body includes case reference (if exists), update content, and date - [ ] View Timeline button links to correct client dashboard route - [ ] All tests pass - [ ] Code formatted with Pint ## Estimation **Complexity:** Low | **Effort:** 2-3 hours