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
SignDocumentAction
95.35% covered (success)
95.35%
41 / 43
66.67% covered (warning)
66.67%
2 / 3
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
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
3
 assertCanSign
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
5.20
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Signatures\Application\Actions;
6
7use Illuminate\Support\Facades\DB;
8use ImovelZapAi\Signatures\Application\Services\SignatureAccessTokenService;
9use ImovelZapAi\Signatures\Application\Services\SignatureAuditWriter;
10use ImovelZapAi\Signatures\Application\Services\SignatureProviderRegistry;
11use ImovelZapAi\Signatures\Application\Services\SignatureStatusSynchronizer;
12use ImovelZapAi\Signatures\Domain\Enums\SignatureAuditEvent;
13use ImovelZapAi\Signatures\Domain\Enums\SignatureMode;
14use ImovelZapAi\Signatures\Domain\Enums\SignatureRequestStatus;
15use ImovelZapAi\Signatures\Domain\Enums\SignerStatus;
16use ImovelZapAi\Signatures\Domain\Events\SignatureCompleted;
17use ImovelZapAi\Signatures\Domain\Exceptions\SignatureException;
18use ImovelZapAi\Signatures\Infrastructure\Persistence\Eloquent\SignatureRequestModel;
19use ImovelZapAi\Signatures\Infrastructure\Persistence\Eloquent\SignatureStepModel;
20use ImovelZapAi\Signatures\Infrastructure\Persistence\Eloquent\SignerModel;
21
22final class SignDocumentAction
23{
24    public function __construct(
25        private readonly SignatureAccessTokenService $tokens,
26        private readonly SignatureProviderRegistry $providers,
27        private readonly SignatureAuditWriter $audit,
28        private readonly SignatureStatusSynchronizer $sync,
29    ) {}
30
31    public function execute(
32        string $plainToken,
33        string $typedName,
34        string $otp,
35        ?string $ip = null,
36        ?string $userAgent = null,
37    ): SignatureRequestModel {
38        return DB::transaction(function () use ($plainToken, $typedName, $otp, $ip, $userAgent): SignatureRequestModel {
39            $resolved = $this->tokens->resolve($plainToken);
40            $signer = $resolved['signer'];
41            $request = $resolved['request']->load('signers');
42
43            $this->assertCanSign($request, $signer);
44
45            $provider = $this->providers->get($request->provider);
46            $provider->completeSigner($request, $signer, [
47                'typed_name' => $typedName,
48                'otp' => $otp,
49                'ip' => $ip,
50                'user_agent' => $userAgent,
51            ]);
52
53            SignatureStepModel::query()
54                ->where('signature_request_id', $request->id)
55                ->where('order_index', $signer->order)
56                ->update(['status' => 'completed', 'completed_at' => now()]);
57
58            $this->audit->write(
59                $request->tenant_id,
60                $request->id,
61                SignatureAuditEvent::Signed,
62                actorType: 'signer',
63                signerId: $signer->id,
64                payload: ['typed_name' => $typedName],
65                message: 'Documento assinado.',
66            );
67
68            $wasComplete = $request->status === SignatureRequestStatus::Signed;
69            $this->sync->sync($request->fresh(['signers']));
70            $request->refresh();
71
72            if (! $wasComplete && $request->status === SignatureRequestStatus::Signed) {
73                SignatureCompleted::dispatch($request->tenant_id, $request->id);
74            }
75
76            return $request->fresh(['signers', 'steps', 'document', 'evidences']);
77        });
78    }
79
80    private function assertCanSign(SignatureRequestModel $request, SignerModel $signer): void
81    {
82        if (! $request->isOpen()) {
83            throw SignatureException::invalidState('solicitação encerrada.');
84        }
85
86        if (! in_array($signer->status, [SignerStatus::Pending, SignerStatus::Notified, SignerStatus::Viewed], true)) {
87            throw SignatureException::invalidState('signatário não pode assinar neste momento.');
88        }
89
90        if ($request->mode === SignatureMode::Sequential) {
91            $blocked = $request->signers
92                ->where('order', '<', $signer->order)
93                ->contains(fn (SignerModel $other): bool => $other->status !== SignerStatus::Signed);
94
95            if ($blocked) {
96                throw SignatureException::invalidState('aguarde a assinatura dos signatários anteriores.');
97            }
98        }
99    }
100}