activity->loadMissing(['decision.events' => function ($q) { $q->wherePivot('active', true); }, 'contract', 'clientCase.client', 'user']); $decision = $activity->decision; if (! $decision) { return; } $events = $decision->events; if ($events->isEmpty()) { return; } // Sort by run_order when provided; otherwise keep natural order $sorted = $events->sortBy(function ($ev) { return $ev->pivot?->run_order ?? PHP_INT_MAX; })->values(); $jobs = []; foreach ($sorted as $ev) { $base = is_array($ev->config ?? null) ? $ev->config : []; $pivotCfgRaw = $ev->pivot?->config ?? null; $pivotCfg = is_array($pivotCfgRaw) ? $pivotCfgRaw : (is_string($pivotCfgRaw) ? (json_decode($pivotCfgRaw, true) ?: []) : []); $effectiveConfig = array_replace_recursive($base, $pivotCfg); $jobs[] = new RunDecisionEvent( activityId: $activity->id, eventId: $ev->id, eventKey: (string) ($ev->key ?? ''), config: $effectiveConfig, ); } // If any event has a finite run_order, chain to enforce order; else dispatch in parallel $hasOrder = $sorted->contains(fn ($ev) => $ev->pivot?->run_order !== null); // Run synchronously for local/dev/testing (or when debug is on) to ensure immediate effects without a queue worker $shouldRunSync = app()->environment(['local', 'development', 'dev', 'testing']) || (bool) config('app.debug') || config('queue.default') === 'sync'; if ($hasOrder) { if ($shouldRunSync) { foreach ($jobs as $job) { Bus::dispatchSync($job); } } else { Bus::chain($jobs)->dispatch(); } } else { if ($shouldRunSync) { foreach ($jobs as $job) { Bus::dispatchSync($job); } } else { foreach ($jobs as $job) { dispatch($job); } } } } }