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 onetime bills already paid, including in other rounds. |
||||
67 | ->orWhere(fn($qwd) => $qwd |
||||
68 | ->whereHas('charge', fn($qc) => |
||||
69 | $qc->once() |
||||
70 | ->whereIn('def_id', $charges->pluck('def_id'))) |
||||
71 | ->whereHas('member', fn($qm) => |
||||
72 | $qm->whereIn('def_id', $members->pluck('def_id'))) |
||||
73 | ->whereHas('bill', fn($qb) => $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
![]() |
|||||
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
|
|||||
125 | $bill->member_def_id === $member->def_id)) |
||||
0 ignored issues
–
show
|
|||||
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 $round |
||||
186 | * @param Collection|array $sessions |
||||
187 | * |
||||
188 | * @return void |
||||
189 | */ |
||||
190 | private function createBills(Charge $charge, Member $member, |
||||
191 | Round $round, Collection|array $sessions): void |
||||
192 | { |
||||
193 | if($charge->def->period_once) |
||||
0 ignored issues
–
show
|
|||||
194 | { |
||||
195 | $this->createOnetimeBill($charge, $member); |
||||
196 | return; |
||||
197 | } |
||||
198 | if($charge->def->period_round) |
||||
0 ignored issues
–
show
|
|||||
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 | if($billIds->count() === 0) |
||||
231 | { |
||||
232 | return; |
||||
233 | } |
||||
234 | |||||
235 | // Will fail if a settlement exists for any of those bills. |
||||
236 | $owner->onetime_bills()->delete(); |
||||
237 | $owner->round_bills()->delete(); |
||||
238 | $owner->session_bills()->delete(); |
||||
239 | $owner->libre_bills()->delete(); |
||||
240 | DB::table('bills')->whereIn('id', $billIds)->delete(); |
||||
241 | } |
||||
242 | |||||
243 | /** |
||||
244 | * @param Round $round |
||||
245 | * @param Charge $charge |
||||
246 | * |
||||
247 | * @return void |
||||
248 | */ |
||||
249 | public function chargeEnabled(Round $round, Charge $charge): void |
||||
250 | { |
||||
251 | if($charge->def->is_fine || $charge->def->is_variable || $round->members->count() === 0) |
||||
0 ignored issues
–
show
|
|||||
252 | { |
||||
253 | return; |
||||
254 | } |
||||
255 | |||||
256 | $this->initBills(collect([$charge]), $round->members); |
||||
0 ignored issues
–
show
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
![]() |
|||||
257 | foreach($round->members as $member) |
||||
258 | { |
||||
259 | $this->createBills($charge, $member, $round, $round->sessions); |
||||
260 | } |
||||
261 | $this->saveBills(); |
||||
262 | } |
||||
263 | |||||
264 | /** |
||||
265 | * @param Round $round |
||||
266 | * @param Charge $charge |
||||
267 | * |
||||
268 | * @return void |
||||
269 | */ |
||||
270 | public function chargeRemoved(Round $round, Charge $charge): void |
||||
0 ignored issues
–
show
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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
271 | { |
||||
272 | $this->deleteBills($charge, 'charge_id'); |
||||
273 | } |
||||
274 | |||||
275 | /** |
||||
276 | * @param Round $round |
||||
277 | * @param Member $member |
||||
278 | * |
||||
279 | * @return void |
||||
280 | */ |
||||
281 | public function memberEnabled(Round $round, Member $member): void |
||||
282 | { |
||||
283 | $charges = $round->charges->filter(fn($charge) => |
||||
284 | $charge->def->is_fee && $charge->def->is_fixed); |
||||
285 | if($charges->count() === 0) |
||||
286 | { |
||||
287 | return; |
||||
288 | } |
||||
289 | |||||
290 | $this->initBills($charges, collect([$member])); |
||||
0 ignored issues
–
show
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
![]() |
|||||
291 | foreach($charges as $charge) |
||||
292 | { |
||||
293 | $this->createBills($charge, $member, $round, $round->sessions); |
||||
294 | } |
||||
295 | $this->saveBills(); |
||||
296 | } |
||||
297 | |||||
298 | /** |
||||
299 | * @param Round $round |
||||
300 | * @param Member $member |
||||
301 | * |
||||
302 | * @return void |
||||
303 | */ |
||||
304 | public function memberRemoved(Round $round, Member $member): void |
||||
0 ignored issues
–
show
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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
305 | { |
||||
306 | $this->deleteBills($member, 'member_id'); |
||||
307 | } |
||||
308 | |||||
309 | /** |
||||
310 | * @param Round $round |
||||
311 | * @param Collection|array $sessions |
||||
312 | * |
||||
313 | * @return void |
||||
314 | */ |
||||
315 | public function sessionsCreated(Round $round, Collection|array $sessions): void |
||||
316 | { |
||||
317 | $charges = $round->charges->filter(fn($charge) => |
||||
318 | $charge->def->is_fee && $charge->def->is_fixed); |
||||
319 | if($charges->count() === 0 || $round->members->count() === 0) |
||||
320 | { |
||||
321 | return; |
||||
322 | } |
||||
323 | |||||
324 | $this->initBills($charges, $round->members); |
||||
325 | foreach($charges as $charge) |
||||
326 | { |
||||
327 | foreach($round->members as $member) |
||||
328 | { |
||||
329 | $this->createBills($charge, $member, $round, $sessions); |
||||
330 | } |
||||
331 | } |
||||
332 | $this->saveBills(); |
||||
333 | } |
||||
334 | |||||
335 | /** |
||||
336 | * @param Session $session |
||||
337 | * |
||||
338 | * @return void |
||||
339 | */ |
||||
340 | public function sessionDeleted(Session $session) |
||||
341 | { |
||||
342 | $billIds = DB::table('session_bills') |
||||
343 | ->where('session_id', $session->id) |
||||
344 | ->select('bill_id') |
||||
345 | ->union(DB::table('libre_bills') |
||||
346 | ->where('session_id', $session->id) |
||||
347 | ->select('bill_id')) |
||||
348 | ->pluck('bill_id'); |
||||
349 | // Will fail if a settlement exists for any of those bills. |
||||
350 | $session->session_bills()->delete(); |
||||
351 | $session->libre_bills()->delete(); |
||||
352 | DB::table('bills')->whereIn('id', $billIds)->delete(); |
||||
353 | } |
||||
354 | } |
||||
355 |