BillRepository   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 651
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 60
eloc 229
dl 0
loc 651
rs 3.6
c 0
b 0
f 0

30 Methods

Rating   Name   Duplication   Size   Complexity  
A setUser() 0 3 1
A getRulesForBill() 0 7 1
A searchBill() 0 5 1
A nextExpectedMatch() 0 36 4
A getYearAverage() 0 22 3
A getPayDatesInRange() 0 33 3
A getRulesForBills() 0 17 3
A linkCollectionToBill() 0 8 2
A nextDateMatch() 0 27 3
A findBill() 0 21 5
A getOverallAverage() 0 18 3
A getPaginator() 0 5 1
A __construct() 0 4 2
A destroy() 0 7 1
A getPaidDatesInRange() 0 7 1
A getActiveBills() 0 9 1
A getNoteText() 0 9 2
A getBills() 0 4 1
A getBillsForAccounts() 0 25 1
A getBillsUnpaidInRangePerCurrency() 0 24 3
A getBillsUnpaidInRange() 0 22 3
A getBillsPaidInRangePerCurrency() 0 19 3
A getBillsPaidInRange() 0 17 3
A update() 0 6 1
A store() 0 7 1
A findByName() 0 14 3
A unlinkAll() 0 3 1
A getAttachments() 0 3 1
A getByIds() 0 3 1
A find() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like BillRepository often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BillRepository, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * BillRepository.php
4
 * Copyright (c) 2019 [email protected]
5
 *
6
 * This file is part of Firefly III (https://github.com/firefly-iii).
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License as
10
 * published by the Free Software Foundation, either version 3 of the
11
 * License, or (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU Affero General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public License
19
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20
 */
21
declare(strict_types=1);
22
23
namespace FireflyIII\Repositories\Bill;
24
25
use Carbon\Carbon;
26
use DB;
27
use FireflyIII\Exceptions\FireflyException;
28
use FireflyIII\Factory\BillFactory;
29
use FireflyIII\Models\Bill;
30
use FireflyIII\Models\Note;
31
use FireflyIII\Models\Transaction;
32
use FireflyIII\Models\TransactionJournal;
33
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
34
use FireflyIII\Services\Internal\Destroy\BillDestroyService;
35
use FireflyIII\Services\Internal\Update\BillUpdateService;
36
use FireflyIII\Support\CacheProperties;
37
use FireflyIII\User;
38
use Illuminate\Database\Query\JoinClause;
39
use Illuminate\Pagination\LengthAwarePaginator;
40
use Illuminate\Support\Collection;
41
use Log;
42
43
/**
44
 * Class BillRepository.
45
 *
46
 */
47
class BillRepository implements BillRepositoryInterface
48
{
49
    /** @var User */
50
    private $user;
51
52
    /**
53
     * Constructor.
54
     */
55
    public function __construct()
56
    {
57
        if ('testing' === config('app.env')) {
58
            Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
59
        }
60
    }
61
62
    /**
63
     * @param Bill $bill
64
     *
65
     * @return bool
66
     *
67
68
     */
69
    public function destroy(Bill $bill): bool
70
    {
71
        /** @var BillDestroyService $service */
72
        $service = app(BillDestroyService::class);
73
        $service->destroy($bill);
74
75
        return true;
76
    }
77
78
    /**
79
     * Find a bill by ID.
80
     *
81
     * @param int $billId
82
     *
83
     * @return Bill
84
     */
85
    public function find(int $billId): ?Bill
86
    {
87
        return $this->user->bills()->find($billId);
88
    }
89
90
    /**
91
     * Find bill by parameters.
92
     *
93
     * @param int|null    $billId
94
     * @param string|null $billName
95
     *
96
     * @return Bill|null
97
     */
98
    public function findBill(?int $billId, ?string $billName): ?Bill
99
    {
100
        if (null !== $billId) {
101
            $searchResult = $this->find((int)$billId);
102
            if (null !== $searchResult) {
103
                Log::debug(sprintf('Found bill based on #%d, will return it.', $billId));
104
105
                return $searchResult;
106
            }
107
        }
108
        if (null !== $billName) {
109
            $searchResult = $this->findByName((string)$billName);
110
            if (null !== $searchResult) {
111
                Log::debug(sprintf('Found bill based on "%s", will return it.', $billName));
112
113
                return $searchResult;
114
            }
115
        }
116
        Log::debug('Found nothing');
117
118
        return null;
119
    }
120
121
    /**
122
     * Find a bill by name.
123
     *
124
     * @param string $name
125
     *
126
     * @return Bill
127
     */
128
    public function findByName(string $name): ?Bill
129
    {
130
        $bills = $this->user->bills()->get(['bills.*']);
131
132
        // TODO no longer need to loop like this
133
134
        /** @var Bill $bill */
135
        foreach ($bills as $bill) {
136
            if ($bill->name === $name) {
137
                return $bill;
138
            }
139
        }
140
141
        return null;
142
    }
143
144
    /**
145
     * @return Collection
146
     */
147
    public function getActiveBills(): Collection
148
    {
149
        /** @var Collection $set */
150
        $set = $this->user->bills()
151
                          ->where('active', 1)
152
                          ->orderBy('bills.name', 'ASC')
153
                          ->get(['bills.*', DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount'),]);
154
155
        return $set;
156
    }
157
158
    /**
159
     * Get all attachments.
160
     *
161
     * @param Bill $bill
162
     *
163
     * @return Collection
164
     */
165
    public function getAttachments(Bill $bill): Collection
166
    {
167
        return $bill->attachments()->get();
168
    }
169
170
    /**
171
     * @return Collection
172
     */
173
    public function getBills(): Collection
174
    {
175
        /** @var Collection $set */
176
        return $this->user->bills()->orderBy('active', 'DESC')->orderBy('name', 'ASC')->get();
177
    }
178
179
    /**
180
     * @param Collection $accounts
181
     *
182
     * @return Collection
183
     */
184
    public function getBillsForAccounts(Collection $accounts): Collection
185
    {
186
        $fields = ['bills.id', 'bills.created_at', 'bills.updated_at', 'bills.deleted_at', 'bills.user_id', 'bills.name', 'bills.amount_min',
187
                   'bills.amount_max', 'bills.date', 'bills.transaction_currency_id', 'bills.repeat_freq', 'bills.skip', 'bills.automatch', 'bills.active',];
188
        $ids    = $accounts->pluck('id')->toArray();
189
        $set    = $this->user->bills()
190
                             ->leftJoin(
191
                                 'transaction_journals',
192
                                 static function (JoinClause $join) {
193
                                     $join->on('transaction_journals.bill_id', '=', 'bills.id')->whereNull('transaction_journals.deleted_at');
194
                                 }
195
                             )
196
                             ->leftJoin(
197
                                 'transactions',
198
                                 static function (JoinClause $join) {
199
                                     $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0);
200
                                 }
201
                             )
202
                             ->whereIn('transactions.account_id', $ids)
203
                             ->whereNull('transaction_journals.deleted_at')
204
                             ->orderBy('bills.active', 'DESC')
205
                             ->orderBy('bills.name', 'ASC')
206
                             ->groupBy($fields)
207
                             ->get($fields);
208
        return $set;
209
    }
210
211
    /**
212
     * Get the total amount of money paid for the users active bills in the date range given.
213
     * This amount will be negative (they're expenses). This method is equal to
214
     * getBillsUnpaidInRange. So the debug comments are gone.
215
     *
216
     * @param Carbon $start
217
     * @param Carbon $end
218
     *
219
     * @return string
220
     */
221
    public function getBillsPaidInRange(Carbon $start, Carbon $end): string
222
    {
223
        $bills = $this->getActiveBills();
224
        $sum   = '0';
225
        /** @var Bill $bill */
226
        foreach ($bills as $bill) {
227
            /** @var Collection $set */
228
            $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
229
            if ($set->count() > 0) {
230
                $journalIds = $set->pluck('id')->toArray();
231
                $amount     = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount');
232
                $sum        = bcadd($sum, $amount);
233
                Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum));
234
            }
235
        }
236
237
        return $sum;
238
    }
239
240
    /**
241
     * Get the total amount of money paid for the users active bills in the date range given,
242
     * grouped per currency.
243
     *
244
     * @param Carbon $start
245
     * @param Carbon $end
246
     *
247
     * @return array
248
     */
249
    public function getBillsPaidInRangePerCurrency(Carbon $start, Carbon $end): array
250
    {
251
        $bills  = $this->getActiveBills();
252
        $return = [];
253
        /** @var Bill $bill */
254
        foreach ($bills as $bill) {
255
            /** @var Collection $set */
256
            $set        = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
257
            $currencyId = (int)$bill->transaction_currency_id;
258
            if ($set->count() > 0) {
259
                $journalIds          = $set->pluck('id')->toArray();
260
                $amount              = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount');
261
                $return[$currencyId] = $return[$currencyId] ?? '0';
262
                $return[$currencyId] = bcadd($amount, $return[$currencyId]);
263
                Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (currency %d)', $amount, $return[$currencyId], $currencyId));
264
            }
265
        }
266
267
        return $return;
268
    }
269
270
    /**
271
     * Get the total amount of money due for the users active bills in the date range given. This amount will be positive.
272
     *
273
     * @param Carbon $start
274
     * @param Carbon $end
275
     *
276
     * @return string
277
     */
278
    public function getBillsUnpaidInRange(Carbon $start, Carbon $end): string
279
    {
280
        $bills = $this->getActiveBills();
281
        $sum   = '0';
282
        /** @var Bill $bill */
283
        foreach ($bills as $bill) {
284
            Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
285
            $dates = $this->getPayDatesInRange($bill, $start, $end);
286
            $count = $bill->transactionJournals()->after($start)->before($end)->count();
287
            $total = $dates->count() - $count;
288
289
            Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
290
291
            if ($total > 0) {
292
                $average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2');
293
                $multi   = bcmul($average, (string)$total);
294
                $sum     = bcadd($sum, $multi);
295
                Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum));
296
            }
297
        }
298
299
        return $sum;
300
    }
301
302
    /**
303
     * Get the total amount of money due for the users active bills in the date range given.
304
     *
305
     * @param Carbon $start
306
     * @param Carbon $end
307
     *
308
     * @return array
309
     */
310
    public function getBillsUnpaidInRangePerCurrency(Carbon $start, Carbon $end): array
311
    {
312
        $bills  = $this->getActiveBills();
313
        $return = [];
314
        /** @var Bill $bill */
315
        foreach ($bills as $bill) {
316
            Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
317
            $dates      = $this->getPayDatesInRange($bill, $start, $end);
318
            $count      = $bill->transactionJournals()->after($start)->before($end)->count();
319
            $total      = $dates->count() - $count;
320
            $currencyId = (int)$bill->transaction_currency_id;
321
322
            Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
323
324
            if ($total > 0) {
325
                $average             = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2');
326
                $multi               = bcmul($average, (string)$total);
327
                $return[$currencyId] = $return[$currencyId] ?? '0';
328
                $return[$currencyId] = bcadd($return[$currencyId], $multi);
329
                Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (for currency %d)', $multi, $return[$currencyId], $currencyId));
330
            }
331
        }
332
333
        return $return;
334
    }
335
336
    /**
337
     * Get all bills with these ID's.
338
     *
339
     * @param array $billIds
340
     *
341
     * @return Collection
342
     */
343
    public function getByIds(array $billIds): Collection
344
    {
345
        return $this->user->bills()->whereIn('id', $billIds)->get();
346
    }
347
348
    /**
349
     * Get text or return empty string.
350
     *
351
     * @param Bill $bill
352
     *
353
     * @return string
354
     */
355
    public function getNoteText(Bill $bill): string
356
    {
357
        /** @var Note $note */
358
        $note = $bill->notes()->first();
359
        if (null !== $note) {
360
            return (string)$note->text;
361
        }
362
363
        return '';
364
    }
365
366
    /**
367
     * @param Bill $bill
368
     *
369
     * @return string
370
     */
371
    public function getOverallAverage(Bill $bill): string
372
    {
373
        /** @var JournalRepositoryInterface $repos */
374
        $repos = app(JournalRepositoryInterface::class);
375
        $repos->setUser($this->user);
376
        $journals = $bill->transactionJournals()->get();
377
        $sum      = '0';
378
        $count    = (string)$journals->count();
379
        /** @var TransactionJournal $journal */
380
        foreach ($journals as $journal) {
381
            $sum = bcadd($sum, $repos->getJournalTotal($journal));
382
        }
383
        $avg = '0';
384
        if ($journals->count() > 0) {
385
            $avg = bcdiv($sum, $count);
386
        }
387
388
        return $avg;
389
    }
390
391
    /**
392
     * @param int $size
393
     *
394
     * @return LengthAwarePaginator
395
     */
396
    public function getPaginator(int $size): LengthAwarePaginator
397
    {
398
        return $this->user->bills()
399
                          ->orderBy('active', 'DESC')
400
                          ->orderBy('name', 'ASC')->paginate($size);
401
    }
402
403
    /**
404
     * The "paid dates" list is a list of dates of transaction journals that are linked to this bill.
405
     *
406
     * @param Bill   $bill
407
     * @param Carbon $start
408
     * @param Carbon $end
409
     *
410
     * @return Collection
411
     */
412
    public function getPaidDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection
413
    {
414
        return $bill->transactionJournals()
415
                    ->before($end)->after($start)->get(
416
                [
417
                    'transaction_journals.id', 'transaction_journals.date',
418
                    'transaction_journals.transaction_group_id',
419
                ]
420
            );
421
    }
422
423
    /**
424
     * Between start and end, tells you on which date(s) the bill is expected to hit.
425
     *
426
     * @param Bill   $bill
427
     * @param Carbon $start
428
     * @param Carbon $end
429
     *
430
     * @return Collection
431
     */
432
    public function getPayDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection
433
    {
434
        $set          = new Collection;
435
        $currentStart = clone $start;
436
        Log::debug(sprintf('Now at bill "%s" (%s)', $bill->name, $bill->repeat_freq));
437
        Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d')));
438
439
        while ($currentStart <= $end) {
440
            Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d')));
441
            $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart);
442
            Log::debug(sprintf('Next Date match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
443
            if ($nextExpectedMatch > $end) {// If nextExpectedMatch is after end, we continue
444
                Log::debug(
445
                    sprintf('nextExpectedMatch %s is after %s, so we skip this bill now.', $nextExpectedMatch->format('Y-m-d'), $end->format('Y-m-d'))
446
                );
447
                break;
448
            }
449
            $set->push(clone $nextExpectedMatch);
450
            Log::debug(sprintf('Now %d dates in set.', $set->count()));
451
            $nextExpectedMatch->addDay();
452
453
            Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
454
455
            $currentStart = clone $nextExpectedMatch;
456
        }
457
        $simple = $set->each(
458
            static function (Carbon $date) {
459
                return $date->format('Y-m-d');
460
            }
461
        );
462
        Log::debug(sprintf('Found dates between %s and %s:', $start->format('Y-m-d'), $end->format('Y-m-d')), $simple->toArray());
463
464
        return $set;
465
    }
466
467
    /**
468
     * Return all rules for one bill
469
     *
470
     * @param Bill $bill
471
     *
472
     * @return Collection
473
     */
474
    public function getRulesForBill(Bill $bill): Collection
475
    {
476
        return $this->user->rules()
477
                          ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id')
478
                          ->where('rule_actions.action_type', 'link_to_bill')
479
                          ->where('rule_actions.action_value', $bill->name)
480
                          ->get(['rules.*']);
481
    }
482
483
    /**
484
     * Return all rules related to the bills in the collection, in an associative array:
485
     * 5= billid
486
     *
487
     * 5 => [['id' => 1, 'title' => 'Some rule'],['id' => 2, 'title' => 'Some other rule']]
488
     *
489
     * @param Collection $collection
490
     *
491
     * @return array
492
     */
493
    public function getRulesForBills(Collection $collection): array
494
    {
495
        $rules = $this->user->rules()
496
                            ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id')
497
                            ->where('rule_actions.action_type', 'link_to_bill')
498
                            ->get(['rules.id', 'rules.title', 'rule_actions.action_value', 'rules.active']);
499
        $array = [];
500
        foreach ($rules as $rule) {
501
            $array[$rule->action_value]   = $array[$rule->action_value] ?? [];
502
            $array[$rule->action_value][] = ['id' => $rule->id, 'title' => $rule->title, 'active' => $rule->active];
503
        }
504
        $return = [];
505
        foreach ($collection as $bill) {
506
            $return[$bill->id] = $array[$bill->name] ?? [];
507
        }
508
509
        return $return;
510
    }
511
512
    /**
513
     * @param Bill   $bill
514
     * @param Carbon $date
515
     *
516
     * @return string
517
     */
518
    public function getYearAverage(Bill $bill, Carbon $date): string
519
    {
520
        /** @var JournalRepositoryInterface $repos */
521
        $repos = app(JournalRepositoryInterface::class);
522
        $repos->setUser($this->user);
523
524
        $journals = $bill->transactionJournals()
525
                         ->where('date', '>=', $date->year . '-01-01 00:00:00')
526
                         ->where('date', '<=', $date->year . '-12-31 23:59:59')
527
                         ->get();
528
        $sum      = '0';
529
        $count    = (string)$journals->count();
530
        /** @var TransactionJournal $journal */
531
        foreach ($journals as $journal) {
532
            $sum = bcadd($sum, $repos->getJournalTotal($journal));
533
        }
534
        $avg = '0';
535
        if ($journals->count() > 0) {
536
            $avg = bcdiv($sum, $count);
537
        }
538
539
        return $avg;
540
    }
541
542
    /**
543
     * Link a set of journals to a bill.
544
     *
545
     * @param Bill  $bill
546
     * @param array $transactions
547
     */
548
    public function linkCollectionToBill(Bill $bill, array $transactions): void
549
    {
550
        /** @var Transaction $transaction */
551
        foreach ($transactions as $transaction) {
552
            $journal          = $bill->user->transactionJournals()->find((int)$transaction['transaction_journal_id']);
553
            $journal->bill_id = $bill->id;
554
            $journal->save();
555
            Log::debug(sprintf('Linked journal #%d to bill #%d', $journal->id, $bill->id));
556
        }
557
    }
558
559
    /**
560
     * Given a bill and a date, this method will tell you at which moment this bill expects its next
561
     * transaction. Whether or not it is there already, is not relevant.
562
     *
563
     * @param Bill   $bill
564
     * @param Carbon $date
565
     *
566
     * @return \Carbon\Carbon
567
     */
568
    public function nextDateMatch(Bill $bill, Carbon $date): Carbon
569
    {
570
        $cache = new CacheProperties;
571
        $cache->addProperty($bill->id);
572
        $cache->addProperty('nextDateMatch');
573
        $cache->addProperty($date);
574
        if ($cache->has()) {
575
            return $cache->get(); // @codeCoverageIgnore
576
        }
577
        // find the most recent date for this bill NOT in the future. Cache this date:
578
        $start = clone $bill->date;
579
        //Log::debug('nextDateMatch: Start is ' . $start->format('Y-m-d'));
580
581
        while ($start < $date) {
582
            //Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
583
            $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
584
            //Log::debug('Start is now ' . $start->format('Y-m-d'));
585
        }
586
587
        $end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
588
589
        Log::debug('nextDateMatch: Final start is ' . $start->format('Y-m-d'));
590
        Log::debug('nextDateMatch: Matching end is ' . $end->format('Y-m-d'));
591
592
        $cache->store($start);
593
594
        return $start;
595
    }
596
597
    /**
598
     * Given the date in $date, this method will return a moment in the future where the bill is expected to be paid.
599
     *
600
     * @param Bill   $bill
601
     * @param Carbon $date
602
     *
603
     * @return Carbon
604
     */
605
    public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon
606
    {
607
        $cache = new CacheProperties;
608
        $cache->addProperty($bill->id);
609
        $cache->addProperty('nextExpectedMatch');
610
        $cache->addProperty($date);
611
        if ($cache->has()) {
612
            return $cache->get(); // @codeCoverageIgnore
613
        }
614
        // find the most recent date for this bill NOT in the future. Cache this date:
615
        $start = clone $bill->date;
616
        Log::debug('nextExpectedMatch: Start is ' . $start->format('Y-m-d'));
617
618
        while ($start < $date) {
619
            Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
620
            $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
621
            Log::debug('Start is now ' . $start->format('Y-m-d'));
622
        }
623
624
        $end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
625
626
        // see if the bill was paid in this period.
627
        $journalCount = $bill->transactionJournals()->before($end)->after($start)->count();
628
629
        if ($journalCount > 0) {
630
            // this period had in fact a bill. The new start is the current end, and we create a new end.
631
            Log::debug(sprintf('Journal count is %d, so start becomes %s', $journalCount, $end->format('Y-m-d')));
632
            $start = clone $end;
633
            $end   = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
634
        }
635
        Log::debug('nextExpectedMatch: Final start is ' . $start->format('Y-m-d'));
636
        Log::debug('nextExpectedMatch: Matching end is ' . $end->format('Y-m-d'));
637
638
        $cache->store($start);
639
640
        return $start;
641
    }
642
643
    /**
644
     * @param string $query
645
     *
646
     * @return Collection
647
     */
648
    public function searchBill(string $query): Collection
649
    {
650
        $query = sprintf('%%%s%%', $query);
651
652
        return $this->user->bills()->where('name', 'LIKE', $query)->get();
653
    }
654
655
    /**
656
     * @param User $user
657
     */
658
    public function setUser(User $user): void
659
    {
660
        $this->user = $user;
661
    }
662
663
    /**
664
     * @param array $data
665
     *
666
     * @return Bill
667
     * @throws FireflyException
668
     */
669
    public function store(array $data): Bill
670
    {
671
        /** @var BillFactory $factory */
672
        $factory = app(BillFactory::class);
673
        $factory->setUser($this->user);
674
675
        return $factory->create($data);
676
    }
677
678
    /**
679
     * @param Bill  $bill
680
     * @param array $data
681
     *
682
     * @return Bill
683
     */
684
    public function update(Bill $bill, array $data): Bill
685
    {
686
        /** @var BillUpdateService $service */
687
        $service = app(BillUpdateService::class);
688
689
        return $service->update($bill, $data);
690
    }
691
692
    /**
693
     * @param Bill $bill
694
     */
695
    public function unlinkAll(Bill $bill): void
696
    {
697
        $this->user->transactionJournals()->where('bill_id', $bill->id)->update(['bill_id' => null]);
698
    }
699
}
700