getAttribute('team_id'); $rules = AutomationRule::where('trigger', $trigger)->where('is_active', true) ->where(fn ($query) => $query->whereNull('team_id')->when($teamId, fn ($team) => $team->orWhere('team_id', $teamId))) ->get(); foreach ($rules as $rule) { if (! $this->matches($rule->conditions ?? [], $subject, $context)) { continue; } $runs = $rule->runs()->where('subject_type', $subject::class)->where('subject_id', $subject->getKey())->count(); if ($runs >= $rule->max_runs_per_record) { continue; } try { $this->engine->run($rule, $subject, "automation:{$rule->id}:{$eventKey}", $context); } catch (\Throwable) { // The run log contains the failure; business lifecycle must remain available. } } } private function matches(array $conditions, Model $subject, array $context): bool { foreach ($conditions as $condition) { $field = $condition['field'] ?? null; $operator = $condition['operator'] ?? 'equals'; $expected = $condition['value'] ?? null; if (! $field) { return false; } $actual = $context[$field] ?? $subject->getAttribute($field); $matches = match ($operator) { 'not_equals' => $actual != $expected, 'greater_than' => is_numeric($actual) && $actual > $expected, 'less_than' => is_numeric($actual) && $actual < $expected, 'contains' => is_string($actual) && str_contains($actual, (string) $expected), default => $actual == $expected, }; if (! $matches) { return false; } } return true; } }