MemberService::getBills()   A
last analyzed

Complexity

Conditions 2
Paths 1

Size

Total Lines 45
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 34
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 45
rs 9.376
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
     * @param Session $session
69
     * @param Member $member|null
70
     *
71
     * @return Collection
72
     */
73
    public function getPayables(Session $session, ?Member $member = null): Collection
74
    {
75
        return $this->sortByMemberName($session->payables()
76
            ->when($member !== null, fn($qw) =>
77
                $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

77
                $this->hasSubscription($qw, /** @scrutinizer ignore-type */ $member))
Loading history...
78
            ->with(['remitment', 'subscription.pool', 'subscription.member'])
79
            ->get()
80
            ->each(function($payable) use($session) {
81
                $pool = $payable->subscription->pool;
82
                $payable->pool = $pool;
83
                $payable->member = $payable->subscription->member;
84
                $payable->paid = $payable->remitment !== null;
85
                $payable->amount = $this->balanceCalculator->getPayableAmount($pool, $session);
86
            }), $member);
87
    }
88
89
    /**
90
     * @param Session $session
91
     * @param Member $member|null
92
     *
93
     * @return Collection
94
     */
95
    public function getAuctions(Session $session, ?Member $member = null): Collection
96
    {
97
        return $this->sortByMemberName($session->auctions()
98
            ->when($member !== null, fn($qm) =>
99
                $qm->whereHas('remitment', fn($qr) =>
100
                    $qr->whereHas('payable', fn($qp) =>
101
                        $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

101
                        $this->hasSubscription($qp, /** @scrutinizer ignore-type */ $member))))
Loading history...
102
            ->with(['remitment.payable.subscription.pool',
103
                'remitment.payable.subscription.member'])
104
            ->get()
105
            ->each(function(Auction $auction) {
106
                $subscription = $auction->remitment->payable->subscription;
107
                $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...
108
                $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...
109
            })
110
            ->keyBy('remitment.payable.id'), $member);
111
    }
112
113
    /**
114
     * @param Session $session
115
     * @param Member $member|null
116
     *
117
     * @return Collection
118
     */
119
    public function getBills(Session $session, ?Member $member = null): Collection
120
    {
121
        $memberCallback = fn($qm) => $qm->where('member_id', $member->id);
122
        return $this->sortByMemberName(Bill::with(['settlement',
123
                'libre_bill.session', 'libre_bill.member', 'round_bill.member',
124
                'onetime_bill.member', 'session_bill.member', 'session_bill.session'])
125
            ->where(fn($query) => $query
126
                // Unsettled bills.
127
                ->orWhereDoesntHave('settlement')
128
                // Bills settled on this session.
129
                ->orWhereHas('settlement', fn(Builder $qs) =>
130
                    $qs->where('session_id', $session->id)))
131
            ->where(fn($query) => $query
132
                // Onetime bills.
133
                ->orWhereHas('onetime_bill', fn(Builder $qb) =>
134
                    $qb->when($member !== null, $memberCallback)
135
                        ->when($member === null, fn($qw) =>
136
                            $qw->whereHas('member', fn($qm) =>
137
                                $qm->where('round_id', $session->round_id))))
138
                // Round bills.
139
                ->orWhereHas('round_bill', fn(Builder $qb) =>
140
                    $qb->where('round_id', $session->round_id)
141
                        ->when($member !== null, $memberCallback))
142
                // Session bills.
143
                ->orWhereHas('session_bill', fn(Builder $qb) =>
144
                    $qb->where('session_id', $session->id)
145
                        ->when($member !== null, $memberCallback))
146
                // Libre bills, all up to this session.
147
                ->orWhereHas('libre_bill', fn(Builder $qb) =>
148
                    $qb->whereHas('session', fn($qs) =>
149
                            $qs->where('round_id', $session->round_id)
150
                                ->where('day_date', '<=', $session->day_date))
151
                        ->when($member !== null, $memberCallback))
152
            )
153
            ->get()
154
            ->each(function($bill) {
155
                // Take the only value which is not null
156
                $_bill = $bill->session_bill ?? $bill->round_bill ??
157
                    $bill->onetime_bill ?? $bill->libre_bill;
158
159
                $bill->paid = $bill->settlement !== null;
160
                $bill->session = $bill->libre_bill ? $_bill->session : null;
161
                $bill->member = $_bill->member;
162
                $bill->charge_id = $_bill->charge_id;
163
            }), $member);
164
    }
165
166
    /**
167
     * @param Session $session
168
     * @param Member $member|null
169
     *
170
     * @return Collection
171
     */
172
    public function getLoans(Session $session, ?Member $member = null): Collection
173
    {
174
        return $this->sortByMemberName($session->loans()
175
            ->when($member !== null, fn($qm) =>
176
                $qm->where('member_id', $member->id))
177
            ->with(['member', 'principal_debt.refund', 'interest_debt.refund'])
178
            ->get(), $member);
179
    }
180
181
    /**
182
     * @param Session $session
183
     * @param Member $member
184
     *
185
     * @return Collection
186
     */
187
    public function getDebts(Session $session, Member $member): Collection
188
    {
189
        return Debt::with(['loan.session', 'refund'])
190
            // Member debts
191
            ->whereHas('loan', fn(Builder $qm) =>
192
                $qm->where('member_id', $member->id))
193
            ->where(function($query) use($session) {
194
                // Take all the debts in the current session
195
                $query->where(fn($ql) =>
196
                    $ql->whereHas('loan', fn(Builder $qs) =>
197
                        $qs->where('session_id', $session->id)));
198
                // The debts in the previous sessions.
199
                $query->orWhere(function($query) use($session) {
200
                    $query->whereHas('loan', fn(Builder $qs) =>
201
                        $qs->whereHas('session', fn($qs) => $qs->precedes($session)))
202
                    ->where(function($query) use($session) {
203
                        // The debts that are not yet refunded.
204
                        $query->orWhereDoesntHave('refund');
205
                        // The debts that are refunded in the current session.
206
                        $query->orWhereHas('refund', fn(Builder $qs) =>
207
                            $qs->where('session_id', $session->id));
208
                    });
209
                });
210
            })
211
            ->get()
212
            ->each(function($debt) {
213
                $debt->paid = $debt->refund !== null;
214
                $debt->session = $debt->loan->session;
215
            });
216
    }
217
218
    /**
219
     * @param Session $session
220
     * @param Member $member|null
221
     *
222
     * @return Collection
223
     */
224
    public function getRefunds(Session $session, ?Member $member = null): Collection
225
    {
226
        $refunds = $this->sortByMemberName($session->refunds()->select('refunds.*')
227
            ->with(['debt.loan.session', 'debt.loan.member'])
228
            // Member refunds
229
            ->when($member !== null, fn(Builder $query) => $query
230
                ->join('debts', 'refunds.debt_id', '=', 'debts.id')
231
                ->join('loans', 'debts.loan_id', '=', 'loans.id')
232
                ->where('loans.member_id', $member->id))
233
            ->get()
234
            ->each(function($refund) {
235
                $refund->amount = $refund->debt->amount;
236
                $refund->debt->session = $refund->debt->loan->session;
237
                $refund->member = $refund->debt->loan->member;
238
                $refund->is_partial = false;
239
            }), $member);
240
        $partialRefunds = $this->sortByMemberName($session->partial_refunds()
241
            ->select('partial_refunds.*')
242
            ->with(['debt.loan.session', 'debt.loan.member'])
243
            // Member refunds
244
            ->when($member !== null, fn(Builder $query) => $query
245
                ->join('debts', 'partial_refunds.debt_id', '=', 'debts.id')
246
                ->join('loans', 'debts.loan_id', '=', 'loans.id')
247
                ->where('loans.member_id', $member->id))
248
            ->get()
249
            ->each(function($refund) {
250
                $refund->debt->session = $refund->debt->loan->session;
251
                $refund->member = $refund->debt->loan->member;
252
                $refund->is_partial = true;
253
            }), $member);
254
255
        return $refunds->concat($partialRefunds);
256
    }
257
258
    /**
259
     * @param Session $session
260
     * @param Member $member|null
261
     *
262
     * @return Collection
263
     */
264
    public function getSavings(Session $session, ?Member $member = null): Collection
265
    {
266
        return $this->sortByMemberName($session->savings()
267
            ->with(['member'])
268
            ->when($member !== null, fn($query) => $query->where('member_id', $member->id))
269
            ->get(), $member);
270
    }
271
272
    /**
273
     * @param Session $session
274
     * @param Member $member|null
275
     *
276
     * @return Collection
277
     */
278
    public function getOutflows(Session $session, ?Member $member = null): Collection
279
    {
280
        return $this->sortByMemberName($session->outflows()
281
            ->with(['member', 'category'])
282
            ->when($member !== null, fn($query) => $query->where('member_id', $member->id))
283
            ->get(), $member);
284
    }
285
}
286