Passed
Push — main ( 1c458a...5a987a )
by Thierry
10:56 queued 05:26
created

BillSyncService::deleteBills()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 18
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 21
rs 9.6666
1
<?php
2
3
namespace Siak\Tontine\Service\Planning;
4
5
use Illuminate\Support\Facades\DB;
6
use Illuminate\Support\Collection;
7
use Siak\Tontine\Model\Bill;
8
use Siak\Tontine\Model\Charge;
9
use Siak\Tontine\Model\Member;
10
use Siak\Tontine\Model\OnetimeBill;
11
use Siak\Tontine\Model\Round;
12
use Siak\Tontine\Model\RoundBill;
13
use Siak\Tontine\Model\Session;
14
use DateTime;
15
16
use function collect;
17
use function count;
18
use function now;
19
20
class BillSyncService
21
{
22
    /**
23
     * @var DateTime
24
     */
25
    private DateTime $today;
26
27
    /**
28
     * @var array
29
     */
30
    private array $existingBills;
31
32
    /**
33
     * @var array
34
     */
35
    private array $newBills;
36
37
    /**
38
     * @param Collection $charges
39
     * @param Collection $members
40
     *
41
     * @return void
42
     */
43
    private function initBills(Collection $charges, Collection $members): void
44
    {
45
        $this->today = now();
46
        $this->newBills = [
47
            'onetime_bills' => [],
48
            'round_bills' => [],
49
            'session_bills' => [],
50
        ];
51
        $this->existingBills = [
52
            'onetime_bills' => [],
53
            'round_bills' => [],
54
        ];
55
56
        // Avoid duplicates creation.
57
        $this->existingBills['onetime_bills'] = OnetimeBill::select([
58
                DB::raw('charges.def_id as charge_def_id'),
59
                DB::raw('members.def_id as member_def_id'),
60
            ])
61
            ->where(fn($qw) => $qw
62
                // Take the bills in the current round.
63
                ->orWhere(fn($qwb) => $qwb
64
                    ->whereIn('charge_id', $charges->pluck('id'))
65
                    ->whereIn('member_id', $members->pluck('id')))
66
                // Take the bills already paid, included in other rounds.
67
                ->orWhere(fn($qwd) => $qwd
68
                    ->whereHas('charge', fn($qc) =>
69
                        $qc->whereIn('def_id', $charges->pluck('def_id')))
70
                    ->whereHas('member', fn($qm) =>
71
                        $qm->whereIn('def_id', $members->pluck('def_id')))
72
                    ->whereHas('bill', fn($qb) =>
73
                        $qb->whereHas('settlement')))
74
            )
75
            ->join('charges', 'charges.id', '=', 'onetime_bills.charge_id')
76
            ->join('members', 'members.id', '=', 'onetime_bills.member_id')
77
            ->distinct()
78
            ->get();
79
        $this->existingBills['round_bills'] = RoundBill::select(['charge_id', 'member_id'])
80
            ->whereIn('charge_id', $charges->pluck('id'))
81
            ->whereIn('member_id', $members->pluck('id'))
82
            ->get();
83
    }
84
85
    /**
86
     * @return void
87
     */
88
    private function saveBills(): void
89
    {
90
        foreach(['onetime_bills', 'round_bills', 'session_bills'] as $table)
91
        {
92
            if(count($this->newBills[$table]) > 0)
93
            {
94
                DB::table($table)->insert($this->newBills[$table]);
95
            }
96
        }
97
    }
98
99
    /**
100
     * @param Charge $charge
101
     *
102
     * @return Bill
103
     */
104
    private function createBill(Charge $charge): Bill
105
    {
106
        return Bill::create([
0 ignored issues
show
Bug Best Practice introduced by
The expression return Siak\Tontine\Mode...d_at' => $this->today)) could return the type Illuminate\Database\Eloq...gHasThroughRelationship which is incompatible with the type-hinted return Siak\Tontine\Model\Bill. Consider adding an additional type-check to rule them out.
Loading history...
107
            'charge' => $charge->def->name,
108
            'amount' => $charge->def->amount,
109
            'lendable' => $charge->def->lendable,
110
            'issued_at' => $this->today,
111
        ]);
112
    }
113
114
    /**
115
     * @param Charge $charge
116
     * @param Member $member
117
     *
118
     * @return void
119
     */
120
    private function createOnetimeBill(Charge $charge, Member $member): void
121
    {
122
        if($this->existingBills['onetime_bills']
123
            ->contains(fn(OnetimeBill $bill) =>
124
                $bill->charge_def_id === $charge->def_id &&
0 ignored issues
show
Bug introduced by
The property charge_def_id does not exist on Siak\Tontine\Model\OnetimeBill. Did you mean charge?
Loading history...
125
                $bill->member_def_id === $member->def_id))
0 ignored issues
show
Bug introduced by
The property member_def_id does not exist on Siak\Tontine\Model\OnetimeBill. Did you mean member?
Loading history...
126
        {
127
            return;
128
        }
129
130
        $bill = $this->createBill($charge);
131
        $this->newBills['onetime_bills'][] = [
132
            'bill_id' => $bill->id,
133
            'charge_id' => $charge->id,
134
            'member_id' => $member->id,
135
        ];
136
    }
137
138
    /**
139
     * @param Charge $charge
140
     * @param Member $member
141
     * @param Round $round
142
     *
143
     * @return void
144
     */
145
    private function createRoundBill(Charge $charge, Member $member, Round $round): void
146
    {
147
        if($this->existingBills['round_bills']
148
            ->contains(fn(RoundBill $bill) =>
149
                $bill->charge_id === $charge->id &&
150
                $bill->member_id === $member->id))
151
        {
152
            return;
153
        }
154
155
        $bill = $this->createBill($charge);
156
        $this->newBills['round_bills'][] = [
157
            'bill_id' => $bill->id,
158
            'charge_id' => $charge->id,
159
            'member_id' => $member->id,
160
            'round_id' => $round->id,
161
        ];
162
    }
163
164
    /**
165
     * @param Charge $charge
166
     * @param Member $member
167
     * @param Session $session
168
     *
169
     * @return void
170
     */
171
    private function createSessionBill(Charge $charge, Member $member, Session $session): void
172
    {
173
        $bill = $this->createBill($charge);
174
        $this->newBills['session_bills'][] = [
175
            'bill_id' => $bill->id,
176
            'charge_id' => $charge->id,
177
            'member_id' => $member->id,
178
            'session_id' => $session->id,
179
        ];
180
    }
181
182
    /**
183
     * @param Charge $charge
184
     * @param Member $member
185
     * @param Round|null $round
186
     * @param Collection|array $sessions
187
     *
188
     * @return void
189
     */
190
    private function createBills(Charge $charge, Member $member,
191
        Round|null $round, Collection|array $sessions): void
192
    {
193
        if($round !== null && $charge->def->period_once)
0 ignored issues
show
Bug introduced by
The property period_once does not exist on Siak\Tontine\Model\ChargeDef. Did you mean period?
Loading history...
194
        {
195
            $this->createOnetimeBill($charge, $member);
196
            return;
197
        }
198
        if($round !== null && $charge->def->period_round)
0 ignored issues
show
Bug introduced by
The property period_round does not exist on Siak\Tontine\Model\ChargeDef. Did you mean period?
Loading history...
199
        {
200
            $this->createRoundBill($charge, $member, $round);
201
            return;
202
        }
203
        foreach($sessions as $session)
204
        {
205
            $this->createSessionBill($charge, $member, $session);
206
        }
207
    }
208
209
    /**
210
     * @param Charge|Member $owner
211
     * @param string $foreignKey
212
     *
213
     * @return void
214
     */
215
    private function deleteBills(Charge|Member $owner, string $foreignKey): void
216
    {
217
        $billIds = DB::table('onetime_bills')
218
            ->where($foreignKey, $owner->id)
219
            ->select('bill_id')
220
            ->union(DB::table('round_bills')
221
                ->where($foreignKey, $owner->id)
222
                ->select('bill_id'))
223
            ->union(DB::table('session_bills')
224
                ->where($foreignKey, $owner->id)
225
                ->select('bill_id'))
226
            ->union(DB::table('libre_bills')
227
                ->where($foreignKey, $owner->id)
228
                ->select('bill_id'))
229
            ->pluck('bill_id');
230
        // Will fail if a settlement exists for any of those bills.
231
        $owner->onetime_bills()->delete();
232
        $owner->round_bills()->delete();
233
        $owner->session_bills()->delete();
234
        $owner->libre_bills()->delete();
235
        DB::table('bills')->whereIn('id', $billIds)->delete();
236
    }
237
238
    /**
239
     * @param Round $round
240
     * @param Charge $charge
241
     *
242
     * @return void
243
     */
244
    public function chargeEnabled(Round $round, Charge $charge): void
245
    {
246
        if($charge->def->is_fine || $charge->def->is_variable || $round->members->count() === 0)
0 ignored issues
show
Bug introduced by
The property is_variable does not seem to exist on Siak\Tontine\Model\ChargeDef. 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...
Bug introduced by
The property is_fine does not seem to exist on Siak\Tontine\Model\ChargeDef. 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...
247
        {
248
            return;
249
        }
250
251
        $this->initBills(collect([$charge]), $round->members);
0 ignored issues
show
Bug introduced by
array($charge) of type array<integer,Siak\Tontine\Model\Charge> 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

251
        $this->initBills(collect(/** @scrutinizer ignore-type */ [$charge]), $round->members);
Loading history...
252
        foreach($round->members as $member)
253
        {
254
            $this->createBills($charge, $member, $round, $round->sessions);
255
        }
256
        $this->saveBills();
257
    }
258
259
    /**
260
     * @param Round $round
261
     * @param Charge $charge
262
     *
263
     * @return void
264
     */
265
    public function chargeRemoved(Round $round, Charge $charge): void
0 ignored issues
show
Unused Code introduced by
The parameter $round is not used and could be removed. ( Ignorable by Annotation )

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

265
    public function chargeRemoved(/** @scrutinizer ignore-unused */ Round $round, Charge $charge): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
266
    {
267
        $this->deleteBills($charge, 'charge_id');
268
    }
269
270
    /**
271
     * @param Round $round
272
     * @param Member $member
273
     *
274
     * @return void
275
     */
276
    public function memberEnabled(Round $round, Member $member): void
277
    {
278
        $charges = $round->charges->filter(fn($charge) =>
279
            $charge->def->is_fee && $charge->def->is_fixed);
280
        if($charges->count() === 0)
281
        {
282
            return;
283
        }
284
285
        $this->initBills($charges, collect([$member]));
0 ignored issues
show
Bug introduced by
array($member) of type array<integer,Siak\Tontine\Model\Member> 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

285
        $this->initBills($charges, collect(/** @scrutinizer ignore-type */ [$member]));
Loading history...
286
        foreach($round->charges as $charge)
287
        {
288
            $this->createBills($charge, $member, $round, $round->sessions);
289
        }
290
        $this->saveBills();
291
    }
292
293
    /**
294
     * @param Round $round
295
     * @param Member $member
296
     *
297
     * @return void
298
     */
299
    public function memberRemoved(Round $round, Member $member): void
0 ignored issues
show
Unused Code introduced by
The parameter $round is not used and could be removed. ( Ignorable by Annotation )

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

299
    public function memberRemoved(/** @scrutinizer ignore-unused */ Round $round, Member $member): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
300
    {
301
        $this->deleteBills($member, 'member_id');
302
    }
303
304
    /**
305
     * @param Round $round
306
     * @param Collection|array $sessions
307
     *
308
     * @return void
309
     */
310
    public function sessionsCreated(Round $round, Collection|array $sessions): void
311
    {
312
        $charges = $round->charges->filter(fn($charge) =>
313
            $charge->def->is_fee && $charge->def->is_fixed);
314
        if($charges->count() === 0 || $round->members->count() === 0)
315
        {
316
            return;
317
        }
318
319
        $this->initBills($charges, $round->members);
320
        foreach($round->charges as $charge)
321
        {
322
            foreach($round->members as $member)
323
            {
324
                $this->createBills($charge, $member, $round, $sessions);
325
            }
326
        }
327
        $this->saveBills();
328
    }
329
330
    /**
331
     * @param Session $session
332
     *
333
     * @return void
334
     */
335
    public function sessionDeleted(Session $session)
336
    {
337
        $billIds = DB::table('session_bills')
338
            ->where('session_id', $session->id)
339
            ->select('bill_id')
340
            ->union(DB::table('libre_bills')
341
                ->where('session_id', $session->id)
342
                ->select('bill_id'))
343
            ->pluck('bill_id');
344
        // Will fail if a settlement exists for any of those bills.
345
        $session->session_bills()->delete();
346
        $session->libre_bills()->delete();
347
        DB::table('bills')->whereIn('id', $billIds)->delete();
348
    }
349
}
350