MemberService::hasSubscription()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 4
rs 10
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\BillView;
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
        // Order the bills by session date desc, then by member name asc.
146
        return BillView::query()
147
            ->select('v_bills.*')
148
            ->join('sessions', 'v_bills.session_id', '=', 'sessions.id')
149
            ->orderByDesc('sessions.day_date')
0 ignored issues
show
Bug introduced by
'sessions.day_date' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $column of Illuminate\Database\Query\Builder::orderByDesc(). ( Ignorable by Annotation )

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

149
            ->orderByDesc(/** @scrutinizer ignore-type */ 'sessions.day_date')
Loading history...
150
            ->when($member !== null, fn(Builder $query) =>
151
                $query->where('v_bills.member_id', $member->id))
152
            ->when($member === null, fn(Builder $query) =>
153
                $query->join('members', 'v_bills.member_id', '=', 'members.id')
154
                    ->join('member_defs', 'members.def_id', '=', 'member_defs.id')
155
                    ->orderBy('member_defs.name'))
0 ignored issues
show
Bug introduced by
'member_defs.name' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $column of Illuminate\Database\Query\Builder::orderBy(). ( Ignorable by Annotation )

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

155
                    ->orderBy(/** @scrutinizer ignore-type */ 'member_defs.name'))
Loading history...
156
            ->where(fn(Builder $query) => $query
157
                ->orWhere(fn(Builder $q1) => $q1->ofTypeSession($session))
158
                ->orWhere(fn(Builder $q2) => $q2->ofTypeNotSession($session)))
159
            ->with(['round', 'session', 'member', 'charge', 'bill', 'bill.settlement'])
160
            ->get()
161
            ->each(function($bill) {
162
                $bill->paid = $bill->bill->settlement !== null;
163
                $bill->amount = $bill->bill->amount;
164
            });
165
    }
166
167
    /**
168
     * @param Session $session
169
     * @param Member $member|null
170
     *
171
     * @return Collection
172
     */
173
    public function getLoans(Session $session, ?Member $member = null): Collection
174
    {
175
        return $this->sortByMemberName($session->loans()
176
            ->when($member !== null, fn($qm) =>
177
                $qm->where('member_id', $member->id))
178
            ->with(['member', 'principal_debt.refund', 'interest_debt.refund'])
179
            ->get(), $member);
180
    }
181
182
    /**
183
     * @param Session $session
184
     * @param Member $member
185
     *
186
     * @return Collection
187
     */
188
    public function getDebts(Session $session, Member $member): Collection
189
    {
190
        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...
191
            // Member debts
192
            ->whereHas('loan', fn(Builder $qm) =>
193
                $qm->where('member_id', $member->id))
194
            ->where(function($query) use($session) {
195
                // Take all the debts in the current session
196
                $query->where(fn($ql) =>
197
                    $ql->whereHas('loan', fn(Builder $qs) =>
198
                        $qs->where('session_id', $session->id)));
199
                // The debts in the previous sessions.
200
                $query->orWhere(function($query) use($session) {
201
                    $query->whereHas('loan', fn(Builder $qs) =>
202
                        $qs->whereHas('session', fn($qs) => $qs->precedes($session)))
203
                    ->where(function($query) use($session) {
204
                        // The debts that are not yet refunded.
205
                        $query->orWhereDoesntHave('refund');
206
                        // The debts that are refunded in the current session.
207
                        $query->orWhereHas('refund', fn(Builder $qs) =>
208
                            $qs->where('session_id', $session->id));
209
                    });
210
                });
211
            })
212
            ->get()
213
            ->each(function($debt) {
214
                $debt->paid = $debt->refund !== null;
215
                $debt->session = $debt->loan->session;
216
            });
217
    }
218
219
    /**
220
     * @param Session $session
221
     * @param Member $member|null
222
     *
223
     * @return Collection
224
     */
225
    public function getRefunds(Session $session, ?Member $member = null): Collection
226
    {
227
        $refunds = $this->sortByMemberName($session->refunds()->select('refunds.*')
228
            ->with(['debt.loan.session', 'debt.loan.member'])
229
            // Member refunds
230
            ->when($member !== null, fn(Builder $query) => $query
231
                ->join('debts', 'refunds.debt_id', '=', 'debts.id')
232
                ->join('loans', 'debts.loan_id', '=', 'loans.id')
233
                ->where('loans.member_id', $member->id))
234
            ->get()
235
            ->each(function($refund) {
236
                $refund->amount = $refund->debt->amount;
237
                $refund->debt->session = $refund->debt->loan->session;
238
                $refund->member = $refund->debt->loan->member;
239
                $refund->is_partial = false;
240
            }), $member);
241
        $partialRefunds = $this->sortByMemberName($session->partial_refunds()
242
            ->select('partial_refunds.*')
243
            ->with(['debt.loan.session', 'debt.loan.member'])
244
            // Member refunds
245
            ->when($member !== null, fn(Builder $query) => $query
246
                ->join('debts', 'partial_refunds.debt_id', '=', 'debts.id')
247
                ->join('loans', 'debts.loan_id', '=', 'loans.id')
248
                ->where('loans.member_id', $member->id))
249
            ->get()
250
            ->each(function($refund) {
251
                $refund->debt->session = $refund->debt->loan->session;
252
                $refund->member = $refund->debt->loan->member;
253
                $refund->is_partial = true;
254
            }), $member);
255
256
        return $refunds->concat($partialRefunds);
257
    }
258
259
    /**
260
     * @param Session $session
261
     * @param Member $member|null
262
     *
263
     * @return Collection
264
     */
265
    public function getTransfers(Session $session, ?Member $member = null): Collection
266
    {
267
        return $this->sortByMemberName($session->transfers()
268
            ->with(['member', 'session'])
269
            ->when($member !== null, fn($query) => $query->where('member_id', $member->id))
270
            ->get(), $member);
271
    }
272
273
    /**
274
     * @param Session $session
275
     * @param Member $member|null
276
     *
277
     * @return Collection
278
     */
279
    public function getOutflows(Session $session, ?Member $member = null): Collection
280
    {
281
        return $this->sortByMemberName($session->outflows()
282
            ->with(['member', 'category'])
283
            ->when($member !== null, fn($query) => $query->where('member_id', $member->id))
284
            ->get(), $member);
285
    }
286
}
287