<?php
 namespace App\Console\Commands; use App\Models\Workflow; use App\Models\WorkflowEdge; use App\Models\WorkflowNode; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; class WorkflowsMigrateToVisualCommand extends Command { protected $signature = 'workflows:migrate-to-visual {--force : Re-migrate even if nodes already exist}'; protected $description = 'Migrate legacy form-based workflows (workflow_conditions/workflow_actions) to the visual node+edge schema'; public function handle(): int { $hasLegacyConditions = Schema::hasTable('workflow_conditions'); $hasLegacyActions = Schema::hasTable('workflow_actions'); if (! $hasLegacyConditions && ! $hasLegacyActions) { $this->info('Legacy tables (workflow_conditions, workflow_actions) already dropped — nothing to migrate.'); return self::SUCCESS; } $workflows = DB::table('workflows')->get(); $migrated = 0; $skipped = 0; foreach ($workflows as $wf) { $workflowModel = Workflow::find($wf->id); if (! $workflowModel) { continue; } if ($workflowModel->nodes()->exists() && ! $this->option('force')) { $this->line("[skip] Workflow #{$wf->id} ({$wf->name}) — already migrated"); $skipped++; continue; } $conditions = $hasLegacyConditions ? DB::table('workflow_conditions')->where('workflow_id', $wf->id)->orderBy('order')->get() : collect(); $actions = $hasLegacyActions ? DB::table('workflow_actions')->where('workflow_id', $wf->id)->orderBy('order')->get() : collect(); DB::transaction(function () use ($wf, $workflowModel, $conditions, $actions) { if ($this->option('force')) { $workflowModel->edges()->delete(); $workflowModel->nodes()->delete(); } $trigger = WorkflowNode::create([ 'workflow_id' => $wf->id, 'type' => 'trigger', 'config' => ['event' => $wf->trigger_event], 'position_x' => 50, 'position_y' => 50, ]); $previousNode = $trigger; $y = 150; foreach ($conditions as $cond) { $condNode = WorkflowNode::create([ 'workflow_id' => $wf->id, 'type' => 'condition', 'config' => [ 'field' => $cond->field, 'operator' => $cond->operator, 'value' => $cond->value, ], 'position_x' => 50, 'position_y' => $y, ]); $sourcePort = $previousNode->type === 'condition' ? 'true' : 'default'; WorkflowEdge::create([ 'workflow_id' => $wf->id, 'from_node_id' => $previousNode->id, 'to_node_id' => $condNode->id, 'source_port' => $sourcePort, ]); $previousNode = $condNode; $y += 100; } foreach ($actions as $action) { $params = is_string($action->parameters) ? (json_decode($action->parameters, true) ?? []) : ($action->parameters ?? []); $actionNode = WorkflowNode::create([ 'workflow_id' => $wf->id, 'type' => 'action', 'config' => [ 'action_type' => $action->action_type, 'parameters' => $params, 'delay_minutes' => $action->delay_minutes ?? 0, ], 'position_x' => 50, 'position_y' => $y, ]); $sourcePort = $previousNode->type === 'condition' ? 'true' : 'default'; WorkflowEdge::create([ 'workflow_id' => $wf->id, 'from_node_id' => $previousNode->id, 'to_node_id' => $actionNode->id, 'source_port' => $sourcePort, ]); $previousNode = $actionNode; $y += 100; } }); $this->info("[ok] Workflow #{$wf->id} ({$wf->name}) — migrated to visual schema"); $migrated++; } $this->info("Done. Migrated: {$migrated}, Skipped: {$skipped}, Total: ".$workflows->count()); return self::SUCCESS; } } 