Passed
Pull Request — main (#62)
by Thierry
05:23
created

RefundTrait::fillDebt()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 6
rs 10
1
<?php
2
3
namespace Siak\Tontine\Service\Meeting\Credit;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Relations\Relation;
7
use Siak\Tontine\Model\Debt;
8
use Siak\Tontine\Model\Fund;
9
use Siak\Tontine\Model\Session;
10
use Siak\Tontine\Service\TenantService;
11
use Siak\Tontine\Service\Tontine\FundService;
12
13
use function tap;
14
15
trait RefundTrait
16
{
17
    /**
18
     * @var TenantService
19
     */
20
    private TenantService $tenantService;
21
22
    /**
23
     * @var FundService
24
     */
25
    private FundService $fundService;
26
27
    /**
28
     * @param Session $session The session
29
     * @param Fund $fund
30
     * @param bool|null $onlyPaid
31
     * @param bool $with
32
     *
33
     * @return Builder|Relation
34
     */
35
    private function getDebtsQuery(Session $session, Fund $fund,
36
        ?bool $onlyPaid, bool $with): Builder|Relation
37
    {
38
        $prevSessions = $this->fundService->getFundSessionIds($session, $fund)
39
            ->filter(fn(int $sessionId) => $sessionId !== $session->id);
40
41
        return Debt::whereHas('loan', function(Builder $query) use($fund) {
0 ignored issues
show
Bug Best Practice introduced by
The expression return Siak\Tontine\Mode...ion(...) { /* ... */ }) could return the type Illuminate\Database\Eloquent\Model which is incompatible with the type-hinted return Illuminate\Database\Eloq...uent\Relations\Relation. Consider adding an additional type-check to rule them out.
Loading history...
42
                $query->where('fund_id', $fund->id);
43
            })
44
            ->when($onlyPaid === false, function(Builder $query) {
45
                return $query->whereDoesntHave('refund');
46
            })
47
            ->when($onlyPaid === true, function(Builder $query) {
48
                return $query->whereHas('refund');
49
            })
50
            ->where(function(Builder $query) use($session, $prevSessions) {
51
                // Take all the debts in the current session
52
                $query->where(function(Builder $query) use($session) {
53
                    $query->whereHas('loan', function(Builder $query) use($session) {
54
                        $query->where('session_id', $session->id);
55
                    });
56
                });
57
                if($prevSessions->count() === 0)
58
                {
59
                    return;
60
                }
61
                // The debts in the previous sessions.
62
                $query->orWhere(function(Builder $query) use($session, $prevSessions) {
63
                    $query->whereHas('loan', function(Builder $query) use($prevSessions) {
64
                        $query->whereIn('session_id', $prevSessions);
65
                    })
66
                    ->where(function(Builder $query) use($session) {
67
                        // The debts that are not yet refunded.
68
                        $query->orWhereDoesntHave('refund');
69
                        // The debts that are refunded in the current session.
70
                        $query->orWhereHas('refund', function(Builder $query) use($session) {
71
                            $query->where('session_id', $session->id);
72
                        });
73
                    });
74
                });
75
            })
76
            ->when($with, function(Builder $query) use($session) {
77
                $query->with([
78
                    'loan.member',
79
                    'loan.session',
80
                    'refund.session',
81
                    'partial_refunds.session',
82
                    'partial_refund' => fn($q) => $q->where('session_id', $session->id),
83
                ]);
84
            });
85
    }
86
87
    /**
88
     * @param Debt $debt
89
     * @param Session $session
90
     *
91
     * @return bool
92
     */
93
    private function canCreateRefund(Debt $debt, Session $session): bool
94
    {
95
        // Already refunded
96
        // Cannot refund the principal debt in the same session as the loan.
97
        if(!$session->opened || $debt->refund !== null ||
98
            $debt->is_principal && $debt->loan->session->id === $session->id)
99
        {
100
            return false;
101
        }
102
103
        // Cannot refund a recurrent interest debt before the principal.
104
        if($debt->is_interest && $debt->loan->recurrent_interest)
0 ignored issues
show
Bug introduced by
The property recurrent_interest does not exist on Siak\Tontine\Model\Loan. Did you mean interest?
Loading history...
105
        {
106
            return $debt->loan->principal_debt->refund !== null;
107
        }
108
109
        // Cannot refund the principal debt before the last partial refund.
110
        $lastRefund = $debt->partial_refunds->sortByDesc('session.start_at')->first();
111
        return !$lastRefund || $lastRefund->session->start_at < $session->start_at;
112
    }
113
114
    /**
115
     * @param Debt $debt
116
     * @param Session $session
117
     *
118
     * @return bool
119
     */
120
    private function canDeleteRefund(Debt $debt, Session $session): bool
121
    {
122
        // A refund can only be deleted in the same session it was created.
123
        if(!$session->opened || !$debt->refund || $debt->refund->session_id !== $session->id)
124
        {
125
            return false;
126
        }
127
        // Cannot delete the principal refund if the interest is also refunded.
128
        if($debt->is_principal && $debt->loan->recurrent_interest)
0 ignored issues
show
Bug introduced by
The property recurrent_interest does not exist on Siak\Tontine\Model\Loan. Did you mean interest?
Loading history...
129
        {
130
            return $debt->loan->interest_debt->refund === null;
131
        }
132
133
        return true;
134
    }
135
136
    /**
137
     * @param Debt $debt
138
     * @param Session $session
139
     *
140
     * @return bool
141
     */
142
    private function canCreatePartialRefund(Debt $debt, Session $session): bool
143
    {
144
        // Cannot refund the principal debt in the same session.
145
        if(!$session->opened || $debt->refund !== null ||
146
            ($debt->is_principal && $debt->loan->session->id === $session->id))
147
        {
148
            return false;
149
        }
150
151
        return true;
152
    }
153
154
    /**
155
     * @param Debt $debt
156
     * @param Session $session
157
     *
158
     * @return void
159
     */
160
    private function fillDebt(Debt $debt, Session $session): void
161
    {
162
        $debt->isEditable = $debt->refund !== null ?
0 ignored issues
show
Bug introduced by
The property isEditable 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...
163
            $this->canDeleteRefund($debt, $session) :
164
            $this->canCreateRefund($debt, $session);
165
        $debt->canPartiallyRefund = $this->canCreatePartialRefund($debt, $session);
0 ignored issues
show
Bug introduced by
The property canPartiallyRefund 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...
166
    }
167
168
    /**
169
     * @param int $debtId
170
     *
171
     * @return Debt|null
172
     */
173
    public function getDebt(int $debtId): ?Debt
174
    {
175
        return Debt::whereHas('loan',
176
            fn(Builder|Relation $loanQuery) => $loanQuery->whereHas('member',
177
                fn(Builder|Relation $memberQuery) => $memberQuery->where('tontine_id',
178
                    $this->tenantService->tontine()->id)))
179
            ->find($debtId);
180
    }
181
182
    /**
183
     * @param Session $session The session
184
     * @param Fund $fund
185
     * @param int $debtId
186
     *
187
     * @return Debt|null
188
     */
189
    public function getFundDebt(Session $session, Fund $fund, int $debtId): ?Debt
190
    {
191
        return tap($this->getDebtsQuery($session, $fund, null, true)->find($debtId),
192
            fn(Debt $debt) => $debt !== null && $this->fillDebt($debt, $session));
193
    }
194
}
195