Passed
Pull Request — main (#49)
by Thierry
14:14
created

SessionService::getBills()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 45
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 38
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 45
rs 9.312
1
<?php
2
3
namespace Siak\Tontine\Service\Report;
4
5
use Closure;
6
use Illuminate\Database\Query\Builder;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Facades\DB;
9
use Siak\Tontine\Model\Debt;
10
use Siak\Tontine\Model\Member;
11
use Siak\Tontine\Model\Session;
12
use Siak\Tontine\Service\BalanceCalculator;
13
use Siak\Tontine\Service\TenantService;
14
15
use function collect;
16
use function count;
17
use function is_array;
18
19
class SessionService
20
{
21
    /**
22
     * @var TenantService
23
     */
24
    protected TenantService $tenantService;
25
26
    /**
27
     * @var BalanceCalculator
28
     */
29
    protected BalanceCalculator $balanceCalculator;
30
31
    /**
32
     * @param TenantService $tenantService
33
     * @param BalanceCalculator $balanceCalculator
34
     */
35
    public function __construct(TenantService $tenantService, BalanceCalculator $balanceCalculator)
36
    {
37
        $this->tenantService = $tenantService;
38
        $this->balanceCalculator = $balanceCalculator;
39
    }
40
41
    /**
42
     * @param Session $session
43
     *
44
     * @return Collection
45
     */
46
    public function getReceivables(Session $session): Collection
47
    {
48
        return $session->round->pools()
49
            ->withCount([
50
                'subscriptions as total_count' => function($query) use($session) {
51
                    $query->whereHas('receivables', function($query) use($session) {
52
                        $query->where('session_id', $session->id);
53
                    });
54
                },
55
                'subscriptions as paid_count' => function($query) use($session) {
56
                    $query->whereHas('receivables', function($query) use($session) {
57
                        $query->where('session_id', $session->id)->whereHas('deposit');
58
                    });
59
                },
60
            ])
61
            ->get()
62
            ->each(function($pool) use($session) {
63
                $pool->total_amount = $pool->amount * $pool->total_count;
64
                $pool->paid_amount = $this->balanceCalculator->getPoolDepositAmount($pool, $session);
65
            });
66
    }
67
68
    /**
69
     * @param Session $session
70
     *
71
     * @return Collection
72
     */
73
    public function getPayables(Session $session): Collection
74
    {
75
        return $session->round->pools()
76
            ->withCount([
77
                'subscriptions as total_count' => function($query) use($session) {
78
                    $query->whereHas('payable', function($query) use($session) {
79
                        $query->where('session_id', $session->id);
80
                    });
81
                },
82
                'subscriptions as paid_count' => function($query) use($session) {
83
                    $query->whereHas('payable', function($query) use($session) {
84
                        $query->where('session_id', $session->id)->whereHas('remitment');
85
                    });
86
                },
87
            ])
88
            ->get()
89
            ->each(function($pool) use($session) {
90
                $sessionCount = $this->tenantService->countEnabledSessions($pool);
91
                $pool->total_amount = $pool->amount * $pool->total_count * $sessionCount;
92
                $pool->paid_amount = $this->balanceCalculator->getPoolRemitmentAmount($pool, $session);
93
            });
94
    }
95
96
    /**
97
     * @param Session $session
98
     *
99
     * @return Collection
100
     */
101
    public function getAuctions(Session $session): Collection
102
    {
103
        return DB::table('auctions')
104
            ->select(DB::raw('sum(auctions.amount) as total'), 'subscriptions.pool_id')
105
            ->join('remitments', 'auctions.remitment_id', '=', 'remitments.id')
106
            ->join('payables', 'remitments.payable_id', '=', 'payables.id')
107
            ->join('subscriptions', 'payables.subscription_id', '=', 'subscriptions.id')
108
            ->where('auctions.session_id', $session->id)
109
            ->where('paid', true)
110
            ->groupBy('subscriptions.pool_id')
111
            ->pluck('total', 'pool_id');
112
    }
113
114
    /**
115
     * @param Collection $chargeIds
116
     * @param Collection $sessionIds
117
     *
118
     * @return Collection
119
     */
120
    public function getDisbursedAmounts(Collection $chargeIds, Collection $sessionIds): Collection
121
    {
122
        if($chargeIds->count() === 0)
123
        {
124
            return collect();
125
        }
126
127
        return DB::table('disbursements')
128
            ->select(DB::raw('sum(amount) as total_amount'),
129
                DB::raw('count(*) as total_count'), 'charge_id')
130
            ->groupBy('charge_id')
131
            ->whereIn('charge_id', $chargeIds)
132
            ->whereIn('session_id', $sessionIds)
133
            ->get()
134
            ->keyBy('charge_id');
135
    }
136
137
    /**
138
     * @param Closure $settlementFilter
139
     * @param Member $member|null
140
     *
141
     * @return Collection
142
     */
143
    private function getBills(Closure $settlementFilter, ?Member $member = null): Collection
144
    {
145
        $tontineBillsQuery = DB::table('bills')
146
            ->join('tontine_bills', 'bills.id', '=', 'tontine_bills.bill_id')
147
            ->select(DB::raw('sum(bills.amount) as total_amount'),
148
                DB::raw('count(bills.id) as total_count'), 'tontine_bills.charge_id')
149
            ->groupBy('tontine_bills.charge_id')
150
            ->whereExists($settlementFilter)
151
            ->when($member !== null, function($query) use($member) {
152
                return $query->where('tontine_bills.member_id', $member->id);
153
            });
154
        $roundBillsQuery = DB::table('bills')
155
            ->join('round_bills', 'bills.id', '=', 'round_bills.bill_id')
156
            ->select(DB::raw('sum(bills.amount) as total_amount'),
157
                DB::raw('count(bills.id) as total_count'), 'round_bills.charge_id')
158
            ->groupBy('round_bills.charge_id')
159
            ->whereExists($settlementFilter)
160
            ->when($member !== null, function($query) use($member) {
161
                return $query->where('round_bills.member_id', $member->id);
162
            });
163
        $sessionBillsQuery = DB::table('bills')
164
            ->join('session_bills', 'bills.id', '=', 'session_bills.bill_id')
165
            ->select(DB::raw('sum(bills.amount) as total_amount'),
166
                DB::raw('count(bills.id) as total_count'), 'session_bills.charge_id')
167
            ->groupBy('session_bills.charge_id')
168
            ->whereExists($settlementFilter)
169
            ->when($member !== null, function($query) use($member) {
170
                return $query->where('session_bills.member_id', $member->id);
171
            });
172
        $libreBillsQuery = DB::table('bills')
173
            ->join('libre_bills', 'bills.id', '=', 'libre_bills.bill_id')
174
            ->select(DB::raw('sum(bills.amount) as total_amount'),
175
                DB::raw('count(bills.id) as total_count'), 'libre_bills.charge_id')
176
            ->groupBy('libre_bills.charge_id')
177
            ->whereExists($settlementFilter)
178
            ->when($member !== null, function($query) use($member) {
179
                return $query->where('libre_bills.member_id', $member->id);
180
            });
181
182
        return $tontineBillsQuery
183
            ->union($roundBillsQuery)
184
            ->union($sessionBillsQuery)
185
            ->union($libreBillsQuery)
186
            ->get()
187
            ->keyBy('charge_id');
188
    }
189
190
    /**
191
     * @param Session $session
192
     *
193
     * @return Collection
194
     */
195
    public function getSessionCharges(Session $session): Collection
196
    {
197
        $charges = $this->tenantService->tontine()->charges()->active()->get();
198
        $settlementFilter = function(Builder $query) use($session) {
199
            return $query->select(DB::raw(1))
200
                ->from('settlements')
201
                ->where('session_id', $session->id)
202
                ->whereColumn('settlements.bill_id', 'bills.id');
203
        };
204
        $bills = $this->getBills($settlementFilter);
205
        $sessionIds = collect([$session->id]);
0 ignored issues
show
Bug introduced by
array($session->id) of type array<integer,integer> is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

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

205
        $sessionIds = collect(/** @scrutinizer ignore-type */ [$session->id]);
Loading history...
206
        $disbursements = $this->getDisbursedAmounts($charges->pluck('id'), $sessionIds);
207
208
        return $charges->each(function($charge) use($bills, $disbursements) {
209
            $bill = $bills[$charge->id] ?? null;
210
            $charge->total_count = $bill ? $bill->total_count : 0;
211
            $charge->total_amount = $bill ? $bill->total_amount : 0;
212
            $charge->disbursement = $disbursements[$charge->id] ?? null;
213
        });
214
    }
215
216
    /**
217
     * @return int
218
     */
219
    private function countActiveMembers(): int
220
    {
221
        // The number of active members is saved in the round, so its current
222
        // value can be retrieved forever, even when the membership will change.
223
        $tontine = $this->tenantService->tontine();
224
        $round = $this->tenantService->round();
225
        if($round->property === null)
0 ignored issues
show
Bug introduced by
The property property does not seem to exist on Siak\Tontine\Model\Round. 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...
226
        {
227
            // Create and save the property with the content
228
            $memberIds = $tontine->members()->active()->pluck('id')->all();
229
            $round->property()->create(['content' => ['members' => $memberIds]]);
230
231
            return count($memberIds);
232
        }
233
234
        $content = $round->property->content;
235
        if(isset($content['members']) && is_array($content['members']))
236
        {
237
            // Return the existing content value
238
            return count($content['members']);
239
        }
240
241
        // Update the existing content with the members
242
        $content['members'] = $tontine->members()->active()->pluck('id')->all();
243
        $round->property->content = $content;
244
        $round->property->save();
245
246
        return count($content['members']);
247
    }
248
249
    /**
250
     * @param Session $session
251
     * @param Member $member|null
252
     *
253
     * @return Collection
254
     */
255
    public function getTotalCharges(Session $session, ?Member $member = null): Collection
256
    {
257
        $charges = $this->tenantService->tontine()->charges()->active()->get();
258
        $sessionIds = $this->tenantService->getSessionIds($session);
259
        $settlementFilter = function(Builder $query) use($sessionIds) {
260
            return $query->select(DB::raw(1))
261
                ->from('settlements')
262
                ->whereIn('session_id', $sessionIds)
263
                ->whereColumn('settlements.bill_id', 'bills.id');
264
        };
265
        $bills = $this->getBills($settlementFilter, $member);
266
        $disbursements = $this->getDisbursedAmounts($charges->pluck('id'), $sessionIds);
267
        if($member !== null)
268
        {
269
            // The disbursement part of each member id calculated by dividing each amount
270
            // by the number of members.
271
            $memberCount = $this->countActiveMembers();
272
            foreach($disbursements as $disbursement)
273
            {
274
                $disbursement->total_amount /= $memberCount;
275
            }
276
        }
277
278
        return $charges->each(function($charge) use($bills, $disbursements) {
279
            $bill = $bills[$charge->id] ?? null;
280
            $charge->total_count = $bill ? $bill->total_count : 0;
281
            $charge->total_amount = $bill ? $bill->total_amount : 0;
282
            $charge->disbursement = $disbursements[$charge->id] ?? null;
283
        });
284
    }
285
286
    /**
287
     * @param Session $session
288
     *
289
     * @return object
290
     */
291
    public function getLoan(Session $session): object
292
    {
293
        $principal = "CASE WHEN debts.type='" . Debt::TYPE_PRINCIPAL . "' THEN amount ELSE 0 END";
294
        $interest = "CASE WHEN debts.type='" . Debt::TYPE_INTEREST . "' THEN amount ELSE 0 END";
295
        $loan = DB::table('loans')
296
            ->join('debts', 'loans.id', '=', 'debts.loan_id')
297
            ->select(DB::raw("sum($principal) as principal"), DB::raw("sum($interest) as interest"))
298
            ->where('loans.session_id', $session->id)
299
            ->first();
300
        if(!$loan->principal)
301
        {
302
            $loan->principal = 0;
303
        }
304
        if(!$loan->interest)
305
        {
306
            $loan->interest = 0;
307
        }
308
309
        return $loan;
310
    }
311
312
    /**
313
     * @param Session $session
314
     *
315
     * @return object
316
     */
317
    public function getRefund(Session $session): object
318
    {
319
        $principal = "CASE WHEN debts.type='" . Debt::TYPE_PRINCIPAL . "' THEN amount ELSE 0 END";
320
        $interest = "CASE WHEN debts.type='" . Debt::TYPE_INTEREST . "' THEN amount ELSE 0 END";
321
        $refund = DB::table('refunds')
322
            ->join('debts', 'refunds.debt_id', '=', 'debts.id')
323
            ->select(DB::raw("sum($principal) as principal"), DB::raw("sum($interest) as interest"))
324
            ->where('refunds.session_id', $session->id)
325
            ->first();
326
        if(!$refund->principal)
327
        {
328
            $refund->principal = 0;
329
        }
330
        if(!$refund->interest)
331
        {
332
            $refund->interest = 0;
333
        }
334
335
        return $refund;
336
    }
337
338
    /**
339
     * @param Session $session
340
     *
341
     * @return object
342
     */
343
    public function getFunding(Session $session): object
344
    {
345
        $funding = DB::table('fundings')
346
            ->select(DB::raw('sum(amount) as total_amount'), DB::raw('count(id) as total_count'))
347
            ->where('session_id', $session->id)
348
            ->first();
349
        if(!$funding->total_amount)
350
        {
351
            $funding->total_amount = 0;
352
        }
353
        if(!$funding->total_count)
354
        {
355
            $funding->total_count = 0;
356
        }
357
358
        return $funding;
359
    }
360
361
    /**
362
     * @param Session $session
363
     *
364
     * @return object
365
     */
366
    public function getDisbursement(Session $session): object
367
    {
368
        $disbursement = DB::table('disbursements')
369
            ->select(DB::raw('sum(amount) as total_amount'), DB::raw('count(id) as total_count'))
370
            ->where('session_id', $session->id)
371
            ->first();
372
        if(!$disbursement->total_amount)
373
        {
374
            $disbursement->total_amount = 0;
375
        }
376
        if(!$disbursement->total_count)
377
        {
378
            $disbursement->total_count = 0;
379
        }
380
381
        return $disbursement;
382
    }
383
}
384