Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.67% covered (success)
98.67%
74 / 75
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
ReviewPortalDocumentAction
98.67% covered (success)
98.67%
74 / 75
50.00% covered (danger)
50.00%
1 / 2
10
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
98.65% covered (success)
98.65%
73 / 74
0.00% covered (danger)
0.00%
0 / 1
9
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\Domain\Enums\DocumentReviewDecision;
12use ImovelZapAi\ClientPortal\Domain\Enums\PortalActivityEvent;
13use ImovelZapAi\ClientPortal\Domain\Enums\RequestedDocumentStatus;
14use ImovelZapAi\ClientPortal\Domain\Events\PortalDocumentReviewed;
15use ImovelZapAi\ClientPortal\Domain\Exceptions\ClientPortalException;
16use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\DocumentCommentModel;
17use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\DocumentReviewModel;
18use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\RequestedDocumentModel;
19use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\UploadedDocumentModel;
20
21final class ReviewPortalDocumentAction
22{
23    public function __construct(
24        private readonly PortalActivityWriter $activity,
25        private readonly PortalProgressCalculator $progress,
26    ) {}
27
28    public function execute(
29        RequestedDocumentModel $requested,
30        User $actor,
31        string $decision,
32        ?string $comment = null,
33    ): RequestedDocumentModel {
34        $decisionEnum = DocumentReviewDecision::tryFrom($decision)
35            ?? throw ClientPortalException::invalidState('decisão inválida.');
36
37        return DB::transaction(function () use ($requested, $actor, $decisionEnum, $comment): RequestedDocumentModel {
38            $upload = UploadedDocumentModel::query()
39                ->where('requested_document_id', $requested->id)
40                ->first();
41
42            if ($upload === null) {
43                throw ClientPortalException::invalidState('ainda não há arquivo enviado.');
44            }
45
46            DocumentReviewModel::query()->create([
47                'tenant_id' => $requested->tenant_id,
48                'uploaded_document_id' => $upload->id,
49                'document_version_id' => $upload->current_version_id,
50                'reviewer_user_id' => $actor->id,
51                'decision' => $decisionEnum,
52                'comment' => $comment,
53            ]);
54
55            if ($comment !== null && trim($comment) !== '') {
56                DocumentCommentModel::query()->create([
57                    'tenant_id' => $requested->tenant_id,
58                    'client_portal_id' => $requested->client_portal_id,
59                    'requested_document_id' => $requested->id,
60                    'author_user_id' => $actor->id,
61                    'author_type' => 'broker',
62                    'body' => $comment,
63                    'visible_to_client' => true,
64                ]);
65            }
66
67            if ($decisionEnum === DocumentReviewDecision::Approved) {
68                $requested->forceFill(['status' => RequestedDocumentStatus::Approved])->save();
69                $upload->forceFill([
70                    'status' => 'approved',
71                    'reviewed_at' => now(),
72                ])->save();
73                $event = PortalActivityEvent::DocumentApproved;
74                $message = 'Documento aprovado: '.$requested->label;
75            } else {
76                $requested->forceFill(['status' => RequestedDocumentStatus::Rejected])->save();
77                $upload->forceFill([
78                    'status' => 'rejected',
79                    'reviewed_at' => now(),
80                ])->save();
81                $event = PortalActivityEvent::DocumentRejected;
82                $message = 'Documento recusado: '.$requested->label.($comment ? " — {$comment}" : '');
83            }
84
85            $this->activity->write(
86                $requested->tenant_id,
87                $requested->client_portal_id,
88                $event,
89                actorType: 'broker',
90                actorUserId: (int) $actor->id,
91                payload: [
92                    'requested_document_id' => $requested->id,
93                    'decision' => $decisionEnum->value,
94                    'comment' => $comment,
95                ],
96                message: $message,
97            );
98
99            if ($comment !== null && trim($comment) !== '') {
100                $this->activity->write(
101                    $requested->tenant_id,
102                    $requested->client_portal_id,
103                    PortalActivityEvent::CommentAdded,
104                    actorType: 'broker',
105                    actorUserId: (int) $actor->id,
106                    message: 'Comentário no documento '.$requested->label,
107                );
108            }
109
110            $portal = $requested->portal;
111            if ($portal !== null) {
112                $this->progress->refresh($portal);
113            }
114
115            PortalDocumentReviewed::dispatch(
116                $requested->tenant_id,
117                $requested->client_portal_id,
118                $requested->id,
119                $decisionEnum->value,
120            );
121
122            return $requested->fresh(['upload.currentVersion']);
123        });
124    }
125}