Passed
Push — main ( 936852...10149e )
by Thierry
15:09
created

BalanceCalculator::getRemitmentAmountSqlValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace Siak\Tontine\Service;
4
5
use Illuminate\Database\Query\Builder;
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Facades\DB;
8
use Illuminate\Support\Facades\Log;
9
use Siak\Tontine\Model\Debt;
10
use Siak\Tontine\Model\Payable;
11
use Siak\Tontine\Model\Receivable;
12
use Siak\Tontine\Model\Session;
13
use Siak\Tontine\Service\TenantService;
14
use Siak\Tontine\Model\Pool;
15
use Siak\Tontine\Service\Meeting\Saving\ProfitService;
16
use Siak\Tontine\Service\Meeting\SessionService;
17
use Siak\Tontine\Service\Planning\PoolService;
18
19
class BalanceCalculator
20
{
21
    /**
22
     * @param TenantService $tenantService
23
     * @param PoolService $poolService
24
     * @param ProfitService $profitService
25
     * @param SessionService $sessionService
26
     */
27
    public function __construct(protected TenantService $tenantService,
28
        protected PoolService $poolService, protected ProfitService $profitService,
29
        protected SessionService $sessionService)
30
    {}
31
32
    /**
33
     * @param Receivable $receivable
34
     *
35
     * @return int
36
     */
37
    public function getReceivableAmount(Receivable $receivable): int
38
    {
39
        if($receivable->subscription->pool->deposit_fixed)
40
        {
41
            return $receivable->subscription->pool->amount;
42
        }
43
44
        return !$receivable->deposit ? 0 : $receivable->deposit->amount;
45
    }
46
47
    /**
48
     * @param bool $withPoolTable
49
     *
50
     * @return Builder
51
     */
52
    private function getDepositQuery(bool $withPoolTable): Builder
53
    {
54
        return DB::table('deposits')
55
            ->join('receivables', 'deposits.receivable_id', '=', 'receivables.id')
56
            ->join('subscriptions', 'receivables.subscription_id', '=', 'subscriptions.id')
57
            ->when($withPoolTable, function(Builder $query) {
58
                $query->join('pools', 'subscriptions.pool_id', '=', 'pools.id');
59
            });
60
    }
61
62
    /**
63
     * @param bool $withPoolTable
64
     *
65
     * @return Builder
66
     */
67
    private function getRemitmentQuery(bool $withPoolTable): Builder
68
    {
69
        return DB::table('remitments')
70
            ->join('payables', 'remitments.payable_id', '=', 'payables.id')
71
            ->join('subscriptions', 'payables.subscription_id', '=', 'subscriptions.id')
72
            ->when($withPoolTable, function(Builder $query) {
73
                $query->join('pools', 'subscriptions.pool_id', '=', 'pools.id')
74
                    ->join('v_pool_counters', 'v_pool_counters.id', '=', 'pools.id');
75
            });
76
    }
77
78
    /**
79
     * @param Pool $pool
80
     * @param Session $session
81
     *
82
     * @return int
83
     */
84
    public function getPoolDepositAmount(Pool $pool, Session $session): int
85
    {
86
        $query = $this->getDepositQuery(false)
87
            ->where('receivables.session_id', $session->id)
88
            ->where('subscriptions.pool_id', $pool->id);
89
90
        return !$pool->deposit_fixed ? $query->sum('deposits.amount') :
91
            $pool->amount * $query->count();
92
    }
93
94
    /**
95
     * @param Payable $payable
96
     * @param Session $session
97
     *
98
     * @return int
99
     */
100
    public function getPayableAmount(Payable $payable, Session $session): int
101
    {
102
        $pool = $payable->subscription->pool;
103
        if($pool->deposit_fixed)
104
        {
105
            return $pool->amount * $this->poolService->getEnabledSessionCount($pool);
106
        }
107
        // Sum the amounts for all deposits
108
        return $this->getPoolDepositAmount($pool, $session);
109
    }
110
111
    /**
112
     * @return string
113
     */
114
    private function getRemitmentAmountSqlValue(): string
115
    {
116
        return 'v_pool_counters.amount * (v_pool_counters.sessions - v_pool_counters.disabled_sessions)';
117
    }
118
119
    /**
120
     * @param Pool $pool
121
     * @param Session $session
122
     *
123
     * @return int
124
     */
125
    public function getPoolRemitmentAmount(Pool $pool, Session $session): int
126
    {
127
        if(!$pool->deposit_fixed)
128
        {
129
            // Sum the amounts for all deposits
130
            return $this->getPoolDepositAmount($pool, $session);
131
        }
132
133
        return $this->getRemitmentQuery(true)
134
            ->where('payables.session_id', $session->id)
135
            ->where('subscriptions.pool_id', $pool->id)
136
            ->sum(DB::raw($this->getRemitmentAmountSqlValue()));
137
    }
138
139
    /**
140
     * @param Collection $sessionIds
141
     * @param bool $lendable
142
     *
143
     * @return int
144
     */
145
    private function getDepositsAmount(Collection $sessionIds, bool $lendable)
146
    {
147
        return $this->getDepositQuery(true)
148
            ->whereIn('deposits.session_id', $sessionIds)
149
            ->when($lendable, function(Builder $query) {
150
                $query->where('pools.properties->deposit->lendable', true);
151
            })
152
            ->sum(DB::raw('deposits.amount + pools.amount'));
153
    }
154
155
    /**
156
     * @param Collection $sessionIds
157
     * @param bool $lendable
158
     *
159
     * @return int
160
     */
161
    private function getRemitmentsAmount(Collection $sessionIds, bool $lendable)
162
    {
163
        return
164
            // Remitment sum for pools with fixed deposits.
165
            // Each value is the pool amount multiply by the number od sessions.
166
            $this->getRemitmentQuery(true)
167
                ->whereIn('payables.session_id', $sessionIds)
168
                ->where('pools.properties->deposit->fixed', true)
169
                ->when($lendable, function(Builder $query) {
170
                    $query->where('pools.properties->deposit->lendable', true);
171
                })
172
                ->sum(DB::raw($this->getRemitmentAmountSqlValue()))
173
            // Remitment sum for pools with libre deposits.
174
            // Each value is the sum of deposits for the given pool.
175
            + $this->getDepositQuery(true)
176
                ->whereIn('deposits.session_id', $sessionIds)
177
                ->whereExists(function(Builder $query) {
178
                    $query->select(DB::raw(1))->from('remitments')
179
                        ->join(DB::raw('payables p'), 'remitments.payable_id', '=', 'p.id')
180
                        ->join(DB::raw('subscriptions s'), 'p.subscription_id', '=', 's.id')
181
                        ->whereColumn('p.session_id', 'deposits.session_id')
182
                        ->whereColumn('s.pool_id', 'pools.id');
183
                })
184
                ->where('pools.properties->deposit->fixed', false)
185
                ->when($lendable, function(Builder $query) {
186
                    $query->where('pools.properties->deposit->lendable', true);
187
                })
188
                ->sum('deposits.amount');
189
    }
190
191
    /**
192
     * @param Collection $sessionIds
193
     *
194
     * @return int
195
     */
196
    private function getAuctionsAmount(Collection $sessionIds)
197
    {
198
        return DB::table('auctions')
199
            ->whereIn('session_id', $sessionIds)
200
            ->where('paid', true)
201
            ->sum('amount');
202
    }
203
204
    /**
205
     * @param Collection $sessionIds
206
     * @param bool $lendable
207
     *
208
     * @return int
209
     */
210
    private function getSettlementsAmount(Collection $sessionIds, bool $lendable)
211
    {
212
        return DB::table('settlements')
213
            ->join('bills', 'settlements.bill_id', '=', 'bills.id')
214
            ->whereIn('settlements.session_id', $sessionIds)
215
            ->when($lendable, function(Builder $query) {
216
                $query->where('bills.lendable', true);
217
            })
218
            ->sum('bills.amount');
219
    }
220
221
    /**
222
     * @param Collection $sessionIds
223
     * @param bool $lendable
224
     *
225
     * @return int
226
     */
227
    private function getDisbursementsAmount(Collection $sessionIds, bool $lendable)
228
    {
229
        return DB::table('disbursements')
230
            ->whereIn('session_id', $sessionIds)
231
            ->when($lendable, function(Builder $query) {
232
                $query->whereNull('charge_id')
233
                    ->orWhereExists(function(Builder $query) {
234
                        $query->select(DB::raw(1))->from('charges')
235
                            ->whereColumn('charges.id', 'disbursements.charge_id')
236
                            ->where('lendable', true);
237
                    });
238
            })
239
            ->sum('amount');
240
    }
241
242
    /**
243
     * @param Collection $sessionIds
244
     * @param int $fundId
245
     *
246
     * @return int
247
     */
248
    private function getSavingsAmount(Collection $sessionIds, int $fundId)
249
    {
250
        return DB::table('savings')
251
            ->when($fundId === 0, fn(Builder $query) => $query->whereNull('savings.fund_id'))
252
            ->when($fundId !== 0, fn(Builder $query) => $query->where('savings.fund_id', $fundId))
253
            ->whereIn('savings.session_id', $sessionIds)
254
            ->sum('savings.amount');
255
    }
256
257
    /**
258
     * @param Collection $sessionIds
259
     * @param int $fundId
260
     *
261
     * @return int
262
     */
263
    private function getRefundsAmount(Collection $sessionIds, int $fundId)
264
    {
265
        return DB::table('refunds')
266
            ->join('debts', 'refunds.debt_id', '=', 'debts.id')
267
            ->join('loans', 'debts.loan_id', '=', 'loans.id')
268
            ->whereIn('refunds.session_id', $sessionIds)
269
            ->when($fundId === 0, fn(Builder $query) => $query->whereNull('loans.fund_id'))
270
            ->when($fundId !== 0, fn(Builder $query) => $query->where('loans.fund_id', $fundId))
271
            ->sum('debts.amount');
272
    }
273
274
    /**
275
     * @param Collection $sessionIds
276
     * @param int $fundId
277
     *
278
     * @return int
279
     */
280
    private function getPartialRefundsAmount(Collection $sessionIds, int $fundId)
281
    {
282
        // Filter on debts that are not yet refunded.
283
        return DB::table('partial_refunds')
284
            ->join('debts', 'partial_refunds.debt_id', '=', 'debts.id')
285
            ->join('loans', 'debts.loan_id', '=', 'loans.id')
286
            ->whereIn('partial_refunds.session_id', $sessionIds)
287
            ->whereNotExists(function (Builder $query) {
288
                $query->select(DB::raw(1))->from('refunds')
289
                    ->whereColumn('refunds.debt_id', 'debts.id');
290
            })
291
            ->when($fundId === 0, fn(Builder $query) => $query->whereNull('loans.fund_id'))
292
            ->when($fundId !== 0, fn(Builder $query) => $query->where('loans.fund_id', $fundId))
293
            ->sum('partial_refunds.amount');
294
    }
295
296
    /**
297
     * @param Collection $sessionIds
298
     * @param int $fundId
299
     *
300
     * @return int
301
     */
302
    private function getLoansAmount(Collection $sessionIds, int $fundId)
303
    {
304
        return DB::table('debts')
305
            ->where('type', Debt::TYPE_PRINCIPAL)
306
            ->join('loans', 'debts.loan_id', '=', 'loans.id')
307
            ->whereIn('loans.session_id', $sessionIds)
308
            ->when($fundId === 0, fn(Builder $query) => $query->whereNull('loans.fund_id'))
309
            ->when($fundId !== 0, fn(Builder $query) => $query->where('loans.fund_id', $fundId))
310
            ->sum('debts.amount');
311
    }
312
313
    /**
314
     * @param Session $session    The session
315
     *
316
     * @return int
317
     */
318
    private function getFundsAmount(Session $session)
319
    {
320
        // Each fund can have a different set of sessions, so we need to loop on all funds.
321
        return $this->tenantService->tontine()->funds()->active()->pluck('id')
322
            ->prepend(0)
323
            ->reduce(function($amount, $fundId) use($session) {
324
                $sessionIds = $this->profitService->getFundSessions($session, $fundId)
325
                    ->pluck('id');
326
327
                return $amount
328
                    + $this->getSavingsAmount($sessionIds, $fundId)
329
                    + $this->getRefundsAmount($sessionIds, $fundId)
330
                    + $this->getPartialRefundsAmount($sessionIds, $fundId)
331
                    - $this->getLoansAmount($sessionIds, $fundId);
332
            }, 0);
333
    }
334
335
    /**
336
     * Get the amount available for loan.
337
     *
338
     * @param Session $session    The session
339
     *
340
     * @return int
341
     */
342
    public function getBalanceForLoan(Session $session): int
343
    {
344
        // Get the ids of all the sessions until the current one.
345
        $sessionIds = $this->sessionService->getRoundSessionIds($session);
346
347
        return $this->getAuctionsAmount($sessionIds)
348
            + $this->getSettlementsAmount($sessionIds, true)
349
            + $this->getDepositsAmount($sessionIds, true)
350
            - $this->getRemitmentsAmount($sessionIds, true)
351
            - $this->getDisbursementsAmount($sessionIds, true)
352
            + $this->getFundsAmount($session);
353
    }
354
355
    /**
356
     * Get the amount available for disbursement.
357
     *
358
     * @param Session $session    The session
359
     *
360
     * @return int
361
     */
362
    public function getTotalBalance(Session $session): int
363
    {
364
        // Get the ids of all the sessions until the current one.
365
        $sessionIds = $this->sessionService->getRoundSessionIds($session);
366
367
        return $this->getAuctionsAmount($sessionIds)
368
            + $this->getSettlementsAmount($sessionIds, false)
369
            + $this->getDepositsAmount($sessionIds, false)
370
            - $this->getRemitmentsAmount($sessionIds, false)
371
            - $this->getDisbursementsAmount($sessionIds, false)
372
            + $this->getFundsAmount($session);
373
    }
374
375
    /**
376
     * Get the detailed amounts.
377
     *
378
     * @param Session $session    The session
379
     * @param bool $lendable
380
     *
381
     * @return array<int>
382
     */
383
    public function getBalances(Session $session, bool $lendable): array
384
    {
385
        $fundAmounts = $this->tenantService->tontine()->funds()->active()->pluck('id')
386
            ->prepend(0)
387
            ->reduce(function($amounts, $fundId) use($session) {
388
                $sessionIds = $this->profitService->getFundSessions($session, $fundId)->pluck('id');
389
390
                return [
391
                    'savings' => $amounts['savings'] + $this->getSavingsAmount($sessionIds, $fundId),
392
                    'loans' => $amounts['loans'] + $this->getLoansAmount($sessionIds, $fundId),
393
                    'refunds' => $amounts['refunds'] + $this->getRefundsAmount($sessionIds, $fundId) +
394
                        $this->getPartialRefundsAmount($sessionIds, $fundId),
395
                ];
396
            }, ['savings' => 0, 'loans' => 0, 'refunds' => 0]);
397
398
        // Get the ids of all the sessions until the current one.
399
        $sessionIds = $this->sessionService->getRoundSessionIds($session);
400
        return [
401
            'auctions' => $this->getAuctionsAmount($sessionIds),
402
            'settlements' => $this->getSettlementsAmount($sessionIds, $lendable),
403
            'deposits' => $this->getDepositsAmount($sessionIds, $lendable),
404
            'remitments' => $this->getRemitmentsAmount($sessionIds, $lendable),
405
            'disbursements' => $this->getDisbursementsAmount($sessionIds, $lendable),
406
            ...$fundAmounts,
407
        ];
408
    }
409
}
410