Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
RecordProposalViewAction
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
2 / 2
9
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%
39 / 39
100.00% covered (success)
100.00%
1 / 1
8
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Proposals\Application\Actions;
6
7use ImovelZapAi\Proposals\Application\Services\ProposalHistoryWriter;
8use ImovelZapAi\Proposals\Application\Services\ProposalShareTokenService;
9use ImovelZapAi\Proposals\Domain\Enums\ProposalHistoryEvent;
10use ImovelZapAi\Proposals\Domain\Events\ProposalViewed;
11use ImovelZapAi\Proposals\Infrastructure\Persistence\Eloquent\ProposalViewModel;
12
13final class RecordProposalViewAction
14{
15    public function __construct(
16        private readonly ProposalShareTokenService $tokens,
17        private readonly ProposalHistoryWriter $history,
18    ) {}
19
20    /** @return array{proposal: \ImovelZapAi\Proposals\Infrastructure\Persistence\Eloquent\ProposalModel, view: ProposalViewModel} */
21    public function execute(string $plainToken, ?string $ip = null, ?string $userAgent = null): array
22    {
23        $resolved = $this->tokens->resolve($plainToken);
24        $proposal = $resolved['proposal'];
25        $share = $resolved['share'];
26
27        $device = 'desktop';
28        $ua = strtolower((string) $userAgent);
29        if (str_contains($ua, 'mobile') || str_contains($ua, 'android') || str_contains($ua, 'iphone')) {
30            $device = 'mobile';
31        } elseif (str_contains($ua, 'tablet') || str_contains($ua, 'ipad')) {
32            $device = 'tablet';
33        }
34
35        $view = ProposalViewModel::query()->create([
36            'tenant_id' => $proposal->tenant_id,
37            'proposal_id' => $proposal->id,
38            'proposal_share_id' => $share->id,
39            'proposal_version_id' => $share->proposal_version_id,
40            'ip' => $ip,
41            'user_agent' => $userAgent ? substr($userAgent, 0, 512) : null,
42            'device' => $device,
43            'started_at' => now(),
44        ]);
45
46        $proposal->forceFill([
47            'last_viewed_at' => now(),
48            'view_count' => $proposal->view_count + 1,
49        ])->save();
50
51        $recent = \ImovelZapAi\Proposals\Infrastructure\Persistence\Eloquent\ProposalHistoryModel::query()
52            ->where('proposal_id', $proposal->id)
53            ->where('event', ProposalHistoryEvent::Viewed)
54            ->where('created_at', '>=', now()->subHour())
55            ->exists();
56
57        if (! $recent) {
58            $this->history->write(
59                $proposal->tenant_id,
60                $proposal->id,
61                ProposalHistoryEvent::Viewed,
62                actorType: 'client',
63                payload: ['device' => $device, 'ip' => $ip],
64                message: 'Cliente abriu a proposta.',
65            );
66            ProposalViewed::dispatch($proposal->tenant_id, $proposal->id);
67        }
68
69        return ['proposal' => $proposal->fresh(['currentVersion', 'comments']), 'view' => $view];
70    }
71}