From 7e2159d12caecab816874fb50567c022a29c5b4e Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 29 May 2019 21:52:08 +0200 Subject: [PATCH] Removed a lot of references to the old collector. --- app/Console/Commands/ApplyRules.php | 183 ++++---- app/Console/Commands/Tools/ApplyRules.php | 5 +- app/Export/ExpandedProcessor.php | 249 +++++----- .../Report/Audit/MonthReportGenerator.php | 43 +- .../Report/Budget/MonthReportGenerator.php | 113 +++-- .../Report/Category/MonthReportGenerator.php | 55 +-- app/Generator/Report/Support.php | 59 ++- .../Report/Tag/MonthReportGenerator.php | 169 ++++--- app/Helpers/Collector/GroupCollector.php | 429 ++++++++++-------- .../Collector/GroupCollectorInterface.php | 19 + public/v1/js/app.js | 10 +- .../transactions/CreateTransaction.vue | 8 + resources/views/v1/reports/audit/report.twig | 2 + resources/views/v1/reports/budget/month.twig | 26 +- .../views/v1/reports/category/month.twig | 37 +- .../v1/reports/partials/journals-audit.twig | 115 ++++- resources/views/v1/reports/tag/month.twig | 18 +- 17 files changed, 876 insertions(+), 664 deletions(-) diff --git a/app/Console/Commands/ApplyRules.php b/app/Console/Commands/ApplyRules.php index 7f8336ac4a..ab2dc139f8 100644 --- a/app/Console/Commands/ApplyRules.php +++ b/app/Console/Commands/ApplyRules.php @@ -25,7 +25,6 @@ declare(strict_types=1); namespace FireflyIII\Console\Commands; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; use FireflyIII\Models\AccountType; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; @@ -41,8 +40,6 @@ use Illuminate\Support\Collection; /** * * Class ApplyRules - * - * @codeCoverageIgnore */ class ApplyRules extends Command { @@ -176,96 +173,6 @@ class ApplyRules extends Command return 0; } - /** - * @param Collection $rules - * @param Collection $transactions - * @param bool $breakProcessing - * - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function applyRuleSelection(Collection $rules, Collection $transactions, bool $breakProcessing): void - { - $bar = $this->output->createProgressBar($rules->count() * $transactions->count()); - - /** @var Rule $rule */ - foreach ($rules as $rule) { - /** @var Processor $processor */ - $processor = app(Processor::class); - $processor->make($rule, true); - - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - /** @noinspection DisconnectedForeachInstructionInspection */ - $bar->advance(); - $result = $processor->handleTransaction($transaction); - if (true === $result) { - $this->results->push($transaction); - } - } - if (true === $rule->stop_processing && true === $breakProcessing) { - $this->line(''); - $this->line(sprintf('Rule #%d ("%s") says to stop processing.', $rule->id, $rule->title)); - - return; - } - } - $this->line(''); - } - - /** - * - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function grabAllRules(): void - { - if (true === $this->option('all_rules')) { - /** @var RuleRepositoryInterface $ruleRepos */ - $ruleRepos = app(RuleRepositoryInterface::class); - $ruleRepos->setUser($this->getUser()); - $this->rules = $ruleRepos->getAll(); - - // reset rule groups. - $this->ruleGroups = new Collection; - } - } - - /** - * - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function parseDates(): void - { - // parse start date. - $startDate = Carbon::now()->startOfMonth(); - $startString = $this->option('start_date'); - if (null === $startString) { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $repository->setUser($this->getUser()); - $first = $repository->firstNull(); - if (null !== $first) { - $startDate = $first->date; - } - } - if (null !== $startString && '' !== $startString) { - $startDate = Carbon::createFromFormat('Y-m-d', $startString); - } - - // parse end date - $endDate = Carbon::now(); - $endString = $this->option('end_date'); - if (null !== $endString && '' !== $endString) { - $endDate = Carbon::createFromFormat('Y-m-d', $endString); - } - - if ($startDate > $endDate) { - [$endDate, $startDate] = [$startDate, $endDate]; - } - - $this->startDate = $startDate; - $this->endDate = $endDate; - } - /** * @return bool * @throws \FireflyIII\Exceptions\FireflyException @@ -417,5 +324,95 @@ class ApplyRules extends Command return true; } + /** + * + * @throws \FireflyIII\Exceptions\FireflyException + */ + private function grabAllRules(): void + { + if (true === $this->option('all_rules')) { + /** @var RuleRepositoryInterface $ruleRepos */ + $ruleRepos = app(RuleRepositoryInterface::class); + $ruleRepos->setUser($this->getUser()); + $this->rules = $ruleRepos->getAll(); + + // reset rule groups. + $this->ruleGroups = new Collection; + } + } + + /** + * + * @throws \FireflyIII\Exceptions\FireflyException + */ + private function parseDates(): void + { + // parse start date. + $startDate = Carbon::now()->startOfMonth(); + $startString = $this->option('start_date'); + if (null === $startString) { + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $repository->setUser($this->getUser()); + $first = $repository->firstNull(); + if (null !== $first) { + $startDate = $first->date; + } + } + if (null !== $startString && '' !== $startString) { + $startDate = Carbon::createFromFormat('Y-m-d', $startString); + } + + // parse end date + $endDate = Carbon::now(); + $endString = $this->option('end_date'); + if (null !== $endString && '' !== $endString) { + $endDate = Carbon::createFromFormat('Y-m-d', $endString); + } + + if ($startDate > $endDate) { + [$endDate, $startDate] = [$startDate, $endDate]; + } + + $this->startDate = $startDate; + $this->endDate = $endDate; + } + + /** + * @param Collection $rules + * @param Collection $transactions + * @param bool $breakProcessing + * + * @throws \FireflyIII\Exceptions\FireflyException + */ + private function applyRuleSelection(Collection $rules, Collection $transactions, bool $breakProcessing): void + { + $bar = $this->output->createProgressBar($rules->count() * $transactions->count()); + + /** @var Rule $rule */ + foreach ($rules as $rule) { + /** @var Processor $processor */ + $processor = app(Processor::class); + $processor->make($rule, true); + + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + /** @noinspection DisconnectedForeachInstructionInspection */ + $bar->advance(); + $result = $processor->handleTransaction($transaction); + if (true === $result) { + $this->results->push($transaction); + } + } + if (true === $rule->stop_processing && true === $breakProcessing) { + $this->line(''); + $this->line(sprintf('Rule #%d ("%s") says to stop processing.', $rule->id, $rule->title)); + + return; + } + } + $this->line(''); + } + } diff --git a/app/Console/Commands/Tools/ApplyRules.php b/app/Console/Commands/Tools/ApplyRules.php index 3b3fb2e87f..172c88f1da 100644 --- a/app/Console/Commands/Tools/ApplyRules.php +++ b/app/Console/Commands/Tools/ApplyRules.php @@ -152,7 +152,7 @@ class ApplyRules extends Command $this->line(sprintf('Will apply %d rules to %d transactions.', $count, count($journals))); // start looping. - $bar = $this->output->createProgressBar(count($journals) * $count); + $bar = $this->output->createProgressBar(count($journals)); Log::debug(sprintf('Now looping %d transactions.', count($journals))); /** @var array $journal */ foreach ($journals as $journal) { @@ -168,7 +168,7 @@ class ApplyRules extends Command $processor = app(Processor::class); $processor->make($rule, true); $ruleTriggered = $processor->handleJournalArray($journal); - $bar->advance(); + if ($ruleTriggered) { $groupTriggered = true; } @@ -187,6 +187,7 @@ class ApplyRules extends Command } } Log::debug('Done with all rules for this group + done with journal.'); + $bar->advance(); } $this->line(''); $this->line('Done!'); diff --git a/app/Export/ExpandedProcessor.php b/app/Export/ExpandedProcessor.php index 4377cb6e10..4fb5d7d1e5 100644 --- a/app/Export/ExpandedProcessor.php +++ b/app/Export/ExpandedProcessor.php @@ -31,7 +31,6 @@ use FireflyIII\Export\Collector\AttachmentCollector; use FireflyIII\Export\Collector\UploadCollector; use FireflyIII\Export\Entry\Entry; use FireflyIII\Export\Exporter\ExporterInterface; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Models\AccountMeta; use FireflyIII\Models\ExportJob; @@ -149,6 +148,109 @@ class ExpandedProcessor implements ProcessorInterface return true; } + /** + * Returns, if present, for the given journal ID's the notes. + * + * @param array $array + * + * @return array + */ + private function getNotes(array $array): array + { + $array = array_unique($array); + $notes = Note::where('notes.noteable_type', TransactionJournal::class) + ->whereIn('notes.noteable_id', $array) + ->get(['notes.*']); + $return = []; + /** @var Note $note */ + foreach ($notes as $note) { + if ('' !== trim((string)$note->text)) { + $id = (int)$note->noteable_id; + $return[$id] = $note->text; + } + } + + return $return; + } + + /** + * Returns a comma joined list of all the users tags linked to these journals. + * + * @param array $array + * + * @return array + * @throws \Illuminate\Contracts\Encryption\DecryptException + */ + private function getTags(array $array): array + { + $set = DB::table('tag_transaction_journal') + ->whereIn('tag_transaction_journal.transaction_journal_id', $array) + ->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id') + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'tag_transaction_journal.transaction_journal_id') + ->where('transaction_journals.user_id', $this->job->user_id) + ->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag']); + $result = []; + foreach ($set as $entry) { + $id = (int)$entry->transaction_journal_id; + $result[$id] = $result[$id] ?? []; + $result[$id][] = $entry->tag; + } + + return $result; + } + + /** + * Get all IBAN / SWIFT / account numbers. + * + * @param array $array + * + * @return array + */ + private function getIbans(array $array): array + { + $array = array_unique($array); + $return = []; + $set = AccountMeta::whereIn('account_id', $array) + ->leftJoin('accounts', 'accounts.id', 'account_meta.account_id') + ->where('accounts.user_id', $this->job->user_id) + ->whereIn('account_meta.name', ['accountNumber', 'BIC', 'currency_id']) + ->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data']); + /** @var AccountMeta $meta */ + foreach ($set as $meta) { + $id = (int)$meta->account_id; + $return[$id][$meta->name] = $meta->data; + } + + return $return; + } + + /** + * Get currencies. + * + * @param array $array + * + * @return array + */ + private function getAccountCurrencies(array $array): array + { + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $return = []; + $ids = []; + $repository->setUser($this->job->user); + foreach ($array as $value) { + $ids[] = (int)($value['currency_id'] ?? 0.0); + } + $ids = array_unique($ids); + $result = $repository->getByIds($ids); + + foreach ($result as $currency) { + $return[$currency->id] = $currency->code; + } + + return $return; + } + /** * Get old oploads. * @@ -216,6 +318,27 @@ class ExpandedProcessor implements ProcessorInterface return true; } + /** + * Get files. + * + * @return Collection + */ + public function getFiles(): Collection + { + return $this->files; + } + + /** + * Delete files. + */ + private function deleteFiles(): void + { + $disk = Storage::disk('export'); + foreach ($this->getFiles() as $file) { + $disk->delete($file); + } + } + /** * Export the journals. * @@ -234,16 +357,6 @@ class ExpandedProcessor implements ProcessorInterface return true; } - /** - * Get files. - * - * @return Collection - */ - public function getFiles(): Collection - { - return $this->files; - } - /** * Save export job settings to class. * @@ -259,118 +372,4 @@ class ExpandedProcessor implements ProcessorInterface $this->includeOldUploads = $settings['includeOldUploads']; $this->job = $settings['job']; } - - /** - * Delete files. - */ - private function deleteFiles(): void - { - $disk = Storage::disk('export'); - foreach ($this->getFiles() as $file) { - $disk->delete($file); - } - } - - /** - * Get currencies. - * - * @param array $array - * - * @return array - */ - private function getAccountCurrencies(array $array): array - { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $return = []; - $ids = []; - $repository->setUser($this->job->user); - foreach ($array as $value) { - $ids[] = (int)($value['currency_id'] ?? 0.0); - } - $ids = array_unique($ids); - $result = $repository->getByIds($ids); - - foreach ($result as $currency) { - $return[$currency->id] = $currency->code; - } - - return $return; - } - - /** - * Get all IBAN / SWIFT / account numbers. - * - * @param array $array - * - * @return array - */ - private function getIbans(array $array): array - { - $array = array_unique($array); - $return = []; - $set = AccountMeta::whereIn('account_id', $array) - ->leftJoin('accounts', 'accounts.id', 'account_meta.account_id') - ->where('accounts.user_id', $this->job->user_id) - ->whereIn('account_meta.name', ['accountNumber', 'BIC', 'currency_id']) - ->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data']); - /** @var AccountMeta $meta */ - foreach ($set as $meta) { - $id = (int)$meta->account_id; - $return[$id][$meta->name] = $meta->data; - } - - return $return; - } - - /** - * Returns, if present, for the given journal ID's the notes. - * - * @param array $array - * - * @return array - */ - private function getNotes(array $array): array - { - $array = array_unique($array); - $notes = Note::where('notes.noteable_type', TransactionJournal::class) - ->whereIn('notes.noteable_id', $array) - ->get(['notes.*']); - $return = []; - /** @var Note $note */ - foreach ($notes as $note) { - if ('' !== trim((string)$note->text)) { - $id = (int)$note->noteable_id; - $return[$id] = $note->text; - } - } - - return $return; - } - - /** - * Returns a comma joined list of all the users tags linked to these journals. - * - * @param array $array - * - * @return array - * @throws \Illuminate\Contracts\Encryption\DecryptException - */ - private function getTags(array $array): array - { - $set = DB::table('tag_transaction_journal') - ->whereIn('tag_transaction_journal.transaction_journal_id', $array) - ->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'tag_transaction_journal.transaction_journal_id') - ->where('transaction_journals.user_id', $this->job->user_id) - ->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag']); - $result = []; - foreach ($set as $entry) { - $id = (int)$entry->transaction_journal_id; - $result[$id] = $result[$id] ?? []; - $result[$id][] = $entry->tag; - } - - return $result; - } } diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php index cadc73b157..08afdd281f 100644 --- a/app/Generator/Report/Audit/MonthReportGenerator.php +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -28,9 +28,8 @@ namespace FireflyIII\Generator\Report\Audit; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Generator\Report\ReportGeneratorInterface; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; -use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Support\Collection; @@ -72,12 +71,10 @@ class MonthReportGenerator implements ReportGeneratorInterface $reportType = 'audit'; $accountIds = implode(',', $this->accounts->pluck('id')->toArray()); $hideable = ['buttons', 'icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', - 'interest_date', 'book_date', 'process_date', - // three new optional fields. - 'due_date', 'payment_date', 'invoice_date', + 'from', 'to', 'budget', 'category', 'bill', + // more new optional fields - 'internal_reference', 'notes', 'create_date', 'update_date', ]; try { @@ -96,7 +93,7 @@ class MonthReportGenerator implements ReportGeneratorInterface * Get the audit report. * * @param Account $account - * @param Carbon $date + * @param Carbon $date * * @return array * @@ -112,11 +109,11 @@ class MonthReportGenerator implements ReportGeneratorInterface $accountRepository = app(AccountRepositoryInterface::class); $accountRepository->setUser($account->user); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end); - $journals = $collector->getTransactions(); - $journals = $journals->reverse(); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end) + ->withAccountInformation(); + $journals = $collector->getExtractedJournals(); $dayBeforeBalance = app('steam')->balance($account, $date); $startBalance = $dayBeforeBalance; $currency = $currencyRepos->findNull((int)$accountRepository->getMetaValue($account, 'currency_id')); @@ -125,23 +122,23 @@ class MonthReportGenerator implements ReportGeneratorInterface throw new FireflyException('Unexpected NULL value in account currency preference.'); } - /** @var Transaction $transaction */ - foreach ($journals as $transaction) { - $transaction->before = $startBalance; - $transactionAmount = $transaction->transaction_amount; + foreach ($journals as $index => $journal) { + $journals[$index]['balance_before'] = $startBalance; + $transactionAmount = $journal['amount']; - if ($currency->id === $transaction->foreign_currency_id) { - $transactionAmount = $transaction->transaction_foreign_amount; + if ($currency->id === $journal['foreign_currency_id']) { + $transactionAmount = $journal['foreign_amount']; } - $newBalance = bcadd($startBalance, $transactionAmount); - $transaction->after = $newBalance; - $startBalance = $newBalance; + $newBalance = bcadd($startBalance, $transactionAmount); + $journals[$index]['balance_after'] = $newBalance; + $startBalance = $newBalance; + } $return = [ - 'journals' => $journals->reverse(), - 'exists' => $journals->count() > 0, + 'journals' => $journals, + 'exists' => count($journals) > 0, 'end' => $this->end->formatLocalized((string)trans('config.month_and_day')), 'endBalance' => app('steam')->balance($account, $this->end), 'dayBefore' => $date->formatLocalized((string)trans('config.month_and_day')), diff --git a/app/Generator/Report/Budget/MonthReportGenerator.php b/app/Generator/Report/Budget/MonthReportGenerator.php index 6b7539c0d4..85c0f70789 100644 --- a/app/Generator/Report/Budget/MonthReportGenerator.php +++ b/app/Generator/Report/Budget/MonthReportGenerator.php @@ -27,11 +27,7 @@ namespace FireflyIII\Generator\Report\Budget; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\Support; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\OpposingAccountFilter; -use FireflyIII\Helpers\Filter\PositiveAmountFilter; -use FireflyIII\Helpers\Filter\TransferFilter; -use FireflyIII\Models\Transaction; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; use Log; @@ -50,7 +46,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface private $budgets; /** @var Carbon The end date. */ private $end; - /** @var Collection The expenses in the report. */ + /** @var array The expenses in the report. */ private $expenses; /** @var Carbon The start date. */ private $start; @@ -93,6 +89,57 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface return $result; } + /** + * Get the expenses. + * + * @return array + */ + protected function getExpenses(): array + { + if (count($this->expenses) > 0) { + Log::debug('Return previous set of expenses.'); + + return $this->expenses; + } + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) + ->setTypes([TransactionType::WITHDRAWAL]) + ->withAccountInformation() + ->withBudgetInformation() + ->setBudgets($this->budgets); + + $journals = $collector->getExtractedJournals(); + $this->expenses = $journals; + + return $journals; + } + + /** + * Summarize a collection by its budget. + * + * @param array $array + * + * @return array + */ + private function summarizeByBudget(array $array): array + { + $result = [ + 'sum' => '0', + ]; + + /** @var array $journal */ + foreach ($array as $journal) { + $budgetId = (int)$journal['budget_id']; + $result[$budgetId] = $result[$budgetId] ?? '0'; + $result[$budgetId] = bcadd($journal['amount'], $result[$budgetId]); + $result['sum'] = bcadd($result['sum'], $journal['amount']); + } + + return $result; + } + /** * Set the involved accounts. * @@ -184,58 +231,4 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface { return $this; } - - /** - * Get the expenses. - * - * @return Collection - */ - protected function getExpenses(): Collection - { - if ($this->expenses->count() > 0) { - Log::debug('Return previous set of expenses.'); - - return $this->expenses; - } - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) - ->setTypes([TransactionType::WITHDRAWAL]) - ->setBudgets($this->budgets)->withOpposingAccount(); - $collector->removeFilter(TransferFilter::class); - - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(PositiveAmountFilter::class); - - $transactions = $collector->getTransactions(); - $this->expenses = $transactions; - - return $transactions; - } - - /** - * Summarize a collection by its budget. - * - * @param Collection $collection - * - * @return array - */ - private function summarizeByBudget(Collection $collection): array - { - $result = [ - 'sum' => '0', - ]; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $jrnlBudId = (int)$transaction->transaction_journal_budget_id; - $transBudId = (int)$transaction->transaction_budget_id; - $budgetId = max($jrnlBudId, $transBudId); - $result[$budgetId] = $result[$budgetId] ?? '0'; - $result[$budgetId] = bcadd($transaction->transaction_amount, $result[$budgetId]); - $result['sum'] = bcadd($result['sum'], $transaction->transaction_amount); - } - - return $result; - } } diff --git a/app/Generator/Report/Category/MonthReportGenerator.php b/app/Generator/Report/Category/MonthReportGenerator.php index cb7a299ac4..2032ff234e 100644 --- a/app/Generator/Report/Category/MonthReportGenerator.php +++ b/app/Generator/Report/Category/MonthReportGenerator.php @@ -27,6 +27,7 @@ namespace FireflyIII\Generator\Report\Category; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\Support; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\TransactionCollectorInterface; use FireflyIII\Helpers\Filter\NegativeAmountFilter; use FireflyIII\Helpers\Filter\OpposingAccountFilter; @@ -51,9 +52,9 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface private $categories; /** @var Carbon The end date */ private $end; - /** @var Collection The expenses */ + /** @var array The expenses */ private $expenses; - /** @var Collection The income in the report. */ + /** @var array The income in the report. */ private $income; /** @var Carbon The start date. */ private $start; @@ -201,27 +202,23 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface /** * Get the expenses for this report. * - * @return Collection + * @return array */ - protected function getExpenses(): Collection + protected function getExpenses(): array { - if ($this->expenses->count() > 0) { + if (count($this->expenses) > 0) { Log::debug('Return previous set of expenses.'); return $this->expenses; } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setCategories($this->categories)->withOpposingAccount(); - $collector->removeFilter(TransferFilter::class); + ->setCategories($this->categories)->withAccountInformation(); - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(PositiveAmountFilter::class); - - $transactions = $collector->getTransactions(); + $transactions = $collector->getExtractedJournals(); $this->expenses = $transactions; return $transactions; @@ -230,24 +227,22 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface /** * Get the income for this report. * - * @return Collection + * @return array */ - protected function getIncome(): Collection + protected function getIncome(): array { - if ($this->income->count() > 0) { + if (count($this->income) > 0) { return $this->income; } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->setCategories($this->categories)->withOpposingAccount(); + ->setCategories($this->categories)->withAccountInformation(); - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(NegativeAmountFilter::class); - - $transactions = $collector->getTransactions(); + $transactions = $collector->getExtractedJournals(); $this->income = $transactions; return $transactions; @@ -256,20 +251,18 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface /** * Summarize the category. * - * @param Collection $collection + * @param array $array * * @return array */ - private function summarizeByCategory(Collection $collection): array + private function summarizeByCategory(array $array): array { $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $jrnlCatId = (int)$transaction->transaction_journal_category_id; - $transCatId = (int)$transaction->transaction_category_id; - $categoryId = max($jrnlCatId, $transCatId); + /** @var array $journal */ + foreach ($array as $journal) { + $categoryId = (int)$journal['category_id']; $result[$categoryId] = $result[$categoryId] ?? '0'; - $result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]); + $result[$categoryId] = bcadd($journal['amount'], $result[$categoryId]); } return $result; diff --git a/app/Generator/Report/Support.php b/app/Generator/Report/Support.php index dad556dc7a..4ea3d00d1e 100644 --- a/app/Generator/Report/Support.php +++ b/app/Generator/Report/Support.php @@ -24,13 +24,12 @@ declare(strict_types=1); namespace FireflyIII\Generator\Report; -use FireflyIII\Models\Transaction; use Illuminate\Support\Collection; /** * Class Support. - * @method Collection getExpenses() - * @method Collection getIncome() + * @method array getExpenses() + * @method array getIncome() * * @codeCoverageIgnore */ @@ -39,53 +38,63 @@ class Support /** * Get the top expenses. * - * @return Collection + * @return array */ - public function getTopExpenses(): Collection + public function getTopExpenses(): array { - return $this->getExpenses()->sortBy('transaction_amount'); + $expenses = $this->getExpenses(); + usort($expenses, function ($a, $b) { + return $a['amount'] <=> $b['amount']; + }); + + return $expenses; } /** * Get the top income. * - * @return Collection + * @return array */ - public function getTopIncome(): Collection + public function getTopIncome(): array { - return $this->getIncome()->sortByDesc('transaction_amount'); + $income = $this->getIncome(); + usort($income, function ($a, $b) { + return $b['amount'] <=> $a['amount']; + }); + + return $income; } /** * Get averages from a collection. * - * @param Collection $collection - * @param int $sortFlag + * @param array $array + * @param int $sortFlag * * @return array */ - protected function getAverages(Collection $collection, int $sortFlag): array + protected function getAverages(array $array, int $sortFlag): array { $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { + /** @var array $journal */ + foreach ($array as $journal) { // opposing name and ID: - $opposingId = $transaction->opposing_account_id; + $opposingId = $journal['destination_account_id']; // is not set? if (!isset($result[$opposingId])) { - $name = $transaction->opposing_account_name; + $name = $journal['destination_account_name']; $result[$opposingId] = [ 'name' => $name, 'count' => 1, 'id' => $opposingId, - 'average' => $transaction->transaction_amount, - 'sum' => $transaction->transaction_amount, + 'average' => $journal['amount'], + 'sum' => $journal['amount'], ]; continue; } ++$result[$opposingId]['count']; - $result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount); + $result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $journal['amount']); $result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], (string)$result[$opposingId]['count']); } @@ -151,18 +160,18 @@ class Support /** * Summarize the data by account. * - * @param Collection $collection + * @param array $array * * @return array */ - protected function summarizeByAccount(Collection $collection): array + protected function summarizeByAccount(array $array): array { $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $accountId = $transaction->account_id; + /** @var array $journal */ + foreach ($array as $journal) { + $accountId = $journal['source_account_id'] ?? 0; $result[$accountId] = $result[$accountId] ?? '0'; - $result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]); + $result[$accountId] = bcadd($journal['amount'], $result[$accountId]); } return $result; diff --git a/app/Generator/Report/Tag/MonthReportGenerator.php b/app/Generator/Report/Tag/MonthReportGenerator.php index 165682c172..7b0c457dbe 100644 --- a/app/Generator/Report/Tag/MonthReportGenerator.php +++ b/app/Generator/Report/Tag/MonthReportGenerator.php @@ -28,12 +28,7 @@ namespace FireflyIII\Generator\Report\Tag; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\Support; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\DoubleTransactionFilter; -use FireflyIII\Helpers\Filter\NegativeAmountFilter; -use FireflyIII\Helpers\Filter\OpposingAccountFilter; -use FireflyIII\Helpers\Filter\PositiveAmountFilter; -use FireflyIII\Helpers\Filter\TransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; @@ -52,9 +47,9 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface private $accounts; /** @var Carbon The end date */ private $end; - /** @var Collection The expenses involved */ + /** @var array The expenses involved */ private $expenses; - /** @var Collection The income involved */ + /** @var array The income involved */ private $income; /** @var Carbon The start date */ private $start; @@ -107,6 +102,80 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface return $result; } + /** + * Get expense collection for report. + * + * @return array + */ + protected function getExpenses(): array + { + if (count($this->expenses) > 0) { + Log::debug('Return previous set of expenses.'); + + return $this->expenses; + } + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) + ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) + ->setTags($this->tags)->withAccountInformation(); + + $journals = $collector->getExtractedJournals(); + $this->expenses = $journals; + + return $journals; + } + + /** + * Get the income for this report. + * + * @return array + */ + protected function getIncome(): array + { + if (count($this->income) > 0) { + return $this->income; + } + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) + ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) + ->setTags($this->tags)->withAccountInformation(); + + $journals = $collector->getExtractedJournals(); + $this->income = $journals; + + return $journals; + } + + /** + * Summarize by tag. + * + * @param array $array + * + * @return array + */ + protected function summarizeByTag(array $array): array + { + $tagIds = array_map('\intval', $this->tags->pluck('id')->toArray()); + $result = []; + /** @var array $journal */ + foreach ($array as $journal) { + /** @var Tag $journalTag */ + foreach ($journal['tag_ids'] as $journalTag) { + $journalTagId = (int)$journalTag; + if (\in_array($journalTagId, $tagIds, true)) { + $result[$journalTagId] = $result[$journalTagId] ?? '0'; + $result[$journalTagId] = bcadd($journal['amount'], $result[$journalTagId]); + } + } + } + + return $result; + } + /** * Set the accounts. * @@ -198,88 +267,4 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface return $this; } - - /** - * Get expense collection for report. - * - * @return Collection - */ - protected function getExpenses(): Collection - { - if ($this->expenses->count() > 0) { - Log::debug('Return previous set of expenses.'); - - return $this->expenses; - } - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) - ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setTags($this->tags)->withOpposingAccount(); - $collector->removeFilter(TransferFilter::class); - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(PositiveAmountFilter::class); - $collector->addFilter(DoubleTransactionFilter::class); - - $transactions = $collector->getTransactions(); - $this->expenses = $transactions; - - return $transactions; - } - - /** - * Get the income for this report. - * - * @return Collection - */ - protected function getIncome(): Collection - { - if ($this->income->count() > 0) { - return $this->income; - } - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) - ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->setTags($this->tags)->withOpposingAccount(); - - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(NegativeAmountFilter::class); - $collector->addFilter(DoubleTransactionFilter::class); - - $transactions = $collector->getTransactions(); - $this->income = $transactions; - - return $transactions; - } - - /** - * Summarize by tag. - * - * @param Collection $collection - * - * @return array - */ - protected function summarizeByTag(Collection $collection): array - { - $tagIds = array_map('\intval', $this->tags->pluck('id')->toArray()); - $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $journal = $transaction->transactionJournal; - $journalTags = $journal->tags; - /** @var Tag $journalTag */ - foreach ($journalTags as $journalTag) { - $journalTagId = (int)$journalTag->id; - if (\in_array($journalTagId, $tagIds, true)) { - $result[$journalTagId] = $result[$journalTagId] ?? '0'; - $result[$journalTagId] = bcadd($transaction->transaction_amount, $result[$journalTagId]); - } - } - } - - return $result; - } } diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index b166c7955b..7525c72a5b 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -37,6 +37,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Query\JoinClause; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; +use function count; +use function get_class; /** * Class GroupCollector @@ -74,7 +76,7 @@ class GroupCollector implements GroupCollectorInterface public function __construct() { if ('testing' === config('app.env')) { - app('log')->warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + app('log')->warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } $this->hasAccountInformation = false; $this->hasCatInformation = false; @@ -139,7 +141,8 @@ class GroupCollector implements GroupCollectorInterface /** @var array $group */ foreach ($selection as $group) { foreach ($group['transactions'] as $journalId => $journal) { - $return[$journalId] = $journal; + $journal['group_title'] = $group['title']; + $return[$journalId] = $journal; } } @@ -161,11 +164,149 @@ class GroupCollector implements GroupCollectorInterface $this->total = $collection->count(); // now filter the array according to the page and the - $offset = $this->page * $this->limit; + $offset = $this->page * $this->limit; + return $collection->slice($offset, $this->limit); } + /** + * @param Collection $collection + * + * @return Collection + * @throws Exception + */ + private function parseArray(Collection $collection): Collection + { + $groups = []; + /** @var TransactionGroup $augmentedGroup */ + foreach ($collection as $augmentedGroup) { + $groupId = $augmentedGroup->transaction_group_id; + if (!isset($groups[$groupId])) { + // make new array + $groupArray = [ + 'id' => $augmentedGroup->transaction_group_id, + 'user_id' => $augmentedGroup->user_id, + 'title' => $augmentedGroup->transaction_group_title, + 'count' => 1, + 'sums' => [], + 'transactions' => [], + ]; + $journalId = (int)$augmentedGroup->transaction_journal_id; + $groupArray['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup); + $groups[$groupId] = $groupArray; + continue; + } + // or parse the rest. + $journalId = (int)$augmentedGroup->transaction_journal_id; + $groups[$groupId]['count']++; + + if (isset($groups[$groupId]['transactions'][$journalId])) { + $groups[$groupId]['transactions'][$journalId] = + $this->mergeTags($groups[$groupId]['transactions'][$journalId], $augmentedGroup); + } + + if (!isset($groups[$groupId]['transactions'][$journalId])) { + $groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup); + } + + } + $groups = $this->parseSums($groups); + + return new Collection($groups); + } + + /** + * @param TransactionGroup $augmentedGroup + * + * @return array + * @throws Exception + */ + private function parseAugmentedGroup(TransactionGroup $augmentedGroup): array + { + $result = $augmentedGroup->toArray(); + $result['tags'] = []; + $result['tag_ids'] = []; + $result['date'] = new Carbon($result['date']); + $result['created_at'] = new Carbon($result['created_at']); + $result['updated_at'] = new Carbon($result['updated_at']); + $result['reconciled'] = 1 === (int)$result['reconciled']; + if (isset($augmentedGroup['tag'])) { + $result['tags'][] = $augmentedGroup['tag']; + } + if (isset($augmentedGroup['tag_id'])) { + $result['tag_ids'][] = $augmentedGroup['tag_id']; + } + + return $result; + } + + /** + * @param array $existingJournal + * @param TransactionGroup $newGroup + * @return array + */ + private function mergeTags(array $existingJournal, TransactionGroup $newGroup): array + { + $newArray = $newGroup->toArray(); + if (isset($newArray['tag_id'])) { + $existingJournal['tag_ids'][] = (int)$newArray['tag_id']; + } + if (isset($newArray['tag'])) { + $existingJournal['tags'][] = $newArray['tag']; + + } + $existingJournal['tags'] = array_unique($existingJournal['tags']); + $existingJournal['tag_ids'] = array_unique($existingJournal['tag_ids']); + + return $existingJournal; + } + + /** + * @param array $groups + * + * @return array + */ + private function parseSums(array $groups): array + { + /** + * @var int $groudId + * @var array $group + */ + foreach ($groups as $groudId => $group) { + /** @var array $transaction */ + foreach ($group['transactions'] as $transaction) { + $currencyId = (int)$transaction['currency_id']; + + // set default: + if (!isset($groups[$groudId]['sums'][$currencyId])) { + $groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId; + $groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['currency_code']; + $groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['currency_symbol']; + $groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places']; + $groups[$groudId]['sums'][$currencyId]['amount'] = '0'; + } + $groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount']); + + if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) { + $currencyId = (int)$transaction['foreign_currency_id']; + + // set default: + if (!isset($groups[$groudId]['sums'][$currencyId])) { + $groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId; + $groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['foreign_currency_code']; + $groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['foreign_currency_symbol']; + $groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places']; + $groups[$groudId]['sums'][$currencyId]['amount'] = '0'; + } + $groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['foreign_amount']); + } + } + } + + return $groups; + } + /** * Same as getGroups but everything is in a paginator. * @@ -217,6 +358,27 @@ class GroupCollector implements GroupCollectorInterface return $this; } + /** + * Will include budget ID + name, if any. + * + * @return GroupCollectorInterface + */ + public function withBudgetInformation(): GroupCollectorInterface + { + if (false === $this->hasBudgetInformation) { + // join link table + $this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + // join cat table + $this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id'); + // add fields + $this->fields[] = 'budgets.id as budget_id'; + $this->fields[] = 'budgets.name as budget_name'; + $this->hasBudgetInformation = true; + } + + return $this; + } + /** * Limit the search to a specific budget. * @@ -262,6 +424,27 @@ class GroupCollector implements GroupCollectorInterface return $this; } + /** + * Will include category ID + name, if any. + * + * @return GroupCollectorInterface + */ + public function withCategoryInformation(): GroupCollectorInterface + { + if (false === $this->hasCatInformation) { + // join link table + $this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + // join cat table + $this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id'); + // add fields + $this->fields[] = 'categories.id as category_id'; + $this->fields[] = 'categories.name as category_name'; + $this->hasCatInformation = true; + } + + return $this; + } + /** * Limit results to a specific currency, either foreign or normal one. * @@ -290,7 +473,7 @@ class GroupCollector implements GroupCollectorInterface */ public function setJournalIds(array $journalIds): GroupCollectorInterface { - if (\count($journalIds) > 0) { + if (count($journalIds) > 0) { $this->query->whereIn('transaction_journals.id', $journalIds); } @@ -366,6 +549,21 @@ class GroupCollector implements GroupCollectorInterface return $this; } + /** + * Join table to get tag information. + */ + private function joinTagTables(): void + { + if (false === $this->hasJoinedTagTables) { + // join some extra tables: + $this->hasJoinedTagTables = true; + $this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + $this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id'); + $this->fields[] = 'tags.id as tag_id'; + $this->fields[] = 'tags.tag as tag'; + } + } + /** * Limit the search to one specific transaction group. * @@ -409,6 +607,44 @@ class GroupCollector implements GroupCollectorInterface return $this; } + /** + * Build the query. + */ + private function startQuery(): void + { + app('log')->debug('TransactionCollector::startQuery'); + $this->query = $this->user + ->transactionGroups() + ->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id') + // join source transaction. + ->leftJoin( + 'transactions as source', function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id') + ->where('source.amount', '<', 0); + } + ) + // join destination transaction + ->leftJoin( + 'transactions as destination', function (JoinClause $join) { + $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id') + ->where('destination.amount', '>', 0); + } + ) + // left join transaction type. + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id') + ->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id') + ->whereNull('transaction_groups.deleted_at') + ->whereNull('transaction_journals.deleted_at') + ->whereNull('source.deleted_at') + ->whereNull('destination.deleted_at') + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') + ->orderBy('transaction_journals.description', 'DESC') + ->orderBy('source.amount', 'DESC'); + } + /** * Automatically include all stuff required to make API calls work. * @@ -482,193 +718,32 @@ class GroupCollector implements GroupCollectorInterface } /** - * Will include budget ID + name, if any. + * Limit the search to a specific bunch of categories. + * + * @param Collection $categories * * @return GroupCollectorInterface */ - public function withBudgetInformation(): GroupCollectorInterface + public function setCategories(Collection $categories): GroupCollectorInterface { - if (false === $this->hasBudgetInformation) { - // join link table - $this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - // join cat table - $this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id'); - // add fields - $this->fields[] = 'budgets.id as budget_id'; - $this->fields[] = 'budgets.name as budget_name'; - $this->hasBudgetInformation = true; - } + $this->withCategoryInformation(); + $this->query->where('categories.id', $categories->pluck('id')->toArray()); return $this; } /** - * Will include category ID + name, if any. + * Limit results to a specific set of tags. + * + * @param Collection $tags * * @return GroupCollectorInterface */ - public function withCategoryInformation(): GroupCollectorInterface + public function setTags(Collection $tags): GroupCollectorInterface { - if (false === $this->hasCatInformation) { - // join link table - $this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - // join cat table - $this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id'); - // add fields - $this->fields[] = 'categories.id as category_id'; - $this->fields[] = 'categories.name as category_name'; - $this->hasCatInformation = true; - } + $this->joinTagTables(); + $this->query->whereIn('tag_transaction_journal.tag_id', $tags->pluck('id')->toArray()); return $this; } - - /** - * Join table to get tag information. - */ - private function joinTagTables(): void - { - if (false === $this->hasJoinedTagTables) { - // join some extra tables: - $this->hasJoinedTagTables = true; - $this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - } - } - - /** - * @param Collection $collection - * - * @return Collection - * @throws Exception - */ - private function parseArray(Collection $collection): Collection - { - $groups = []; - /** @var TransactionGroup $augmentedGroup */ - foreach ($collection as $augmentedGroup) { - $groupId = $augmentedGroup->transaction_group_id; - if (!isset($groups[$groupId])) { - // make new array - $groupArray = [ - 'id' => $augmentedGroup->transaction_group_id, - 'user_id' => $augmentedGroup->user_id, - 'title' => $augmentedGroup->transaction_group_title, - 'count' => 1, - 'sums' => [], - 'transactions' => [], - ]; - $journalId = (int)$augmentedGroup->transaction_journal_id; - $groupArray['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup); - $groups[$groupId] = $groupArray; - continue; - } - // or parse the rest. - $journalId = (int)$augmentedGroup->transaction_journal_id; - $groups[$groupId]['count']++; - $groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup); - } - $groups = $this->parseSums($groups); - - return new Collection($groups); - } - - /** - * @param TransactionGroup $augmentedGroup - * - * @return array - * @throws Exception - */ - private function parseAugmentedGroup(TransactionGroup $augmentedGroup): array - { - $result = $augmentedGroup->toArray(); - $result['date'] = new Carbon($result['date']); - $result['created_at'] = new Carbon($result['created_at']); - $result['updated_at'] = new Carbon($result['updated_at']); - $result['reconciled'] = 1 === (int)$result['reconciled']; - - return $result; - } - - /** - * @param array $groups - * - * @return array - */ - private function parseSums(array $groups): array - { - /** - * @var int $groudId - * @var array $group - */ - foreach ($groups as $groudId => $group) { - /** @var array $transaction */ - foreach ($group['transactions'] as $transaction) { - $currencyId = (int)$transaction['currency_id']; - - // set default: - if (!isset($groups[$groudId]['sums'][$currencyId])) { - $groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId; - $groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['currency_code']; - $groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['currency_symbol']; - $groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places']; - $groups[$groudId]['sums'][$currencyId]['amount'] = '0'; - } - $groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount']); - - if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) { - $currencyId = (int)$transaction['foreign_currency_id']; - - // set default: - if (!isset($groups[$groudId]['sums'][$currencyId])) { - $groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId; - $groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['foreign_currency_code']; - $groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['foreign_currency_symbol']; - $groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places']; - $groups[$groudId]['sums'][$currencyId]['amount'] = '0'; - } - $groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['foreign_amount']); - } - } - } - - return $groups; - } - - /** - * Build the query. - */ - private function startQuery(): void - { - app('log')->debug('TransactionCollector::startQuery'); - $this->query = $this->user - ->transactionGroups() - ->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id') - // join source transaction. - ->leftJoin( - 'transactions as source', function (JoinClause $join) { - $join->on('source.transaction_journal_id', '=', 'transaction_journals.id') - ->where('source.amount', '<', 0); - } - ) - // join destination transaction - ->leftJoin( - 'transactions as destination', function (JoinClause $join) { - $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id') - ->where('destination.amount', '>', 0); - } - ) - // left join transaction type. - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id') - ->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id') - ->whereNull('transaction_groups.deleted_at') - ->whereNull('transaction_journals.deleted_at') - ->whereNull('source.deleted_at') - ->whereNull('destination.deleted_at') - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') - ->orderBy('transaction_journals.description', 'DESC') - ->orderBy('source.amount', 'DESC'); - } } \ No newline at end of file diff --git a/app/Helpers/Collector/GroupCollectorInterface.php b/app/Helpers/Collector/GroupCollectorInterface.php index 09fb4acbf7..8684b2b082 100644 --- a/app/Helpers/Collector/GroupCollectorInterface.php +++ b/app/Helpers/Collector/GroupCollectorInterface.php @@ -105,6 +105,7 @@ interface GroupCollectorInterface */ public function setCategory(Category $category): GroupCollectorInterface; + /** * Limit results to a specific currency, either foreign or normal one. * @@ -160,6 +161,15 @@ interface GroupCollectorInterface */ public function setTag(Tag $tag): GroupCollectorInterface; + /** + * Limit results to a specific set of tags. + * + * @param Collection $tags + * + * @return GroupCollectorInterface + */ + public function setTags(Collection $tags): GroupCollectorInterface; + /** * Limit the search to one specific transaction group. * @@ -201,6 +211,15 @@ interface GroupCollectorInterface */ public function withAccountInformation(): GroupCollectorInterface; + /** + * Limit the search to a specific bunch of categories. + * + * @param Collection $categories + * + * @return GroupCollectorInterface + */ + public function setCategories(Collection $categories): GroupCollectorInterface; + /** * Include bill name + ID. * diff --git a/public/v1/js/app.js b/public/v1/js/app.js index 54310ae926..c7746307f6 100644 --- a/public/v1/js/app.js +++ b/public/v1/js/app.js @@ -54641,7 +54641,7 @@ exports = module.exports = __webpack_require__(0)(false); // module -exports.push([module.i, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", ""]); +exports.push([module.i, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", ""]); // exports @@ -54961,6 +54961,14 @@ Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); foreignCurrency = null; } + // correct some id's + if (0 === destId) { + destId = null; + } + if (0 === sourceId) { + sourceId = null; + } + currentArray = { type: transactionType, date: date, diff --git a/resources/assets/js/components/transactions/CreateTransaction.vue b/resources/assets/js/components/transactions/CreateTransaction.vue index 50a80b1180..3d369b5b56 100644 --- a/resources/assets/js/components/transactions/CreateTransaction.vue +++ b/resources/assets/js/components/transactions/CreateTransaction.vue @@ -309,6 +309,14 @@ foreignCurrency = null; } + // correct some id's + if(0 === destId) { + destId = null; + } + if(0 === sourceId) { + sourceId = null; + } + currentArray = { type: transactionType, diff --git a/resources/views/v1/reports/audit/report.twig b/resources/views/v1/reports/audit/report.twig index 2a22974078..88eb8baf8a 100644 --- a/resources/views/v1/reports/audit/report.twig +++ b/resources/views/v1/reports/audit/report.twig @@ -47,6 +47,7 @@ start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat), })|raw }} + {% else %} @@ -60,6 +61,7 @@ end: auditData[account.id].end, balance: formatAmountByAccount(account,auditData[account.id].endBalance) })|raw }} +

{% include 'reports.partials.journals-audit' with {'journals': auditData[account.id].journals,'account':account} %}

diff --git a/resources/views/v1/reports/budget/month.twig b/resources/views/v1/reports/budget/month.twig index e86ffbb997..35db32221c 100644 --- a/resources/views/v1/reports/budget/month.twig +++ b/resources/views/v1/reports/budget/month.twig @@ -201,7 +201,7 @@ {% endif %} - {% if topExpenses.count > 0 %} + {% if topExpenses|length > 0 %}

@@ -221,16 +221,18 @@ {% set totalSum = 0 %} {% for row in topExpenses %} - {% set totalSum = totalSum + row.transaction_amount %} + + {% set totalSum = totalSum + row.amount %} + {% if loop.index > listLength %} {% else %} {% endif %} - - {% if row.transaction_description|length > 0 %} - {{ row.transaction_description }} ({{ row.description }}) + + {% if row.group_title|length > 0 %} + {{ row.group_title }} ({{ row.description }}) {% else %} {{ row.description }} {% endif %} @@ -239,14 +241,18 @@ {{ row.date.formatLocalized(monthAndDayFormat) }} - - - {{ row.opposing_account_name }} + + + + {{ row.destination_account_name }} - - {{ row.transaction_amount|formatAmount }} + + + + {{ formatAmountBySymbol(row.amount, row.currency_symbol, row.currency_symbol_decimal_places) }} + {% endfor %} diff --git a/resources/views/v1/reports/category/month.twig b/resources/views/v1/reports/category/month.twig index 3eb88dae79..4c2cad458d 100644 --- a/resources/views/v1/reports/category/month.twig +++ b/resources/views/v1/reports/category/month.twig @@ -25,7 +25,8 @@ {% for account in accounts %} - {{ account.name }} + {{ account.name }} {% if accountSummary[account.id] %} - {{ category.name }} + {{ category.name }} {% if categorySummary[category.id] %}
+ {% if averageExpenses|length > 0 %}
@@ -217,7 +220,8 @@ {% if averageExpenses|length > listLength %} - {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} + {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} {% endif %} @@ -234,12 +238,13 @@
{% endif %} - {% if topExpenses.count > 0 %} + {% if topExpenses|length > 0 %}
-

{{ 'expenses'|_ }} ({{ trans('firefly.topX', {number: listLength}) }})

+

{{ 'expenses'|_ }} ({{ trans('firefly.topX', {number: listLength}) }} + )

@@ -261,9 +266,9 @@ {% endif %} - - {% endfor %} @@ -287,7 +292,8 @@ {% if topExpenses|length > listLength %} {% endif %} @@ -407,7 +413,8 @@ {% if topIncome.count > listLength %} {% endif %} diff --git a/resources/views/v1/reports/partials/journals-audit.twig b/resources/views/v1/reports/partials/journals-audit.twig index 6a041ad85b..f800753e16 100644 --- a/resources/views/v1/reports/partials/journals-audit.twig +++ b/resources/views/v1/reports/partials/journals-audit.twig @@ -1 +1,114 @@ -

REPLACE ME

\ No newline at end of file +
- - {% if row.transaction_description|length > 0 %} - {{ row.transaction_description }} ({{ row.description }}) + + {% if row.group_title|length > 0 %} + {{ row.group_title }} ({{ row.description }}) {% else %} {{ row.description }} {% endif %} @@ -272,13 +277,13 @@ {{ row.date.formatLocalized(monthAndDayFormat) }} - - {{ row.opposing_account_name }} + + + {{ row.destination_account_name }} - {{ row.transaction_amount|formatAmount }} + + {{ formatAmountBySymbol(row.amount, row.currency_symbol, row.currency_symbol_decimal_places) }}
- {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} + {{ trans('firefly.show_full_list',{number:incomeTopLength}) }}
- {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} + {{ trans('firefly.show_full_list',{number:incomeTopLength}) }}
+ + + + + + + + + + + + {# new optional fields (3x) #} + + + + + + + + {# more optional fields (2x) #} + + + + + + + {% for journal in journals %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% endfor %} + + +
  {{ trans('list.description') }}{{ trans('list.balance_before') }}{{ trans('list.amount') }}{{ trans('list.balance_after') }}{{ trans('list.date') }}{{ trans('list.from') }}{{ trans('list.to') }}{{ trans('list.bill') }}{{ trans('list.create_date') }}{{ trans('list.update_date') }}
+
+ + +
+
+ {% if journal.transaction_type_type == 'Withdrawal' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Deposit' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Transfer' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Reconciliation' %} + XX + {% endif %} + + {% if journal.transaction_type_type == 'Opening balance' %} + XX + {% endif %} + + + + {% if journal.group_title|length > 0 %} + {{ journal.group_title }} ({{ journal.description }}) + {% else %} + {{ journal.description }} + {% endif %} + + + {{ formatAmountBySymbol(journal.balance_before, journal.currency_symbol, journal.currency_symbol_decimal_places) }} + + {{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_symbol_decimal_places) }} + + {{ formatAmountBySymbol(journal.balance_after, journal.currency_symbol, journal.currency_symbol_decimal_places) }} + {{ journal.date.formatLocalized(monthAndDayFormat) }} + {{ journal.source_account_name }} + + {{ journal.destination_account_name }} + + TODO BUDGET + + TODO CATEGORY + + TODO BILL + + {{ journal.created_at.formatLocalized(dateTimeFormat) }} + + {{ journal.updated_at.formatLocalized(dateTimeFormat) }} +
diff --git a/resources/views/v1/reports/tag/month.twig b/resources/views/v1/reports/tag/month.twig index 04442d99d4..b58e3a001f 100644 --- a/resources/views/v1/reports/tag/month.twig +++ b/resources/views/v1/reports/tag/month.twig @@ -256,7 +256,7 @@
{% endif %} - {% if topExpenses.count > 0 %} + {% if topExpenses|length > 0 %}