Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
23 / 23 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
| InvestorAgreementService | |
100.00% |
23 / 23 |
|
100.00% |
2 / 2 |
7 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| listForInvestor | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
6 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace App\Domain\Document\Service; |
| 6 | |
| 7 | use App\Domain\Document\Data\PandaDocDocumentData; |
| 8 | use App\Domain\Document\Repository\InvestorAgreementRepository; |
| 9 | use RuntimeException; |
| 10 | |
| 11 | /** |
| 12 | * Reads an investor's outbound PandaDoc agreements and refreshes their live |
| 13 | * status from PandaDoc on every read, syncing changes back to the local |
| 14 | * investor_documents mirror. |
| 15 | * |
| 16 | * Used by both the investor-facing list (caller's own agreements) and the |
| 17 | * admin progress view (a specific investor's agreements), so the refresh + |
| 18 | * KYC-bridge logic lives in one place. |
| 19 | */ |
| 20 | final readonly class InvestorAgreementService |
| 21 | { |
| 22 | public function __construct( |
| 23 | private PandaDocClientInterface $pandaDocClient, |
| 24 | private IdentityVerificationService $identityVerification, |
| 25 | private InvestorAgreementRepository $repository, |
| 26 | ) {} |
| 27 | |
| 28 | /** |
| 29 | * @param int $investorId |
| 30 | * @return list<array<string, mixed>> |
| 31 | */ |
| 32 | public function listForInvestor(int $investorId): array |
| 33 | { |
| 34 | $storedDocs = $this->repository->findByInvestorId($investorId); |
| 35 | |
| 36 | $documents = []; |
| 37 | foreach ($storedDocs as $storedDoc) { |
| 38 | try { |
| 39 | $details = $this->pandaDocClient->getDocumentDetails($storedDoc['pandadocId']); |
| 40 | $documents[] = $details->toArray(); |
| 41 | |
| 42 | // Sync status back to the local mirror if it changed. |
| 43 | // Both sides use the full PandaDoc form ('document.sent', etc.). |
| 44 | if ($details->status !== '' && $details->status !== $storedDoc['status']) { |
| 45 | $this->repository->updateStatus($storedDoc['pandadocId'], $details->status); |
| 46 | |
| 47 | // A document reaching completion behind an ID Check is proof |
| 48 | // the recipient passed identity verification — surface them |
| 49 | // for admin KYC review. No-op for non-ID-verification docs. |
| 50 | if ($details->status === 'document.completed') { |
| 51 | $this->identityVerification->markIdentityVerifiedIfApplicable( |
| 52 | $investorId, |
| 53 | $storedDoc['pandadocId'], |
| 54 | ); |
| 55 | } |
| 56 | } |
| 57 | } catch (RuntimeException) { |
| 58 | // If PandaDoc fetch fails, return what we have locally. |
| 59 | // Stored status is already in the full form the frontend expects. |
| 60 | $fallback = PandaDocDocumentData::fromStoredFallback( |
| 61 | pandadocId: $storedDoc['pandadocId'], |
| 62 | documentName: $storedDoc['documentName'], |
| 63 | status: $storedDoc['status'], |
| 64 | createdAt: $storedDoc['createdAt'], |
| 65 | updatedAt: $storedDoc['updatedAt'], |
| 66 | ); |
| 67 | $documents[] = $fallback->toArray(); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | return $documents; |
| 72 | } |
| 73 | } |