Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
CreateDealFromConversationAction
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
2 / 2
6
100.00% covered (success)
100.00%
1 / 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%
36 / 36
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Sales\Application\Actions;
6
7use App\Models\User;
8use ImovelZapAi\Conversations\Infrastructure\Persistence\Eloquent\ContactModel;
9use ImovelZapAi\Conversations\Infrastructure\Persistence\Eloquent\ConversationModel;
10use ImovelZapAi\Properties\Infrastructure\Persistence\Eloquent\PropertyInterestModel;
11use ImovelZapAi\Sales\Domain\Enums\DealStageKey;
12use ImovelZapAi\Sales\Domain\Exceptions\DealException;
13use ImovelZapAi\Sales\Infrastructure\Persistence\Eloquent\DealModel;
14
15final class CreateDealFromConversationAction
16{
17    public function __construct(
18        private readonly CreateDealAction $createDeal,
19    ) {}
20
21    /**
22     * @param  array{
23     *     amount?: float|string|null,
24     *     priority?: string|null,
25     *     property_id?: string|null,
26     *     property_interest_id?: string|null,
27     *     stage_key?: string|null,
28     *     next_action_at?: string|null,
29     *     next_action_type?: string|null,
30     * }  $extra
31     */
32    public function execute(string $tenantId, User $actor, string $conversationId, array $extra = []): DealModel
33    {
34        $conversation = ConversationModel::query()
35            ->where('tenant_id', $tenantId)
36            ->whereKey($conversationId)
37            ->firstOrFail();
38
39        /** @var ContactModel $contact */
40        $contact = ContactModel::query()->findOrFail($conversation->contact_id);
41
42        $interestId = $extra['property_interest_id'] ?? null;
43        $propertyId = $extra['property_id'] ?? null;
44
45        if ($interestId === null) {
46            $interest = PropertyInterestModel::query()
47                ->where('tenant_id', $tenantId)
48                ->where('conversation_id', $conversation->id)
49                ->latest()
50                ->first();
51            $interestId = $interest?->id;
52            $propertyId ??= $interest?->property_id;
53        }
54
55        $existing = DealModel::query()
56            ->where('tenant_id', $tenantId)
57            ->where('conversation_id', $conversation->id)
58            ->whereNull('closed_at')
59            ->first();
60
61        if ($existing !== null) {
62            throw DealException::invalidState('já existe oportunidade aberta para esta conversa.');
63        }
64
65        $title = 'Oportunidade — '.($contact->display_name ?: $contact->phone ?: 'Pessoa');
66
67        return $this->createDeal->execute($tenantId, $actor, [
68            'contact_id' => $contact->id,
69            'conversation_id' => $conversation->id,
70            'property_id' => $propertyId,
71            'property_interest_id' => $interestId,
72            'title' => $title,
73            'amount' => $extra['amount'] ?? null,
74            'priority' => $extra['priority'] ?? null,
75            'stage_key' => $extra['stage_key'] ?? DealStageKey::Qualified->value,
76            'next_action_at' => $extra['next_action_at'] ?? now()->addDay()->toDateTimeString(),
77            'next_action_type' => $extra['next_action_type'] ?? 'whatsapp',
78            'next_action_notes' => 'Primeiro contato comercial',
79        ]);
80    }
81}