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

15 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


Dev Agent Record

Status

Ready for Review

Agent Model Used

Claude Opus 4.5

File List

File Action Description
app/Notifications/TimelineUpdateNotification.php Modified Updated notification to use markdown() method, renamed property to update, added case name to subject line
resources/views/emails/timeline/update/ar.blade.php Created Arabic email template with case details, update text, and timeline link
resources/views/emails/timeline/update/en.blade.php Created English email template with case details, update text, and timeline link
resources/views/livewire/admin/timelines/show.blade.php Modified Added isActive() check before sending notification to prevent emails to deactivated users
tests/Feature/Admin/TimelineUpdatesManagementTest.php Modified Added 8 new tests for notification behavior (deactivated users, language templates, subject lines, queueing)

Change Log

  • Updated TimelineUpdateNotification class to match story specifications:
    • Changed property name from $timelineUpdate to $update
    • Updated toMail() to use markdown() method instead of view()
    • Added case name to email subject line for both languages
    • Template path changed from emails.timeline-update to emails.timeline.update.{locale}
  • Created separate Arabic and English markdown email templates at resources/views/emails/timeline/update/
  • Added isActive() check in addUpdate() method to prevent notifications to deactivated users
  • Added comprehensive tests covering:
    • Deactivated user exclusion
    • Arabic subject with case name
    • English subject with case name
    • Default language behavior
    • Queue implementation verification
    • Correct markdown template selection

Completion Notes

  1. The notification class already existed and implemented ShouldQueue, so emails are automatically queued
  2. The notification trigger already existed in the Volt component but lacked the isActive() check - this was added
  3. Used <x-mail::message> component syntax in templates as specified in story rather than @component syntax used elsewhere in codebase - both work with Laravel's mail markdown system
  4. All 37 tests in TimelineUpdatesManagementTest.php pass, including 8 new notification-specific tests
  5. Full test suite passes with 364 tests and 944 assertions

DOD Checklist

  1. Requirements Met:

    • All functional requirements specified in the story are implemented
    • All acceptance criteria defined in the story are met
  2. Coding Standards & Project Structure:

    • Code adheres to Operational Guidelines (class-based Volt, Flux UI patterns)
    • Code aligns with Project Structure (notification in app/Notifications/, templates in resources/views/emails/)
    • Tech Stack followed (Laravel Notifications, Livewire, Pest)
    • Security best practices applied (HTML sanitization via clean() already in place)
    • No new linter errors (Pint passed)
    • [N/A] Code comments - logic is straightforward and self-documenting
  3. Testing:

    • Unit tests implemented (8 new notification tests)
    • Integration tests via Volt component tests
    • All 364 tests pass
    • Test coverage adequate for notification functionality
  4. Functionality & Verification:

    • Tests verify notification sending behavior
    • Edge cases handled (deactivated users, null language defaults to 'ar')
  5. Story Administration:

    • All tasks marked complete
    • Changes documented in this section
    • Story wrap up complete
  6. Dependencies, Build & Configuration:

    • Project builds successfully
    • Linting passes (Pint)
    • [N/A] No new dependencies added
    • [N/A] No new environment variables
  7. Documentation:

    • [N/A] No new public APIs requiring JSDoc
    • [N/A] No user-facing documentation changes needed
    • [N/A] No architectural changes

QA Results

Review Date: 2025-12-27

Reviewed By: Quinn (Test Architect)

Code Quality Assessment

The implementation is clean, well-structured, and follows Laravel best practices. The notification class properly implements ShouldQueue for performance, uses constructor property promotion, and has clear separation of concerns. Email templates are properly organized by locale and use Laravel's markdown mail component syntax correctly.

Refactoring Performed

None required - code quality is high and follows project conventions.

Compliance Check

  • Coding Standards: ✓ Code follows Laravel conventions, Pint passes
  • Project Structure: ✓ Files in correct locations (Notifications, email views)
  • Testing Strategy: ✓ Comprehensive Pest tests with proper assertions
  • All ACs Met: ✓ All 11 acceptance criteria verified

Improvements Checklist

All items already addressed by development:

  • Notification class implements ShouldQueue for queued delivery
  • isActive() check prevents notifications to deactivated users
  • Arabic and English templates properly render with case details
  • Subject line includes case name in appropriate language
  • HTML sanitization via clean() protects against XSS in update text
  • Email templates handle both individual (full_name) and company (company_name) clients

Security Review

Status: PASS

  • HTML sanitization using clean() function prevents XSS in update_text
  • No sensitive data exposed in email beyond necessary case information
  • Proper authorization through admin middleware on update creation endpoint

Performance Considerations

Status: PASS

  • Notification implements ShouldQueue for async delivery
  • No N+1 queries - timeline relationship is eagerly loaded
  • Email rendering happens in queue worker, not blocking HTTP request

Files Modified During Review

None - no modifications were necessary.

Gate Status

Gate: PASS → docs/qa/gates/4.6-timeline-update-notifications.yml

✓ Ready for Done - All acceptance criteria met, tests pass, code quality is excellent.