Passed
Push — main ( 5b7f28...7283a1 )
by Thierry
05:45
created

SessionService::getRefund()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 16
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 19
rs 9.7333
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 getSettlements(Closure $settlementFilter, ?Member $member = null): Collection
103
    {
104
        return DB::table('v_settlements')
105
            ->select(DB::raw('sum(amount) as total_amount'),
106
                DB::raw('count(*) as total_count'), 'charge_id')
107
            ->groupBy('charge_id')
108
            ->where($settlementFilter)
109
            ->when($member !== null, fn($qm) =>
110
                $qm->where('member_id', $member->id))
111
            ->get()
112
            ->keyBy('charge_id');
113
    }
114
115
    /**
116
     * @param Session $session
117
     * @param bool $onlyCurrent
118
     *
119
     * @return Collection
120
     */
121
    private function getDisbursedAmounts(Session $session, bool $onlyCurrent): Collection
122
    {
123
        return Outflow::select(DB::raw('sum(amount) as total_amount'),
124
                DB::raw('count(*) as total_count'), 'charge_id')
125
            ->groupBy('charge_id')
126
            ->whereHas('charge', fn($qc) => $qc->where('round_id', $session->round_id))
127
            ->when($onlyCurrent, fn($qo) => $qo->where('session_id', $session->id))
128
            ->when(!$onlyCurrent, fn($qo) =>
129
                $qo->whereHas('session', fn($qs) => $qs->precedes($session)))
130
            ->get()
131
            ->keyBy('charge_id');
132
    }
133
134
    /**
135
     * @param Session $session
136
     *
137
     * @return Collection
138
     */
139
    public function getSessionCharges(Session $session): Collection
140
    {
141
        $settlementFilter = fn(Builder $qs) =>
142
            $qs->where('session_id', $session->id);
143
        $settlements = $this->getSettlements($settlementFilter);
144
        $outflows = $this->getDisbursedAmounts($session, true);
145
146
        return $session->round->charges
147
            ->map(function($charge) use($settlements, $outflows) {
148
                $settlement = $settlements[$charge->id] ?? null;
149
                // We need to clone the model, or else the getTotalCharges()
150
                // method will modify and return the same object and values.
151
                $clone = $charge->replicateQuietly();
152
                $clone->id = $charge->id; // The replicate() function doesn't copy the id.
153
                $clone->total_count = $settlement?->total_count ?? 0;
154
                $clone->total_amount = $settlement?->total_amount ?? 0;
155
                $clone->outflow = $outflows[$charge->id] ?? null;
156
                return $clone;
157
            });
158
    }
159
160
    /**
161
     * @param Session $session
162
     * @param Member $member|null
163
     *
164
     * @return Collection
165
     */
166
    public function getTotalCharges(Session $session, ?Member $member = null): Collection
167
    {
168
        $settlementFilter = fn(Builder $qs) =>
169
            $qs->whereIn('session_id', DB::table('sessions')
170
                ->select('id')
171
                ->where('round_id', $session->round_id)
172
                ->where('day_date', '<=', $session->day_date));
173
        $settlements = $this->getSettlements($settlementFilter, $member);
174
        $outflows = $this->getDisbursedAmounts($session, false);
175
        if($member !== null)
176
        {
177
            // The outflow part of each member id calculated by dividing each amount
178
            // by the number of members.
179
            $memberCount = $session->round->members->count();
180
            foreach($outflows as $outflow)
181
            {
182
                $outflow->total_amount /= $memberCount;
183
            }
184
        }
185
186
        return $session->round->charges
187
            ->map(function($charge) use($settlements, $outflows) {
188
                $settlement = $settlements[$charge->id] ?? null;
189
                // We need to clone the model, or else the getSessionCharges()
190
                // method will modify and return the same object and values.
191
                $clone = $charge->replicateQuietly();
192
                $clone->id = $charge->id; // The replicate() function doesn't copy the id.
193
                $clone->total_count = $settlement?->total_count ?? 0;
194
                $clone->total_amount = $settlement?->total_amount ?? 0;
195
                $clone->outflow = $outflows[$charge->id] ?? null;
196
                return $clone;
197
            });
198
    }
199
200
    /**
201
     * @param Session $session
202
     *
203
     * @return object
204
     */
205
    public function getLoan(Session $session): object
206
    {
207
        $loan = DB::table('loans')
208
            ->join('debts', 'loans.id', '=', 'debts.loan_id')
209
            ->select('debts.type', DB::raw('sum(debts.amount) as total_amount'))
210
            ->where('loans.session_id', $session->id)
211
            ->groupBy('debts.type')
212
            ->pluck('total_amount', 'type');
213
214
        return (object)[
215
            'principal' => $loan[Debt::TYPE_PRINCIPAL] ?? 0,
216
            'interest' => $loan[Debt::TYPE_INTEREST] ?? 0,
217
        ];
218
    }
219
220
    /**
221
     * @param Session $session
222
     *
223
     * @return object
224
     */
225
    public function getRefund(Session $session): object
226
    {
227
        $refund = $this->getRefundQuery()
228
            ->addSelect('debts.type')
229
            ->where('refunds.session_id', $session->id)
230
            ->groupBy('debts.type')
231
            ->pluck('total_amount', 'type');
232
        $partialRefund = DB::table('partial_refunds')
233
            ->join('debts', 'partial_refunds.debt_id', '=', 'debts.id')
234
            ->where('partial_refunds.session_id', $session->id)
235
            ->select('debts.type', DB::raw('sum(partial_refunds.amount) as total_amount'))
236
            ->groupBy('debts.type')
237
            ->pluck('total_amount', 'type');
238
239
        return (object)[
240
            'principal' => ($refund[Debt::TYPE_PRINCIPAL] ?? 0) +
241
                ($partialRefund[Debt::TYPE_PRINCIPAL] ?? 0),
242
            'interest' => ($refund[Debt::TYPE_INTEREST] ?? 0) +
243
                ($partialRefund[Debt::TYPE_INTEREST] ?? 0),
244
        ];
245
    }
246
247
    /**
248
     * @param Session $session
249
     *
250
     * @return object
251
     */
252
    public function getSaving(Session $session): object
253
    {
254
        $saving = DB::table('savings')
255
            ->select(DB::raw('sum(amount) as total_amount'),
256
                DB::raw('count(id) as total_count'))
257
            ->where('session_id', $session->id)
258
            ->first();
259
        if(!$saving->total_amount)
260
        {
261
            $saving->total_amount = 0;
262
        }
263
        if(!$saving->total_count)
264
        {
265
            $saving->total_count = 0;
266
        }
267
268
        return $saving;
269
    }
270
271
    /**
272
     * @param Session $session
273
     *
274
     * @return object
275
     */
276
    public function getTransfer(Session $session): object
277
    {
278
        $transfer = DB::table('settlements')
279
            ->join('bills', 'settlements.bill_id', '=', 'bills.id')
280
            ->select(DB::raw('sum(bills.amount) as total_amount'),
281
                DB::raw('count(settlements.id) as total_count'))
282
            ->where('settlements.session_id', $session->id)
283
            ->whereNotNull('settlements.fund_id')
284
            ->first();
285
        if(!$transfer->total_amount)
286
        {
287
            $transfer->total_amount = 0;
288
        }
289
        if(!$transfer->total_count)
290
        {
291
            $transfer->total_count = 0;
292
        }
293
294
        return $transfer;
295
    }
296
297
    /**
298
     * @param Session $session
299
     *
300
     * @return object
301
     */
302
    public function getOutflow(Session $session): object
303
    {
304
        $outflow = DB::table('outflows')
305
            ->select(DB::raw('sum(amount) as total_amount'),
306
                DB::raw('count(id) as total_count'))
307
            ->where('session_id', $session->id)
308
            ->first();
309
        if(!$outflow->total_amount)
310
        {
311
            $outflow->total_amount = 0;
312
        }
313
        if(!$outflow->total_count)
314
        {
315
            $outflow->total_count = 0;
316
        }
317
318
        return $outflow;
319
    }
320
}
321