Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
FundCashflowRequestService
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 5
240
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 createAddFundsRequest
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 createWithdrawalRequest
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
42
 getRequestsForUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 generateWireReference
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace App\Domain\Funds\Service;
6
7use App\Domain\Exception\NotFoundException;
8use App\Domain\Funds\Data\FundCashflowRequestData;
9use App\Domain\Funds\Repository\FundCashflowRequestRepository;
10use App\Domain\Funds\Repository\FundsRepository;
11use DomainException;
12use InvalidArgumentException;
13
14final readonly class FundCashflowRequestService
15{
16    public function __construct(
17        private FundsRepository $fundsRepository,
18        private FundCashflowRequestRepository $requestRepository,
19    ) {}
20
21    public function createAddFundsRequest(
22        int $userId,
23        string $fundId,
24        string $amount,
25        ?string $notes,
26    ): FundCashflowRequestData {
27        $fund = $this->fundsRepository->findPublishedFundById($fundId);
28        if ($fund === null) {
29            throw new NotFoundException('Fund not found');
30        }
31
32        $amountFloat = (float)$amount;
33        if ($amountFloat <= 0) {
34            throw new InvalidArgumentException('Amount must be greater than zero');
35        }
36
37        $minimum = $fund->minInvestment !== null ? (float)$fund->minInvestment : 0.0;
38        if ($minimum > 0 && $amountFloat < $minimum) {
39            throw new DomainException('Amount is below minimum investment for this fund');
40        }
41
42        $wireReference = $this->generateWireReference($fundId, $userId);
43
44        return $this->requestRepository->create(
45            userId: $userId,
46            fundId: $fundId,
47            requestType: 'add_funds',
48            amount: number_format($amountFloat, 2, '.', ''),
49            wireReference: $wireReference,
50            reason: null,
51            notes: $notes,
52        );
53    }
54
55    public function createWithdrawalRequest(
56        int $userId,
57        string $fundId,
58        string $amount,
59        ?string $reason,
60        ?string $notes,
61    ): FundCashflowRequestData {
62        $fund = $this->fundsRepository->findPublishedFundById($fundId);
63        if ($fund === null) {
64            throw new NotFoundException('Fund not found');
65        }
66
67        $amountFloat = (float)$amount;
68        if ($amountFloat <= 0) {
69            throw new InvalidArgumentException('Amount must be greater than zero');
70        }
71
72        $allocations = $this->fundsRepository->findUserFundAllocations([$fundId], (string)$userId);
73        $current = isset($allocations[$fundId]['currentValue'])
74            ? (float)$allocations[$fundId]['currentValue']
75            : 0.0;
76        if ($current <= 0) {
77            throw new DomainException('No available balance in selected fund');
78        }
79        if ($amountFloat > $current) {
80            throw new DomainException('Amount exceeds available balance');
81        }
82
83        return $this->requestRepository->create(
84            userId: $userId,
85            fundId: $fundId,
86            requestType: 'withdrawal',
87            amount: number_format($amountFloat, 2, '.', ''),
88            wireReference: null,
89            reason: $reason,
90            notes: $notes,
91        );
92    }
93
94    /**
95     * @return array<int, \App\Domain\Funds\Data\FundCashflowRequestData>
96     */
97    public function getRequestsForUser(int $userId): array
98    {
99        return $this->requestRepository->findByUserId($userId);
100    }
101
102    private function generateWireReference(string $fundId, int $userId): string
103    {
104        $fundPart = strtoupper(substr(str_replace('-', '', $fundId), 0, 6));
105        $userPart = str_pad((string)$userId, 4, '0', STR_PAD_LEFT);
106        $timePart = gmdate('ymdHis');
107
108        return 'WIRE-' . $fundPart . '-' . $userPart . '-' . $timePart;
109    }
110}
111