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>
16 KiB
Story 0.1: Rename Folders to Declarations in Database
Status: done
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
- Given the existing
folderstable exists with data, When the migration runs, Then thefolderstable is renamed todeclarations - And all
folder_idforeign key columns across related tables are renamed todeclaration_id - And the
folder_invitationstable is renamed todeclaration_invitations - And all indexes referencing "folder" are renamed to reference "declaration"
- And the migration is reversible (rollback renames back to "folder")
- And existing data is preserved with zero data loss
Tasks / Subtasks
- Task 1: Create the rename migration file (AC: #1, #2, #3, #4, #5, #6)
- 1.1: Create migration
xxxx_rename_folders_to_declarations.php - 1.2: In
up()— Drop foreign key constraints:messages_folder_id_foreignandfolder_invitations_folder_id_foreign - 1.3: In
up()— Rename columnfolder_id→declaration_idinmessagestable - 1.4: In
up()— Drop indexmessages_folder_id_created_at_index, recreate as[declaration_id, created_at] - 1.5: In
up()— Rename columnfolder_id→declaration_idinfolder_invitationstable - 1.6: In
up()— Rename tablefolders→declarations - 1.7: In
up()— Rename tablefolder_invitations→declaration_invitations - 1.8: In
up()— Re-add FK constraints using explicit->constrained('declarations')->cascadeOnDelete()on both tables - 1.9: Implement
down()with explicit reverse steps (see Dev Notes)
- 1.1: Create migration
- Task 2: Verify migration executes successfully (AC: #1, #2, #3, #4, #6)
- 2.1: Run
php artisan migrate— confirm zero errors - 2.2: Verify tables:
php artisan tinker --execute="echo Schema::hasTable('declarations') ? 'OK' : 'FAIL';" - 2.3: Verify columns:
php artisan tinker --execute="echo Schema::hasColumn('messages', 'declaration_id') ? 'OK' : 'FAIL';" - 2.4: Verify composite index exists on
messages(declaration_id, created_at) - 2.5: Verify existing seed/test data is intact after migration
- 2.1: Run
- Task 3: Verify rollback works (AC: #5)
- 3.1: Run
php artisan migrate:rollback— confirm tables revert tofolders,folder_invitations - 3.2: Verify foreign key columns revert to
folder_id - 3.3: Re-run
php artisan migrateto confirm re-apply also works
- 3.1: Run
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
mediatable has a polymorphicmodel_typecolumn storingApp\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_logtable hassubject_typestoringApp\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_typemorphs — not folder-related, no action needed
Migration Strategy — Order of Operations
The order matters to avoid foreign key constraint violations:
up() method:
- Drop FK constraints:
$table->dropForeign('messages_folder_id_foreign')and$table->dropForeign('folder_invitations_folder_id_foreign') - Rename columns
folder_id→declaration_idvia$table->renameColumn('folder_id', 'declaration_id')inmessagesandfolder_invitations - Drop index
messages_folder_id_created_at_index, create new$table->index(['declaration_id', 'created_at']) Schema::rename('folders', 'declarations')Schema::rename('folder_invitations', 'declaration_invitations')- Re-add FKs:
$table->foreign('declaration_id')->references('id')->on('declarations')->cascadeOnDelete()on bothmessagesanddeclaration_invitations
down() method (explicit reverse):
- Drop FK constraints:
$table->dropForeign('messages_declaration_id_foreign')and$table->dropForeign('declaration_invitations_declaration_id_foreign') Schema::rename('declaration_invitations', 'folder_invitations')Schema::rename('declarations', 'folders')- Drop index on
messages(declaration_id, created_at), recreate as$table->index(['folder_id', 'created_at']) - Rename columns
declaration_id→folder_idinmessagesandfolder_invitations - 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()andrenameColumn()work natively. Laravel handles SQLite's limitedALTER TABLEvia internal table recreation - MySQL 8.4 (production): Native
RENAME TABLEandALTER TABLE ... RENAME COLUMNare well-supported - Recommendation: Use Laravel's
Schemafacade 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):
Foldermodel → tablefoldersnot foundFolderInvitationmodel → tablefolder_invitationsnot found,$fillablecontainsfolder_id(column is nowdeclaration_id)Messagemodel →$fillablecontainsfolder_id,folder()relationship references wrong column- Middleware alias
folder.invitationinbootstrap/app.phpand route reference inweb.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— Createsfolderstable with all columnsdatabase/migrations/2026_03_01_144731_create_folder_invitations_table.php— Createsfolder_invitationswithfolder_idFKdatabase/migrations/2026_03_01_144730_create_messages_table.php— Createsmessageswithfolder_idFK and composite indexdatabase/migrations/2026_03_01_144736_add_confirmation_fields_to_folders_table.php— Adds confirmation columns tofoldersdatabase/migrations/2026_03_09_000003_split_vat_folder_type.php— Data migration onfolderstable
Testing Standards
- Use Pest syntax (
test()closures), never PHPUnit class-based tests RefreshDatabaseis auto-applied viaPest.php— don't add it manually- Run tests:
composer test - After migration, existing tests WILL fail (they reference
Foldermodel). 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 referencesDB::table('folders')) on bothmigrateandmigrate:fresh - Laravel 12 handles column renames natively — no doctrine/dbal needed
Migration Code Skeleton
<?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 syntaxdropForeign(['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.xmlto useDB_CONNECTION=sqliteandDB_DATABASE=:memory:for test isolation
Completion Notes List
- Created migration
2026_03_11_000001_rename_folders_to_declarations.phpimplementing 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 FKsdown()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)