Files
L-Ami-Fiduciaire/_bmad-output/implementation-artifacts/3-1-notification-infrastructure-setup.md
Saad Zoubir 1ab3cfc445 feat: add notification infrastructure with database channel, enum, and notification classes (Story 3.1)
Set up Laravel notification system with NotificationType enum (5 types),
NudgeNotification, DocumentUploadedNotification, and DeclarationOverdueNotification
classes with database + mail channels. Add email templates, infrastructure tests,
and fix existing NotificationController tests for workspace compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:26:03 +01:00

16 KiB

Story 3.1: Notification Infrastructure Setup

Status: review

Story

As a developer, I want the Laravel notification system configured with database and mail channels, a NotificationType enum, and foundational notification classes, so that all future notification features (nudges, alerts, bulk notifications) have a reliable, consistent foundation to build on.

Acceptance Criteria

  1. AC1: Notifications table migration exists and runs

    • Given Redis queue is configured (from Story 0.4)
    • When php artisan migrate is run
    • Then the notifications table exists with Laravel's standard schema (uuid id, type, morphs notifiable, text data, read_at, timestamps)
  2. AC2: User model confirms Notifiable trait

    • Then User model uses Illuminate\Notifications\Notifiable trait
  3. AC3: NotificationType enum created

    • Then app/Enums/NotificationType.php exists using bensampo/laravel-enum
    • And values include: nudge, declaration_overdue, document_uploaded, bulk_notification, status_changed
  4. AC4: Base notification pattern established

    • Then all new notification classes implement ShouldQueue, use Queueable trait
    • And all notification toArray()/toDatabase() payloads include workspace_id
    • And all notification toArray()/toDatabase() payloads include notification_type from the NotificationType enum
  5. AC5: NudgeNotification class created

    • Then app/Notifications/NudgeNotification.php exists with database and mail channels
    • And payload includes: workspace_id, declaration_id, sender_id, notification_type = nudge
    • And toMail() returns a queued NudgeNotificationMail Markdown mailable
  6. AC6: DocumentUploadedNotification class created

    • Then app/Notifications/DocumentUploadedNotification.php exists with database channel only (no email — reduces noise)
    • And payload includes: workspace_id, declaration_id, client_id, notification_type = document_uploaded
  7. AC7: DeclarationOverdueNotification class created

    • Then app/Notifications/DeclarationOverdueNotification.php exists with database and mail channels
    • And payload includes: workspace_id, declaration_id, notification_type = declaration_overdue
    • And queued email sending with up to 3 retries ($tries = 3 per NFR26)
  8. AC8: NudgeNotificationMail mailable created

    • Then app/Mail/NudgeNotificationMail.php exists as a Markdown mailable
    • And it renders with sender name, declaration details (client name, type, due_date), and a direct link button ("Voir la declaration")
    • And uses professional French copy
  9. AC9: Notification queries are workspace-scoped

    • Then when fetching notifications, they are filtered by workspace_id in the JSON data payload
    • And NotificationController is extended with an index method returning workspace-scoped notifications
  10. AC10: Routes registered for notification endpoints

    • Then GET /notifications route exists → NotificationController@index (Inertia page)
    • And POST /notifications/mark-all-read route exists → NotificationController@markAllAsRead
    • And existing POST /notifications/{id}/read route remains functional
  11. AC11: All notification classes have retry configuration

    • Then notification classes with mail channel set public $tries = 3 (NFR26)

Tasks / Subtasks

  • Task 1: Verify existing notifications infrastructure (AC: #1, #2)

    • 1.1 Confirm notifications table migration already exists (2026_03_09_000004_create_notifications_table.php) — do NOT create a duplicate
    • 1.2 Confirm User model already has Notifiable trait — already present, no changes needed
    • 1.3 Run php artisan migrate to confirm table exists
  • Task 2: Create NotificationType enum (AC: #3)

    • 2.1 Create app/Enums/NotificationType.php using bensampo/laravel-enum pattern
    • 2.2 Values: nudge, declaration_overdue, document_uploaded, bulk_notification, status_changed
    • 2.3 Follow existing enum patterns (see DeclarationStatus.php for reference)
  • Task 3: Create NudgeNotification class (AC: #4, #5, #11)

    • 3.1 Create app/Notifications/NudgeNotification.php
    • 3.2 Implement ShouldQueue, use Queueable trait, set $tries = 3
    • 3.3 Channels: ['database', 'mail']
    • 3.4 toDatabase() payload: workspace_id, declaration_id, sender_id, notification_typenudge
    • 3.5 toMail() returns NudgeNotificationMail mailable
  • Task 4: Create DocumentUploadedNotification class (AC: #4, #6)

    • 4.1 Create app/Notifications/DocumentUploadedNotification.php
    • 4.2 Implement ShouldQueue, use Queueable trait
    • 4.3 Channels: ['database'] only — no mail
    • 4.4 toDatabase() payload: workspace_id, declaration_id, client_id, notification_typedocument_uploaded
  • Task 5: Create DeclarationOverdueNotification class (AC: #4, #7, #11)

    • 5.1 Create app/Notifications/DeclarationOverdueNotification.php
    • 5.2 Implement ShouldQueue, use Queueable trait, set $tries = 3
    • 5.3 Channels: ['database', 'mail']
    • 5.4 toDatabase() payload: workspace_id, declaration_id, notification_typedeclaration_overdue
    • 5.5 toMail() returns MailMessage with declaration details and link
  • Task 6: Create NudgeNotificationMail mailable (AC: #8)

    • 6.1 Create app/Mail/NudgeNotificationMail.php as Markdown mailable
    • 6.2 Create Markdown template resources/views/emails/nudge-notification.blade.php
    • 6.3 Include: sender name, declaration client name, declaration type, due_date, direct link button ("Voir la declaration")
    • 6.4 Professional French copy, include workspace firm name in header
  • Task 7: Extend NotificationController with index (AC: #9, #10)

    • 7.1 Add index() method to existing NotificationController
    • 7.2 Fetch user notifications, filter by workspace_id in JSON data matching current workspace
    • 7.3 Paginate 25 per page, reverse chronological order
    • 7.4 Return Inertia render to notifications/Index page
    • 7.5 Register GET /notifications route in web.php
  • Task 8: Clean up legacy notification (no AC — housekeeping)

    • 8.1 Evaluate FolderMentionNotification.php — this references the deprecated Folder model and folders.show route. Flag for removal or skip if out of scope
  • Task 9: Create notification type definitions for frontend (AC: #9)

    • 9.1 Create resources/js/types/notification.ts with TypeScript types for notification data
  • Task 10: Write tests (all ACs)

    • 10.1 Create tests/Feature/Notifications/NotificationInfrastructureTest.php
    • 10.2 Test: NotificationType enum has all expected values
    • 10.3 Test: NudgeNotification sends via database and mail channels (Notification::fake())
    • 10.4 Test: DocumentUploadedNotification sends via database channel only
    • 10.5 Test: DeclarationOverdueNotification sends via database and mail channels
    • 10.6 Test: All notification payloads include workspace_id and notification_type
    • 10.7 Test: NotificationController@index returns paginated, workspace-scoped notifications
    • 10.8 Test: NotificationController@markAsRead marks notification as read
    • 10.9 Test: NotificationController@markAllAsRead marks all as read
    • 10.10 Test: Unauthenticated users cannot access notification routes

Retrospective Intelligence

From Epic 2 Retrospective (2026-03-24):

  • CRITICAL — Pre-existing test failures: 15 tests were failing across 3 retrospectives. Commit 716e9fc resolved them. Verify all 222+ tests still pass before starting and after finishing this story.
  • Team Agreement — Load retro as context: This story was created with full retrospective intelligence (this section proves it).
  • Team Agreement — Pre-existing test failures must not carry: Run full test suite before declaring this story done. Zero failures tolerance.
  • Deferred Item D-1: Nudge/Reassign dropdown items in dashboard are unconditionally disabled — Story 3.2 will enable them. This story creates the NudgeNotification they depend on.
  • Architecture doc drift was corrected in commit 6956f7bdue_date (not deadline), no Declaration::workspace() scope, mise_en_demeure status documented.
  • withoutVite() global workaround in tests/Pest.php — confirmed compatible with notification tests (no Vite manifest needed for backend tests).
  • Context management is the team's superpower — rich story specs with previous intelligence prevent errors across stories.

From Epic 1 Retrospective:

  • withPivot gotchas now documented in project-context.md (commit 6956f7b). When loading workspace members, always chain ->withPivot('role', 'permissions').

Dev Notes

Existing Infrastructure (DO NOT recreate)

  • notifications table migration ALREADY EXISTS: database/migrations/2026_03_09_000004_create_notifications_table.php — standard Laravel schema with uuid id, type, morphs notifiable, text data, read_at, timestamps. Do NOT run php artisan notifications:table or create a duplicate.
  • User model ALREADY has Notifiable trait: Line 19 of app/Models/User.phpuse HasFactory, LogsActivity, Notifiable, SoftDeletes, TwoFactorAuthenticatable;
  • NotificationController ALREADY EXISTS: app/Http/Controllers/NotificationController.php with markAsRead() and markAllAsRead() methods. Extend this controller, do not replace it.
  • Existing routes: POST /notifications/{id}/read and POST /notifications/read-all already registered in web.php. Add the new GET /notifications route alongside them.
  • Existing notification classes: DeclarationMentionNotification.php (database + mail, ShouldQueue, Queueable — good pattern to follow) and FolderMentionNotification.php (legacy — references deprecated Folder model).

Existing Notification Pattern to Follow

The DeclarationMentionNotification is the reference implementation:

  • Constructor with promoted properties: public Declaration $declaration, public User $mentionedBy, public string $message
  • via() returns ['database', 'mail']
  • toDatabase() returns array with declaration details + URL via route() helper
  • toMail() returns MailMessage with ->markdown() template
  • French subject line pattern: 'Vous avez été mentionné - '.$this->declaration->title

Architecture Constraints

  • D8: Use Laravel's built-in DatabaseNotification system — no custom notification tables
  • D5: Redis queue driver — all notification classes implement ShouldQueue + Queueable
  • D10: No real-time features for MVP — notifications load on page visit only
  • Workspace scoping: Filter notifications by workspace_id in the JSON data column, not by a direct relationship. Use whereJsonContains('data->workspace_id', $workspace->id) or filter after fetching.
  • Notification type enum values: snake_case (nudge, declaration_overdue, etc.)
  • Route pattern from architecture: Route::get('notifications', [NotificationController::class, 'index'])->name('notifications.index');

Critical Gotchas

  • Declaration column is due_date NOT deadline — use $declaration->due_date everywhere
  • Declaration scoping: No Declaration::workspace() scope — use Declaration::where('workspace_id', $workspace->id)
  • Declaration statuses: 6 values — created, en_cours, en_attente_client, mise_en_demeure, termine, ferme
  • Authorization returns 404 not 403 for workspace boundary violations
  • Workspace is session-based — resolve from $request->user()->currentWorkspace or session current_workspace_id
  • Enum pattern: Use bensampo/laravel-enum — look at DeclarationStatus.php for exact syntax
  • All URLs passed as props from PHP controllers — never hardcode in Vue
  • Use route() helper for URLs in notification payloads and tests

Testing Standards

  • Pest syntax with test() closures
  • Notification::fake() for testing notification dispatch
  • Mail::fake() if testing mail separately
  • Feature tests in tests/Feature/Notifications/
  • Use route() helper for URLs, never hardcoded paths
  • RefreshDatabase is auto-applied via Pest.php — don't add manually
  • Run composer test to verify all tests pass (currently 222+ tests)

Project Structure Notes

New files to create:

  • app/Enums/NotificationType.php — follows existing enum pattern in app/Enums/
  • app/Notifications/NudgeNotification.php — follows existing DeclarationMentionNotification.php pattern
  • app/Notifications/DocumentUploadedNotification.php — database channel only
  • app/Notifications/DeclarationOverdueNotification.php — database + mail channels
  • app/Mail/NudgeNotificationMail.php — Markdown mailable, follows existing mail pattern in app/Mail/
  • resources/views/emails/nudge-notification.blade.php — Markdown mail template
  • resources/js/types/notification.ts — TypeScript type definitions
  • tests/Feature/Notifications/NotificationInfrastructureTest.php

Existing files to modify:

  • app/Http/Controllers/NotificationController.php — add index() method
  • routes/web.php — add GET /notifications route

Files NOT to touch:

  • database/migrations/2026_03_09_000004_create_notifications_table.php — already exists
  • app/Models/User.php — already has Notifiable trait
  • resources/js/components/ui/* — shadcn-vue, never modify

References

  • [Source: _bmad-output/planning-artifacts/architecture.md#D8 — In-App Notification System]
  • [Source: _bmad-output/planning-artifacts/architecture.md#D5 — Queue Driver]
  • [Source: _bmad-output/planning-artifacts/architecture.md#Notification Patterns]
  • [Source: _bmad-output/planning-artifacts/architecture.md#Phase 3 File Structure]
  • [Source: _bmad-output/planning-artifacts/epics.md#Story 3.1]
  • [Source: _bmad-output/planning-artifacts/prd.md#FR29-FR33]
  • [Source: _bmad-output/implementation-artifacts/epic-2-retro-2026-03-24.md#Action Items]
  • [Source: _bmad-output/project-context.md#Critical Implementation Rules]

Dev Agent Record

Agent Model Used

Claude Opus 4.6 (1M context)

Debug Log References

  • Baseline test suite: 223 tests passed before implementation
  • Final test suite: 237 tests passed (14 new notification tests added), zero failures
  • One test fix: created notifications/Index.vue page component to satisfy Inertia page existence check in test

Completion Notes List

  • Verified existing infrastructure: notifications migration, User Notifiable trait, NotificationController with markAsRead/markAllAsRead
  • Created NotificationType enum with 5 values following bensampo/laravel-enum pattern
  • Created NudgeNotification (database+mail, $tries=3, toMail returns NudgeNotificationMail)
  • Created DocumentUploadedNotification (database only, no mail to reduce noise)
  • Created DeclarationOverdueNotification (database+mail, $tries=3, French subject line)
  • Created NudgeNotificationMail as Markdown mailable with professional French copy
  • Created declaration-overdue email template
  • Extended NotificationController with workspace-scoped index() using whereJsonContains
  • Added GET /notifications and POST /notifications/mark-all-read routes inside workspace middleware
  • Created notifications/Index.vue page with basic notification list UI
  • Flagged FolderMentionNotification as legacy (references deprecated Folder model) — skipped removal as out of scope
  • Created TypeScript notification types in resources/js/types/notification.ts
  • 14 comprehensive tests covering all ACs

File List

  • app/Enums/NotificationType.php (new)
  • app/Notifications/NudgeNotification.php (new)
  • app/Notifications/DocumentUploadedNotification.php (new)
  • app/Notifications/DeclarationOverdueNotification.php (new)
  • app/Mail/NudgeNotificationMail.php (new)
  • resources/views/emails/nudge-notification.blade.php (new)
  • resources/views/emails/declaration-overdue.blade.php (new)
  • resources/js/pages/notifications/Index.vue (new)
  • resources/js/types/notification.ts (new)
  • resources/js/types/index.ts (modified — added notification export)
  • app/Http/Controllers/NotificationController.php (modified — added index method)
  • routes/web.php (modified — added GET /notifications and POST /notifications/mark-all-read routes)
  • tests/Feature/Notifications/NotificationInfrastructureTest.php (new)

Change Log

  • 2026-03-24: Story 3.1 implementation complete — notification infrastructure with enum, 3 notification classes, mailable, controller index, routes, frontend types, and 14 tests