where('migration', 'like', '%create_communications_table%')->delete(); } Artisan::call('migrate', ['--force' => true]); // Direct DDL Fallback if artisan migrate didn't create them if (! Schema::hasTable('communications')) { Schema::create('communications', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); $table->foreignId('order_id')->nullable()->constrained('orders')->nullOnDelete(); $table->string('name'); $table->string('email')->index(); $table->string('phone')->nullable(); $table->string('subject'); $table->text('message'); $table->string('status')->default('new')->index(); $table->text('admin_notes')->nullable(); $table->string('ip_address', 45)->nullable(); $table->text('user_agent')->nullable(); $table->timestamp('last_replied_at')->nullable(); $table->timestamps(); }); } if (! Schema::hasTable('communication_replies')) { Schema::create('communication_replies', function (Blueprint $table) { $table->id(); $table->foreignId('communication_id')->constrained('communications')->cascadeOnDelete(); $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); $table->boolean('is_admin_reply')->default(true); $table->text('message'); $table->timestamps(); }); } } catch (\Throwable $e) { Log::error('ensureCommunicationsTables error: '.$e->getMessage(), ['trace' => $e->getTraceAsString()]); } } }