Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
71 / 71
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
CreateClientPortalAction
100.00% covered (success)
100.00%
71 / 71
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%
70 / 70
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\ClientPortal\Application\Actions;
6
7use App\Models\User;
8use Illuminate\Support\Facades\DB;
9use ImovelZapAi\ClientPortal\Application\Services\PortalActivityWriter;
10use ImovelZapAi\ClientPortal\Application\Services\PortalProgressCalculator;
11use ImovelZapAi\ClientPortal\Application\Services\PortalTokenService;
12use ImovelZapAi\ClientPortal\Domain\Enums\PortalActivityEvent;
13use ImovelZapAi\ClientPortal\Domain\Enums\PortalStatus;
14use ImovelZapAi\ClientPortal\Domain\Enums\RequestedDocumentStatus;
15use ImovelZapAi\ClientPortal\Domain\Events\ClientPortalCreated;
16use ImovelZapAi\ClientPortal\Domain\Exceptions\ClientPortalException;
17use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\ClientPortalModel;
18use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\DocumentRequestModel;
19use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\RequestedDocumentModel;
20use ImovelZapAi\Conversations\Infrastructure\Persistence\Eloquent\ContactModel;
21use ImovelZapAi\Sales\Infrastructure\Persistence\Eloquent\DealModel;
22
23final class CreateClientPortalAction
24{
25    public function __construct(
26        private readonly PortalTokenService $tokens,
27        private readonly PortalActivityWriter $activity,
28        private readonly PortalProgressCalculator $progress,
29    ) {}
30
31    /**
32     * @param  array{
33     *     contact_id: string,
34     *     document_keys: list<string>,
35     *     deal_id?: string|null,
36     *     conversation_id?: string|null,
37     *     owner_id?: string|null,
38     *     title?: string|null,
39     *     greeting_name?: string|null,
40     *     intro_message?: string|null,
41     *     expires_at?: string|null,
42     *     responsible_user_id?: int|null,
43     * }  $data
44     * @return array{portal: ClientPortalModel, plain_token: string, url: string}
45     */
46    public function execute(string $tenantId, User $actor, array $data): array
47    {
48        $keys = array_values(array_unique($data['document_keys'] ?? []));
49        if ($keys === []) {
50            throw ClientPortalException::invalidState('selecione ao menos um documento.');
51        }
52
53        $catalog = collect(config('client_portal.document_catalog', []))
54            ->keyBy('key');
55
56        return DB::transaction(function () use ($tenantId, $actor, $data, $keys, $catalog): array {
57            $contact = ContactModel::query()
58                ->where('tenant_id', $tenantId)
59                ->whereKey($data['contact_id'])
60                ->firstOrFail();
61
62            $dealId = $data['deal_id'] ?? null;
63            if ($dealId !== null) {
64                DealModel::query()->where('tenant_id', $tenantId)->whereKey($dealId)->firstOrFail();
65            }
66
67            $greeting = $data['greeting_name']
68                ?? $contact->display_name
69                ?? 'cliente';
70
71            $portal = ClientPortalModel::query()->create([
72                'tenant_id' => $tenantId,
73                'contact_id' => $contact->id,
74                'deal_id' => $dealId,
75                'conversation_id' => $data['conversation_id'] ?? null,
76                'owner_id' => $data['owner_id'] ?? null,
77                'created_by' => $actor->id,
78                'responsible_user_id' => $data['responsible_user_id'] ?? $actor->id,
79                'title' => $data['title'] ?? ('Documentos â€” '.$greeting),
80                'greeting_name' => $greeting,
81                'status' => PortalStatus::Active,
82                'intro_message' => $data['intro_message']
83                    ?? 'Para continuarmos sua negociação, envie os documentos abaixo.',
84                'expires_at' => isset($data['expires_at'])
85                    ? $data['expires_at']
86                    : now()->addDays((int) config('client_portal.token_ttl_days', 14)),
87                'last_activity_at' => now(),
88                'documents_total' => count($keys),
89            ]);
90
91            $request = DocumentRequestModel::query()->create([
92                'tenant_id' => $tenantId,
93                'client_portal_id' => $portal->id,
94                'status' => 'open',
95            ]);
96
97            $position = 1;
98            foreach ($keys as $key) {
99                $item = $catalog->get($key);
100                RequestedDocumentModel::query()->create([
101                    'tenant_id' => $tenantId,
102                    'client_portal_id' => $portal->id,
103                    'document_request_id' => $request->id,
104                    'document_type_key' => $key,
105                    'label' => $item['label'] ?? strtoupper($key),
106                    'position' => $position++,
107                    'is_required' => true,
108                    'status' => RequestedDocumentStatus::Pending,
109                ]);
110            }
111
112            $issued = $this->tokens->issue($portal, $portal->expires_at);
113
114            $this->activity->write(
115                $tenantId,
116                $portal->id,
117                PortalActivityEvent::PortalCreated,
118                actorType: 'broker',
119                actorUserId: (int) $actor->id,
120                message: 'Portal de documentos criado.',
121            );
122
123            $this->progress->refresh($portal);
124            ClientPortalCreated::dispatch($tenantId, $portal->id);
125
126            return [
127                'portal' => $portal->fresh(['requestedDocuments', 'contact', 'deal']),
128                'plain_token' => $issued['plain'],
129                'url' => $issued['url'],
130            ];
131        });
132    }
133}