Passed
Pull Request — main (#67)
by Thierry
06:31
created

MemberService   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 294
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 145
c 2
b 0
f 0
dl 0
loc 294
rs 10
wmc 15

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A getReceivables() 0 13 1
A sortByMemberName() 0 4 2
A hasSubscription() 0 4 1
A getLoans() 0 7 1
A getRefunds() 0 32 1
A getOutflows() 0 6 1
A getPayables() 0 14 1
A getDebts() 0 28 1
A getBills() 0 45 2
A getAuctions() 0 16 1
A getExtraDeposits() 0 14 1
A getSavings() 0 6 1
1
<?php
2
3
namespace Siak\Tontine\Service\Report;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Support\Collection;
7
use Siak\Tontine\Model\Auction;
8
use Siak\Tontine\Model\Bill;
9
use Siak\Tontine\Model\Debt;
10
use Siak\Tontine\Model\Member;
11
use Siak\Tontine\Model\Session;
12
use Siak\Tontine\Service\Payment\BalanceCalculator;
13
14
class MemberService
15
{
16
    /**
17
     * @param BalanceCalculator $balanceCalculator
18
     */
19
    public function __construct(private BalanceCalculator $balanceCalculator)
20
    {}
21
22
    /**
23
     * @param Collection $collection
24
     * @param Member|null $member
25
     *
26
     * @return Collection
27
     */
28
    private function sortByMemberName(Collection $collection, ?Member $member): Collection
29
    {
30
        return $member !== null ? $collection :
31
            $collection->sortBy('member.name', SORT_LOCALE_STRING)->values();
32
    }
33
34
    /**
35
     * @param Builder $query
36
     * @param Member $member
37
     *
38
     * @return Builder
39
     */
40
    private function hasSubscription(Builder $query, Member $member): Builder
41
    {
42
        return $query->whereHas('subscription', fn(Builder $qm) =>
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->whereHas(...ion(...) { /* ... */ }) could return the type Illuminate\Database\Query\Builder which is incompatible with the type-hinted return Illuminate\Database\Eloquent\Builder. Consider adding an additional type-check to rule them out.
Loading history...
43
            $qm->where('member_id', $member->id));
44
    }
45
46
    /**
47
     * @param Session $session
48
     * @param Member $member|null
49
     *
50
     * @return Collection
51
     */
52
    public function getReceivables(Session $session, ?Member $member = null): Collection
53
    {
54
        return $this->sortByMemberName($session->receivables()
55
            ->when($member !== null, fn($qw) =>
56
                $this->hasSubscription($qw, $member))
0 ignored issues
show
Bug introduced by
It seems like $member can also be of type null; however, parameter $member of Siak\Tontine\Service\Rep...vice::hasSubscription() does only seem to accept Siak\Tontine\Model\Member, maybe add an additional type check? ( Ignorable by Annotation )

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

56
                $this->hasSubscription($qw, /** @scrutinizer ignore-type */ $member))
Loading history...
57
            ->with(['deposit', 'subscription.pool', 'subscription.member'])
58
            ->get()
59
            ->each(function($receivable) {
60
                $receivable->pool = $receivable->subscription->pool;
61
                $receivable->member = $receivable->subscription->member;
62
                $receivable->paid = $receivable->deposit !== null;
63
                $receivable->amount = $this->balanceCalculator->getReceivableAmount($receivable);
64
            }), $member);
65
    }
66
67
    /**
68
     * Get the deposits in the current session for receivables of different sessions.
69
     *
70
     * @param Session $session
71
     * @param Member $member|null
72
     *
73
     * @return Collection
74
     */
75
    public function getExtraDeposits(Session $session, ?Member $member = null): Collection
76
    {
77
        return $this->sortByMemberName($session->deposits()
78
            ->whereHas('receivable', fn($qr) => $qr
79
                ->where('session_id', '!=', $session->id)
80
                ->when($member !== null, fn($qm) =>
81
                    $this->hasSubscription($qm, $member)))
0 ignored issues
show
Bug introduced by
It seems like $member can also be of type null; however, parameter $member of Siak\Tontine\Service\Rep...vice::hasSubscription() does only seem to accept Siak\Tontine\Model\Member, maybe add an additional type check? ( Ignorable by Annotation )

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

81
                    $this->hasSubscription($qm, /** @scrutinizer ignore-type */ $member)))
Loading history...
82
            ->with(['receivable.session', 'receivable.subscription.pool',
83
                'receivable.subscription.member'])
84
            ->get()
85
            ->each(function($deposit) {
86
                $deposit->pool = $deposit->receivable->subscription->pool;
87
                $deposit->member = $deposit->receivable->subscription->member;
88
            }), $member);
89
    }
90
91
    /**
92
     * @param Session $session
93
     * @param Member $member|null
94
     *
95
     * @return Collection
96
     */
97
    public function getPayables(Session $session, ?Member $member = null): Collection
98
    {
99
        return $this->sortByMemberName($session->payables()
100
            ->when($member !== null, fn($qw) =>
101
                $this->hasSubscription($qw, $member))
0 ignored issues
show
Bug introduced by
It seems like $member can also be of type null; however, parameter $member of Siak\Tontine\Service\Rep...vice::hasSubscription() does only seem to accept Siak\Tontine\Model\Member, maybe add an additional type check? ( Ignorable by Annotation )

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

101
                $this->hasSubscription($qw, /** @scrutinizer ignore-type */ $member))
Loading history...
102
            ->with(['remitment', 'subscription.pool', 'subscription.member'])
103
            ->get()
104
            ->each(function($payable) use($session) {
105
                $pool = $payable->subscription->pool;
106
                $payable->pool = $pool;
107
                $payable->member = $payable->subscription->member;
108
                $payable->paid = $payable->remitment !== null;
109
                $payable->amount = $this->balanceCalculator->getPayableAmount($pool, $session);
110
            }), $member);
111
    }
112
113
    /**
114
     * @param Session $session
115
     * @param Member $member|null
116
     *
117
     * @return Collection
118
     */
119
    public function getAuctions(Session $session, ?Member $member = null): Collection
120
    {
121
        return $this->sortByMemberName($session->auctions()
122
            ->when($member !== null, fn($qm) =>
123
                $qm->whereHas('remitment', fn($qr) =>
124
                    $qr->whereHas('payable', fn($qp) =>
125
                        $this->hasSubscription($qp, $member))))
0 ignored issues
show
Bug introduced by
It seems like $member can also be of type null; however, parameter $member of Siak\Tontine\Service\Rep...vice::hasSubscription() does only seem to accept Siak\Tontine\Model\Member, maybe add an additional type check? ( Ignorable by Annotation )

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

125
                        $this->hasSubscription($qp, /** @scrutinizer ignore-type */ $member))))
Loading history...
126
            ->with(['remitment.payable.subscription.pool',
127
                'remitment.payable.subscription.member'])
128
            ->get()
129
            ->each(function(Auction $auction) {
130
                $subscription = $auction->remitment->payable->subscription;
131
                $auction->member = $subscription->member;
0 ignored issues
show
Bug introduced by
The property member does not seem to exist on Siak\Tontine\Model\Auction. 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...
132
                $auction->pool = $subscription->pool;
0 ignored issues
show
Bug introduced by
The property pool does not seem to exist on Siak\Tontine\Model\Auction. 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...
133
            })
134
            ->keyBy('remitment.payable.id'), $member);
135
    }
136
137
    /**
138
     * @param Session $session
139
     * @param Member $member|null
140
     *
141
     * @return Collection
142
     */
143
    public function getBills(Session $session, ?Member $member = null): Collection
144
    {
145
        $memberCallback = fn($qm) => $qm->where('member_id', $member->id);
146
        return $this->sortByMemberName(Bill::with(['settlement',
147
                'libre_bill.session', 'libre_bill.member', 'round_bill.member',
148
                'onetime_bill.member', 'session_bill.member', 'session_bill.session'])
149
            ->where(fn($query) => $query
150
                // Unsettled bills.
151
                ->orWhereDoesntHave('settlement')
152
                // Bills settled on this session.
153
                ->orWhereHas('settlement', fn(Builder $qs) =>
154
                    $qs->where('session_id', $session->id)))
155
            ->where(fn($query) => $query
156
                // Onetime bills.
157
                ->orWhereHas('onetime_bill', fn(Builder $qb) =>
158
                    $qb->when($member !== null, $memberCallback)
159
                        ->when($member === null, fn($qw) =>
160
                            $qw->whereHas('member', fn($qm) =>
161
                                $qm->where('round_id', $session->round_id))))
162
                // Round bills.
163
                ->orWhereHas('round_bill', fn(Builder $qb) =>
164
                    $qb->where('round_id', $session->round_id)
165
                        ->when($member !== null, $memberCallback))
166
                // Session bills.
167
                ->orWhereHas('session_bill', fn(Builder $qb) =>
168
                    $qb->where('session_id', $session->id)
169
                        ->when($member !== null, $memberCallback))
170
                // Libre bills, all up to this session.
171
                ->orWhereHas('libre_bill', fn(Builder $qb) =>
172
                    $qb->whereHas('session', fn($qs) =>
173
                            $qs->where('round_id', $session->round_id)
174
                                ->where('day_date', '<=', $session->day_date))
175
                        ->when($member !== null, $memberCallback))
176
            )
177
            ->get()
178
            ->each(function($bill) {
179
                // Take the only value which is not null
180
                $_bill = $bill->session_bill ?? $bill->round_bill ??
181
                    $bill->onetime_bill ?? $bill->libre_bill;
182
183
                $bill->paid = $bill->settlement !== null;
184
                $bill->session = $bill->libre_bill ? $_bill->session : null;
185
                $bill->member = $_bill->member;
186
                $bill->charge_id = $_bill->charge_id;
187
            }), $member);
188
    }
189
190
    /**
191
     * @param Session $session
192
     * @param Member $member|null
193
     *
194
     * @return Collection
195
     */
196
    public function getLoans(Session $session, ?Member $member = null): Collection
197
    {
198
        return $this->sortByMemberName($session->loans()
199
            ->when($member !== null, fn($qm) =>
200
                $qm->where('member_id', $member->id))
201
            ->with(['member', 'principal_debt.refund', 'interest_debt.refund'])
202
            ->get(), $member);
203
    }
204
205
    /**
206
     * @param Session $session
207
     * @param Member $member
208
     *
209
     * @return Collection
210
     */
211
    public function getDebts(Session $session, Member $member): Collection
212
    {
213
        return Debt::with(['loan.session', 'refund'])
0 ignored issues
show
Bug Best Practice introduced by
The expression return Siak\Tontine\Mode...ion(...) { /* ... */ }) could return the type Illuminate\Database\Eloq...gHasThroughRelationship which is incompatible with the type-hinted return Illuminate\Support\Collection. Consider adding an additional type-check to rule them out.
Loading history...
214
            // Member debts
215
            ->whereHas('loan', fn(Builder $qm) =>
216
                $qm->where('member_id', $member->id))
217
            ->where(function($query) use($session) {
218
                // Take all the debts in the current session
219
                $query->where(fn($ql) =>
220
                    $ql->whereHas('loan', fn(Builder $qs) =>
221
                        $qs->where('session_id', $session->id)));
222
                // The debts in the previous sessions.
223
                $query->orWhere(function($query) use($session) {
224
                    $query->whereHas('loan', fn(Builder $qs) =>
225
                        $qs->whereHas('session', fn($qs) => $qs->precedes($session)))
226
                    ->where(function($query) use($session) {
227
                        // The debts that are not yet refunded.
228
                        $query->orWhereDoesntHave('refund');
229
                        // The debts that are refunded in the current session.
230
                        $query->orWhereHas('refund', fn(Builder $qs) =>
231
                            $qs->where('session_id', $session->id));
232
                    });
233
                });
234
            })
235
            ->get()
236
            ->each(function($debt) {
237
                $debt->paid = $debt->refund !== null;
238
                $debt->session = $debt->loan->session;
239
            });
240
    }
241
242
    /**
243
     * @param Session $session
244
     * @param Member $member|null
245
     *
246
     * @return Collection
247
     */
248
    public function getRefunds(Session $session, ?Member $member = null): Collection
249
    {
250
        $refunds = $this->sortByMemberName($session->refunds()->select('refunds.*')
251
            ->with(['debt.loan.session', 'debt.loan.member'])
252
            // Member refunds
253
            ->when($member !== null, fn(Builder $query) => $query
254
                ->join('debts', 'refunds.debt_id', '=', 'debts.id')
255
                ->join('loans', 'debts.loan_id', '=', 'loans.id')
256
                ->where('loans.member_id', $member->id))
257
            ->get()
258
            ->each(function($refund) {
259
                $refund->amount = $refund->debt->amount;
260
                $refund->debt->session = $refund->debt->loan->session;
261
                $refund->member = $refund->debt->loan->member;
262
                $refund->is_partial = false;
263
            }), $member);
264
        $partialRefunds = $this->sortByMemberName($session->partial_refunds()
265
            ->select('partial_refunds.*')
266
            ->with(['debt.loan.session', 'debt.loan.member'])
267
            // Member refunds
268
            ->when($member !== null, fn(Builder $query) => $query
269
                ->join('debts', 'partial_refunds.debt_id', '=', 'debts.id')
270
                ->join('loans', 'debts.loan_id', '=', 'loans.id')
271
                ->where('loans.member_id', $member->id))
272
            ->get()
273
            ->each(function($refund) {
274
                $refund->debt->session = $refund->debt->loan->session;
275
                $refund->member = $refund->debt->loan->member;
276
                $refund->is_partial = true;
277
            }), $member);
278
279
        return $refunds->concat($partialRefunds);
280
    }
281
282
    /**
283
     * @param Session $session
284
     * @param Member $member|null
285
     *
286
     * @return Collection
287
     */
288
    public function getSavings(Session $session, ?Member $member = null): Collection
289
    {
290
        return $this->sortByMemberName($session->savings()
291
            ->with(['member'])
292
            ->when($member !== null, fn($query) => $query->where('member_id', $member->id))
293
            ->get(), $member);
294
    }
295
296
    /**
297
     * @param Session $session
298
     * @param Member $member|null
299
     *
300
     * @return Collection
301
     */
302
    public function getOutflows(Session $session, ?Member $member = null): Collection
303
    {
304
        return $this->sortByMemberName($session->outflows()
305
            ->with(['member', 'category'])
306
            ->when($member !== null, fn($query) => $query->where('member_id', $member->id))
307
            ->get(), $member);
308
    }
309
}
310