libra/docs/stories/story-4.6-timeline-update-n...

8.9 KiB

Story 4.6: Timeline Update Notifications

Epic Reference

Epic 4: Case Timeline System

User Story

As a client, I want to receive email notifications when my timeline is updated, So that I stay informed about my case progress without checking the portal.

Story Context

Existing System Integration

  • Integrates with: timeline_updates creation, email system
  • Technology: Laravel Notifications, queued emails
  • Follows pattern: Event-driven notification pattern
  • Touch points: Timeline update creation

Previous Story Context (Story 4.2)

Story 4.2 established:

  • TimelineUpdate model with fields: timeline_id, admin_id, update_text
  • TimelineUpdate belongs to Timeline and User (admin)
  • Timeline has many TimelineUpdate records
  • Updates created via addUpdate() method in timeline management Volt component
  • Notification trigger point: after $this->timeline->updates()->create([...])

Prerequisites

The following must exist before implementing this story:

  • User model requirements:
    • preferred_language field (string, defaults to 'ar') - from Epic 2 user registration
    • isActive() method returning boolean (checks status !== 'deactivated') - from Epic 2
  • Route requirement:
    • client.timelines.show route must exist (from Story 4.5)
  • Models available:
    • Timeline model with user() relationship
    • TimelineUpdate model with timeline() relationship

Implementation Files

  • Notification: app/Notifications/TimelineUpdateNotification.php
  • Arabic Template: resources/views/emails/timeline/update/ar.blade.php
  • English Template: resources/views/emails/timeline/update/en.blade.php
  • Modify: Story 4.2 Volt component to trigger notification

Acceptance Criteria

Notification Trigger

  • Email sent when new update added to timeline
  • Triggered automatically on TimelineUpdate creation
  • Queued for performance

Email Content

  • Case name and reference
  • Update summary or full text
  • Date of update
  • Link to view timeline
  • Libra branding

Language Support

  • Email in client's preferred language
  • Arabic template
  • English template

Exclusions

  • No email for archived timeline reactivation
  • No email if client deactivated

Quality Requirements

  • Professional email template
  • Tests for notification sending
  • Error handling for failed sends

Technical Notes

Notification Class

<?php

namespace App\Notifications;

use App\Models\TimelineUpdate;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;

class TimelineUpdateNotification extends Notification
{
    use Queueable;

    public function __construct(
        public TimelineUpdate $update
    ) {}

    public function via(object $notifiable): array
    {
        return ['mail'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        $locale = $notifiable->preferred_language ?? 'ar';
        $timeline = $this->update->timeline;

        return (new MailMessage)
            ->subject($this->getSubject($locale, $timeline->case_name))
            ->markdown('emails.timeline.update.' . $locale, [
                'update' => $this->update,
                'timeline' => $timeline,
                'user' => $notifiable,
            ]);
    }

    private function getSubject(string $locale, string $caseName): string
    {
        return $locale === 'ar'
            ? "تحديث جديد على قضيتك: {$caseName}"
            : "New update on your case: {$caseName}";
    }
}

Email Templates

Arabic

<x-mail::message>
# تحديث جديد على قضيتك

عزيزي {{ $user->name }}،

تم إضافة تحديث جديد على قضيتك:

**القضية:** {{ $timeline->case_name }}
@if($timeline->case_reference)
**الرقم المرجعي:** {{ $timeline->case_reference }}
@endif

**تاريخ التحديث:** {{ $update->created_at->translatedFormat('d M Y - g:i A') }}

---

{!! $update->update_text !!}

---

<x-mail::button :url="route('client.timelines.show', $timeline)">
عرض التفاصيل الكاملة
</x-mail::button>

مع أطيب التحيات،
مكتب ليبرا للمحاماة
</x-mail::message>

English

<x-mail::message>
# New Update on Your Case

Dear {{ $user->name }},

A new update has been added to your case:

**Case:** {{ $timeline->case_name }}
@if($timeline->case_reference)
**Reference:** {{ $timeline->case_reference }}
@endif

**Update Date:** {{ $update->created_at->format('M d, Y - g:i A') }}

---

{!! $update->update_text !!}

---

<x-mail::button :url="route('client.timelines.show', $timeline)">
View Full Details
</x-mail::button>

Best regards,
Libra Law Firm
</x-mail::message>

Trigger in Update Creation

// In Story 4.2 addUpdate method
$update = $this->timeline->updates()->create([...]);

// Check if user is active before notifying
if ($this->timeline->user->isActive()) {
    $this->timeline->user->notify(new TimelineUpdateNotification($update));
}

Test Scenarios

  • Notification sent when timeline update created
  • No notification to deactivated user (status === 'deactivated')
  • Arabic template renders with correct content (case name, update text, date)
  • English template renders with correct content
  • Uses client's preferred_language for template selection (defaults to 'ar')
  • Email is queued (uses ShouldQueue interface)
  • Email contains valid link to timeline view
  • Subject line includes case name in correct language

Testing Examples

use App\Models\{User, Timeline, TimelineUpdate};
use App\Notifications\TimelineUpdateNotification;
use Illuminate\Support\Facades\Notification;

it('sends notification when timeline update created', function () {
    Notification::fake();

    $user = User::factory()->create(['status' => 'active']);
    $timeline = Timeline::factory()->for($user)->create();
    $update = TimelineUpdate::factory()->create(['timeline_id' => $timeline->id]);

    // Simulate the trigger from Story 4.2
    if ($timeline->user->isActive()) {
        $timeline->user->notify(new TimelineUpdateNotification($update));
    }

    Notification::assertSentTo($timeline->user, TimelineUpdateNotification::class);
});

it('does not send notification to deactivated user', function () {
    Notification::fake();

    $user = User::factory()->create(['status' => 'deactivated']);
    $timeline = Timeline::factory()->for($user)->create();
    $update = TimelineUpdate::factory()->create(['timeline_id' => $timeline->id]);

    // Simulate the trigger from Story 4.2 - should NOT notify
    if ($timeline->user->isActive()) {
        $timeline->user->notify(new TimelineUpdateNotification($update));
    }

    Notification::assertNotSentTo($user, TimelineUpdateNotification::class);
});

it('uses arabic template for arabic-preferred user', function () {
    Notification::fake();

    $user = User::factory()->create(['preferred_language' => 'ar']);
    $timeline = Timeline::factory()->for($user)->create(['case_name' => 'Test Case']);
    $update = TimelineUpdate::factory()->create(['timeline_id' => $timeline->id]);

    $user->notify(new TimelineUpdateNotification($update));

    Notification::assertSentTo($user, TimelineUpdateNotification::class, function ($notification, $channels, $notifiable) {
        $mail = $notification->toMail($notifiable);
        return str_contains($mail->subject, 'تحديث جديد على قضيتك');
    });
});

it('uses english template for english-preferred user', function () {
    Notification::fake();

    $user = User::factory()->create(['preferred_language' => 'en']);
    $timeline = Timeline::factory()->for($user)->create(['case_name' => 'Test Case']);
    $update = TimelineUpdate::factory()->create(['timeline_id' => $timeline->id]);

    $user->notify(new TimelineUpdateNotification($update));

    Notification::assertSentTo($user, TimelineUpdateNotification::class, function ($notification, $channels, $notifiable) {
        $mail = $notification->toMail($notifiable);
        return str_contains($mail->subject, 'New update on your case');
    });
});

Definition of Done

  • Email sent on new update
  • Arabic template works
  • English template works
  • Uses client's preferred language
  • Link to timeline works
  • Queued for performance
  • No email to deactivated users
  • Tests pass
  • Code formatted with Pint

Dependencies

  • Story 4.2: Timeline updates management (REQUIRED - notification triggered from addUpdate method)
  • Story 4.5: Client timeline view (REQUIRED - provides client.timelines.show route used in email link)
  • Epic 2: User model with preferred_language field and isActive() method
  • Epic 8: Email infrastructure (SOFT DEPENDENCY - notification will queue but email delivery requires Epic 8)

Estimation

Complexity: Low-Medium Estimated Effort: 2-3 hours