libra/docs/stories/story-1.1-project-setup-dat...

14 KiB

Story 1.1: Project Setup & Database Schema

Status

Draft

Epic Reference

Epic 1: Core Foundation & Infrastructure

Story

As a developer, I want the Laravel 12 project configured with all required packages and complete database schema, So that the foundation is established for all subsequent feature development.

Acceptance Criteria

  1. Laravel 12 project created with Livewire 3, Volt, Flux UI
  2. Tailwind CSS 4 configured with @theme directive
  3. Database migrations for all core tables:
    • users (with user_type, national_id, company fields)
    • consultations
    • timelines
    • timeline_updates
    • posts
    • working_hours
    • blocked_times
    • notifications
    • admin_logs
  4. Model factories created for testing
  5. Development environment working (composer run dev)
  6. SQLite configured for development database
  7. All migrations run without errors
  8. Factories generate valid test data
  9. All database tables have proper indexes
  10. Foreign key constraints properly defined

Tasks / Subtasks

  • Task 1: Create PHP Enums (AC: 3)

    • Create app/Enums/UserType.php enum (admin, individual, company)
    • Create app/Enums/UserStatus.php enum (active, deactivated)
    • Create app/Enums/ConsultationType.php enum (free, paid)
    • Create app/Enums/ConsultationStatus.php enum (pending, approved, rejected, completed, no_show, cancelled)
    • Create app/Enums/PaymentStatus.php enum (pending, received, na)
    • Create app/Enums/TimelineStatus.php enum (active, archived)
    • Create app/Enums/PostStatus.php enum (draft, published)
  • Task 2: Create Database Migrations (AC: 3, 7, 9, 10)

    • Modify existing users migration to add: user_type, full_name, national_id, company_name, company_cert_number, contact_person_name, contact_person_id, phone, status, preferred_language, two_factor fields
    • Create consultations migration with: user_id (FK), booking_date, booking_time, problem_summary, consultation_type, payment_amount, payment_status, status, admin_notes
    • Create timelines migration with: user_id (FK), case_name, case_reference (unique nullable), status
    • Create timeline_updates migration with: timeline_id (FK), admin_id (FK), update_text
    • Create posts migration with: title (JSON), body (JSON), status, published_at
    • Create working_hours migration with: day_of_week, start_time, end_time, is_active
    • Create blocked_times migration with: block_date, start_time (nullable), end_time (nullable), reason
    • Create notifications migration with: user_id (FK), type, data (JSON), read_at, sent_at
    • Create admin_logs migration with: admin_id (FK), action, target_type, target_id, old_values (JSON), new_values (JSON), ip_address
    • Add indexes: users.email (unique), consultations.booking_date, consultations.user_id, timelines.user_id, posts.status
  • Task 3: Create Eloquent Models (AC: 3, 4)

    • Update app/Models/User.php with relationships, casts, scopes, and helper methods
    • Create app/Models/Consultation.php with user relationship and enum casts
    • Create app/Models/Timeline.php with user relationship and updates relationship
    • Create app/Models/TimelineUpdate.php with timeline and admin relationships
    • Create app/Models/Post.php with JSON casts for bilingual fields
    • Create app/Models/WorkingHour.php
    • Create app/Models/BlockedTime.php
    • Create app/Models/Notification.php with user relationship and JSON data cast
    • Create app/Models/AdminLog.php with admin relationship and JSON casts
  • Task 4: Create Model Factories (AC: 4, 8)

    • Update database/factories/UserFactory.php with states: admin(), individual(), company(), client()
    • Create database/factories/ConsultationFactory.php
    • Create database/factories/TimelineFactory.php
    • Create database/factories/TimelineUpdateFactory.php
    • Create database/factories/PostFactory.php with bilingual fake data
    • Create database/factories/WorkingHourFactory.php with weekdays() state
    • Create database/factories/BlockedTimeFactory.php
    • Create database/factories/NotificationFactory.php
    • Create database/factories/AdminLogFactory.php
  • Task 5: Write Tests (AC: 7, 8)

    • Create tests/Unit/Models/UserTest.php - test enum validation, relationships
    • Create tests/Unit/Models/ConsultationTest.php - test enum validation, relationships
    • Create tests/Unit/Models/TimelineTest.php - test relationships
    • Create tests/Unit/Enums/ tests for all enums
    • Create tests/Feature/Database/MigrationTest.php - test migrate:fresh and rollback
    • Create tests/Feature/Database/FactoryTest.php - test all factories create valid models
  • Task 6: Verify Development Environment (AC: 5, 6)

    • Ensure composer run dev starts without errors
    • Verify SQLite database is created and migrations run
    • Run php artisan migrate:fresh successfully
    • Run vendor/bin/pint to format code

Dev Notes

Relevant Source Tree

app/
├── Enums/                          # PHP 8.1+ backed enums
│   ├── UserType.php
│   ├── UserStatus.php
│   ├── ConsultationType.php
│   ├── ConsultationStatus.php
│   ├── PaymentStatus.php
│   ├── TimelineStatus.php
│   └── PostStatus.php
├── Models/
│   ├── User.php                    # Existing - needs modification
│   ├── Consultation.php
│   ├── Timeline.php
│   ├── TimelineUpdate.php
│   ├── Post.php
│   ├── WorkingHour.php
│   ├── BlockedTime.php
│   ├── Notification.php
│   └── AdminLog.php
database/
├── factories/
│   ├── UserFactory.php             # Existing - needs modification
│   ├── ConsultationFactory.php
│   ├── TimelineFactory.php
│   ├── TimelineUpdateFactory.php
│   ├── PostFactory.php
│   ├── WorkingHourFactory.php
│   ├── BlockedTimeFactory.php
│   ├── NotificationFactory.php
│   └── AdminLogFactory.php
├── migrations/
│   ├── 0001_01_01_000000_create_users_table.php  # Existing - needs modification
│   ├── YYYY_MM_DD_HHMMSS_create_consultations_table.php
│   ├── YYYY_MM_DD_HHMMSS_create_timelines_table.php
│   ├── YYYY_MM_DD_HHMMSS_create_timeline_updates_table.php
│   ├── YYYY_MM_DD_HHMMSS_create_posts_table.php
│   ├── YYYY_MM_DD_HHMMSS_create_working_hours_table.php
│   ├── YYYY_MM_DD_HHMMSS_create_blocked_times_table.php
│   ├── YYYY_MM_DD_HHMMSS_create_notifications_table.php
│   └── YYYY_MM_DD_HHMMSS_create_admin_logs_table.php
tests/
├── Unit/
│   ├── Models/
│   └── Enums/
└── Feature/
    └── Database/

Database Schema (Aligned with Architecture)

users (modify existing migration):

- id (bigint, PK)
- user_type (enum: admin|individual|company)
- full_name (string)
- national_id (string, nullable, encrypted)
- company_name (string, nullable)
- company_cert_number (string, nullable)
- contact_person_name (string, nullable)
- contact_person_id (string, nullable)
- email (string, unique)
- phone (string)
- password (string, hashed)
- status (enum: active|deactivated, default: active)
- preferred_language (string: ar|en, default: ar)
- email_verified_at (timestamp, nullable)
- two_factor_secret (text, nullable)
- two_factor_recovery_codes (text, nullable)
- two_factor_confirmed_at (timestamp, nullable)
- remember_token (string, nullable)
- created_at, updated_at (timestamps)

consultations:

- id (bigint, PK)
- user_id (bigint, FK -> users.id, cascade delete)
- booking_date (date)
- booking_time (time)
- problem_summary (text)
- consultation_type (enum: free|paid)
- payment_amount (decimal 10,2, nullable)
- payment_status (enum: pending|received|na, default: na)
- status (enum: pending|approved|rejected|completed|no_show|cancelled, default: pending)
- admin_notes (text, nullable)
- created_at, updated_at (timestamps)
INDEX: booking_date, user_id

timelines:

- id (bigint, PK)
- user_id (bigint, FK -> users.id, cascade delete)
- case_name (string)
- case_reference (string, nullable, unique)
- status (enum: active|archived, default: active)
- created_at, updated_at (timestamps)
INDEX: user_id

timeline_updates:

- id (bigint, PK)
- timeline_id (bigint, FK -> timelines.id, cascade delete)
- admin_id (bigint, FK -> users.id, cascade delete)
- update_text (text)
- created_at, updated_at (timestamps)

posts:

- id (bigint, PK)
- title (json) - {"ar": "...", "en": "..."}
- body (json) - {"ar": "...", "en": "..."}
- status (enum: draft|published, default: draft)
- published_at (timestamp, nullable)
- created_at, updated_at (timestamps)
INDEX: status

working_hours:

- id (bigint, PK)
- day_of_week (tinyint, 0-6, Sunday=0)
- start_time (time)
- end_time (time)
- is_active (boolean, default: true)
- created_at, updated_at (timestamps)

blocked_times:

- id (bigint, PK)
- block_date (date)
- start_time (time, nullable) - null means full day
- end_time (time, nullable) - null means full day
- reason (string, nullable)
- created_at, updated_at (timestamps)

notifications (custom, NOT Laravel's built-in):

- id (bigint, PK)
- user_id (bigint, FK -> users.id, cascade delete)
- type (string)
- data (json)
- read_at (timestamp, nullable)
- sent_at (timestamp, nullable)
- created_at, updated_at (timestamps)

admin_logs:

- id (bigint, PK)
- admin_id (bigint, FK -> users.id, cascade delete)
- action (string)
- target_type (string)
- target_id (bigint, nullable)
- old_values (json, nullable)
- new_values (json, nullable)
- ip_address (string)
- created_at (timestamp)

Foreign Key Constraints

Column References On Delete
consultations.user_id users.id CASCADE
timelines.user_id users.id CASCADE
timeline_updates.timeline_id timelines.id CASCADE
timeline_updates.admin_id users.id CASCADE
notifications.user_id users.id CASCADE
admin_logs.admin_id users.id CASCADE

Enum Definitions (from Architecture Section 4)

// app/Enums/UserType.php
enum UserType: string {
    case Admin = 'admin';
    case Individual = 'individual';
    case Company = 'company';
}

// app/Enums/UserStatus.php
enum UserStatus: string {
    case Active = 'active';
    case Deactivated = 'deactivated';
}

// app/Enums/ConsultationType.php
enum ConsultationType: string {
    case Free = 'free';
    case Paid = 'paid';
}

// app/Enums/ConsultationStatus.php
enum ConsultationStatus: string {
    case Pending = 'pending';
    case Approved = 'approved';
    case Rejected = 'rejected';
    case Completed = 'completed';
    case NoShow = 'no_show';
    case Cancelled = 'cancelled';
}

// app/Enums/PaymentStatus.php
enum PaymentStatus: string {
    case Pending = 'pending';
    case Received = 'received';
    case NotApplicable = 'na';
}

// app/Enums/TimelineStatus.php
enum TimelineStatus: string {
    case Active = 'active';
    case Archived = 'archived';
}

// app/Enums/PostStatus.php
enum PostStatus: string {
    case Draft = 'draft';
    case Published = 'published';
}

Technical Standards

  • Database: SQLite for development (DB_CONNECTION=sqlite)
  • Pattern: Class-based Volt components
  • Structure: Laravel 12 streamlined (no app/Http/Middleware, use bootstrap/app.php)
  • Models: Create with php artisan make:model ModelName -mf
  • Enums: Use PHP 8.1+ backed enums with string values

Testing

Test Location: tests/Unit/ and tests/Feature/Database/

Testing Framework: Pest 4

  • Create tests with: php artisan make:test TestName --pest
  • Run tests with: php artisan test

Required Tests:

  1. Migration tests - verify migrate:fresh and migrate:rollback work
  2. Factory tests - verify each factory creates valid models
  3. Enum validation tests - verify models reject invalid enum values
  4. Relationship tests - verify foreign keys and Eloquent relationships work
  5. JSON field tests - verify JSON columns store/retrieve correctly

Test Patterns:

// Example factory test
test('consultation factory creates valid consultation', function () {
    $consultation = Consultation::factory()->create();

    expect($consultation)->toBeInstanceOf(Consultation::class)
        ->and($consultation->user)->toBeInstanceOf(User::class)
        ->and($consultation->status)->toBeInstanceOf(ConsultationStatus::class);
});

// Example enum test
test('user type only accepts valid values', function () {
    expect(UserType::cases())->toHaveCount(3)
        ->and(UserType::Admin->value)->toBe('admin')
        ->and(UserType::Individual->value)->toBe('individual')
        ->and(UserType::Company->value)->toBe('company');
});

Definition of Done

  • All database migrations created and run without errors
  • All model factories functional and generate valid data
  • All Eloquent models have proper relationships and casts
  • All PHP enums created with correct values
  • Development environment runs with composer run dev
  • Tests pass for all models, factories, and enums
  • Code formatted with vendor/bin/pint
  • No errors on fresh install (php artisan migrate:fresh)

Dependencies

  • None (this is the foundation story)

Risk Assessment

Risk Impact Likelihood Mitigation
Schema design changes required later Medium Low Follow architecture document closely
Migration order issues (FK constraints) Low Medium Create migrations in dependency order
Enum value mismatches Low Low Use architecture document as source of truth

Error Handling

  • Migration failures: Run php artisan migrate:status to identify failed migrations
  • Foreign key errors: Ensure referenced tables are created before dependent tables (users before consultations, timelines before timeline_updates)
  • Factory errors: Ensure factories define all required (non-nullable) fields and use valid enum values
  • Rollback: Use php artisan migrate:fresh to reset in development

Change Log

Date Version Description Author
Dec 21, 2025 1.0 Initial story draft SM Agent
Dec 21, 2025 1.1 Fixed schema alignment with architecture (booking_date/time), added Tasks/Subtasks, added Dev Notes with source tree and enums, added Change Log Validation Task