Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.35% covered (success)
95.35%
41 / 43
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ShareDocumentAction
95.35% covered (success)
95.35%
41 / 43
66.67% covered (warning)
66.67%
2 / 3
11
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
95.12% covered (success)
95.12%
39 / 41
0.00% covered (danger)
0.00%
0 / 1
9
 isProposalType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Documents\Application\Actions;
6
7use App\Models\User;
8use Illuminate\Support\Facades\DB;
9use ImovelZapAi\Conversations\Application\Actions\SendOutboundMessageAction;
10use ImovelZapAi\Documents\Application\Services\DocumentHistoryWriter;
11use ImovelZapAi\Documents\Application\Services\DocumentPdfGenerator;
12use ImovelZapAi\Documents\Application\Services\DocumentShareTokenService;
13use ImovelZapAi\Documents\Domain\Enums\DocumentHistoryEvent;
14use ImovelZapAi\Documents\Domain\Enums\DocumentStatus;
15use ImovelZapAi\Documents\Domain\Events\DocumentShared;
16use ImovelZapAi\Documents\Domain\Exceptions\DocumentException;
17use ImovelZapAi\Documents\Infrastructure\Persistence\Eloquent\BusinessDocumentModel;
18use ImovelZapAi\Sales\Application\Actions\ChangeDealStageAction;
19use ImovelZapAi\Sales\Domain\Enums\DealStageKey;
20use ImovelZapAi\Documents\Domain\Enums\DocumentType;
21
22final class ShareDocumentAction
23{
24    public function __construct(
25        private readonly DocumentShareTokenService $tokens,
26        private readonly DocumentPdfGenerator $pdf,
27        private readonly DocumentHistoryWriter $history,
28        private readonly SendOutboundMessageAction $sendText,
29        private readonly ChangeDealStageAction $changeDealStage,
30    ) {}
31
32    /** @return array{url: string, plain_token: string} */
33    public function execute(BusinessDocumentModel $document, User $actor, bool $sendWhatsapp = true): array
34    {
35        if ($document->current_version_id === null) {
36            throw DocumentException::invalidState('documento sem versão.');
37        }
38
39        $result = DB::transaction(function () use ($document, $actor): array {
40            $document->loadMissing(['contact', 'currentVersion', 'deal']);
41            $issued = $this->tokens->issue($document, $document->current_version_id, $document->expires_at);
42
43            $pdf = $this->pdf->generate($document, $document->currentVersion, $issued['url']);
44            $document->currentVersion->forceFill([
45                'pdf_disk' => $pdf['disk'],
46                'pdf_path' => $pdf['path'],
47            ])->save();
48
49            $document->forceFill([
50                'status' => DocumentStatus::Shared,
51                'shared_at' => now(),
52            ])->save();
53
54            if ($document->deal !== null && $document->deal->closed_at === null && $this->isProposalType($document->document_type)) {
55                try {
56                    $this->changeDealStage->execute($document->deal, $actor, DealStageKey::Proposal->value);
57                } catch (\Throwable) {
58                }
59            }
60
61            $this->history->write(
62                $document->tenant_id,
63                $document->id,
64                DocumentHistoryEvent::Shared,
65                actorType: 'broker',
66                actorUserId: (int) $actor->id,
67                payload: ['url' => $issued['url']],
68                message: 'Documento compartilhado no portal.',
69            );
70
71            DocumentShared::dispatch($document->tenant_id, $document->id);
72
73            return ['url' => $issued['url'], 'plain_token' => $issued['plain']];
74        });
75
76        if ($sendWhatsapp) {
77            try {
78                $document->loadMissing(['contact', 'deal.conversation']);
79                $name = $document->contact?->display_name ?: 'olá';
80                $body = "Olá {$name}! Seu documento está pronto. Acesse pelo link seguro:\n{$result['url']}\n\nSem anexos — tudo no portal.";
81                $this->sendText->execute(
82                    $document->tenant_id,
83                    $document->contact,
84                    $body,
85                    $document->deal?->conversation,
86                );
87            } catch (\Throwable) {
88                // Link já publicado; falha de WhatsApp não bloqueia o compartilhamento.
89            }
90        }
91
92        return $result;
93    }
94
95    private function isProposalType(DocumentType $type): bool
96    {
97        return in_array($type, [DocumentType::ProposalSale, DocumentType::ProposalRent], true);
98    }
99}