SessionService::getSaving()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 17
rs 9.9332
1
<?php
2
3
namespace Siak\Tontine\Service\Report;
4
5
use Closure;
6
use Illuminate\Database\Eloquent\Builder as ElBuilder;
7
use Illuminate\Database\Query\Builder;
8
use Illuminate\Support\Collection;
9
use Illuminate\Support\Facades\DB;
10
use Siak\Tontine\Model\Debt;
11
use Siak\Tontine\Model\Deposit;
12
use Siak\Tontine\Model\Member;
13
use Siak\Tontine\Model\Outflow;
14
use Siak\Tontine\Model\Session;
15
use Siak\Tontine\Service\Payment\BalanceCalculator;
16
17
class SessionService
18
{
19
    use Traits\Queries;
20
21
    /**
22
     * @param BalanceCalculator $balanceCalculator
23
     */
24
    public function __construct(private BalanceCalculator $balanceCalculator)
25
    {}
26
27
    /**
28
     * @param Session $session
29
     *
30
     * @return Collection
31
     */
32
    public function getReceivables(Session $session): Collection
33
    {
34
        return $session->pools()
35
            ->addSelect([
36
                'paid_amount' => Deposit::select(DB::raw('sum(amount)'))
37
                    ->whereColumn('pool_id', 'pools.id')
38
                    ->whereHas('receivable', fn(ElBuilder $qr) =>
39
                        $qr->whereSession($session)),
40
            ])
41
            ->withCount([
42
                'receivables as total_count' => fn(ElBuilder $query) =>
43
                    $query->whereSession($session),
44
                'receivables as paid_count' => fn(ElBuilder $query) =>
45
                    $query->whereSession($session)->paidHere($session),
46
                'receivables as late_count' => fn(ElBuilder $query) =>
47
                    $query->whereSession($session)->paidLater($session),
48
            ])
49
            ->get()
50
            ->each(function($pool) {
51
                $pool->paid_amount ??= 0;
52
                $pool->total_amount = $pool->amount * $pool->total_count;
53
            });
54
    }
55
56
    /**
57
     * @param Session $session
58
     *
59
     * @return Collection
60
     */
61
    public function getPayables(Session $session): Collection
62
    {
63
        return $session->pools()
64
            ->withCount([
65
                'sessions',
66
                'payables as total_count' => fn(ElBuilder $query) =>
67
                    $query->whereSession($session),
68
                'payables as paid_count' => fn(ElBuilder $query) =>
69
                    $query->whereSession($session)->whereHas('remitment'),
70
            ])
71
            ->get()
72
            ->each(function($pool) use($session) {
73
                $pool->total_amount = $pool->amount * $pool->total_count * $pool->sessions_count;
74
                $pool->paid_amount = $this->balanceCalculator->getPoolRemitmentAmount($pool, $session);
75
            });
76
    }
77
78
    /**
79
     * @param Session $session
80
     *
81
     * @return Collection
82
     */
83
    public function getAuctions(Session $session): Collection
84
    {
85
        return DB::table('auctions')
86
            ->select(DB::raw('sum(auctions.amount) as total'), 'subscriptions.pool_id')
87
            ->join('remitments', 'auctions.remitment_id', '=', 'remitments.id')
88
            ->join('payables', 'remitments.payable_id', '=', 'payables.id')
89
            ->join('subscriptions', 'payables.subscription_id', '=', 'subscriptions.id')
90
            ->where('auctions.session_id', $session->id)
91
            ->where('paid', true)
92
            ->groupBy('subscriptions.pool_id')
93
            ->pluck('total', 'pool_id');
94
    }
95
96
    /**
97
     * @param Closure $settlementFilter
98
     * @param Member $member|null
99
     *
100
     * @return Collection
101
     */
102
    private function getBills(Closure $settlementFilter, ?Member $member = null): Collection
103
    {
104
        $onetimeBillsQuery = DB::table('bills')
105
            ->join(DB::raw('onetime_bills as ob'), 'bills.id', '=', 'ob.bill_id')
106
            ->select(DB::raw('sum(bills.amount) as total_amount'),
107
                DB::raw('count(bills.id) as total_count'), 'ob.charge_id')
108
            ->groupBy('ob.charge_id')
109
            ->whereExists($settlementFilter)
110
            ->when($member !== null, fn($qm) =>
111
                $qm->where('ob.member_id', $member->id));
112
        $roundBillsQuery = DB::table('bills')
113
            ->join(DB::raw('round_bills as rb'), 'bills.id', '=', 'rb.bill_id')
114
            ->select(DB::raw('sum(bills.amount) as total_amount'),
115
                DB::raw('count(bills.id) as total_count'), 'rb.charge_id')
116
            ->groupBy('rb.charge_id')
117
            ->whereExists($settlementFilter)
118
            ->when($member !== null, fn($qm) =>
119
                $qm->where('rb.member_id', $member->id));
120
        $sessionBillsQuery = DB::table('bills')
121
            ->join(DB::raw('session_bills as sb'), 'bills.id', '=', 'sb.bill_id')
122
            ->select(DB::raw('sum(bills.amount) as total_amount'),
123
                DB::raw('count(bills.id) as total_count'), 'sb.charge_id')
124
            ->groupBy('sb.charge_id')
125
            ->whereExists($settlementFilter)
126
            ->when($member !== null, fn($qm) =>
127
                $qm->where('sb.member_id', $member->id));
128
        $libreBillsQuery = DB::table('bills')
129
            ->join(DB::raw('libre_bills as lb'), 'bills.id', '=', 'lb.bill_id')
130
            ->select(DB::raw('sum(bills.amount) as total_amount'),
131
                DB::raw('count(bills.id) as total_count'), 'lb.charge_id')
132
            ->groupBy('lb.charge_id')
133
            ->whereExists($settlementFilter)
134
            ->when($member !== null, fn($qm) =>
135
                $qm->where('lb.member_id', $member->id));
136
137
        return $onetimeBillsQuery
138
            ->union($roundBillsQuery)
139
            ->union($sessionBillsQuery)
140
            ->union($libreBillsQuery)
141
            ->get()
142
            ->keyBy('charge_id');
143
    }
144
145
    /**
146
     * @param Session $session
147
     * @param bool $onlyCurrent
148
     *
149
     * @return Collection
150
     */
151
    private function getDisbursedAmounts(Session $session, bool $onlyCurrent): Collection
152
    {
153
        return Outflow::select(DB::raw('sum(amount) as total_amount'),
154
                DB::raw('count(*) as total_count'), 'charge_id')
155
            ->groupBy('charge_id')
156
            ->whereHas('charge', fn($qc) => $qc->where('round_id', $session->round_id))
157
            ->when($onlyCurrent, fn($qo) => $qo->where('session_id', $session->id))
158
            ->when(!$onlyCurrent, fn($qo) =>
159
                $qo->whereHas('session', fn($qs) => $qs->precedes($session)))
160
            ->get()
161
            ->keyBy('charge_id');
162
    }
163
164
    /**
165
     * @param Session $session
166
     *
167
     * @return Collection
168
     */
169
    public function getSessionCharges(Session $session): Collection
170
    {
171
        $settlementFilter = fn(Builder $query) => $query
172
            ->select(DB::raw(1))
173
            ->from(DB::raw('settlements as st'))
0 ignored issues
show
Bug introduced by
Illuminate\Support\Facad...aw('settlements as st') of type Illuminate\Contracts\Database\Query\Expression is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $table of Illuminate\Database\Query\Builder::from(). ( Ignorable by Annotation )

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

173
            ->from(/** @scrutinizer ignore-type */ DB::raw('settlements as st'))
Loading history...
174
            ->where('session_id', $session->id)
175
            ->whereColumn('st.bill_id', 'bills.id');
176
        $bills = $this->getBills($settlementFilter);
177
        $outflows = $this->getDisbursedAmounts($session, true);
178
179
        return $session->round->charges
180
            ->each(function($charge) use($bills, $outflows) {
181
                $bill = $bills[$charge->id] ?? null;
182
                $charge->total_count = $bill ? $bill->total_count : 0;
183
                $charge->total_amount = $bill ? $bill->total_amount : 0;
184
                $charge->outflow = $outflows[$charge->id] ?? null;
185
            });
186
    }
187
188
    /**
189
     * @param Session $session
190
     * @param Member $member|null
191
     *
192
     * @return Collection
193
     */
194
    public function getTotalCharges(Session $session, ?Member $member = null): Collection
195
    {
196
        $sessionFilter = fn(Builder $query) => $query
197
            ->select(DB::raw(1))
198
            ->from(DB::raw('sessions as se'))
0 ignored issues
show
Bug introduced by
Illuminate\Support\Facad...::raw('sessions as se') of type Illuminate\Contracts\Database\Query\Expression is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $table of Illuminate\Database\Query\Builder::from(). ( Ignorable by Annotation )

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

198
            ->from(/** @scrutinizer ignore-type */ DB::raw('sessions as se'))
Loading history...
199
            ->whereColumn('se.id', 'st.session_id')
200
            ->where('se.round_id', $session->round_id)
201
            ->where('se.day_date', '<=', $session->day_date);
202
        $settlementFilter = fn(Builder $query) => $query
203
            ->select(DB::raw(1))
204
            ->from(DB::raw('settlements as st'))
0 ignored issues
show
Bug introduced by
Illuminate\Support\Facad...aw('settlements as st') of type Illuminate\Contracts\Database\Query\Expression is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $table of Illuminate\Database\Query\Builder::from(). ( Ignorable by Annotation )

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

204
            ->from(/** @scrutinizer ignore-type */ DB::raw('settlements as st'))
Loading history...
205
            ->whereExists($sessionFilter)
206
            ->whereColumn('st.bill_id', 'bills.id');
207
        $bills = $this->getBills($settlementFilter, $member);
208
        $outflows = $this->getDisbursedAmounts($session, false);
209
        if($member !== null)
210
        {
211
            // The outflow part of each member id calculated by dividing each amount
212
            // by the number of members.
213
            $memberCount = $session->round->members->count();
214
            foreach($outflows as $outflow)
215
            {
216
                $outflow->total_amount /= $memberCount;
217
            }
218
        }
219
220
        return $session->round->charges
221
            ->each(function($charge) use($bills, $outflows) {
222
                $bill = $bills[$charge->id] ?? null;
223
                $charge->total_count = $bill ? $bill->total_count : 0;
224
                $charge->total_amount = $bill ? $bill->total_amount : 0;
225
                $charge->outflow = $outflows[$charge->id] ?? null;
226
            });
227
    }
228
229
    /**
230
     * @param Session $session
231
     *
232
     * @return object
233
     */
234
    public function getLoan(Session $session): object
235
    {
236
        $loan = DB::table('loans')
237
            ->join('debts', 'loans.id', '=', 'debts.loan_id')
238
            ->select('debts.type', DB::raw('sum(debts.amount) as total_amount'))
239
            ->where('loans.session_id', $session->id)
240
            ->groupBy('debts.type')
241
            ->pluck('total_amount', 'type');
242
243
        return (object)[
244
            'principal' => $loan[Debt::TYPE_PRINCIPAL] ?? 0,
245
            'interest' => $loan[Debt::TYPE_INTEREST] ?? 0,
246
        ];
247
    }
248
249
    /**
250
     * @param Session $session
251
     *
252
     * @return object
253
     */
254
    public function getRefund(Session $session): object
255
    {
256
        $refund = $this->getRefundQuery()
257
            ->addSelect('debts.type')
258
            ->where('refunds.session_id', $session->id)
259
            ->groupBy('debts.type')
260
            ->pluck('total_amount', 'type');
261
        $partialRefund = DB::table('partial_refunds')
262
            ->join('debts', 'partial_refunds.debt_id', '=', 'debts.id')
263
            ->where('partial_refunds.session_id', $session->id)
264
            ->select('debts.type', DB::raw('sum(partial_refunds.amount) as total_amount'))
265
            ->groupBy('debts.type')
266
            ->pluck('total_amount', 'type');
267
268
        return (object)[
269
            'principal' => ($refund[Debt::TYPE_PRINCIPAL] ?? 0) +
270
                ($partialRefund[Debt::TYPE_PRINCIPAL] ?? 0),
271
            'interest' => ($refund[Debt::TYPE_INTEREST] ?? 0) +
272
                ($partialRefund[Debt::TYPE_INTEREST] ?? 0),
273
        ];
274
    }
275
276
    /**
277
     * @param Session $session
278
     *
279
     * @return object
280
     */
281
    public function getSaving(Session $session): object
282
    {
283
        $saving = DB::table('savings')
284
            ->select(DB::raw('sum(amount) as total_amount'),
285
                DB::raw('count(id) as total_count'))
286
            ->where('session_id', $session->id)
287
            ->first();
288
        if(!$saving->total_amount)
289
        {
290
            $saving->total_amount = 0;
291
        }
292
        if(!$saving->total_count)
293
        {
294
            $saving->total_count = 0;
295
        }
296
297
        return $saving;
298
    }
299
300
    /**
301
     * @param Session $session
302
     *
303
     * @return object
304
     */
305
    public function getOutflow(Session $session): object
306
    {
307
        $outflow = DB::table('outflows')
308
            ->select(DB::raw('sum(amount) as total_amount'),
309
                DB::raw('count(id) as total_count'))
310
            ->where('session_id', $session->id)
311
            ->first();
312
        if(!$outflow->total_amount)
313
        {
314
            $outflow->total_amount = 0;
315
        }
316
        if(!$outflow->total_count)
317
        {
318
            $outflow->total_count = 0;
319
        }
320
321
        return $outflow;
322
    }
323
}
324