GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

BillRepository::getBillsPaidInRangePerCurrency()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 19
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 2
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
        Log::debug('Now in getPaidDatesInRange()');
415
        return $bill->transactionJournals()
416
                    ->before($end)->after($start)->get(
417
                [
418
                    'transaction_journals.id', 'transaction_journals.date',
419
                    'transaction_journals.transaction_group_id',
420
                ]
421
            );
422
    }
423
424
    /**
425
     * Between start and end, tells you on which date(s) the bill is expected to hit.
426
     *
427
     * @param Bill   $bill
428
     * @param Carbon $start
429
     * @param Carbon $end
430
     *
431
     * @return Collection
432
     */
433
    public function getPayDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection
434
    {
435
        $set          = new Collection;
436
        $currentStart = clone $start;
437
        Log::debug(sprintf('Now at bill "%s" (%s)', $bill->name, $bill->repeat_freq));
438
        Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d')));
439
440
        while ($currentStart <= $end) {
441
            Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d')));
442
            $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart);
443
            Log::debug(sprintf('Next Date match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
444
            if ($nextExpectedMatch > $end) {// If nextExpectedMatch is after end, we continue
445
                Log::debug(
446
                    sprintf('nextExpectedMatch %s is after %s, so we skip this bill now.', $nextExpectedMatch->format('Y-m-d'), $end->format('Y-m-d'))
447
                );
448
                break;
449
            }
450
            $set->push(clone $nextExpectedMatch);
451
            Log::debug(sprintf('Now %d dates in set.', $set->count()));
452
            $nextExpectedMatch->addDay();
453
454
            Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
455
456
            $currentStart = clone $nextExpectedMatch;
457
        }
458
        $simple = $set->each(
459
            static function (Carbon $date) {
460
                return $date->format('Y-m-d');
461
            }
462
        );
463
        Log::debug(sprintf('Found dates between %s and %s:', $start->format('Y-m-d'), $end->format('Y-m-d')), $simple->toArray());
464
465
        return $set;
466
    }
467
468
    /**
469
     * Return all rules for one bill
470
     *
471
     * @param Bill $bill
472
     *
473
     * @return Collection
474
     */
475
    public function getRulesForBill(Bill $bill): Collection
476
    {
477
        return $this->user->rules()
478
                          ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id')
479
                          ->where('rule_actions.action_type', 'link_to_bill')
480
                          ->where('rule_actions.action_value', $bill->name)
481
                          ->get(['rules.*']);
482
    }
483
484
    /**
485
     * Return all rules related to the bills in the collection, in an associative array:
486
     * 5= billid
487
     *
488
     * 5 => [['id' => 1, 'title' => 'Some rule'],['id' => 2, 'title' => 'Some other rule']]
489
     *
490
     * @param Collection $collection
491
     *
492
     * @return array
493
     */
494
    public function getRulesForBills(Collection $collection): array
495
    {
496
        $rules = $this->user->rules()
497
                            ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id')
498
                            ->where('rule_actions.action_type', 'link_to_bill')
499
                            ->get(['rules.id', 'rules.title', 'rule_actions.action_value', 'rules.active']);
500
        $array = [];
501
        foreach ($rules as $rule) {
502
            $array[$rule->action_value]   = $array[$rule->action_value] ?? [];
503
            $array[$rule->action_value][] = ['id' => $rule->id, 'title' => $rule->title, 'active' => $rule->active];
504
        }
505
        $return = [];
506
        foreach ($collection as $bill) {
507
            $return[$bill->id] = $array[$bill->name] ?? [];
508
        }
509
510
        return $return;
511
    }
512
513
    /**
514
     * @param Bill   $bill
515
     * @param Carbon $date
516
     *
517
     * @return string
518
     */
519
    public function getYearAverage(Bill $bill, Carbon $date): string
520
    {
521
        /** @var JournalRepositoryInterface $repos */
522
        $repos = app(JournalRepositoryInterface::class);
523
        $repos->setUser($this->user);
524
525
        $journals = $bill->transactionJournals()
526
                         ->where('date', '>=', $date->year . '-01-01 00:00:00')
527
                         ->where('date', '<=', $date->year . '-12-31 23:59:59')
528
                         ->get();
529
        $sum      = '0';
530
        $count    = (string)$journals->count();
531
        /** @var TransactionJournal $journal */
532
        foreach ($journals as $journal) {
533
            $sum = bcadd($sum, $repos->getJournalTotal($journal));
534
        }
535
        $avg = '0';
536
        if ($journals->count() > 0) {
537
            $avg = bcdiv($sum, $count);
538
        }
539
540
        return $avg;
541
    }
542
543
    /**
544
     * Link a set of journals to a bill.
545
     *
546
     * @param Bill  $bill
547
     * @param array $transactions
548
     */
549
    public function linkCollectionToBill(Bill $bill, array $transactions): void
550
    {
551
        /** @var Transaction $transaction */
552
        foreach ($transactions as $transaction) {
553
            $journal          = $bill->user->transactionJournals()->find((int)$transaction['transaction_journal_id']);
554
            $journal->bill_id = $bill->id;
555
            $journal->save();
556
            Log::debug(sprintf('Linked journal #%d to bill #%d', $journal->id, $bill->id));
557
        }
558
    }
559
560
    /**
561
     * Given a bill and a date, this method will tell you at which moment this bill expects its next
562
     * transaction. Whether or not it is there already, is not relevant.
563
     *
564
     * @param Bill   $bill
565
     * @param Carbon $date
566
     *
567
     * @return \Carbon\Carbon
568
     */
569
    public function nextDateMatch(Bill $bill, Carbon $date): Carbon
570
    {
571
        $cache = new CacheProperties;
572
        $cache->addProperty($bill->id);
573
        $cache->addProperty('nextDateMatch');
574
        $cache->addProperty($date);
575
        if ($cache->has()) {
576
            return $cache->get(); // @codeCoverageIgnore
577
        }
578
        // find the most recent date for this bill NOT in the future. Cache this date:
579
        $start = clone $bill->date;
580
        //Log::debug('nextDateMatch: Start is ' . $start->format('Y-m-d'));
581
582
        while ($start < $date) {
583
            //Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
584
            $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
585
            //Log::debug('Start is now ' . $start->format('Y-m-d'));
586
        }
587
588
        $end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
589
590
        Log::debug('nextDateMatch: Final start is ' . $start->format('Y-m-d'));
591
        Log::debug('nextDateMatch: Matching end is ' . $end->format('Y-m-d'));
592
593
        $cache->store($start);
594
595
        return $start;
596
    }
597
598
    /**
599
     * Given the date in $date, this method will return a moment in the future where the bill is expected to be paid.
600
     *
601
     * @param Bill   $bill
602
     * @param Carbon $date
603
     *
604
     * @return Carbon
605
     */
606
    public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon
607
    {
608
        $cache = new CacheProperties;
609
        $cache->addProperty($bill->id);
610
        $cache->addProperty('nextExpectedMatch');
611
        $cache->addProperty($date);
612
        if ($cache->has()) {
613
            return $cache->get(); // @codeCoverageIgnore
614
        }
615
        // find the most recent date for this bill NOT in the future. Cache this date:
616
        $start = clone $bill->date;
617
        Log::debug('nextExpectedMatch: Start is ' . $start->format('Y-m-d'));
618
619
        while ($start < $date) {
620
            Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
621
            $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
622
            Log::debug('Start is now ' . $start->format('Y-m-d'));
623
        }
624
625
        $end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
626
627
        // see if the bill was paid in this period.
628
        $journalCount = $bill->transactionJournals()->before($end)->after($start)->count();
629
630
        if ($journalCount > 0) {
631
            // this period had in fact a bill. The new start is the current end, and we create a new end.
632
            Log::debug(sprintf('Journal count is %d, so start becomes %s', $journalCount, $end->format('Y-m-d')));
633
            $start = clone $end;
634
            $end   = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
635
        }
636
        Log::debug('nextExpectedMatch: Final start is ' . $start->format('Y-m-d'));
637
        Log::debug('nextExpectedMatch: Matching end is ' . $end->format('Y-m-d'));
638
639
        $cache->store($start);
640
641
        return $start;
642
    }
643
644
    /**
645
     * @param string $query
646
     *
647
     * @return Collection
648
     */
649
    public function searchBill(string $query): Collection
650
    {
651
        $query = sprintf('%%%s%%', $query);
652
653
        return $this->user->bills()->where('name', 'LIKE', $query)->get();
654
    }
655
656
    /**
657
     * @param User $user
658
     */
659
    public function setUser(User $user): void
660
    {
661
        $this->user = $user;
662
    }
663
664
    /**
665
     * @param array $data
666
     *
667
     * @return Bill
668
     * @throws FireflyException
669
     */
670
    public function store(array $data): Bill
671
    {
672
        /** @var BillFactory $factory */
673
        $factory = app(BillFactory::class);
674
        $factory->setUser($this->user);
675
676
        return $factory->create($data);
677
    }
678
679
    /**
680
     * @param Bill  $bill
681
     * @param array $data
682
     *
683
     * @return Bill
684
     */
685
    public function update(Bill $bill, array $data): Bill
686
    {
687
        /** @var BillUpdateService $service */
688
        $service = app(BillUpdateService::class);
689
690
        return $service->update($bill, $data);
691
    }
692
693
    /**
694
     * @param Bill $bill
695
     */
696
    public function unlinkAll(Bill $bill): void
697
    {
698
        $this->user->transactionJournals()->where('bill_id', $bill->id)->update(['bill_id' => null]);
699
    }
700
}
701