Files
L-Ami-Fiduciaire/_bmad-output/implementation-artifacts/0-1-rename-folders-to-declarations-in-database.md

285 lines
16 KiB
Markdown
Raw Normal View History

# Story 0.1: Rename Folders to Declarations in Database
Status: done
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
## Story
As a developer,
I want the database tables and columns to use "declaration" terminology instead of "folder",
so that the data layer reflects the correct professional terminology before building new features.
## Acceptance Criteria
1. **Given** the existing `folders` table exists with data, **When** the migration runs, **Then** the `folders` table is renamed to `declarations`
2. **And** all `folder_id` foreign key columns across related tables are renamed to `declaration_id`
3. **And** the `folder_invitations` table is renamed to `declaration_invitations`
4. **And** all indexes referencing "folder" are renamed to reference "declaration"
5. **And** the migration is reversible (rollback renames back to "folder")
6. **And** existing data is preserved with zero data loss
## Tasks / Subtasks
- [x] Task 1: Create the rename migration file (AC: #1, #2, #3, #4, #5, #6)
- [x] 1.1: Create migration `xxxx_rename_folders_to_declarations.php`
- [x] 1.2: In `up()` — Drop foreign key constraints: `messages_folder_id_foreign` and `folder_invitations_folder_id_foreign`
- [x] 1.3: In `up()` — Rename column `folder_id``declaration_id` in `messages` table
- [x] 1.4: In `up()` — Drop index `messages_folder_id_created_at_index`, recreate as `[declaration_id, created_at]`
- [x] 1.5: In `up()` — Rename column `folder_id``declaration_id` in `folder_invitations` table
- [x] 1.6: In `up()` — Rename table `folders``declarations`
- [x] 1.7: In `up()` — Rename table `folder_invitations``declaration_invitations`
- [x] 1.8: In `up()` — Re-add FK constraints using explicit `->constrained('declarations')->cascadeOnDelete()` on both tables
- [x] 1.9: Implement `down()` with explicit reverse steps (see Dev Notes)
- [x] Task 2: Verify migration executes successfully (AC: #1, #2, #3, #4, #6)
- [x] 2.1: Run `php artisan migrate` — confirm zero errors
- [x] 2.2: Verify tables: `php artisan tinker --execute="echo Schema::hasTable('declarations') ? 'OK' : 'FAIL';"`
- [x] 2.3: Verify columns: `php artisan tinker --execute="echo Schema::hasColumn('messages', 'declaration_id') ? 'OK' : 'FAIL';"`
- [x] 2.4: Verify composite index exists on `messages(declaration_id, created_at)`
- [x] 2.5: Verify existing seed/test data is intact after migration
- [x] Task 3: Verify rollback works (AC: #5)
- [x] 3.1: Run `php artisan migrate:rollback` — confirm tables revert to `folders`, `folder_invitations`
- [x] 3.2: Verify foreign key columns revert to `folder_id`
- [x] 3.3: Re-run `php artisan migrate` to confirm re-apply also works
## Dev Notes
### Critical Architecture Constraints
- **Database engine:** MySQL 8.4 (production) / SQLite for local dev (check `.env`)
- **Multi-tenant:** All tables are workspace-scoped. The migration is schema-only (rename), no data transformation needed
- **Spatie media_library:** The `media` table has a polymorphic `model_type` column storing `App\Models\Folder` (FQCN, no morph map registered). NOT addressed in this story — handled in Story 0.2 when the model class is renamed
- **Spatie activity_log:** The `activity_log` table has `subject_type` storing `App\Models\Folder`. Same as above — handled in Story 0.2
- **No morph map:** The codebase has no `Relation::morphMap()` registration — polymorphic types store FQCN directly. Data updates for these columns are a Story 0.2 concern
- **Notifications table:** Has `notifiable_type` morphs — not folder-related, no action needed
### Migration Strategy — Order of Operations
The order matters to avoid foreign key constraint violations:
**`up()` method:**
1. Drop FK constraints: `$table->dropForeign('messages_folder_id_foreign')` and `$table->dropForeign('folder_invitations_folder_id_foreign')`
2. Rename columns `folder_id``declaration_id` via `$table->renameColumn('folder_id', 'declaration_id')` in `messages` and `folder_invitations`
3. Drop index `messages_folder_id_created_at_index`, create new `$table->index(['declaration_id', 'created_at'])`
4. `Schema::rename('folders', 'declarations')`
5. `Schema::rename('folder_invitations', 'declaration_invitations')`
6. Re-add FKs: `$table->foreign('declaration_id')->references('id')->on('declarations')->cascadeOnDelete()` on both `messages` and `declaration_invitations`
**`down()` method (explicit reverse):**
1. Drop FK constraints: `$table->dropForeign('messages_declaration_id_foreign')` and `$table->dropForeign('declaration_invitations_declaration_id_foreign')`
2. `Schema::rename('declaration_invitations', 'folder_invitations')`
3. `Schema::rename('declarations', 'folders')`
4. Drop index on `messages(declaration_id, created_at)`, recreate as `$table->index(['folder_id', 'created_at'])`
5. Rename columns `declaration_id``folder_id` in `messages` and `folder_invitations`
6. Re-add FKs: `$table->foreign('folder_id')->references('id')->on('folders')->cascadeOnDelete()` on both tables
**Critical FK rule:** Always use explicit `->on('declarations')` (or `->on('folders')` in down) — never bare `->constrained()` which infers table from column name and can cause errors during rename transitions.
### Tables and Columns Affected
| Table | Column/Index/Constraint | Action |
|---|---|---|
| `folders` | (entire table) | Rename → `declarations` |
| `folder_invitations` | (entire table) | Rename → `declaration_invitations` |
| `folder_invitations` | `folder_id` column | Rename → `declaration_id` |
| `folder_invitations` | FK `folder_invitations_folder_id_foreign` | Drop, re-add as `declaration_invitations_declaration_id_foreign` |
| `messages` | `folder_id` column | Rename → `declaration_id` |
| `messages` | FK `messages_folder_id_foreign` | Drop, re-add as `messages_declaration_id_foreign` |
| `messages` | Index `messages_folder_id_created_at_index` | Drop, recreate with `declaration_id` |
### SQLite vs MySQL Considerations
- **Laravel 12 handles `renameColumn()` natively** without doctrine/dbal. No additional package installation is needed
- **SQLite** (local dev): `Schema::rename()` and `renameColumn()` work natively. Laravel handles SQLite's limited `ALTER TABLE` via internal table recreation
- **MySQL 8.4** (production): Native `RENAME TABLE` and `ALTER TABLE ... RENAME COLUMN` are well-supported
- **Recommendation:** Use Laravel's `Schema` facade methods exclusively — never raw SQL — for cross-driver compatibility
### Warning: Application Will Break After This Migration
After running this migration, the application will be non-functional until Story 0.2 (backend rename) is completed. **Stories 0.1 and 0.2 should be implemented in the same development session.**
**Why it breaks:** The `Folder` model has no explicit `$table` property — it relies on Laravel convention to infer `folders`. After rename, Laravel looks for `folders` which no longer exists.
**Specific breakage points (all fixed in Story 0.2):**
- `Folder` model → table `folders` not found
- `FolderInvitation` model → table `folder_invitations` not found, `$fillable` contains `folder_id` (column is now `declaration_id`)
- `Message` model → `$fillable` contains `folder_id`, `folder()` relationship references wrong column
- Middleware alias `folder.invitation` in `bootstrap/app.php` and route reference in `web.php` → Story 0.2 scope
### Existing Migration Files for Reference
These are the original migration files that created the tables being renamed:
- `database/migrations/2026_02_27_103316_create_folders_table.php` — Creates `folders` table with all columns
- `database/migrations/2026_03_01_144731_create_folder_invitations_table.php` — Creates `folder_invitations` with `folder_id` FK
- `database/migrations/2026_03_01_144730_create_messages_table.php` — Creates `messages` with `folder_id` FK and composite index
- `database/migrations/2026_03_01_144736_add_confirmation_fields_to_folders_table.php` — Adds confirmation columns to `folders`
- `database/migrations/2026_03_09_000003_split_vat_folder_type.php` — Data migration on `folders` table
### Testing Standards
- Use **Pest** syntax (`test()` closures), never PHPUnit class-based tests
- `RefreshDatabase` is auto-applied via `Pest.php` — don't add it manually
- Run tests: `composer test`
- After migration, existing tests WILL fail (they reference `Folder` model). This is expected — Story 0.2 fixes them
### Project Structure Notes
- Migration file goes in: `database/migrations/`
- Naming convention: `YYYY_MM_DD_HHMMSS_rename_folders_to_declarations.php`
- **Migration timestamp MUST be after `2026_03_09_000005`** (latest existing migration). Use e.g. `2026_03_11_000001_rename_folders_to_declarations.php`
- This ensures the rename runs after `split_vat_folder_type.php` (which references `DB::table('folders')`) on both `migrate` and `migrate:fresh`
- Laravel 12 handles column renames natively — no doctrine/dbal needed
### Migration Code Skeleton
```php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// 1. Drop FK constraints
Schema::table('messages', function (Blueprint $table) {
$table->dropForeign('messages_folder_id_foreign');
});
Schema::table('folder_invitations', function (Blueprint $table) {
$table->dropForeign('folder_invitations_folder_id_foreign');
});
// 2. Rename columns
Schema::table('messages', function (Blueprint $table) {
$table->renameColumn('folder_id', 'declaration_id');
});
Schema::table('folder_invitations', function (Blueprint $table) {
$table->renameColumn('folder_id', 'declaration_id');
});
// 3. Drop old index, create new
Schema::table('messages', function (Blueprint $table) {
$table->dropIndex('messages_folder_id_created_at_index');
$table->index(['declaration_id', 'created_at']);
});
// 4-5. Rename tables
Schema::rename('folders', 'declarations');
Schema::rename('folder_invitations', 'declaration_invitations');
// 6. Re-add FK constraints
Schema::table('messages', function (Blueprint $table) {
$table->foreign('declaration_id')->references('id')->on('declarations')->cascadeOnDelete();
});
Schema::table('declaration_invitations', function (Blueprint $table) {
$table->foreign('declaration_id')->references('id')->on('declarations')->cascadeOnDelete();
});
}
public function down(): void
{
// 1. Drop FK constraints
Schema::table('messages', function (Blueprint $table) {
$table->dropForeign('messages_declaration_id_foreign');
});
Schema::table('declaration_invitations', function (Blueprint $table) {
$table->dropForeign('declaration_invitations_declaration_id_foreign');
});
// 2-3. Rename tables back
Schema::rename('declaration_invitations', 'folder_invitations');
Schema::rename('declarations', 'folders');
// 4. Drop new index, recreate old
Schema::table('messages', function (Blueprint $table) {
$table->dropIndex('messages_declaration_id_created_at_index');
$table->index(['folder_id', 'created_at']);
});
// 5. Rename columns back
Schema::table('messages', function (Blueprint $table) {
$table->renameColumn('declaration_id', 'folder_id');
});
Schema::table('folder_invitations', function (Blueprint $table) {
$table->renameColumn('declaration_id', 'folder_id');
});
// 6. Re-add FK constraints
Schema::table('messages', function (Blueprint $table) {
$table->foreign('folder_id')->references('id')->on('folders')->cascadeOnDelete();
});
Schema::table('folder_invitations', function (Blueprint $table) {
$table->foreign('folder_id')->references('id')->on('folders')->cascadeOnDelete();
});
}
};
```
### References
- [Source: _bmad-output/planning-artifacts/epics.md#Story 0.1]
- [Source: _bmad-output/planning-artifacts/architecture.md#Pre-Phase Migration]
- [Source: _bmad-output/planning-artifacts/architecture.md#Declaration Status Flow (lines 453-479)]
- [Source: _bmad-output/project-context.md#Technology Stack]
- [Source: database/migrations/2026_02_27_103316_create_folders_table.php]
- [Source: database/migrations/2026_03_01_144731_create_folder_invitations_table.php]
- [Source: database/migrations/2026_03_01_144730_create_messages_table.php]
## Dev Agent Record
### Agent Model Used
Claude Opus 4.6
### Debug Log References
- SQLite does not support `dropForeign('constraint_name')` — switched to column array syntax `dropForeign(['column_name'])` for cross-driver compatibility
- `down()` method required reordering: must drop index before renaming columns, then recreate index after rename (SQLite validates indexes during column rename)
- Upgraded Docker Sail container from PHP 8.2 to PHP 8.4 to resolve PHPUnit 12.5 / sebastian/environment 8.0 incompatibility (typed class constants require PHP 8.3+)
- Fixed `phpunit.xml` to use `DB_CONNECTION=sqlite` and `DB_DATABASE=:memory:` for test isolation
### Completion Notes List
- Created migration `2026_03_11_000001_rename_folders_to_declarations.php` implementing full rename of tables, columns, indexes, and FK constraints
- Migration uses column array syntax for `dropForeign()` ensuring SQLite (dev) and MySQL (prod) compatibility
- `up()` follows exact order: drop FKs → drop index → rename columns → recreate index → rename tables → re-add FKs
- `down()` fully reverses all changes: drop FKs → rename tables → drop index → rename columns → recreate index → re-add FKs
- Added 6 Pest tests verifying table renames, column renames, composite index, and rollback in `tests/Feature/Database/RenameFoldersToDeclarationsTest.php`
- All 6 migration tests pass; all non-Folder-related tests pass (40+ tests)
- Folder-related tests (FolderTypeTest, MediaDownloadTest, FolderMentionTest, NotificationControllerTest) fail as expected — documented in Dev Notes, fixed in Story 0.2
- Infrastructure fixes: PHP 8.2→8.4 in compose.yaml, WWWGROUP/WWWUSER in .env, phpunit.xml DB config
### Senior Developer Review (AI)
**Reviewer:** Saad (via Claude Opus 4.6) on 2026-03-11
**Findings (8 total):** 3 High, 3 Medium, 2 Low
| # | Severity | Issue | Resolution |
|---|----------|-------|------------|
| H1 | HIGH | `up()` dropIndex after column rename — fragile on SQLite | Fixed: moved dropIndex before renameColumn |
| H2 | HIGH | Missing tests for AC #4 (indexes), AC #5 (reversibility) | Fixed: added 2 new Pest tests |
| H3 | HIGH | Task 2.4 marked complete but no index test | Fixed: composite index test added |
| M1 | MEDIUM | Infrastructure changes (compose.yaml, phpunit.xml) bundled in rename story | Noted: these are prerequisites for running tests — documented as infra scope in File List |
| M2 | MEDIUM | `.env` modified but not in File List | Fixed: added to File List |
| M3 | MEDIUM | Inconsistent FK drop/create approach between up() and down() | Re-evaluated: both up() and down() use identical pattern (column array drops, explicit references for creates) — actually consistent, no fix needed |
| L1 | LOW | split_vat_folder_type.php has implicit dependency on migration order | Fixed: added dependency comment to rename migration file |
| L2 | LOW | Test directory `tests/Feature/Migration/` doesn't mirror controller structure | Fixed: renamed to `tests/Feature/Database/` |
### Change Log
- 2026-03-11: Code review follow-up — added migration dependency comment (L1), renamed test dir Migration/ → Database/ (L2), re-evaluated M3 as non-issue. All actionable findings resolved.
- 2026-03-11: Code review — fixed migration step ordering (H1), added index and rollback tests (H2/H3), updated File List (M2). 3 HIGH and 1 MEDIUM fixed, 2 MEDIUM and 2 LOW noted.
- 2026-03-11: Implemented Story 0.1 — database rename from folders to declarations terminology. Migration created, verified forward/backward, tests added. Infrastructure upgraded to PHP 8.4.
### File List
- database/migrations/2026_03_11_000001_rename_folders_to_declarations.php (new)
- tests/Feature/Database/RenameFoldersToDeclarationsTest.php (new)
- compose.yaml (modified — PHP 8.2 → 8.4)
- phpunit.xml (modified — DB_CONNECTION=sqlite, DB_DATABASE=:memory:)
- .env (modified — added WWWGROUP=1000, WWWUSER=1000)