Passed
Push — main ( 34d123...876c9b )
by Thierry
05:11
created

DebtCalculator::getDebtTotalPaidAmount()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Siak\Tontine\Service\Meeting\Credit;
4
5
use Closure;
6
use Illuminate\Support\Collection;
7
use Siak\Tontine\Model\Debt;
8
use Siak\Tontine\Model\PartialRefund;
9
use Siak\Tontine\Model\Refund;
10
use Siak\Tontine\Model\Session;
11
12
use function pow;
13
14
class DebtCalculator
15
{
16
    /**
17
     * Count the sessions.
18
     *
19
     * @param Debt $debt
20
     * @param Session $fromSession The session to start from
21
     * @param Session $toSession The session to end to
22
     *
23
     * @return int
24
     */
25
    private function getSessionCount(Debt $debt, Session $fromSession, Session $toSession): int
26
    {
27
        return $debt->loan->fund->sessions
28
            ->filter(fn($session) => $session->day_date <= $toSession->day_date &&
29
                $session->day_date > $fromSession->day_date)
30
            ->count();
31
    }
32
33
    /**
34
     * Get the last session for interest calculation
35
     *
36
     * @param Debt $debt
37
     * @param Session $currentSession
38
     *
39
     * @return Session
40
     */
41
    private function getLastSession(Debt $debt, Session $currentSession): Session
42
    {
43
        return $debt->refund &&
44
            $debt->refund->session->day_date < $currentSession->day_date ?
45
            $debt->refund->session : $currentSession;
46
    }
47
48
    /**
49
     * @param Session $current
50
     * @param bool $withCurrent Also take the refunds in the current session.
51
     *
52
     * @return Closure
53
     */
54
    private function getRefundFilter(Session $current, bool $withCurrent): Closure
55
    {
56
        return !$withCurrent ?
57
            fn(PartialRefund|Refund $refund) => $refund->session->day_date < $current->day_date :
58
            fn(PartialRefund|Refund $refund) => $refund->session->day_date <= $current->day_date;
59
    }
60
61
    /**
62
     * @param Debt $debt
63
     * @param Session $current
64
     * @param bool $withCurrent Also take the refunds in the current session.
65
     *
66
     * @return Collection
67
     */
68
    private function getPartialRefunds(Debt $debt, Session $current, bool $withCurrent): Collection
69
    {
70
        return $debt->partial_refunds->filter($this->getRefundFilter($current, $withCurrent));
71
    }
72
73
    /**
74
     * @param Debt $debt
75
     * @param Session $current
76
     *
77
     * @return Session
78
     */
79
    private function getLastSessionForInterest(Debt $debt, Session $current): Session
80
    {
81
        $endSession = $debt->loan->fund->interest;
82
        return $current->day_date < $endSession->day_date ? $current : $endSession;
83
    }
84
85
    /**
86
     * Get the simple interest amount.
87
     *
88
     * @param Debt $debt
89
     * @param Session $session
90
     *
91
     * @return int
92
     */
93
    private function getSimpleInterestAmount(Debt $debt, Session $session): int
94
    {
95
        $principalDebt = $debt->loan->principal_debt;
96
        $loanAmount = $principalDebt->amount;
97
        // The interest rate is multiplied by 100 in the database.
98
        $interestRate = $debt->loan->interest_rate / 10000;
99
        $interestAmount = 0;
100
101
        $startSession = $debt->loan->session;
102
        $endSession = $this->getLastSessionForInterest($debt, $session);
103
104
        // Take refunds before the end session and sort by session date.
105
        $partialRefunds = $this->getPartialRefunds($principalDebt, $endSession, false)
106
            ->sortBy('session.day_date');
107
        foreach($partialRefunds as $refund)
108
        {
109
            $sessionCount = $this->getSessionCount($debt, $startSession, $refund->session);
110
            $interestAmount += (int)($loanAmount * $interestRate * $sessionCount);
111
            // For the next loop
112
            $loanAmount -= $refund->amount;
113
            $startSession = $refund->session;
114
        }
115
116
        $lastSession = $this->getLastSession($principalDebt, $endSession);
117
        $sessionCount = $this->getSessionCount($debt, $startSession, $lastSession);
118
119
        return $interestAmount + (int)($loanAmount * $interestRate * $sessionCount);
120
    }
121
122
    /**
123
     * Get the compound interest amount.
124
     *
125
     * @param Debt $debt
126
     * @param Session $session
127
     *
128
     * @return int
129
     */
130
    private function getCompoundInterestAmount(Debt $debt, Session $session): int
131
    {
132
        $principalDebt = $debt->loan->principal_debt;
133
        $loanAmount = $principalDebt->amount;
134
        // The interest rate is multiplied by 100 in the database.
135
        $interestRate = $debt->loan->interest_rate / 10000;
136
        $interestAmount = 0;
137
138
        $startSession = $debt->loan->session;
139
        $endSession = $this->getLastSessionForInterest($debt, $session);
140
141
        // Take refunds before the current session and sort by session date.
142
        $partialRefunds = $this->getPartialRefunds($principalDebt, $endSession, false)
143
            ->sortBy('session.day_date');
144
        foreach($partialRefunds as $refund)
145
        {
146
            $sessionCount = $this->getSessionCount($debt, $startSession, $refund->session);
147
            $interestAmount += (int)($loanAmount * (pow(1 + $interestRate, $sessionCount) - 1));
148
            // For the next loop
149
            $loanAmount -= $refund->amount - $interestAmount;
150
            $startSession = $refund->session;
151
        }
152
153
        $lastSession = $this->getLastSession($principalDebt, $endSession);
154
        $sessionCount = $this->getSessionCount($debt, $startSession, $lastSession);
155
156
        return $interestAmount + (int)($loanAmount * (pow(1 + $interestRate, $sessionCount) - 1));
157
    }
158
159
    /**
160
     * @param Debt $debt
161
     *
162
     * @return bool
163
     */
164
    private function debtAmountIsFinal(Debt $debt): bool
165
    {
166
        // The amount in a debt model is final if it is a principal debt, it has already been
167
        // totally refunded, it is an interest debt with fixed amount or unique interest rate.
168
        return $debt->is_principal || $debt->refund !== null || !$debt->loan->recurrent_interest;
0 ignored issues
show
Bug introduced by
The property is_principal does not seem to exist on Siak\Tontine\Model\Debt. 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...
Bug introduced by
The property recurrent_interest does not exist on Siak\Tontine\Model\Loan. Did you mean interest?
Loading history...
169
    }
170
171
    /**
172
     * Get the amount of a given debt.
173
     *
174
     * @param Debt $debt
175
     * @param Session $session
176
     *
177
     * @return int
178
     */
179
    public function getDebtAmount(Debt $debt, Session $session): int
180
    {
181
        return $this->debtAmountIsFinal($debt) ? $debt->amount :
182
            ($debt->loan->simple_interest ?
183
                $this->getSimpleInterestAmount($debt, $session) :
184
                $this->getCompoundInterestAmount($debt, $session));
185
    }
186
187
    /**
188
     * Get the paid amount for a given debt at a given session.
189
     *
190
     * @param Debt $debt
191
     * @param Session $session
192
     * @param bool $withCurrent Take the current session into account.
193
     *
194
     * @return int
195
     */
196
    public function getDebtPaidAmount(Debt $debt, Session $session, bool $withCurrent): int
197
    {
198
        $refundFilter = $this->getRefundFilter($session, $withCurrent);
199
        return ($debt->refund !== null && $refundFilter($debt->refund)) ? $debt->amount :
200
            $this->getPartialRefunds($debt, $session, $withCurrent)->sum('amount');
201
    }
202
203
    /**
204
     * Get the amount due of a given debt before the given session.
205
     *
206
     * @param Debt $debt
207
     * @param Session $session
208
     * @param bool $withCurrent Take the current session into account.
209
     *
210
     * @return int
211
     */
212
    public function getDebtDueAmount(Debt $debt, Session $session, bool $withCurrent): int
213
    {
214
        $refundFilter = $this->getRefundFilter($session, $withCurrent);
215
        if($debt->refund !== null && $refundFilter($debt->refund))
216
        {
217
            return 0; // The debt was refunded before the current session.
218
        }
219
220
        return $this->getDebtAmount($debt, $session) -
221
            $this->getPartialRefunds($debt, $session, $withCurrent)->sum('amount');
222
    }
223
224
    /**
225
     * Get the max amount that can be paid for a given debt at a given session.
226
     *
227
     * @param Debt $debt
228
     * @param Session $session
229
     *
230
     * @return int
231
     */
232
    public function getDebtPayableAmount(Debt $debt, Session $session): int
233
    {
234
        return $this->getDebtDueAmount($debt, $session, false);
235
    }
236
237
    /**
238
     * Get the total refunded amount.
239
     *
240
     * @param Debt $debt
241
     *
242
     * @return int
243
     */
244
    public function getDebtTotalPaidAmount(Debt $debt): int
245
    {
246
        return $debt->refund !== null ? $debt->amount : $debt->partial_refunds->sum('amount');
247
    }
248
249
    /**
250
     * Get the amount due after the current session
251
     *
252
     * @param Debt $debt
253
     * @param Session $session
254
     * @param int $dueBefore
255
     *
256
     * @return int
257
     */
258
    private function getAmountDueAfter(Debt $debt, Session $session, int $dueBefore): int
259
    {
260
        $refundFilter = $this->getRefundFilter($session, true);
261
        if($debt->refund !== null && $refundFilter($debt->refund))
262
        {
263
            return 0; // The debt was refunded before the current session.
264
        }
265
266
        return $dueBefore - ($debt->partial_refund?->amount ?? 0);
267
    }
268
269
    /**
270
     * @param Debt $debt
271
     * @param Session $session
272
     *
273
     * @return array
274
     */
275
    public function getAmounts(Debt $debt, Session $session): array
276
    {
277
        // The total debt amount.
278
        $debtAmount = $this->getDebtAmount($debt, $session);
279
        // The total paid amount.
280
        $totalPaidAmount = $this->getDebtTotalPaidAmount($debt);
281
        // The amount paid before the session.
282
        $paidAmount = $this->getDebtPaidAmount($debt, $session, false);
283
        // The amount due before the session;
284
        $amountDueBefore = $debtAmount - $paidAmount;
285
        // The amount due after the session;
286
        $amountDueAfter = $this->getAmountDueAfter($debt, $session, $amountDueBefore);
287
288
        return [
289
            'debt' => $debt,
290
            'session' => $session,
291
            'debtAmount' => $debtAmount,
292
            'totalPaidAmount' => $totalPaidAmount,
293
            'paidAmount' => $paidAmount,
294
            'amountDueBeforeSession' => $amountDueBefore,
295
            'amountDueAfterSession' => $amountDueAfter,
296
            'payableAmount' => $amountDueBefore,
297
        ];
298
    }
299
}
300