Passed
Push — main ( 5b7914...28aa50 )
by Thierry
05:31
created

BillService::getMember()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 17
c 1
b 0
f 0
nc 4
nop 3
dl 0
loc 26
rs 9.7
1
<?php
2
3
namespace Siak\Tontine\Service\Meeting\Charge;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Relations\Relation;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Facades\DB;
9
use Siak\Tontine\Exception\MessageException;
10
use Siak\Tontine\Model\Bill;
11
use Siak\Tontine\Model\Charge;
12
use Siak\Tontine\Model\LibreBill;
13
use Siak\Tontine\Model\Session;
14
use Siak\Tontine\Model\Member;
15
use Siak\Tontine\Model\Settlement;
16
use Siak\Tontine\Service\LocaleService;
17
use Siak\Tontine\Service\TenantService;
18
19
use function strtolower;
20
use function trans;
21
use function trim;
22
23
class BillService
24
{
25
    /**
26
     * @param SettlementTargetService $targetService
27
     * @param TenantService $tenantService
28
     * @param LocaleService $localeService
29
     */
30
    public function __construct(protected SettlementTargetService $targetService,
31
        protected TenantService $tenantService, protected LocaleService $localeService)
32
    {}
33
34
    /**
35
     * @param Charge $charge
36
     * @param Session $session
37
     * @param string $search
38
     * @param bool $onlyPaid|null
39
     *
40
     * @return Builder
41
     */
42
    private function getBillsQuery(Charge $charge, Session $session,
43
        string $search = '', ?bool $onlyPaid = null): Builder|Relation
44
    {
45
        $search = trim($search);
46
        return Bill::ofSession($session)->with('session')
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\Database\Eloq...uent\Relations\Relation. Consider adding an additional type-check to rule them out.
Loading history...
47
            ->where('charge_id', $charge->id)
48
            ->when($search !== '', function($query) use($search) {
49
                $query->where(DB::raw('lower(member)'), 'like', $search);
50
            })
51
            ->when($onlyPaid === false, function($query) {
52
                $query->unpaid();
53
            })
54
            ->when($onlyPaid === true, function($query) use($session) {
55
                $query->whereHas('settlement', function(Builder $query) use($session) {
56
                    $query->where('session_id', $session->id);
57
                });
58
            });
59
    }
60
61
    /**
62
     * @param Charge $charge
63
     * @param Session $session
64
     * @param string $search
65
     * @param bool $onlyPaid|null
66
     *
67
     * @return int
68
     */
69
    public function getBillCount(Charge $charge, Session $session,
70
        string $search = '', ?bool $onlyPaid = null): int
71
    {
72
        return $this->getBillsQuery($charge, $session, $search, $onlyPaid)->count();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getBillsQu...ch, $onlyPaid)->count() could return the type Illuminate\Database\Eloq...uent\Relations\Relation which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
73
    }
74
75
    /**
76
     * @param Charge $charge
77
     * @param Session $session
78
     * @param string $search
79
     * @param bool $onlyPaid|null
80
     * @param int $page
81
     *
82
     * @return Collection
83
     */
84
    public function getBills(Charge $charge, Session $session,
85
        string $search = '', ?bool $onlyPaid = null, int $page = 0): Collection
86
    {
87
        return $this->getBillsQuery($charge, $session, $search, $onlyPaid)
88
            ->with('settlement')
89
            ->page($page, $this->tenantService->getLimit())
90
            ->orderBy('member', 'asc')
91
            ->orderBy('bill_date', 'asc')
0 ignored issues
show
Bug introduced by
'bill_date' 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

91
            ->orderBy(/** @scrutinizer ignore-type */ 'bill_date', 'asc')
Loading history...
92
            ->get();
93
    }
94
95
    /**
96
     * @param Charge $charge
97
     * @param Session $session
98
     * @param int $billId
99
     *
100
     * @return Bill|null
101
     */
102
    public function getBill(Charge $charge, Session $session, int $billId): ?Bill
103
    {
104
        return $this->getBillsQuery($charge, $session)->find($billId);
105
    }
106
107
    /**
108
     * @param Charge $charge
109
     * @param Session $session
110
     *
111
     * @return Bill
112
     */
113
    public function getSettlementCount(Charge $charge, Session $session): Bill
114
    {
115
        return $this->getBillsQuery($charge, $session, '', true)
116
            ->select(DB::raw('count(*) as total'), DB::raw('sum(bills.amount) as amount'))
117
            ->first();
118
    }
119
120
    /**
121
     * @param Charge $charge
122
     * @param Session $session
123
     * @param string $search
124
     * @param bool $filter|null
125
     *
126
     * @return Builder|Relation
127
     */
128
    private function getMembersQuery(Charge $charge, Session $session,
129
        string $search = '', ?bool $filter = null): Builder|Relation
130
    {
131
        $filterFunction = function($query) use($charge, $session) {
132
            $query->where('charge_id', $charge->id)->where('session_id', $session->id);
133
        };
134
135
        return $this->tenantService->tontine()->members()->active()
136
            ->when($search !== '', function($query) use($search) {
137
                return $query->where(DB::raw('lower(name)'), 'like', '%' . strtolower($search) . '%');
138
            })
139
            ->when($filter === false, function($query) use($filterFunction) {
140
                return $query->whereDoesntHave('libre_bills', $filterFunction);
141
            })
142
            ->when($filter === true, function($query) use($filterFunction) {
143
                $query->whereHas('libre_bills', $filterFunction);
144
            });
145
    }
146
147
    /**
148
     * @param Charge $charge
149
     * @param Session $session
150
     * @param string $search
151
     * @param bool $filter|null
152
     * @param int $page
153
     *
154
     * @return Collection
155
     */
156
    public function getMembers(Charge $charge, Session $session,
157
        string $search = '', ?bool $filter = null, int $page = 0): Collection
158
    {
159
        $members = $this->getMembersQuery($charge, $session, $search, $filter)
160
            ->page($page, $this->tenantService->getLimit())
161
            ->with([
162
                'libre_bills' => fn($query) => $query
163
                    ->where('charge_id', $charge->id)->where('session_id', $session->id),
164
            ])
165
            ->orderBy('name', 'asc')
166
            ->get()
167
            ->each(function($member) {
168
                $member->remaining = 0;
169
                $member->bill = $member->libre_bills->first()?->bill ?? null;
170
            });
171
        // Check if there is a settlement target.
172
        if(!($target = $this->targetService->getTarget($charge, $session)))
173
        {
174
            return $members;
175
        }
176
177
        $settlements = $this->targetService->getMembersSettlements($charge, $target, $members);
178
179
        return $members->each(function($member) use($target, $settlements) {
180
            $member->target = $target->amount;
181
            $paid = $settlements[$member->id] ?? 0;
182
            $member->remaining = $target->amount > $paid ? $target->amount - $paid : 0;
183
        });
184
    }
185
186
    /**
187
     * @param Charge $charge
188
     * @param Session $session
189
     * @param string $search
190
     * @param bool $filter|null
191
     *
192
     * @return int
193
     */
194
    public function getMemberCount(Charge $charge, Session $session,
195
        string $search = '', ?bool $filter = null): int
196
    {
197
        return $this->getMembersQuery($charge, $session, $search, $filter)->count();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getMembers...arch, $filter)->count() could return the type Illuminate\Database\Eloq...uent\Relations\Relation which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
198
    }
199
200
    /**
201
     * @param Charge $charge
202
     * @param Session $session
203
     * @param int $memberId
204
     *
205
     * @return Member|null
206
     */
207
    public function getMember(Charge $charge, Session $session, int $memberId): ?Member
208
    {
209
        $members = $this->getMembersQuery($charge, $session)
210
            ->where('id', $memberId)
211
            ->with([
212
                'libre_bills.bill',
213
                'libre_bills' => fn($query) => $query
214
                    ->where('charge_id', $charge->id)->where('session_id', $session->id),
215
            ])
216
            ->get();
217
        if($members->count() === 0)
218
        {
219
            return null;
220
        }
221
222
        $member = $members->first();
223
        $member->bill = $member->libre_bills->first()?->bill ?? null;
224
        $member->remaining = 0;
225
        // Check if there is a settlement target.
226
        if(($target = $this->targetService->getTarget($charge, $session)) !== null)
227
        {
228
            $settlements = $this->targetService->getMembersSettlements($charge, $target, $members);
229
            $paid = $settlements[$member->id] ?? 0;
230
            $member->remaining = $target->amount > $paid ? $target->amount - $paid : 0;
231
        }
232
        return $member;
233
    }
234
235
    /**
236
     * @param Charge $charge
237
     * @param Session $session
238
     * @param int $memberId
239
     * @param bool $paid
240
     * @param float $amount
241
     *
242
     * @return void
243
     */
244
    public function createBill(Charge $charge, Session $session,
245
        int $memberId, bool $paid, float $amount = 0): void
246
    {
247
        $member = $this->tenantService->tontine()->members()->find($memberId);
248
        if(!$member)
249
        {
250
            throw new MessageException(trans('tontine.member.errors.not_found'));
251
        }
252
253
        if($amount !== 0)
254
        {
255
            $amount = $this->localeService->convertMoneyToInt($amount);
256
        }
257
        DB::transaction(function() use($charge, $session, $member, $paid, $amount) {
258
            $bill = Bill::create([
259
                'charge' => $charge->name,
260
                'amount' => $charge->has_amount ? $charge->amount : $amount,
261
                'lendable' => $charge->lendable,
262
                'issued_at' => now(),
263
            ]);
264
            $libreBill = new LibreBill();
265
            $libreBill->charge()->associate($charge);
266
            $libreBill->member()->associate($member);
267
            $libreBill->session()->associate($session);
268
            $libreBill->bill()->associate($bill);
269
            $libreBill->save();
270
            if($paid)
271
            {
272
                $settlement = new Settlement();
273
                $settlement->bill()->associate($bill);
274
                $settlement->session()->associate($session);
275
                $settlement->save();
276
            }
277
        });
278
    }
279
280
    /**
281
     * @param Charge $charge
282
     * @param Session $session
283
     * @param int $memberId
284
     *
285
     * @return LibreBill|null
286
     */
287
    public function getMemberBill(Charge $charge, Session $session, int $memberId): ?LibreBill
288
    {
289
        if(!($member = $this->tenantService->tontine()->members()->find($memberId)))
290
        {
291
            throw new MessageException(trans('tontine.member.errors.not_found'));
292
        }
293
294
        return LibreBill::with(['bill.settlement'])
295
            ->where('charge_id', $charge->id)
296
            ->where('session_id', $session->id)
297
            ->where('member_id', $member->id)
298
            ->first();
299
    }
300
301
    /**
302
     * @param Charge $charge
303
     * @param Session $session
304
     * @param int $memberId
305
     * @param float $amount
306
     *
307
     * @return void
308
     */
309
    public function updateBill(Charge $charge, Session $session, int $memberId, float $amount): void
310
    {
311
        if(!($libreBill = $this->getMemberBill($charge, $session, $memberId)))
312
        {
313
            return; // throw new MessageException(trans('tontine.bill.errors.not_found'));
314
        }
315
316
        $libreBill->bill->update([
317
            'amount'=> $this->localeService->convertMoneyToInt($amount),
318
        ]);
319
    }
320
321
    /**
322
     * @param Charge $charge
323
     * @param Session $session
324
     * @param int $memberId
325
     *
326
     * @return void
327
     */
328
    public function deleteBill(Charge $charge, Session $session, int $memberId): void
329
    {
330
        if(!($libreBill = $this->getMemberBill($charge, $session, $memberId)))
331
        {
332
            return; // throw new MessageException(trans('tontine.bill.errors.not_found'));
333
        }
334
335
        DB::transaction(function() use($libreBill, $session) {
336
            $bill = $libreBill->bill;
337
            $libreBill->delete();
338
            if($bill !== null)
339
            {
340
                // Delete the settlement only if it is on the same session
341
                if($bill->settlement !== null && $bill->settlement->session_id === $session->id)
342
                {
343
                    $bill->settlement->delete();
344
                }
345
                $bill->delete();
346
            }
347
        });
348
    }
349
}
350