BalanceCalculator::getFundsAmount()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 13
rs 10
1
<?php
2
3
namespace Siak\Tontine\Service\Payment;
4
5
use Illuminate\Database\Query\Builder;
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Facades\DB;
8
use Siak\Tontine\Model\Auction;
9
use Siak\Tontine\Model\Bill;
10
use Siak\Tontine\Model\Debt;
11
use Siak\Tontine\Model\Outflow;
12
use Siak\Tontine\Model\Fund;
13
use Siak\Tontine\Model\PartialRefund;
14
use Siak\Tontine\Model\Pool;
15
use Siak\Tontine\Model\Receivable;
16
use Siak\Tontine\Model\Session;
17
use Siak\Tontine\Service\Meeting\Saving\FundService;
18
19
class BalanceCalculator
20
{
21
    /**
22
     * @param FundService $fundService
23
     */
24
    public function __construct(private FundService $fundService)
25
    {}
26
27
    /**
28
     * @param Receivable $receivable
29
     *
30
     * @return int
31
     */
32
    public function getReceivableAmount(Receivable $receivable): int
33
    {
34
        if($receivable->subscription->pool->deposit_fixed)
0 ignored issues
show
Bug introduced by
The property deposit_fixed does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
35
        {
36
            return $receivable->subscription->pool->amount;
0 ignored issues
show
Bug introduced by
The property amount does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
37
        }
38
39
        return !$receivable->deposit ? 0 : $receivable->deposit->amount;
40
    }
41
42
    /**
43
     * @param bool $withPoolTable
44
     *
45
     * @return Builder
46
     */
47
    private function getDepositQuery(bool $withPoolTable): Builder
48
    {
49
        return DB::table('deposits')
50
            ->join('receivables', 'deposits.receivable_id', '=', 'receivables.id')
51
            ->join('subscriptions', 'receivables.subscription_id', '=', 'subscriptions.id')
52
            ->when($withPoolTable, function(Builder $query) {
53
                $query->join('pools', 'subscriptions.pool_id', '=', 'pools.id')
54
                    ->join(DB::raw('pool_defs as pd'), 'pools.def_id', '=', 'pd.id');
55
            });
56
    }
57
58
    /**
59
     * @return Builder
60
     */
61
    private function getRemitmentQuery(): Builder
62
    {
63
        return DB::table('remitments')
64
            ->join('payables', 'remitments.payable_id', '=', 'payables.id')
65
            ->join('subscriptions', 'payables.subscription_id', '=', 'subscriptions.id')
66
            ->join('pools', 'subscriptions.pool_id', '=', 'pools.id')
67
            ->join(DB::raw('v_pools as vp'), 'vp.pool_id', '=', 'pools.id')
68
            ->join(DB::raw('pool_defs as pd'), 'pools.def_id', '=', 'pd.id');
69
    }
70
71
    /**
72
     * @param Pool $pool
73
     * @param Session $session
74
     *
75
     * @return int
76
     */
77
    public function getPoolDepositAmount(Pool $pool, Session $session): int
78
    {
79
        $query = $this->getDepositQuery(false)
80
            ->where('receivables.session_id', $session->id)
81
            ->where('subscriptions.pool_id', $pool->id);
82
        return !$pool->deposit_fixed ? $query->sum('deposits.amount') :
0 ignored issues
show
Bug introduced by
The property deposit_fixed does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
83
            $pool->amount * $query->count();
0 ignored issues
show
Bug introduced by
The property amount does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
84
    }
85
86
    /**
87
     * @param Pool $pool
88
     * @param Session $session
89
     *
90
     * @return int
91
     */
92
    public function getPayableAmount(Pool $pool, Session $session): int
93
    {
94
        if(!$pool->deposit_fixed)
0 ignored issues
show
Bug introduced by
The property deposit_fixed does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
95
        {
96
            // Sum the amounts for all deposits
97
            return $this->getPoolDepositAmount($pool, $session);
98
        }
99
        return $pool->amount * $pool->sessions()->count();
0 ignored issues
show
Bug introduced by
The property amount does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
100
    }
101
102
    /**
103
     * @return string
104
     */
105
    private function getRemitmentAmountSqlValue(): string
106
    {
107
        return 'pd.amount * vp.sessions_count';
108
    }
109
110
    /**
111
     * @param Pool $pool
112
     * @param Session $session
113
     *
114
     * @return int
115
     */
116
    public function getPoolRemitmentAmount(Pool $pool, Session $session): int
117
    {
118
        if(!$pool->deposit_fixed)
0 ignored issues
show
Bug introduced by
The property deposit_fixed does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
119
        {
120
            // Sum the amounts for all deposits
121
            return $this->getPoolDepositAmount($pool, $session);
122
        }
123
124
        return $this->getRemitmentQuery()
125
            ->where('payables.session_id', $session->id)
126
            ->where('subscriptions.pool_id', $pool->id)
127
            ->sum(DB::raw($this->getRemitmentAmountSqlValue()));
128
    }
129
130
    /**
131
     * @param Collection $sessionIds
132
     * @param bool $lendable
133
     *
134
     * @return int
135
     */
136
    private function getDepositsAmount(Collection $sessionIds, bool $lendable)
137
    {
138
        return $this->getDepositQuery(true)
139
            ->whereIn('deposits.session_id', $sessionIds)
140
            ->when($lendable, function(Builder $query) {
141
                $query->where('pd.properties->deposit->lendable', true);
142
            })
143
            ->sum(DB::raw('deposits.amount + pd.amount'));
144
    }
145
146
    /**
147
     * @param Collection $sessionIds
148
     * @param bool $lendable
149
     *
150
     * @return int
151
     */
152
    private function getRemitmentsAmount(Collection $sessionIds, bool $lendable)
153
    {
154
        return
155
            // Remitment sum for pools with fixed deposits.
156
            // Each value is the pool amount multiply by the number od sessions.
157
            $this->getRemitmentQuery()
158
                ->whereIn('payables.session_id', $sessionIds)
159
                ->where('pd.properties->deposit->fixed', true)
160
                ->when($lendable, function(Builder $query) {
161
                    $query->where('pd.properties->deposit->lendable', true);
162
                })
163
                ->sum(DB::raw($this->getRemitmentAmountSqlValue()))
164
            // Remitment sum for pools with libre deposits.
165
            // Each value is the sum of deposits for the given pool.
166
            + $this->getDepositQuery(true)
167
                ->whereIn('deposits.session_id', $sessionIds)
168
                ->whereExists(function(Builder $query) {
169
                    $query->select(DB::raw(1))->from('remitments')
0 ignored issues
show
Bug introduced by
'remitments' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $table of Illuminate\Database\Query\Builder::from(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

169
                    $query->select(DB::raw(1))->from(/** @scrutinizer ignore-type */ 'remitments')
Loading history...
170
                        ->join(DB::raw('payables p'), 'remitments.payable_id', '=', 'p.id')
171
                        ->join(DB::raw('subscriptions s'), 'p.subscription_id', '=', 's.id')
172
                        ->whereColumn('p.session_id', 'deposits.session_id')
173
                        ->whereColumn('s.pool_id', 'pools.id');
174
                })
175
                ->where('pd.properties->deposit->fixed', false)
176
                ->when($lendable, function(Builder $query) {
177
                    $query->where('pd.properties->deposit->lendable', true);
178
                })
179
                ->sum('deposits.amount');
180
    }
181
182
    /**
183
     * @param Collection $sessionIds
184
     *
185
     * @return int
186
     */
187
    private function getAuctionsAmount(Collection $sessionIds)
188
    {
189
        return Auction::paid()->whereIn('session_id', $sessionIds)->sum('amount');
0 ignored issues
show
Bug Best Practice introduced by
The expression return Siak\Tontine\Mode...sionIds)->sum('amount') also could return the type Illuminate\Database\Eloq...\Database\Query\Builder which is incompatible with the documented return type integer.
Loading history...
190
    }
191
192
    /**
193
     * @param Collection $sessionIds
194
     * @param bool $lendable
195
     *
196
     * @return int
197
     */
198
    private function getSettlementsAmount(Collection $sessionIds, bool $lendable)
199
    {
200
        return Bill::whereHas('settlement', fn($qs) => $qs->whereIn('session_id', $sessionIds))
0 ignored issues
show
Bug Best Practice introduced by
The expression return Siak\Tontine\Mode..... */ })->sum('amount') also could return the type Illuminate\Database\Eloq...gHasThroughRelationship which is incompatible with the documented return type integer.
Loading history...
201
            ->when($lendable, fn($qb) => $qb->lendable(true))
202
            ->sum('amount');
203
    }
204
205
    /**
206
     * @param Collection $sessionIds
207
     * @param bool $lendable
208
     *
209
     * @return int
210
     */
211
    private function getOutflowsAmount(Collection $sessionIds, bool $lendable)
212
    {
213
        return Outflow::whereIn('session_id', $sessionIds)
0 ignored issues
show
Bug Best Practice introduced by
The expression return Siak\Tontine\Mode..... */ })->sum('amount') also could return the type Illuminate\Database\Eloq...gHasThroughRelationship which is incompatible with the documented return type integer.
Loading history...
214
            ->when($lendable, function($query) {
215
                $query->where(function($query) {
216
                    $query->whereDoesntHave('charge')
217
                        ->orWhereHas('charge', fn($qb) => $qb->lendable(true));
218
                });
219
            })
220
            ->sum('amount');
221
    }
222
223
    /**
224
     * @param Collection $sessionIds
225
     * @param Fund $fund
226
     *
227
     * @return int
228
     */
229
    public function getSavingsAmount(Collection $sessionIds, Fund $fund)
230
    {
231
        return $fund->savings()
232
            ->whereIn('session_id', $sessionIds)
233
            ->sum('amount');
234
    }
235
236
    /**
237
     * @param Collection $sessionIds
238
     * @param Fund $fund
239
     *
240
     * @return int
241
     */
242
    public function getRefundsAmount(Collection $sessionIds, Fund $fund)
243
    {
244
        return Debt::interest()
245
            ->whereHas('refund', fn($query) => $query->whereIn('session_id', $sessionIds))
246
            ->whereHas('loan', fn($query) => $query->where('fund_id', $fund->id))
247
            ->sum('amount');
248
    }
249
250
    /**
251
     * @param Collection $sessionIds
252
     * @param Fund $fund
253
     *
254
     * @return int
255
     */
256
    public function getPartialRefundsAmount(Collection $sessionIds, Fund $fund)
257
    {
258
        // Filter on debts that are not yet refunded.
259
        return PartialRefund::whereIn('session_id', $sessionIds)
260
            ->whereHas('debt', function($query) use($fund) {
261
                $query->interest()
262
                    ->whereDoesntHave('refund')
263
                    ->whereHas('loan', fn($query) => $query->where('fund_id', $fund->id));
264
            })
265
            ->sum('amount');
266
    }
267
268
    /**
269
     * @param Collection $sessionIds
270
     * @param Fund $fund
271
     *
272
     * @return int
273
     */
274
    private function getLoansAmount(Collection $sessionIds, Fund $fund)
275
    {
276
        return Debt::principal()
277
            ->whereHas('loan', fn($ql) => $ql->where('fund_id', $fund->id)
278
                ->whereIn('session_id', $sessionIds))
279
            ->sum('amount');
280
    }
281
282
    /**
283
     * @param Session $session    The session
284
     *
285
     * @return int
286
     */
287
    private function getFundsAmount(Session $session)
288
    {
289
        // Each fund can have a different set of sessions, so we need to loop on all funds.
290
        return $this->fundService->getSessionFunds($session)
291
            ->reduce(function(int $amount, Fund $fund) use($session) {
292
                $sessionIds = $this->fundService->getFundSessionIds($fund, $session);
293
294
                return $amount
295
                    + $this->getSavingsAmount($sessionIds, $fund)
296
                    + $this->getRefundsAmount($sessionIds, $fund)
297
                    + $this->getPartialRefundsAmount($sessionIds, $fund)
298
                    - $this->getLoansAmount($sessionIds, $fund);
299
            }, 0);
0 ignored issues
show
Bug introduced by
0 of type integer is incompatible with the type Illuminate\Support\Traits\TReduceInitial expected by parameter $initial of Illuminate\Support\Collection::reduce(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
            }, /** @scrutinizer ignore-type */ 0);
Loading history...
300
    }
301
302
    /**
303
     * Get the amount available for loan.
304
     *
305
     * @param Session $session    The session
306
     *
307
     * @return int
308
     */
309
    public function getBalanceForLoan(Session $session): int
310
    {
311
        // Get the ids of all the sessions until the current one.
312
        $sessionIds = Session::precedes($session)->pluck('id');
313
314
        return $this->getAuctionsAmount($sessionIds)
315
            + $this->getSettlementsAmount($sessionIds, true)
316
            + $this->getDepositsAmount($sessionIds, true)
317
            - $this->getRemitmentsAmount($sessionIds, true)
318
            - $this->getOutflowsAmount($sessionIds, true)
319
            + $this->getFundsAmount($session);
320
    }
321
322
    /**
323
     * Get the amount available for outflow.
324
     *
325
     * @param Session $session    The session
326
     *
327
     * @return int
328
     */
329
    public function getTotalBalance(Session $session): int
330
    {
331
        // Get the ids of all the sessions until the current one.
332
        $sessionIds = Session::precedes($session)->pluck('id');
333
334
        return $this->getAuctionsAmount($sessionIds)
335
            + $this->getSettlementsAmount($sessionIds, false)
336
            + $this->getDepositsAmount($sessionIds, false)
337
            - $this->getRemitmentsAmount($sessionIds, false)
338
            - $this->getOutflowsAmount($sessionIds, false)
339
            + $this->getFundsAmount($session);
340
    }
341
342
    /**
343
     * Get the detailed amounts.
344
     *
345
     * @param Session $session    The session
346
     * @param bool $lendable
347
     *
348
     * @return array<int>
349
     */
350
    public function getBalances(Session $session, bool $lendable): array
351
    {
352
        $fundAmounts = $this->fundService->getSessionFunds($session)
353
            ->reduce(function(array $amounts, Fund $fund) use($session) {
354
                $sessionIds = $this->fundService->getFundSessionIds($fund, $session);
355
356
                return [
357
                    'savings' => $amounts['savings'] + $this->getSavingsAmount($sessionIds, $fund),
358
                    'loans' => $amounts['loans'] + $this->getLoansAmount($sessionIds, $fund),
359
                    'refunds' => $amounts['refunds'] + $this->getRefundsAmount($sessionIds, $fund) +
360
                        $this->getPartialRefundsAmount($sessionIds, $fund),
361
                ];
362
            }, ['savings' => 0, 'loans' => 0, 'refunds' => 0]);
0 ignored issues
show
Bug introduced by
array('savings' => 0, 'l...' => 0, 'refunds' => 0) of type array<string,integer> is incompatible with the type Illuminate\Support\Traits\TReduceInitial expected by parameter $initial of Illuminate\Support\Collection::reduce(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

362
            }, /** @scrutinizer ignore-type */ ['savings' => 0, 'loans' => 0, 'refunds' => 0]);
Loading history...
363
364
        // Get the ids of all the sessions until the current one.
365
        $sessionIds = Session::precedes($session)->pluck('id');
366
        return [
367
            'auctions' => $this->getAuctionsAmount($sessionIds),
368
            'charges' => $this->getSettlementsAmount($sessionIds, $lendable),
369
            'deposits' => $this->getDepositsAmount($sessionIds, $lendable),
370
            'remitments' => $this->getRemitmentsAmount($sessionIds, $lendable),
371
            'outflows' => $this->getOutflowsAmount($sessionIds, $lendable),
372
            ...$fundAmounts,
373
        ];
374
    }
375
}
376