Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.16% covered (success)
92.16%
47 / 51
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemindPendingSignersAction
92.16% covered (success)
92.16%
47 / 51
33.33% covered (danger)
33.33%
1 / 3
13.08
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
93.48% covered (success)
93.48%
43 / 46
0.00% covered (danger)
0.00%
0 / 1
10.03
 resolveContact
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Signatures\Application\Actions;
6
7use ImovelZapAi\Conversations\Application\Actions\SendOutboundMessageAction;
8use ImovelZapAi\Conversations\Infrastructure\Persistence\Eloquent\ContactModel;
9use ImovelZapAi\Signatures\Application\Services\SignatureAuditWriter;
10use ImovelZapAi\Signatures\Domain\Enums\SignatureAuditEvent;
11use ImovelZapAi\Signatures\Domain\Enums\SignerStatus;
12use ImovelZapAi\Signatures\Infrastructure\Persistence\Eloquent\SignatureRequestModel;
13use ImovelZapAi\Signatures\Infrastructure\Persistence\Eloquent\SignatureReminderModel;
14use ImovelZapAi\Signatures\Infrastructure\Persistence\Eloquent\SignerModel;
15
16final class RemindPendingSignersAction
17{
18    public function __construct(
19        private readonly SignatureAuditWriter $audit,
20        private readonly SendOutboundMessageAction $sendText,
21    ) {}
22
23    public function execute(?string $requestId = null): int
24    {
25        $query = SignatureReminderModel::query()
26            ->whereNull('sent_at')
27            ->where('scheduled_at', '<=', now())
28            ->where('status', 'pending')
29            ->with(['signer', 'request.deal.conversation', 'request.document.contact']);
30
31        if ($requestId !== null) {
32            $query->where('signature_request_id', $requestId);
33        }
34
35        $reminded = 0;
36
37        foreach ($query->cursor() as $reminder) {
38            $request = $reminder->request;
39            $signer = $reminder->signer;
40
41            if ($request === null || $signer === null || ! $request->isOpen()) {
42                $reminder->forceFill(['status' => 'cancelled'])->save();
43
44                continue;
45            }
46
47            if ($signer->status === SignerStatus::Signed) {
48                $reminder->forceFill(['status' => 'cancelled'])->save();
49
50                continue;
51            }
52
53            $signerUrls = (array) ($request->meta['signer_urls'] ?? []);
54            $url = $signerUrls[$signer->id] ?? null;
55            if ($url === null) {
56                continue;
57            }
58
59            $contact = $this->resolveContact($signer, $request);
60            if ($contact !== null) {
61                try {
62                    $body = "Lembrete: você ainda precisa assinar \"{$request->title}\".\n{$url}";
63                    $this->sendText->execute(
64                        $request->tenant_id,
65                        $contact,
66                        $body,
67                        $request->deal?->conversation,
68                    );
69                } catch (\Throwable) {
70                    // Canal indisponível; o link permanece válido e o lembrete é registrado.
71                }
72            }
73
74            $reminder->forceFill([
75                'sent_at' => now(),
76                'status' => 'sent',
77            ])->save();
78
79            $this->audit->write(
80                $request->tenant_id,
81                $request->id,
82                SignatureAuditEvent::Reminded,
83                actorType: 'system',
84                signerId: $signer->id,
85                payload: ['url' => $url],
86                message: 'Lembrete de assinatura enviado.',
87            );
88
89            $reminded++;
90        }
91
92        return $reminded;
93    }
94
95    private function resolveContact(SignerModel $signer, SignatureRequestModel $request): ?ContactModel
96    {
97        if ($signer->contact_id !== null) {
98            $signer->loadMissing('contact');
99
100            return $signer->contact;
101        }
102
103        return $request->document?->contact;
104    }
105}