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