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.

GroupCollector   C
last analyzed

Complexity

Total Complexity 55

Size/Duplication

Total Lines 633
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 55
eloc 260
c 6
b 0
f 0
dl 0
loc 633
rs 6

25 Methods

Rating   Name   Duplication   Size   Complexity  
A setGroup() 0 5 1
A withAttachmentInformation() 0 6 1
A setTransactionGroup() 0 5 1
A joinAttachmentTables() 0 10 2
A setUser() 0 6 1
A getSum() 0 11 2
A dumpQuery() 0 6 1
A setCurrency() 0 10 1
A setPage() 0 7 2
A mergeAttachments() 0 11 2
A setJournalIds() 0 7 2
A parseAugmentedJournal() 0 43 5
A startQuery() 0 38 1
A getGroups() 0 22 3
B parseSums() 0 41 7
A setTypes() 0 5 1
A setSearchWords() 0 24 3
A withAPIInformation() 0 12 1
A getExtractedJournals() 0 15 3
A getPaginatedGroups() 0 5 1
A setLimit() 0 6 1
A mergeTags() 0 22 3
A __construct() 0 70 2
A convertToInteger() 0 7 3
A parseArray() 0 43 5

How to fix   Complexity   

Complex Class

Complex classes like GroupCollector 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 GroupCollector, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * GroupCollector.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
22
declare(strict_types=1);
23
24
namespace FireflyIII\Helpers\Collector;
25
26
use Carbon\Carbon;
27
use Carbon\Exceptions\InvalidDateException;
28
use Exception;
29
use FireflyIII\Helpers\Collector\Extensions\AccountCollection;
30
use FireflyIII\Helpers\Collector\Extensions\AmountCollection;
31
use FireflyIII\Helpers\Collector\Extensions\CollectorProperties;
32
use FireflyIII\Helpers\Collector\Extensions\MetaCollection;
33
use FireflyIII\Helpers\Collector\Extensions\TimeCollection;
34
use FireflyIII\Models\Bill;
35
use FireflyIII\Models\Budget;
36
use FireflyIII\Models\Category;
37
use FireflyIII\Models\Tag;
38
use FireflyIII\Models\TransactionCurrency;
39
use FireflyIII\Models\TransactionGroup;
40
use FireflyIII\Models\TransactionJournal;
41
use FireflyIII\User;
42
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
43
use Illuminate\Database\Eloquent\Relations\HasMany;
44
use Illuminate\Database\Query\JoinClause;
45
use Illuminate\Pagination\LengthAwarePaginator;
46
use Illuminate\Support\Collection;
47
use Log;
48
49
/**
50
 * Class GroupCollector
51
 *
52
 * @codeCoverageIgnore
53
 */
54
class GroupCollector implements GroupCollectorInterface
55
{
56
    use CollectorProperties, AccountCollection, AmountCollection, TimeCollection, MetaCollection;
0 ignored issues
show
Bug introduced by
The trait FireflyIII\Helpers\Colle...tensions\MetaCollection requires the property $id which is not provided by FireflyIII\Helpers\Collector\GroupCollector.
Loading history...
57
58
    /**
59
     * Group collector constructor.
60
     */
61
    public function __construct()
62
    {
63
        if ('testing' === config('app.env')) {
64
            app('log')->warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
65
        }
66
        $this->hasAccountInfo       = false;
67
        $this->hasCatInformation    = false;
68
        $this->hasBudgetInformation = false;
69
        $this->hasBillInformation   = false;
70
        $this->hasJoinedTagTables   = false;
71
        $this->hasJoinedAttTables   = false;
72
        $this->integerFields        = [
73
            'transaction_group_id',
74
            'user_id',
75
            'transaction_journal_id',
76
            'transaction_type_id',
77
            'order',
78
            'source_transaction_id',
79
            'source_account_id',
80
            'currency_id',
81
            'currency_decimal_places',
82
            'foreign_currency_id',
83
            'foreign_currency_decimal_places',
84
            'destination_transaction_id',
85
            'destination_account_id',
86
            'category_id',
87
            'budget_id',
88
        ];
89
        $this->total                = 0;
90
        $this->fields               = [
91
            # group
92
            'transaction_groups.id as transaction_group_id',
93
            'transaction_groups.user_id as user_id',
94
            'transaction_groups.created_at as created_at',
95
            'transaction_groups.updated_at as updated_at',
96
            'transaction_groups.title as transaction_group_title',
97
98
            # journal
99
            'transaction_journals.id as transaction_journal_id',
100
            'transaction_journals.transaction_type_id',
101
            'transaction_journals.description',
102
            'transaction_journals.date',
103
            'transaction_journals.order',
104
105
            # types
106
            'transaction_types.type as transaction_type_type',
107
108
            # source info (always present)
109
            'source.id as source_transaction_id',
110
            'source.account_id as source_account_id',
111
            'source.reconciled',
112
113
            # currency info:
114
            'source.amount as amount',
115
            'source.transaction_currency_id as currency_id',
116
            'currency.code as currency_code',
117
            'currency.name as currency_name',
118
            'currency.symbol as currency_symbol',
119
            'currency.decimal_places as currency_decimal_places',
120
121
            # foreign currency info
122
            'source.foreign_amount as foreign_amount',
123
            'source.foreign_currency_id as foreign_currency_id',
124
            'foreign_currency.code as foreign_currency_code',
125
            'foreign_currency.name as foreign_currency_name',
126
            'foreign_currency.symbol as foreign_currency_symbol',
127
            'foreign_currency.decimal_places as foreign_currency_decimal_places',
128
129
            # destination account info (always present)
130
            'destination.account_id as destination_account_id',
131
        ];
132
    }
133
134
    /**
135
     *
136
     */
137
    public function dumpQuery(): void
138
    {
139
        echo $this->query->toSql();
140
        echo '<pre>';
141
        print_r($this->query->getBindings());
142
        echo '</pre>';
143
    }
144
145
    /**
146
     * Return the transaction journals without group information. Is useful in some instances.
147
     *
148
     * @return array
149
     */
150
    public function getExtractedJournals(): array
151
    {
152
        $selection = $this->getGroups();
153
        $return    = [];
154
        /** @var array $group */
155
        foreach ($selection as $group) {
156
            $count = count($group['transactions']);
157
            foreach ($group['transactions'] as $journalId => $journal) {
158
                $journal['group_title']       = $group['title'];
159
                $journal['journals_in_group'] = $count;
160
                $return[$journalId]           = $journal;
161
            }
162
        }
163
164
        return $return;
165
    }
166
167
    /**
168
     * Return the groups.
169
     *
170
     * @return Collection
171
     */
172
    public function getGroups(): Collection
173
    {
174
        //$start = microtime(true);
175
        /** @var Collection $result */
176
        $result = $this->query->get($this->fields);
177
        //$end    = round(microtime(true) - $start, 5);
178
        // log info about query time.
179
        //Log::info(sprintf('Query took Firefly III %s seconds', $end));
180
        //Log::info($this->query->toSql(), $this->query->getBindings());
181
182
        // now to parse this into an array.
183
        $collection  = $this->parseArray($result);
184
        $this->total = $collection->count();
185
186
        // now filter the array according to the page and the limit (if necessary)
187
        if (null !== $this->limit && null !== $this->page) {
188
            $offset = ($this->page - 1) * $this->limit;
189
190
            return $collection->slice($offset, $this->limit);
191
        }
192
193
        return $collection;
194
    }
195
196
    /**
197
     * Same as getGroups but everything is in a paginator.
198
     *
199
     * @return LengthAwarePaginator
200
     */
201
    public function getPaginatedGroups(): LengthAwarePaginator
202
    {
203
        $set = $this->getGroups();
204
205
        return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page);
206
    }
207
208
    /**
209
     * Return the sum of all journals.
210
     * TODO ignores the currency.
211
     *
212
     * @return string
213
     */
214
    public function getSum(): string
215
    {
216
        $journals = $this->getExtractedJournals();
217
        $sum      = '0';
218
        /** @var array $journal */
219
        foreach ($journals as $journal) {
220
            $amount = (string) $journal['amount'];
221
            $sum    = bcadd($sum, $amount);
222
        }
223
224
        return $sum;
225
    }
226
227
    /**
228
     * Limit results to a specific currency, either foreign or normal one.
229
     *
230
     * @param TransactionCurrency $currency
231
     *
232
     * @return GroupCollectorInterface
233
     */
234
    public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface
235
    {
236
        $this->query->where(
237
            static function (EloquentBuilder $q) use ($currency) {
238
                $q->where('source.transaction_currency_id', $currency->id);
239
                $q->orWhere('source.foreign_currency_id', $currency->id);
240
            }
241
        );
242
243
        return $this;
244
    }
245
246
    /**
247
     * Limit the result to a specific transaction group.
248
     *
249
     * @param TransactionGroup $transactionGroup
250
     *
251
     * @return GroupCollectorInterface
252
     */
253
    public function setGroup(TransactionGroup $transactionGroup): GroupCollectorInterface
254
    {
255
        $this->query->where('transaction_groups.id', $transactionGroup->id);
256
257
        return $this;
258
    }
259
260
    /**
261
     * Limit the result to a set of specific journals.
262
     *
263
     * @param array $journalIds
264
     *
265
     * @return GroupCollectorInterface
266
     */
267
    public function setJournalIds(array $journalIds): GroupCollectorInterface
268
    {
269
        if (count($journalIds) > 0) {
270
            $this->query->whereIn('transaction_journals.id', $journalIds);
271
        }
272
273
        return $this;
274
    }
275
276
    /**
277
     * Limit the number of returned entries.
278
     *
279
     * @param int $limit
280
     *
281
     * @return GroupCollectorInterface
282
     */
283
    public function setLimit(int $limit): GroupCollectorInterface
284
    {
285
        $this->limit = $limit;
286
        app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
287
288
        return $this;
289
    }
290
291
    /**
292
     * Set the page to get.
293
     *
294
     * @param int $page
295
     *
296
     * @return GroupCollectorInterface
297
     */
298
    public function setPage(int $page): GroupCollectorInterface
299
    {
300
        $page       = 0 === $page ? 1 : $page;
301
        $this->page = $page;
302
        app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
303
304
        return $this;
305
    }
306
307
    /**
308
     * Search for words in descriptions.
309
     *
310
     * @param array $array
311
     *
312
     * @return GroupCollectorInterface
313
     */
314
    public function setSearchWords(array $array): GroupCollectorInterface
315
    {
316
        $this->query->where(
317
            static function (EloquentBuilder $q) use ($array) {
318
                $q->where(
319
                    function (EloquentBuilder $q1) use ($array) {
320
                        foreach ($array as $word) {
321
                            $keyword = sprintf('%%%s%%', $word);
322
                            $q1->where('transaction_journals.description', 'LIKE', $keyword);
323
                        }
324
                    }
325
                );
326
                $q->orWhere(
327
                    static function (EloquentBuilder $q2) use ($array) {
328
                        foreach ($array as $word) {
329
                            $keyword = sprintf('%%%s%%', $word);
330
                            $q2->where('transaction_groups.title', 'LIKE', $keyword);
331
                        }
332
                    }
333
                );
334
            }
335
        );
336
337
        return $this;
338
    }
339
340
341
    /**
342
     * Limit the search to one specific transaction group.
343
     *
344
     * @param TransactionGroup $transactionGroup
345
     *
346
     * @return GroupCollectorInterface
347
     */
348
    public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface
349
    {
350
        $this->query->where('transaction_groups.id', $transactionGroup->id);
351
352
        return $this;
353
    }
354
355
    /**
356
     * Limit the included transaction types.
357
     *
358
     * @param array $types
359
     *
360
     * @return GroupCollectorInterface
361
     */
362
    public function setTypes(array $types): GroupCollectorInterface
363
    {
364
        $this->query->whereIn('transaction_types.type', $types);
365
366
        return $this;
367
    }
368
369
    /**
370
     * Set the user object and start the query.
371
     *
372
     * @param User $user
373
     *
374
     * @return GroupCollectorInterface
375
     */
376
    public function setUser(User $user): GroupCollectorInterface
377
    {
378
        $this->user = $user;
379
        $this->startQuery();
380
381
        return $this;
382
    }
383
384
    /**
385
     * Automatically include all stuff required to make API calls work.
386
     *
387
     * @return GroupCollectorInterface
388
     */
389
    public function withAPIInformation(): GroupCollectorInterface
390
    {
391
        // include source + destination account name and type.
392
        $this->withAccountInformation()
393
            // include category ID + name (if any)
394
             ->withCategoryInformation()
395
            // include budget ID + name (if any)
396
             ->withBudgetInformation()
397
            // include bill ID + name (if any)
398
             ->withBillInformation();
399
400
        return $this;
401
    }
402
403
    /**
404
     * @inheritDoc
405
     */
406
    public function withAttachmentInformation(): GroupCollectorInterface
407
    {
408
        $this->fields[] = 'attachments.id as attachment_id';
409
        $this->joinAttachmentTables();
410
411
        return $this;
412
    }
413
414
415
    /**
416
     * Convert a selected set of fields to arrays.
417
     *
418
     * @param array $array
419
     *
420
     * @return array
421
     */
422
    private function convertToInteger(array $array): array
423
    {
424
        foreach ($this->integerFields as $field) {
425
            $array[$field] = isset($array[$field]) ? (int) $array[$field] : null;
426
        }
427
428
        return $array;
429
    }
430
431
    /**
432
     * Join table to get attachment information.
433
     */
434
    private function joinAttachmentTables(): void
435
    {
436
        if (false === $this->hasJoinedAttTables) {
437
            // join some extra tables:
438
            $this->hasJoinedAttTables = true;
439
            $this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id')
440
                        ->where(
441
                            static function (EloquentBuilder $q1) {
442
                                $q1->where('attachments.attachable_type', TransactionJournal::class);
443
                                $q1->orWhereNull('attachments.attachable_type');
444
                            }
445
                        );
446
        }
447
    }
448
449
    /**
450
     * @param array              $existingJournal
451
     * @param TransactionJournal $newJournal
452
     *
453
     * @return array
454
     */
455
    private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array
456
    {
457
        $newArray = $newJournal->toArray();
458
        if (isset($newArray['attachment_id'])) {
459
            $attachmentId                                  = (int) $newJournal['tag_id'];
460
            $existingJournal['attachments'][$attachmentId] = [
461
                'id' => $attachmentId,
462
            ];
463
        }
464
465
        return $existingJournal;
466
    }
467
468
    /**
469
     * @param array              $existingJournal
470
     * @param TransactionJournal $newJournal
471
     *
472
     * @return array
473
     */
474
    private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array
475
    {
476
        $newArray = $newJournal->toArray();
477
        if (isset($newArray['tag_id'])) { // assume the other fields are present as well.
478
            $tagId = (int) $newJournal['tag_id'];
479
480
            $tagDate = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $tagDate is dead and can be removed.
Loading history...
481
            try {
482
                $tagDate = Carbon::parse($newArray['tag_date']);
483
            } catch (InvalidDateException $e) {
484
                Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
485
            }
486
487
            $existingJournal['tags'][$tagId] = [
488
                'id'          => (int) $newArray['tag_id'],
489
                'name'        => $newArray['tag_name'],
490
                'date'        => $tagDate,
491
                'description' => $newArray['tag_description'],
492
            ];
493
        }
494
495
        return $existingJournal;
496
    }
497
498
    /**
499
     * @param Collection $collection
500
     *
501
     * @return Collection
502
     */
503
    private function parseArray(Collection $collection): Collection
504
    {
505
        $groups = [];
506
        /** @var TransactionJournal $augumentedJournal */
507
        foreach ($collection as $augumentedJournal) {
508
            $groupId = $augumentedJournal->transaction_group_id;
509
510
            if (!isset($groups[$groupId])) {
511
                // make new array
512
                $parsedGroup                            = $this->parseAugmentedJournal($augumentedJournal);
513
                $groupArray                             = [
514
                    'id'               => (int) $augumentedJournal->transaction_group_id,
515
                    'user_id'          => (int) $augumentedJournal->user_id,
516
                    'title'            => $augumentedJournal->transaction_group_title,
517
                    'transaction_type' => $parsedGroup['transaction_type_type'],
518
                    'count'            => 1,
519
                    'sums'             => [],
520
                    'transactions'     => [],
521
                ];
522
                $journalId                              = (int) $augumentedJournal->transaction_journal_id;
523
                $groupArray['transactions'][$journalId] = $parsedGroup;
524
                $groups[$groupId]                       = $groupArray;
525
                continue;
526
            }
527
            // or parse the rest.
528
            $journalId = (int) $augumentedJournal->transaction_journal_id;
529
            $groups[$groupId]['count']++;
530
531
            if (isset($groups[$groupId]['transactions'][$journalId])) {
532
                // append data to existing group + journal (for multiple tags or multiple attachments)
533
                $groups[$groupId]['transactions'][$journalId] = $this->mergeTags($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
534
                $groups[$groupId]['transactions'][$journalId] = $this->mergeAttachments($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
535
            }
536
537
            if (!isset($groups[$groupId]['transactions'][$journalId])) {
538
                // create second, third, fourth split:
539
                $groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
540
            }
541
        }
542
543
        $groups = $this->parseSums($groups);
544
545
        return new Collection($groups);
546
    }
547
548
    /**
549
     * @param TransactionJournal $augumentedJournal
550
     *
551
     * @return array
552
     */
553
    private function parseAugmentedJournal(TransactionJournal $augumentedJournal): array
554
    {
555
        $result                = $augumentedJournal->toArray();
556
        $result['tags']        = [];
557
        $result['attachments'] = [];
558
        try {
559
            $result['date']       = new Carbon($result['date']);
560
            $result['created_at'] = new Carbon($result['created_at']);
561
            $result['updated_at'] = new Carbon($result['updated_at']);
562
        } catch (Exception $e) {
563
            Log::error($e->getMessage());
564
        }
565
566
        // convert values to integers:
567
        $result = $this->convertToInteger($result);
568
569
        $result['reconciled'] = 1 === (int) $result['reconciled'];
570
        if (isset($augumentedJournal['tag_id'])) { // assume the other fields are present as well.
571
            $tagId   = (int) $augumentedJournal['tag_id'];
572
            $tagDate = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $tagDate is dead and can be removed.
Loading history...
573
            try {
574
                $tagDate = Carbon::parse($augumentedJournal['tag_date']);
575
            } catch (InvalidDateException $e) {
576
                Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
577
            }
578
579
            $result['tags'][$tagId] = [
580
                'id'          => (int) $result['tag_id'],
581
                'name'        => $result['tag_name'],
582
                'date'        => $tagDate,
583
                'description' => $result['tag_description'],
584
            ];
585
        }
586
587
        // also merge attachments:
588
        if (isset($augumentedJournal['attachment_id'])) {
589
            $attachmentId                         = (int) $augumentedJournal['attachment_id'];
590
            $result['attachments'][$attachmentId] = [
591
                'id' => $attachmentId,
592
            ];
593
        }
594
595
        return $result;
596
    }
597
598
    /**
599
     * @param array $groups
600
     *
601
     * @return array
602
     */
603
    private function parseSums(array $groups): array
604
    {
605
        /**
606
         * @var int   $groudId
607
         * @var array $group
608
         */
609
        foreach ($groups as $groudId => $group) {
610
            /** @var array $transaction */
611
            foreach ($group['transactions'] as $transaction) {
612
                $currencyId = (int) $transaction['currency_id'];
613
614
                // set default:
615
                if (!isset($groups[$groudId]['sums'][$currencyId])) {
616
                    $groups[$groudId]['sums'][$currencyId]['currency_id']             = $currencyId;
617
                    $groups[$groudId]['sums'][$currencyId]['currency_code']           = $transaction['currency_code'];
618
                    $groups[$groudId]['sums'][$currencyId]['currency_symbol']         = $transaction['currency_symbol'];
619
                    $groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places'];
620
                    $groups[$groudId]['sums'][$currencyId]['amount']                  = '0';
621
                }
622
                $groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount'] ?? '0');
623
624
                if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
625
                    $currencyId = (int) $transaction['foreign_currency_id'];
626
627
                    // set default:
628
                    if (!isset($groups[$groudId]['sums'][$currencyId])) {
629
                        $groups[$groudId]['sums'][$currencyId]['currency_id']             = $currencyId;
630
                        $groups[$groudId]['sums'][$currencyId]['currency_code']           = $transaction['foreign_currency_code'];
631
                        $groups[$groudId]['sums'][$currencyId]['currency_symbol']         = $transaction['foreign_currency_symbol'];
632
                        $groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places'];
633
                        $groups[$groudId]['sums'][$currencyId]['amount']                  = '0';
634
                    }
635
                    $groups[$groudId]['sums'][$currencyId]['amount'] = bcadd(
636
                        $groups[$groudId]['sums'][$currencyId]['amount'],
637
                        $transaction['foreign_amount'] ?? '0'
638
                    );
639
                }
640
            }
641
        }
642
643
        return $groups;
644
    }
645
646
    /**
647
     * Build the query.
648
     */
649
    private function startQuery(): void
650
    {
651
        app('log')->debug('GroupCollector::startQuery');
652
        $this->query = $this->user
653
            //->transactionGroups()
654
            //->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
655
            ->transactionJournals()
656
            ->leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
657
658
            // join source transaction.
659
            ->leftJoin(
660
                'transactions as source',
661
                function (JoinClause $join) {
662
                    $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')
663
                     ->where('source.amount', '<', 0);
664
                }
665
            )
666
            // join destination transaction
667
            ->leftJoin(
668
                'transactions as destination',
669
                function (JoinClause $join) {
670
                    $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')
671
                     ->where('destination.amount', '>', 0);
672
                }
673
            )
674
            // left join transaction type.
675
            ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
676
            ->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id')
677
            ->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id')
678
            ->whereNull('transaction_groups.deleted_at')
679
            ->whereNull('transaction_journals.deleted_at')
680
            ->whereNull('source.deleted_at')
681
            ->whereNull('destination.deleted_at')
682
            ->orderBy('transaction_journals.date', 'DESC')
683
            ->orderBy('transaction_journals.order', 'ASC')
684
            ->orderBy('transaction_journals.id', 'DESC')
685
            ->orderBy('transaction_journals.description', 'DESC')
686
            ->orderBy('source.amount', 'DESC');
687
    }
688
}
689