Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
FundCashflowRequestRepository
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 7
1056
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
 create
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 findByUserId
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 findById
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 findByIdWithDetails
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 findByFilters
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
272
 updateStatus
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3declare(strict_types=1);
4
5namespace App\Domain\Funds\Repository;
6
7use App\Domain\Funds\Data\FundCashflowRequestData;
8use PDO;
9use RuntimeException;
10
11final readonly class FundCashflowRequestRepository
12{
13    public function __construct(
14        private PDO $pdo,
15    ) {}
16
17    public function create(
18        int $userId,
19        string $fundId,
20        string $requestType,
21        string $amount,
22        ?string $wireReference,
23        ?string $reason,
24        ?string $notes,
25    ): FundCashflowRequestData {
26        $sql = "
27            INSERT INTO fund_cashflow_requests (
28                id,
29                user_id,
30                fund_id,
31                request_type,
32                amount,
33                status,
34                wire_reference,
35                reason,
36                notes
37            ) VALUES (
38                gen_random_uuid()::uuid,
39                :user_id,
40                :fund_id,
41                :request_type,
42                :amount,
43                'pending',
44                :wire_reference,
45                :reason,
46                :notes
47            )
48            RETURNING
49                id::TEXT AS \"id\",
50                user_id AS \"userId\",
51                fund_id::TEXT AS \"fundId\",
52                request_type AS \"requestType\",
53                amount::TEXT AS \"amount\",
54                status AS \"status\",
55                wire_reference AS \"wireReference\",
56                reason AS \"reason\",
57                notes AS \"notes\",
58                TO_CHAR(created_at, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS \"createdAt\"
59        ";
60
61        $stmt = $this->pdo->prepare($sql);
62        if ($stmt === false) {
63            throw new RuntimeException('Failed to prepare create fund cashflow request statement');
64        }
65
66        $stmt->execute([
67            'user_id' => $userId,
68            'fund_id' => $fundId,
69            'request_type' => $requestType,
70            'amount' => $amount,
71            'wire_reference' => $wireReference,
72            'reason' => $reason,
73            'notes' => $notes,
74        ]);
75
76        $row = $stmt->fetch(PDO::FETCH_ASSOC);
77        if ($row === false) {
78            throw new RuntimeException('Failed to retrieve created fund cashflow request');
79        }
80
81        return FundCashflowRequestData::fromRow($row);
82    }
83
84    /**
85     * @return FundCashflowRequestData[]
86     */
87    public function findByUserId(int $userId): array
88    {
89        $sql = '
90            SELECT
91                id::TEXT AS "id",
92                user_id AS "userId",
93                fund_id::TEXT AS "fundId",
94                request_type AS "requestType",
95                amount::TEXT AS "amount",
96                status AS "status",
97                wire_reference AS "wireReference",
98                reason AS "reason",
99                notes AS "notes",
100                TO_CHAR(created_at, \'YYYY-MM-DD"T"HH24:MI:SS"Z"\') AS "createdAt"
101            FROM fund_cashflow_requests
102            WHERE user_id = :user_id
103            ORDER BY created_at DESC
104        ';
105
106        $stmt = $this->pdo->prepare($sql);
107        if ($stmt === false) {
108            throw new RuntimeException('Failed to prepare find fund cashflow requests by user statement');
109        }
110
111        $stmt->execute(['user_id' => $userId]);
112
113        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
114        if ($rows === false) {
115            return [];
116        }
117
118        return array_map(static fn(array $row): FundCashflowRequestData => FundCashflowRequestData::fromRow($row), $rows);
119    }
120
121    public function findById(string $id): ?FundCashflowRequestData
122    {
123        $sql = '
124            SELECT
125                id::TEXT AS "id",
126                user_id AS "userId",
127                fund_id::TEXT AS "fundId",
128                request_type AS "requestType",
129                amount::TEXT AS "amount",
130                status AS "status",
131                wire_reference AS "wireReference",
132                reason AS "reason",
133                notes AS "notes",
134                TO_CHAR(created_at, \'YYYY-MM-DD"T"HH24:MI:SS"Z"\') AS "createdAt"
135            FROM fund_cashflow_requests
136            WHERE id = :id
137        ';
138
139        $stmt = $this->pdo->prepare($sql);
140        if ($stmt === false) {
141            throw new RuntimeException('Failed to prepare find fund cashflow request by id statement');
142        }
143
144        $stmt->execute(['id' => $id]);
145        $row = $stmt->fetch(PDO::FETCH_ASSOC);
146
147        return $row === false ? null : FundCashflowRequestData::fromRow($row);
148    }
149
150    public function findByIdWithDetails(string $id): ?array
151    {
152        $sql = '
153            SELECT
154                r.id::TEXT AS "id",
155                r.user_id AS "userId",
156                r.fund_id::TEXT AS "fundId",
157                r.request_type AS "requestType",
158                r.amount::TEXT AS "amount",
159                r.status AS "status",
160                r.wire_reference AS "wireReference",
161                r.reason AS "reason",
162                r.notes AS "notes",
163                TO_CHAR(r.created_at, \'YYYY-MM-DD"T"HH24:MI:SS"Z"\') AS "createdAt",
164                i.email AS "investorEmail",
165                i.first_name AS "userFirstName",
166                i.last_name AS "userLastName",
167                f.name AS "fundName"
168            FROM fund_cashflow_requests r
169            JOIN users u ON u.user_id = r.user_id
170            JOIN investors i ON i.investor_id = u.investor_id
171            JOIN funds f ON f.id = r.fund_id
172            WHERE r.id = :id
173        ';
174
175        $stmt = $this->pdo->prepare($sql);
176        if ($stmt === false) {
177            throw new RuntimeException('Failed to prepare find fund cashflow request with details by id statement');
178        }
179
180        $stmt->execute(['id' => $id]);
181        $row = $stmt->fetch(PDO::FETCH_ASSOC);
182
183        return $row === false ? null : $row;
184    }
185
186    /**
187     * @return array{data: array<int, array<string, mixed>>, total: int}
188     */
189    public function findByFilters(
190        int $page,
191        int $limit,
192        ?string $status,
193        ?string $requestType,
194        ?string $search,
195        ?string $startDate,
196        ?string $endDate,
197    ): array {
198        $offset = ($page - 1) * $limit;
199        $conditions = [];
200        $params = [];
201
202        if ($status !== null && $status !== '' && $status !== 'all') {
203            $conditions[] = 'r.status = :status';
204            $params['status'] = $status;
205        }
206
207        if ($requestType !== null && $requestType !== '') {
208            $conditions[] = 'r.request_type = :request_type';
209            $params['request_type'] = $requestType;
210        }
211
212        if ($search !== null && $search !== '') {
213            $conditions[] = '(
214                u.email ILIKE :search OR
215                i.first_name ILIKE :search OR
216                i.last_name ILIKE :search OR
217                f.name ILIKE :search
218            )';
219            $params['search'] = '%' . $search . '%';
220        }
221
222        if ($startDate !== null && $startDate !== '') {
223            $conditions[] = 'r.created_at >= :start_date';
224            $params['start_date'] = $startDate . ' 00:00:00';
225        }
226
227        if ($endDate !== null && $endDate !== '') {
228            $conditions[] = 'r.created_at <= :end_date';
229            $params['end_date'] = $endDate . ' 23:59:59';
230        }
231
232        $whereClause = count($conditions) > 0 ? 'WHERE ' . implode(' AND ', $conditions) : '';
233
234        $countSql = "
235            SELECT COUNT(*)
236            FROM fund_cashflow_requests r
237            JOIN users u ON u.user_id = r.user_id
238            JOIN investors i ON i.investor_id = u.investor_id
239            JOIN funds f ON f.id = r.fund_id
240            {$whereClause}
241        ";
242
243        $countStmt = $this->pdo->prepare($countSql);
244        if ($countStmt === false) {
245            throw new RuntimeException('Failed to prepare fund cashflow request count statement');
246        }
247
248        $countStmt->execute($params);
249        $total = (int)$countStmt->fetchColumn();
250
251        $sql = "
252            SELECT
253                r.id::TEXT AS \"id\",
254                r.user_id AS \"userId\",
255                r.fund_id::TEXT AS \"fundId\",
256                r.request_type AS \"requestType\",
257                r.amount::TEXT AS \"amount\",
258                r.status AS \"status\",
259                r.wire_reference AS \"wireReference\",
260                r.reason AS \"reason\",
261                r.notes AS \"notes\",
262                TO_CHAR(r.created_at, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS \"createdAt\",
263                u.email AS \"userEmail\",
264                i.first_name AS \"userFirstName\",
265                i.last_name AS \"userLastName\",
266                f.name AS \"fundName\"
267            FROM fund_cashflow_requests r
268            JOIN users u ON u.user_id = r.user_id
269            JOIN investors i ON i.investor_id = u.investor_id
270            JOIN funds f ON f.id = r.fund_id
271            {$whereClause}
272            ORDER BY r.created_at DESC
273            LIMIT :limit OFFSET :offset
274        ";
275
276        $stmt = $this->pdo->prepare($sql);
277        if ($stmt === false) {
278            throw new RuntimeException('Failed to prepare fund cashflow request query');
279        }
280
281        foreach ($params as $key => $value) {
282            $stmt->bindValue($key, $value);
283        }
284
285        $stmt->bindValue('limit', $limit, PDO::PARAM_INT);
286        $stmt->bindValue('offset', $offset, PDO::PARAM_INT);
287        $stmt->execute();
288
289        return [
290            'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
291            'total' => $total,
292        ];
293    }
294
295    public function updateStatus(string $id, string $status, ?string $adminReason = null): FundCashflowRequestData
296    {
297        $sql = "
298            UPDATE fund_cashflow_requests
299            SET status = :status,
300                updated_at = CURRENT_TIMESTAMP,
301                reason = COALESCE(:reason, reason)
302            WHERE id = :id
303            RETURNING
304                id::TEXT AS \"id\",
305                user_id AS \"userId\",
306                fund_id::TEXT AS \"fundId\",
307                request_type AS \"requestType\",
308                amount::TEXT AS \"amount\",
309                status AS \"status\",
310                wire_reference AS \"wireReference\",
311                reason AS \"reason\",
312                notes AS \"notes\",
313                TO_CHAR(created_at, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS \"createdAt\"
314        ";
315
316        $stmt = $this->pdo->prepare($sql);
317        if ($stmt === false) {
318            throw new RuntimeException('Failed to prepare update fund cashflow request status statement');
319        }
320
321        $stmt->execute([
322            'status' => $status,
323            'reason' => $adminReason,
324            'id' => $id,
325        ]);
326
327        $row = $stmt->fetch(PDO::FETCH_ASSOC);
328        if ($row === false) {
329            throw new RuntimeException('Failed to update fund cashflow request status');
330        }
331
332        return FundCashflowRequestData::fromRow($row);
333    }
334}
335