Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.31% covered (success)
98.31%
58 / 59
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
CreateDealAction
98.31% covered (success)
98.31%
58 / 59
66.67% covered (warning)
66.67%
2 / 3
7
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
1 / 1
2
 syncInterest
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Sales\Application\Actions;
6
7use App\Models\User;
8use Illuminate\Support\Facades\DB;
9use ImovelZapAi\Conversations\Application\Actions\UpdatePersonInterestStageAction;
10use ImovelZapAi\Properties\Domain\Enums\PropertyInterestStatus;
11use ImovelZapAi\Properties\Infrastructure\Persistence\Eloquent\PropertyInterestModel;
12use ImovelZapAi\Sales\Application\Services\DealActivityRecorder;
13use ImovelZapAi\Sales\Application\Services\DealHistoryWriter;
14use ImovelZapAi\Sales\Application\Services\DefaultPipelineSeeder;
15use ImovelZapAi\Sales\Domain\Enums\DealActivityType;
16use ImovelZapAi\Sales\Domain\Enums\DealHistoryEvent;
17use ImovelZapAi\Sales\Domain\Enums\DealPriority;
18use ImovelZapAi\Sales\Domain\Enums\DealStageKey;
19use ImovelZapAi\Sales\Domain\Enums\DealTaskType;
20use ImovelZapAi\Sales\Domain\Events\DealCreated;
21use ImovelZapAi\Sales\Infrastructure\Persistence\Eloquent\DealModel;
22
23final class CreateDealAction
24{
25    public function __construct(
26        private readonly DefaultPipelineSeeder $pipelineSeeder,
27        private readonly DealActivityRecorder $activities,
28        private readonly DealHistoryWriter $history,
29        private readonly UpdatePersonInterestStageAction $updateInterestStage,
30    ) {}
31
32    /**
33     * @param  array{
34     *     contact_id: string,
35     *     title?: string|null,
36     *     property_id?: string|null,
37     *     conversation_id?: string|null,
38     *     property_interest_id?: string|null,
39     *     visit_id?: string|null,
40     *     amount?: float|string|null,
41     *     priority?: string|null,
42     *     responsible_user_id?: int|null,
43     *     next_action_at?: string|null,
44     *     next_action_type?: string|null,
45     *     next_action_notes?: string|null,
46     *     stage_key?: string|null,
47     * }  $data
48     */
49    public function execute(string $tenantId, User $actor, array $data): DealModel
50    {
51        return DB::transaction(function () use ($tenantId, $actor, $data): DealModel {
52            $pipeline = $this->pipelineSeeder->ensureForTenant($tenantId);
53            $stageKey = $data['stage_key'] ?? DealStageKey::Qualified->value;
54            $stage = $this->pipelineSeeder->stageByKey($tenantId, $stageKey, $pipeline->id);
55
56            $title = $data['title'] ?? 'Oportunidade';
57            $deal = DealModel::query()->create([
58                'tenant_id' => $tenantId,
59                'pipeline_id' => $pipeline->id,
60                'stage_id' => $stage->id,
61                'contact_id' => $data['contact_id'],
62                'property_id' => $data['property_id'] ?? null,
63                'conversation_id' => $data['conversation_id'] ?? null,
64                'property_interest_id' => $data['property_interest_id'] ?? null,
65                'visit_id' => $data['visit_id'] ?? null,
66                'responsible_user_id' => $data['responsible_user_id'] ?? $actor->id,
67                'created_by' => $actor->id,
68                'title' => $title,
69                'amount' => $data['amount'] ?? null,
70                'currency' => 'BRL',
71                'priority' => DealPriority::tryFrom((string) ($data['priority'] ?? 'medium')) ?? DealPriority::Medium,
72                'next_action_at' => $data['next_action_at'] ?? null,
73                'next_action_type' => isset($data['next_action_type'])
74                    ? DealTaskType::tryFrom((string) $data['next_action_type'])
75                    : null,
76                'next_action_notes' => $data['next_action_notes'] ?? null,
77                'stage_entered_at' => now(),
78                'last_activity_at' => now(),
79            ]);
80
81            $this->activities->record(
82                $deal,
83                DealActivityType::Created,
84                'Oportunidade criada',
85                actorUserId: (int) $actor->id,
86            );
87            $this->activities->record(
88                $deal,
89                DealActivityType::ServiceStarted,
90                'Atendimento iniciado',
91                actorUserId: (int) $actor->id,
92            );
93
94            $this->history->write(
95                $tenantId,
96                $deal->id,
97                DealHistoryEvent::Created,
98                actorUserId: (int) $actor->id,
99                after: ['stage' => $stage->key, 'title' => $deal->title],
100                message: 'Oportunidade criada.',
101            );
102
103            $this->syncInterest($deal);
104
105            DealCreated::dispatch($tenantId, $deal->id);
106
107            return $deal->fresh(['stage', 'contact', 'property', 'responsible']);
108        });
109    }
110
111    private function syncInterest(DealModel $deal): void
112    {
113        if ($deal->property_interest_id === null) {
114            return;
115        }
116
117        $interest = PropertyInterestModel::query()->find($deal->property_interest_id);
118        if ($interest === null) {
119            return;
120        }
121
122        if ($interest->status?->canTransitionTo(PropertyInterestStatus::Negotiating) ?? false) {
123            $this->updateInterestStage->execute($deal->tenant_id, $interest, PropertyInterestStatus::Negotiating);
124        }
125    }
126}