fix(migrations): drop FK antes que índice al soltar phase_id (MySQL) + idempotencia

- El orden en up() era incorrecto para MySQL: intentaba dropear el índice
  inspection_templates_phase_id_index antes de la FK que lo sustentaba, dando
  "Cannot drop index: needed in a foreign key constraint (1553)". Corregido:
  primero dropForeign, después dropIndex, después dropColumn.
- Creación del pivot ahora protegida con Schema::hasTable, para poder reintentar
  la migración si un fallo previo dejó la tabla creada pero la migración sin
  marcar como completa.

Sin cambios funcionales; suite 91 verde (sqlite).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 16:32:00 +02:00
co-authored by Claude Opus 4.7
parent 51c07ede38
commit 308906b328
@@ -15,14 +15,17 @@ return new class extends Migration
*/
public function up(): void
{
// Pivot template ↔ project
Schema::create('inspection_template_project', function (Blueprint $table) {
$table->id();
$table->foreignId('inspection_template_id')->constrained()->cascadeOnDelete();
$table->foreignId('project_id')->constrained()->cascadeOnDelete();
$table->timestamps();
$table->unique(['inspection_template_id', 'project_id'], 'itp_template_project_unique');
});
// Pivot template ↔ project (idempotente: si el up() falló a mitad la primera
// vez, la tabla puede existir ya; en ese caso saltamos su creación).
if (! Schema::hasTable('inspection_template_project')) {
Schema::create('inspection_template_project', function (Blueprint $table) {
$table->id();
$table->foreignId('inspection_template_id')->constrained()->cascadeOnDelete();
$table->foreignId('project_id')->constrained()->cascadeOnDelete();
$table->timestamps();
$table->unique(['inspection_template_id', 'project_id'], 'itp_template_project_unique');
});
}
// Migrar la asignación existente (project_id en inspection_templates → pivot)
$rows = DB::table('inspection_templates')->whereNotNull('project_id')->get(['id', 'project_id']);
@@ -37,12 +40,16 @@ return new class extends Migration
);
}
// Drop columna phase_id (la asociación a fase deja de existir)
// Drop columna phase_id (la asociación a fase deja de existir).
// ORDEN IMPORTANTE en MySQL: primero FK, luego índice, luego columna.
if (Schema::hasColumn('inspection_templates', 'phase_id')) {
Schema::table('inspection_templates', function (Blueprint $table) {
// Algunos motores (SQLite) requieren dropear el índice antes de la columna.
// 1) FK primero: si no la dropeamos, MySQL rechaza soltar el índice
// que la sustenta ("needed in a foreign key constraint").
try { $table->dropForeign(['phase_id']); } catch (\Throwable $e) { /* sqlite sin FKs / ya inexistente */ }
// 2) Índice después (nombre por convención de Laravel).
try { $table->dropIndex('inspection_templates_phase_id_index'); } catch (\Throwable $e) { /* ya no existe */ }
try { $table->dropForeign(['phase_id']); } catch (\Throwable $e) { /* sqlite sin FKs */ }
// 3) Columna al final.
$table->dropColumn('phase_id');
});
}