Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
91 / 91
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
UploadPortalDocumentAction
100.00% covered (success)
100.00%
91 / 91
100.00% covered (success)
100.00%
2 / 2
10
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%
90 / 90
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\ClientPortal\Application\Actions;
6
7use Illuminate\Http\UploadedFile;
8use Illuminate\Support\Facades\DB;
9use ImovelZapAi\ClientPortal\Application\Services\PortalActivityWriter;
10use ImovelZapAi\ClientPortal\Application\Services\PortalDocumentStorage;
11use ImovelZapAi\ClientPortal\Application\Services\PortalProgressCalculator;
12use ImovelZapAi\ClientPortal\Application\Services\PortalTokenService;
13use ImovelZapAi\ClientPortal\Domain\Enums\DocumentScanStatus;
14use ImovelZapAi\ClientPortal\Domain\Enums\PortalActivityEvent;
15use ImovelZapAi\ClientPortal\Domain\Enums\RequestedDocumentStatus;
16use ImovelZapAi\ClientPortal\Domain\Events\PortalDocumentUploaded;
17use ImovelZapAi\ClientPortal\Domain\Exceptions\ClientPortalException;
18use ImovelZapAi\ClientPortal\Infrastructure\Jobs\ScanPortalDocumentJob;
19use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\DocumentVersionModel;
20use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\PortalSessionModel;
21use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\RequestedDocumentModel;
22use ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\UploadedDocumentModel;
23
24final class UploadPortalDocumentAction
25{
26    public function __construct(
27        private readonly PortalTokenService $tokens,
28        private readonly PortalDocumentStorage $storage,
29        private readonly PortalActivityWriter $activity,
30        private readonly PortalProgressCalculator $progress,
31    ) {}
32
33    /**
34     * @return array{requested: RequestedDocumentModel, uploaded: UploadedDocumentModel}
35     */
36    public function execute(
37        string $plainToken,
38        string $requestedDocumentId,
39        UploadedFile $file,
40        string $uploadedVia = 'file',
41        ?string $ip = null,
42        ?string $userAgent = null,
43    ): array {
44        $resolved = $this->tokens->resolve($plainToken);
45        /** @var \ImovelZapAi\ClientPortal\Infrastructure\Persistence\Eloquent\ClientPortalModel $portal */
46        $portal = $resolved['portal'];
47        $token = $resolved['token'];
48
49        return DB::transaction(function () use ($portal, $token, $requestedDocumentId, $file, $uploadedVia, $ip, $userAgent): array {
50            $requested = RequestedDocumentModel::query()
51                ->where('client_portal_id', $portal->id)
52                ->whereKey($requestedDocumentId)
53                ->firstOrFail();
54
55            if ($requested->status === RequestedDocumentStatus::Approved) {
56                throw ClientPortalException::invalidState('documento já aprovado.');
57            }
58
59            $stored = $this->storage->store($portal->tenant_id, $portal->id, $requested->id, $file);
60
61            $upload = UploadedDocumentModel::query()
62                ->where('requested_document_id', $requested->id)
63                ->first();
64
65            $isResubmit = $upload !== null;
66
67            if ($upload === null) {
68                $upload = UploadedDocumentModel::query()->create([
69                    'tenant_id' => $portal->tenant_id,
70                    'client_portal_id' => $portal->id,
71                    'requested_document_id' => $requested->id,
72                    'status' => 'submitted',
73                    'scan_status' => DocumentScanStatus::Pending,
74                    'submitted_at' => now(),
75                ]);
76                $versionNumber = 1;
77            } else {
78                $versionNumber = ((int) $upload->versions()->max('version')) + 1;
79                $upload->forceFill([
80                    'status' => 'submitted',
81                    'scan_status' => DocumentScanStatus::Pending,
82                    'submitted_at' => now(),
83                    'reviewed_at' => null,
84                ])->save();
85            }
86
87            $version = DocumentVersionModel::query()->create([
88                'tenant_id' => $portal->tenant_id,
89                'uploaded_document_id' => $upload->id,
90                'version' => $versionNumber,
91                'disk' => $stored['disk'],
92                'path' => $stored['path'],
93                'original_name' => $stored['original_name'],
94                'mime' => $stored['mime'],
95                'size_bytes' => $stored['size_bytes'],
96                'checksum' => $stored['checksum'],
97                'uploaded_via' => $uploadedVia,
98            ]);
99
100            $upload->forceFill(['current_version_id' => $version->id])->save();
101
102            $underAnalysis = (bool) config('client_portal.antivirus_enabled', true);
103            $requested->forceFill([
104                'status' => $underAnalysis
105                    ? RequestedDocumentStatus::UnderAnalysis
106                    : RequestedDocumentStatus::UnderReview,
107            ])->save();
108
109            if (! $underAnalysis) {
110                $upload->forceFill([
111                    'scan_status' => DocumentScanStatus::Skipped,
112                    'status' => 'under_review',
113                ])->save();
114            }
115
116            PortalSessionModel::query()->updateOrCreate(
117                [
118                    'tenant_id' => $portal->tenant_id,
119                    'client_portal_id' => $portal->id,
120                    'portal_access_token_id' => $token->id,
121                    'ip' => $ip,
122                ],
123                [
124                    'user_agent' => $userAgent ? substr($userAgent, 0, 512) : null,
125                    'started_at' => now(),
126                    'last_seen_at' => now(),
127                ],
128            );
129
130            $this->activity->write(
131                $portal->tenant_id,
132                $portal->id,
133                $isResubmit ? PortalActivityEvent::DocumentResubmitted : PortalActivityEvent::DocumentUploaded,
134                actorType: 'client',
135                payload: [
136                    'requested_document_id' => $requested->id,
137                    'label' => $requested->label,
138                    'version' => $versionNumber,
139                ],
140                message: ($isResubmit ? 'Novo envio: ' : 'Documento enviado: ').$requested->label,
141            );
142
143            $this->progress->refresh($portal);
144            PortalDocumentUploaded::dispatch($portal->tenant_id, $portal->id, $requested->id);
145
146            if ($underAnalysis) {
147                ScanPortalDocumentJob::dispatch($upload->id)->onQueue((string) config('client_portal.queue'));
148            }
149
150            return [
151                'requested' => $requested->fresh(),
152                'uploaded' => $upload->fresh(['currentVersion']),
153            ];
154        });
155    }
156}