Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 120
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ActionRunner
0.00% covered (danger)
0.00%
0 / 120
0.00% covered (danger)
0.00%
0 / 9
1332
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 run
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
210
 createNotification
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 createTask
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
30
 createReminder
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
30
 sendWhatsapp
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 sendEmail
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 stubAction
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 log
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Automation\Application\Services;
6
7use App\Models\User;
8use Illuminate\Support\Facades\Log;
9use ImovelZapAi\Automation\Domain\Enums\ActionKey;
10use ImovelZapAi\Automation\Domain\Enums\LogLevel;
11use ImovelZapAi\Automation\Infrastructure\Persistence\Eloquent\AutomationActionModel;
12use ImovelZapAi\Automation\Infrastructure\Persistence\Eloquent\AutomationExecutionModel;
13use ImovelZapAi\Automation\Infrastructure\Persistence\Eloquent\AutomationLogModel;
14use ImovelZapAi\Sales\Application\Actions\CreateDealTaskAction;
15use ImovelZapAi\Sales\Domain\Enums\DealTaskType;
16use ImovelZapAi\Sales\Infrastructure\Persistence\Eloquent\DealModel;
17
18class ActionRunner
19{
20    public function __construct(
21        private readonly CreateDealTaskAction $createDealTask,
22    ) {}
23
24    /**
25     * @param  array<string, mixed>  $context
26     */
27    public function run(
28        AutomationExecutionModel $execution,
29        AutomationActionModel $action,
30        array $context,
31        ?User $actor = null,
32    ): void {
33        $rawKey = (string) ($action->getRawOriginal('action_key') ?? '');
34        $key = ActionKey::tryFrom($rawKey);
35
36        if ($key === null) {
37            $this->log($execution, LogLevel::Warning, 'Ação desconhecida ignorada.', ['action_key' => $rawKey]);
38
39            return;
40        }
41
42        if ($key === ActionKey::Wait) {
43            $this->log($execution, LogLevel::Info, 'Aguardando '.$action->delay_seconds.' segundos.', [
44                'delay_seconds' => $action->delay_seconds,
45            ]);
46
47            return;
48        }
49
50        match ($key) {
51            ActionKey::CreateNotification => $this->createNotification($execution, $action, $context),
52            ActionKey::CreateTask => $this->createTask($execution, $action, $context, $actor),
53            ActionKey::CreateReminder => $this->createReminder($execution, $action, $context, $actor),
54            ActionKey::SendWhatsapp => $this->sendWhatsapp($execution, $action, $context),
55            ActionKey::SendEmail => $this->sendEmail($execution, $action, $context),
56            ActionKey::MoveDealStage => $this->stubAction($execution, $key, 'Mudança de etapa registrada'),
57            ActionKey::AssignBroker => $this->stubAction($execution, $key, 'Atribuição de corretor registrada'),
58            ActionKey::GenerateDocument => $this->stubAction($execution, $key, 'Geração de documento registrada'),
59            ActionKey::RequestDocuments => $this->stubAction($execution, $key, 'Solicitação de documentos registrada'),
60            ActionKey::StartSignature => $this->stubAction($execution, $key, 'Assinatura registrada'),
61            ActionKey::CreateVisit => $this->stubAction($execution, $key, 'Visita registrada'),
62        };
63    }
64
65    /**
66     * @param  array<string, mixed>  $context
67     */
68    private function createNotification(
69        AutomationExecutionModel $execution,
70        AutomationActionModel $action,
71        array $context,
72    ): void {
73        $config = $action->config ?? [];
74        $title = (string) ($config['title'] ?? 'Notificação automática');
75        $body = (string) ($config['body'] ?? 'Automação executada com sucesso.');
76
77        $this->log($execution, LogLevel::Info, $title, [
78            'type' => 'notification',
79            'title' => $title,
80            'body' => $body,
81            'context' => $context,
82        ]);
83    }
84
85    /**
86     * @param  array<string, mixed>  $context
87     */
88    private function createTask(
89        AutomationExecutionModel $execution,
90        AutomationActionModel $action,
91        array $context,
92        ?User $actor,
93    ): void {
94        $dealId = (string) ($context['dealId'] ?? $context['deal_id'] ?? '');
95
96        if ($dealId === '' || $actor === null) {
97            $this->stubAction($execution, ActionKey::CreateTask, 'Tarefa registrada (sem oportunidade vinculada)');
98
99            return;
100        }
101
102        $deal = DealModel::query()->find($dealId);
103
104        if ($deal === null) {
105            $this->log($execution, LogLevel::Warning, 'Oportunidade não encontrada para criar tarefa.', ['deal_id' => $dealId]);
106
107            return;
108        }
109
110        $config = $action->config ?? [];
111
112        try {
113            $task = $this->createDealTask->execute($deal, $actor, [
114                'type' => (string) ($config['type'] ?? DealTaskType::FollowUp->value),
115                'title' => (string) ($config['title'] ?? 'Tarefa automática'),
116                'description' => $config['description'] ?? null,
117                'due_at' => $config['due_at'] ?? null,
118                'is_automatic' => true,
119                'set_as_next_action' => (bool) ($config['set_as_next_action'] ?? false),
120            ]);
121
122            $this->log($execution, LogLevel::Info, 'Tarefa criada na oportunidade.', [
123                'task_id' => $task->id,
124                'deal_id' => $dealId,
125            ]);
126        } catch (\Throwable $e) {
127            $this->log($execution, LogLevel::Warning, 'Falha ao criar tarefa: '.$e->getMessage(), [
128                'deal_id' => $dealId,
129            ]);
130        }
131    }
132
133    /**
134     * @param  array<string, mixed>  $context
135     */
136    private function createReminder(
137        AutomationExecutionModel $execution,
138        AutomationActionModel $action,
139        array $context,
140        ?User $actor,
141    ): void {
142        $config = $action->config ?? [];
143        $hours = (int) ($config['hours'] ?? 24);
144        $dueAt = now()->addHours(max(1, $hours))->toDateTimeString();
145
146        $dealId = (string) ($context['dealId'] ?? $context['deal_id'] ?? '');
147
148        if ($dealId !== '' && $actor !== null) {
149            $deal = DealModel::query()->find($dealId);
150            if ($deal !== null) {
151                try {
152                    $task = $this->createDealTask->execute($deal, $actor, [
153                        'type' => DealTaskType::FollowUp->value,
154                        'title' => (string) ($config['title'] ?? 'Lembrete automático'),
155                        'description' => $config['description'] ?? 'Lembrete criado pela automação.',
156                        'due_at' => $dueAt,
157                        'is_automatic' => true,
158                        'set_as_next_action' => true,
159                    ]);
160
161                    $this->log($execution, LogLevel::Info, 'Lembrete criado como tarefa na oportunidade.', [
162                        'task_id' => $task->id,
163                        'due_at' => $dueAt,
164                    ]);
165
166                    return;
167                } catch (\Throwable $e) {
168                    $this->log($execution, LogLevel::Warning, 'Falha ao criar lembrete: '.$e->getMessage());
169                }
170            }
171        }
172
173        $this->log($execution, LogLevel::Info, (string) ($config['title'] ?? 'Lembrete registrado'), [
174            'type' => 'reminder',
175            'due_at' => $dueAt,
176            'context' => $context,
177        ]);
178    }
179
180    /**
181     * @param  array<string, mixed>  $context
182     */
183    private function sendWhatsapp(
184        AutomationExecutionModel $execution,
185        AutomationActionModel $action,
186        array $context,
187    ): void {
188        $config = $action->config ?? [];
189        $requiresDelivery = (bool) ($config['require_delivery'] ?? false);
190
191        try {
192            $this->log($execution, LogLevel::Info, 'Mensagem WhatsApp enfileirada (simulado).', [
193                'to' => $config['to'] ?? $context['phone'] ?? null,
194                'template' => $config['template'] ?? null,
195            ]);
196        } catch (\Throwable $e) {
197            $level = $requiresDelivery ? LogLevel::Error : LogLevel::Warning;
198            $this->log($execution, $level, 'Falha ao enviar WhatsApp: '.$e->getMessage());
199            if ($requiresDelivery) {
200                throw $e;
201            }
202        }
203    }
204
205    /**
206     * @param  array<string, mixed>  $context
207     */
208    private function sendEmail(
209        AutomationExecutionModel $execution,
210        AutomationActionModel $action,
211        array $context,
212    ): void {
213        $config = $action->config ?? [];
214        $requiresDelivery = (bool) ($config['require_delivery'] ?? false);
215
216        try {
217            $this->log($execution, LogLevel::Info, 'E-mail enfileirado (simulado).', [
218                'to' => $config['to'] ?? $context['email'] ?? null,
219                'subject' => $config['subject'] ?? 'Automação ImóvelZapAi',
220            ]);
221        } catch (\Throwable $e) {
222            $level = $requiresDelivery ? LogLevel::Error : LogLevel::Warning;
223            $this->log($execution, $level, 'Falha ao enviar e-mail: '.$e->getMessage());
224            if ($requiresDelivery) {
225                throw $e;
226            }
227        }
228    }
229
230    private function stubAction(AutomationExecutionModel $execution, ActionKey $key, string $message): void
231    {
232        $this->log($execution, LogLevel::Info, $message, [
233            'action' => $key->value,
234            'status' => 'ação registrada',
235        ]);
236    }
237
238    /** @param  array<string, mixed>|null  $payload */
239    private function log(
240        AutomationExecutionModel $execution,
241        LogLevel $level,
242        string $message,
243        ?array $payload = null,
244    ): void {
245        AutomationLogModel::query()->create([
246            'tenant_id' => $execution->tenant_id,
247            'execution_id' => $execution->id,
248            'level' => $level,
249            'message' => $message,
250            'payload' => $payload,
251            'created_at' => now(),
252        ]);
253
254        Log::info('[Automation] '.$message, ['execution_id' => $execution->id] + ($payload ?? []));
255    }
256}