Files
L-Ami-Fiduciaire/_bmad-output/implementation-artifacts/0-1-rename-folders-to-declarations-in-database.md
Saad Ibn-Ezzoubayr 35545c2a8f feat: L'Ami Fiduciaire V1.0.0 — full codebase with Story 0.1 complete
Initial commit of the L'Ami Fiduciaire SaaS platform built on Laravel 12,
Vue 3, Inertia.js 2, and Tailwind CSS 4.

Story 0.1 (rename folders to declarations in database) is implemented and
code-reviewed: migration, rollback, and 6 Pest tests all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 23:33:10 +00:00

285 lines
16 KiB
Markdown

# 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)