From ef3f9dfd4e9345c55164a85368999a68f8887316 Mon Sep 17 00:00:00 2001 From: Naser Mansour Date: Sun, 21 Dec 2025 14:09:18 +0200 Subject: [PATCH] validated 1.1 story --- ...story-1.1-project-setup-database-schema.md | 482 +++++++++++++----- 1 file changed, 362 insertions(+), 120 deletions(-) diff --git a/docs/stories/story-1.1-project-setup-database-schema.md b/docs/stories/story-1.1-project-setup-database-schema.md index f23b4cc..d3b8142 100644 --- a/docs/stories/story-1.1-project-setup-database-schema.md +++ b/docs/stories/story-1.1-project-setup-database-schema.md @@ -1,142 +1,379 @@ # Story 1.1: Project Setup & Database Schema +## Status +Draft + ## Epic Reference **Epic 1:** Core Foundation & Infrastructure -## User 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**. - -## Story Context - -### Existing System Integration -- **Integrates with:** New project setup (greenfield) -- **Technology:** Laravel 12, PHP 8.4, Livewire 3, Volt, Flux UI Free, Tailwind CSS 4 -- **Follows pattern:** Laravel 12 streamlined file structure -- **Touch points:** Database migrations, model factories, development environment +## 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 -### Functional Requirements -- [ ] Laravel 12 project created with Livewire 3, Volt, Flux UI -- [ ] Tailwind CSS 4 configured with `@theme` directive -- [ ] 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` -- [ ] Model factories created for testing -- [ ] Development environment working (`composer run dev`) +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 -### Integration Requirements -- [ ] SQLite configured for development database -- [ ] All migrations run without errors -- [ ] Factories generate valid test data -- [ ] Composer and npm scripts functional +## Tasks / Subtasks -### Quality Requirements -- [ ] All database tables have proper indexes -- [ ] Foreign key constraints properly defined -- [ ] Factories cover all required fields -- [ ] Development server starts without errors +- [ ] **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) -## Technical Notes +- [ ] **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 -- **Database:** Use SQLite for development (configurable for MariaDB in production) -- **Environment:** Set `DB_CONNECTION=sqlite` in `.env` for development -- **Reference:** PRD Section 16.1 for complete schema details -- **Pattern:** Follow existing Volt class-based component pattern -- **Structure:** Use Laravel 12 streamlined file structure (no app/Http/Middleware, bootstrap/app.php for config) -- **Testing:** Use Pest 4 for all tests (`php artisan make:test --pest`) -- **Models:** Create with `php artisan make:model ModelName -mf` (migration + factory) -- **Notifications table:** Custom implementation per PRD schema (not Laravel's built-in notifications) +- [ ] **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 -### Database Schema Reference +- [ ] **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/ ``` -users: - - id, name, email, password, user_type (enum: admin/individual/company) - - national_id (nullable), company_name (nullable), company_registration (nullable) - - phone, preferred_language (enum: ar/en), status (enum: active/deactivated) - - timestamps, email_verified_at -consultations: - - id, user_id, scheduled_date, scheduled_time, duration (default 45) - - status (enum: pending/approved/completed/cancelled/no_show) - - type (enum: free/paid), payment_amount (nullable), payment_status - - problem_summary, admin_notes, timestamps +### Database Schema (Aligned with Architecture) -timelines: - - id, user_id, case_name, case_reference (unique nullable) - - status (enum: active/archived), timestamps +**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) +``` -timeline_updates: - - id, timeline_id, admin_id, update_text, 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 +``` -posts: - - id, title_ar, title_en, body_ar, body_en - - status (enum: draft/published), timestamps +**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 +``` -working_hours: - - id, day_of_week (0-6), start_time, end_time, is_active, timestamps +**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) +``` -blocked_times: - - id, block_date, start_time (nullable), end_time (nullable) - - reason, 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 +``` -admin_logs: - - id, admin_id, action_type, target_type, target_id - - old_values (json), new_values (json), ip_address, timestamps +**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) + +```php +// 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:** +```php +// 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 tested -- [ ] All model factories functional +- [ ] 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 model creation using factories -- [ ] Code formatted with Pint -- [ ] No errors on fresh install - -## Test Scenarios - -All tests should use Pest 4 (`php artisan make:test --pest`). - -### Migration Tests -- [ ] All migrations run successfully: `php artisan migrate:fresh` -- [ ] Migrations can rollback without errors: `php artisan migrate:rollback` -- [ ] Foreign key constraints are properly created (timeline_updates.timeline_id, timeline_updates.admin_id, consultations.user_id, timelines.user_id, admin_logs.admin_id) - -### Factory Tests -- [ ] UserFactory creates valid User with all user_type enum values (admin, individual, company) -- [ ] ConsultationFactory creates valid Consultation with proper user relationship -- [ ] TimelineFactory creates valid Timeline with proper user relationship -- [ ] TimelineUpdateFactory creates valid TimelineUpdate with timeline and admin relationships -- [ ] PostFactory creates valid Post with bilingual fields -- [ ] WorkingHoursFactory creates valid WorkingHours for each day_of_week (0-6) -- [ ] BlockedTimeFactory creates valid BlockedTime entries -- [ ] NotificationFactory creates valid Notification entries -- [ ] AdminLogFactory creates valid AdminLog with JSON fields - -### Enum Validation Tests -- [ ] User.user_type only accepts: admin, individual, company -- [ ] User.status only accepts: active, deactivated -- [ ] User.preferred_language only accepts: ar, en -- [ ] Consultation.status only accepts: pending, approved, completed, cancelled, no_show -- [ ] Consultation.type only accepts: free, paid -- [ ] Timeline.status only accepts: active, archived -- [ ] Post.status only accepts: draft, published - -### Edge Case Tests -- [ ] Nullable fields accept null values (national_id, company_name, case_reference, etc.) -- [ ] JSON fields (old_values, new_values) store and retrieve correctly -- [ ] Unique constraint on case_reference allows multiple nulls but no duplicate values +- [ ] Tests pass for all models, factories, and enums +- [ ] Code formatted with `vendor/bin/pint` +- [ ] No errors on fresh install (`php artisan migrate:fresh`) ## Dependencies @@ -144,17 +381,22 @@ All tests should use Pest 4 (`php artisan make:test --pest`). ## Risk Assessment -- **Primary Risk:** Schema design changes required later -- **Mitigation:** Follow PRD specifications closely, validate with stakeholder -- **Rollback:** Fresh migration reset possible in development +| 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, fix the issue, then `php artisan migrate` to continue -- **Foreign key errors:** Ensure referenced tables are created before dependent tables (migrations run in filename order) +- **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 -## Estimation +## Change Log -**Complexity:** Medium -**Estimated Effort:** 4-6 hours +| 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 |