libra/docs/stories/story-8.3-booking-submitted...

15 KiB

Story 8.3: Booking Submitted Confirmation

Note: The color values in this story were implemented with the original Navy+Gold palette. These colors were updated in Epic 10 (Brand Color Refresh) to the new Charcoal+Warm Gray palette. See docs/brand.md for current color specifications.

Epic Reference

Epic 8: Email Notification System

Dependencies

  • Story 8.1: Email Infrastructure Setup (base templates, SMTP config, queue setup)
    • Provides: Base Mailable layout with Libra branding (navy #0A1F44 / gold #D4AF37), logo header, footer with firm contact info, mobile-responsive design, and queue configuration

User Story

As a client, I want to receive confirmation when I submit a booking request, So that I know my request was received and what to expect next.

Acceptance Criteria

Trigger

  • Sent immediately after successful consultation creation
  • Consultation status: pending
  • Email queued for async delivery

Content

  • Subject line: "Your consultation request has been submitted" / "تم استلام طلب الاستشارة"
  • Personalized greeting with client name
  • "Your consultation request has been submitted" message
  • Requested date and time (formatted per user's language preference)
  • Problem summary preview (first 200 characters, with "..." if truncated)
  • "Pending Review" status note with visual indicator
  • Expected response timeframe: "We will review your request and respond within 1-2 business days"
  • Contact information for questions

Language

  • Email sent in client's preferred_language (default: 'ar')
  • Arabic template for Arabic users
  • English template for English users

Design

  • Uses base email template from Story 8.1
  • No action required message (informational only)
  • Professional template with Libra branding
  • Gold call-to-action style for "View My Bookings" link (optional)

Technical Notes

Files to Create

app/Mail/BookingSubmittedEmail.php
resources/views/emails/booking/submitted/ar.blade.php
resources/views/emails/booking/submitted/en.blade.php

Mailable Implementation

<?php

namespace App\Mail;

use App\Models\Consultation;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class BookingSubmittedEmail extends Mailable implements ShouldQueue
{
    use Queueable, SerializesModels;

    public function __construct(
        public Consultation $consultation
    ) {}

    public function envelope(): Envelope
    {
        $locale = $this->consultation->user->preferred_language ?? 'ar';

        return new Envelope(
            subject: $locale === 'ar'
                ? 'تم استلام طلب الاستشارة'
                : 'Your Consultation Request Has Been Submitted',
        );
    }

    public function content(): Content
    {
        $locale = $this->consultation->user->preferred_language ?? 'ar';

        return new Content(
            markdown: "emails.booking.submitted.{$locale}",
            with: [
                'consultation' => $this->consultation,
                'user' => $this->consultation->user,
                'summaryPreview' => $this->getSummaryPreview(),
                'formattedDate' => $this->getFormattedDate($locale),
                'formattedTime' => $this->getFormattedTime($locale),
            ],
        );
    }

    private function getSummaryPreview(): string
    {
        $summary = $this->consultation->problem_summary ?? '';
        return strlen($summary) > 200
            ? substr($summary, 0, 200) . '...'
            : $summary;
    }

    private function getFormattedDate(string $locale): string
    {
        $date = $this->consultation->booking_date;
        return $locale === 'ar'
            ? $date->format('d/m/Y')
            : $date->format('m/d/Y');
    }

    private function getFormattedTime(string $locale): string
    {
        return $this->consultation->booking_time->format('h:i A');
    }
}

Dispatch Point

Send the email after successful consultation creation. Typical location:

// In app/Actions/Consultation/CreateConsultationAction.php
// OR in the controller handling booking submission

use App\Mail\BookingSubmittedEmail;
use Illuminate\Support\Facades\Mail;

// After consultation is created successfully:
Mail::to($consultation->user->email)
    ->send(new BookingSubmittedEmail($consultation));

Edge Cases

  • If preferred_language is null, default to 'ar' (Arabic)
  • If problem_summary is null or empty, show "No summary provided"
  • Ensure consultation has valid booking_date and booking_time before sending

Testing Requirements

Unit Tests

test('booking submitted email has correct subject in Arabic', function () {
    $user = User::factory()->create(['preferred_language' => 'ar']);
    $consultation = Consultation::factory()->create(['user_id' => $user->id]);

    $mailable = new BookingSubmittedEmail($consultation);

    expect($mailable->envelope()->subject)
        ->toBe('تم استلام طلب الاستشارة');
});

test('booking submitted email has correct subject in English', function () {
    $user = User::factory()->create(['preferred_language' => 'en']);
    $consultation = Consultation::factory()->create(['user_id' => $user->id]);

    $mailable = new BookingSubmittedEmail($consultation);

    expect($mailable->envelope()->subject)
        ->toBe('Your Consultation Request Has Been Submitted');
});

test('problem summary is truncated at 200 characters', function () {
    $longSummary = str_repeat('a', 250);
    $user = User::factory()->create();
    $consultation = Consultation::factory()->create([
        'user_id' => $user->id,
        'problem_summary' => $longSummary,
    ]);

    $mailable = new BookingSubmittedEmail($consultation);
    $content = $mailable->content();

    expect($content->with['summaryPreview'])
        ->toHaveLength(203) // 200 + '...'
        ->toEndWith('...');
});

test('date is formatted as d/m/Y for Arabic users', function () {
    $user = User::factory()->create(['preferred_language' => 'ar']);
    $consultation = Consultation::factory()->create([
        'user_id' => $user->id,
        'booking_date' => '2025-03-15',
    ]);

    $mailable = new BookingSubmittedEmail($consultation);
    $content = $mailable->content();

    expect($content->with['formattedDate'])->toBe('15/03/2025');
});

test('date is formatted as m/d/Y for English users', function () {
    $user = User::factory()->create(['preferred_language' => 'en']);
    $consultation = Consultation::factory()->create([
        'user_id' => $user->id,
        'booking_date' => '2025-03-15',
    ]);

    $mailable = new BookingSubmittedEmail($consultation);
    $content = $mailable->content();

    expect($content->with['formattedDate'])->toBe('03/15/2025');
});

test('defaults to Arabic when preferred_language is null', function () {
    $user = User::factory()->create(['preferred_language' => null]);
    $consultation = Consultation::factory()->create(['user_id' => $user->id]);

    $mailable = new BookingSubmittedEmail($consultation);

    expect($mailable->envelope()->subject)
        ->toBe('تم استلام طلب الاستشارة');
});

test('empty problem summary returns empty string', function () {
    $user = User::factory()->create();
    $consultation = Consultation::factory()->create([
        'user_id' => $user->id,
        'problem_summary' => '',
    ]);

    $mailable = new BookingSubmittedEmail($consultation);
    $content = $mailable->content();

    expect($content->with['summaryPreview'])->toBe('');
});

Feature Tests

test('email is sent when consultation is created', function () {
    Mail::fake();

    $user = User::factory()->create();
    // Trigger consultation creation...

    Mail::assertSent(BookingSubmittedEmail::class, function ($mail) use ($user) {
        return $mail->hasTo($user->email);
    });
});

test('email is queued for async delivery', function () {
    Mail::fake();

    $user = User::factory()->create();
    $consultation = Consultation::factory()->create(['user_id' => $user->id]);

    Mail::to($user->email)->send(new BookingSubmittedEmail($consultation));

    Mail::assertQueued(BookingSubmittedEmail::class);
});

References

  • PRD Section 8.2: Email Templates - "Booking Confirmation - Request submitted successfully"
  • PRD Section 5.4: Booking Flow - Step 2 "Request Status: Pending"
  • Story 8.1: Base email template structure and branding
  • Story 8.2: Welcome email pattern (similar Mailable structure)

Definition of Done

  • BookingSubmittedEmail Mailable class created
  • Arabic template created and renders correctly
  • English template created and renders correctly
  • Email dispatched on consultation creation
  • Email queued (implements ShouldQueue)
  • Date/time formatted per user language
  • Summary preview truncated at 200 chars
  • Pending status clearly communicated
  • Response timeframe included
  • Unit tests pass
  • Feature tests pass

Estimation

Complexity: Low | Effort: 2 hours


Dev Agent Record

Status

Ready for Review

Agent Model Used

Claude Opus 4.5 (claude-opus-4-5-20251101)

File List

File Action Description
app/Mail/BookingSubmittedMail.php Modified Updated to implement ShouldQueue, add summary preview truncation, locale-based date/time formatting, and use separate language templates
resources/views/emails/booking/submitted/ar.blade.php Created Arabic email template with RTL support, pending status panel, and response timeframe
resources/views/emails/booking/submitted/en.blade.php Created English email template with pending status panel and response timeframe
tests/Feature/Mail/BookingSubmittedMailTest.php Created 16 tests covering subject lines, summary truncation, date formatting, template selection, and queue behavior

Change Log

  • Updated BookingSubmittedMail to implement ShouldQueue interface for async delivery
  • Added getSummaryPreview() method to truncate problem summary at 200 characters
  • Added getFormattedDate() method for locale-based date formatting (d/m/Y for Arabic, m/d/Y for English)
  • Added getFormattedTime() method for 12-hour time format
  • Changed from single view to separate markdown templates per language
  • Created Arabic template with proper RTL direction and Arabic content
  • Created English template with LTR content
  • Both templates include pending status panel and 1-2 business days response timeframe

Completion Notes

  • Used existing BookingSubmittedMail.php class name (not BookingSubmittedEmail.php as specified in story) to maintain compatibility with existing codebase and tests
  • Email dispatch already exists in resources/views/livewire/client/consultations/book.blade.php:142-144
  • Database constraints prevent null preferred_language and problem_summary, so edge case tests were adjusted accordingly
  • All 16 BookingSubmittedMail tests pass
  • All 21 BookingSubmission feature tests pass

Debug Log References

None - implementation completed without issues


QA Results

Review Date: 2026-01-02

Reviewed By: Quinn (Test Architect)

Code Quality Assessment

Overall: Excellent - The implementation is clean, well-structured, and follows Laravel best practices. The BookingSubmittedMail class properly implements the ShouldQueue interface for async delivery, uses Laravel's Mailable pattern correctly, and provides clear separation between Arabic and English templates.

Strengths:

  • Clean class structure with public methods for helper functions (getSummaryPreview, getFormattedDate, getFormattedTime)
  • Proper use of Carbon for time parsing
  • Locale-aware date formatting (d/m/Y for Arabic, m/d/Y for English)
  • Summary truncation with proper boundary handling (200 chars + "...")
  • Email templates use the base email layout from Story 8.1 with Libra branding (navy header, gold accents, bilingual footer)
  • RTL support properly implemented in Arabic template

Minor Observations:

  • The locale property is set in constructor but not used; envelope and content methods recalculate locale each time (minor redundancy, not a defect)

Refactoring Performed

None required - code quality is production-ready.

Compliance Check

  • Coding Standards: ✓ Follows Laravel Mailable conventions, proper namespacing
  • Project Structure: ✓ Files in correct locations (app/Mail/, resources/views/emails/booking/submitted/)
  • Testing Strategy: ✓ 16 unit tests covering all acceptance criteria
  • All ACs Met: ✓ All 10 acceptance criteria verified

Requirements Traceability

AC Requirement Test Coverage
1 Sent immediately after consultation creation BookingSubmissionTest::emails are sent to client and admin after submission
2 Consultation status: pending BookingSubmissionTest::booking is created with pending status
3 Email queued for async delivery BookingSubmittedMailTest::booking submitted email implements ShouldQueue, email is queued when sent
4 Correct Arabic subject line BookingSubmittedMailTest::booking submitted email has correct subject in Arabic
5 Correct English subject line BookingSubmittedMailTest::booking submitted email has correct subject in English
6 Summary preview truncated at 200 chars BookingSubmittedMailTest::problem summary is truncated at 200 characters, summary exactly 200 characters is not truncated, summary under 200 characters is not truncated
7 Date formatted per locale BookingSubmittedMailTest::date is formatted as d/m/Y for Arabic users, date is formatted as m/d/Y for English users
8 Time formatted as h:i A BookingSubmittedMailTest::time is formatted as h:i A
9 Correct template per language BookingSubmittedMailTest::uses correct Arabic template for Arabic users, uses correct English template for English users
10 All content data passed to template BookingSubmittedMailTest::content includes all required data

Improvements Checklist

  • Implements ShouldQueue for async delivery
  • Locale-based subject lines (Arabic/English)
  • Summary preview truncation at 200 characters
  • Date formatting per user language preference
  • Time formatting in 12-hour format
  • Separate Arabic and English templates
  • Arabic template has RTL direction
  • Pending status panel in both templates
  • Response timeframe (1-2 business days) included
  • Uses base email layout with Libra branding

Security Review

Status: PASS

  • No security concerns identified
  • Email content is properly escaped through Blade templates
  • No user input directly concatenated into HTML
  • Uses Laravel's built-in email sanitization

Performance Considerations

Status: PASS

  • Email is queued (implements ShouldQueue) preventing blocking of HTTP requests
  • Consultation model is serialized for queue, avoiding N+1 issues
  • Carbon parsing is minimal and efficient

Files Modified During Review

None - no modifications required.

Gate Status

Gate: PASS → docs/qa/gates/8.3-booking-submitted-confirmation.yml

Ready for Done - All acceptance criteria met, comprehensive test coverage (37 tests passing), code quality is excellent, and follows project standards.