diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index 6bc0f60d2e..eb5b1bb2cc 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -32,6 +32,7 @@ use FireflyIII\Models\Category; use FireflyIII\Models\Tag; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; use FireflyIII\User; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -42,6 +43,7 @@ use Log; /** * Class GroupCollector + * * @codeCoverageIgnore */ class GroupCollector implements GroupCollectorInterface @@ -129,15 +131,129 @@ class GroupCollector implements GroupCollectorInterface } /** - * Same as getGroups but everything is in a paginator. + * Get transactions with a specific amount. * - * @return LengthAwarePaginator + * @param string $amount + * + * @return GroupCollectorInterface */ - public function getPaginatedGroups(): LengthAwarePaginator + public function amountIs(string $amount): GroupCollectorInterface { - $set = $this->getGroups(); + $this->query->where( + function (EloquentBuilder $q) use ($amount) { + $q->where('source.amount', app('steam')->negative($amount)); + } + ); - return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); + return $this; + } + + /** + * Get transactions where the amount is less than. + * + * @param string $amount + * + * @return GroupCollectorInterface + */ + public function amountLess(string $amount): GroupCollectorInterface + { + $this->query->where( + function (EloquentBuilder $q) use ($amount) { + $q->where('destination.amount', '<', app('steam')->positive($amount)); + } + ); + + return $this; + } + + /** + * Get transactions where the amount is more than. + * + * @param string $amount + * + * @return GroupCollectorInterface + */ + public function amountMore(string $amount): GroupCollectorInterface + { + $this->query->where( + function (EloquentBuilder $q) use ($amount) { + $q->where('destination.amount', '>', app('steam')->positive($amount)); + } + ); + + return $this; + } + + /** + * + */ + public function dumpQuery(): void + { + + echo $this->query->toSql(); + echo '
';
+        print_r($this->query->getBindings());
+        echo '
'; + } + + /** + * These accounts must not be destination accounts. + * + * @param Collection $accounts + * + * @return GroupCollectorInterface + */ + public function excludeDestinationAccounts(Collection $accounts): GroupCollectorInterface + { + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $this->query->whereNotIn('destination.account_id', $accountIds); + + app('log')->debug(sprintf('GroupCollector: excludeDestinationAccounts: %s', implode(', ', $accountIds))); + } + + return $this; + } + + /** + * These accounts must not be source accounts. + * + * @param Collection $accounts + * + * @return GroupCollectorInterface + */ + public function excludeSourceAccounts(Collection $accounts): GroupCollectorInterface + { + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $this->query->whereNotIn('source.account_id', $accountIds); + + app('log')->debug(sprintf('GroupCollector: excludeSourceAccounts: %s', implode(', ', $accountIds))); + } + + return $this; + } + + /** + * Return the transaction journals without group information. Is useful in some instances. + * + * @return array + */ + public function getExtractedJournals(): array + { + $selection = $this->getGroups(); + $return = []; + /** @var array $group */ + foreach ($selection as $group) { + $count = count($group['transactions']); + foreach ($group['transactions'] as $journalId => $journal) { + $journal['group_title'] = $group['title']; + $journal['journals_in_group'] = $count; + $return[$journalId] = $journal; + } + } + + return $return; } /** @@ -162,15 +278,46 @@ class GroupCollector implements GroupCollectorInterface // now filter the array according to the page and the limit (if necessary) if (null !== $this->limit && null !== $this->page) { - $offset = ($this->page-1) * $this->limit; + $offset = ($this->page - 1) * $this->limit; - return $collection->slice($offset, $this->limit); + return $collection->slice($offset, $this->limit); } return $collection; } + /** + * Same as getGroups but everything is in a paginator. + * + * @return LengthAwarePaginator + */ + public function getPaginatedGroups(): LengthAwarePaginator + { + $set = $this->getGroups(); + + return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); + } + + /** + * Return the sum of all journals. + * TODO ignores the currency. + * + * @return string + */ + public function getSum(): string + { + $journals = $this->getExtractedJournals(); + $sum = '0'; + /** @var array $journal */ + foreach ($journals as $journal) { + $amount = (string)$journal['amount']; + $sum = bcadd($sum, $amount); + } + + return $sum; + } + /** * Define which accounts can be part of the source and destination transactions. * @@ -195,24 +342,150 @@ class GroupCollector implements GroupCollectorInterface } /** - * Define which accounts can be part of the source and destination transactions. + * Collect transactions after a specific date. * - * @param Collection $accounts + * @param Carbon $date * * @return GroupCollectorInterface */ - public function setSourceAccounts(Collection $accounts): GroupCollectorInterface + public function setAfter(Carbon $date): GroupCollectorInterface { - if ($accounts->count() > 0) { - $accountIds = $accounts->pluck('id')->toArray(); - $this->query->whereIn('source.account_id', $accountIds); + $afterStr = $date->format('Y-m-d 00:00:00'); + $this->query->where('transaction_journals.date', '>=', $afterStr); + Log::debug(sprintf('GroupCollector range is now after %s (inclusive)', $afterStr)); - app('log')->debug(sprintf('GroupCollector: setSourceAccounts: %s', implode(', ', $accountIds))); + return $this; + } + + /** + * Collect transactions before a specific date. + * + * @param Carbon $date + * + * @return GroupCollectorInterface + */ + public function setBefore(Carbon $date): GroupCollectorInterface + { + $beforeStr = $date->format('Y-m-d 00:00:00'); + $this->query->where('transaction_journals.date', '<=', $beforeStr); + Log::debug(sprintf('GroupCollector range is now before %s (inclusive)', $beforeStr)); + + return $this; + } + + /** + * Limit the search to a specific bill. + * + * @param Bill $bill + * + * @return GroupCollectorInterface + */ + public function setBill(Bill $bill): GroupCollectorInterface + { + $this->withBillInformation(); + $this->query->where('transaction_journals.bill_id', '=', $bill->id); + + return $this; + } + + /** + * Limit the search to a specific set of bills. + * + * @param Collection $bills + * + * @return GroupCollectorInterface + */ + public function setBills(Collection $bills): GroupCollectorInterface + { + $this->withBillInformation(); + $this->query->whereIn('transaction_journals.bill_id', $bills->pluck('id')->toArray()); + + return $this; + } + + /** + * Limit the search to a specific budget. + * + * @param Budget $budget + * + * @return GroupCollectorInterface + */ + public function setBudget(Budget $budget): GroupCollectorInterface + { + $this->withBudgetInformation(); + $this->query->where('budgets.id', $budget->id); + + return $this; + } + + /** + * Limit the search to a specific set of budgets. + * + * @param Collection $budgets + * + * @return GroupCollectorInterface + */ + public function setBudgets(Collection $budgets): GroupCollectorInterface + { + if ($budgets->count() > 0) { + $this->withBudgetInformation(); + $this->query->whereIn('budgets.id', $budgets->pluck('id')->toArray()); } return $this; } + /** + * Limit the search to a specific bunch of categories. + * + * @param Collection $categories + * + * @return GroupCollectorInterface + */ + public function setCategories(Collection $categories): GroupCollectorInterface + { + if ($categories->count() > 0) { + $this->withCategoryInformation(); + $this->query->whereIn('categories.id', $categories->pluck('id')->toArray()); + } + + return $this; + } + + /** + * Limit the search to a specific category. + * + * @param Category $category + * + * @return GroupCollectorInterface + */ + public function setCategory(Category $category): GroupCollectorInterface + { + $this->withCategoryInformation(); + $this->query->where('categories.id', $category->id); + + return $this; + } + + /** + * Limit results to a specific currency, either foreign or normal one. + * + * @param TransactionCurrency $currency + * + * @return GroupCollectorInterface + */ + public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface + { + $this->query->where( + function (EloquentBuilder $q) use ($currency) { + $q->where('source.transaction_currency_id', $currency->id); + $q->orWhere('source.foreign_currency_id', $currency->id); + } + ); + + return $this; + } + /** * Define which accounts can be part of the source and destination transactions. * @@ -233,143 +506,15 @@ class GroupCollector implements GroupCollectorInterface } /** - * Limit the search to a specific bill. + * Limit the result to a specific transaction group. * - * @param Bill $bill + * @param TransactionGroup $transactionGroup * * @return GroupCollectorInterface */ - public function setBill(Bill $bill): GroupCollectorInterface + public function setGroup(TransactionGroup $transactionGroup): GroupCollectorInterface { - $this->withBillInformation(); - $this->query->where('transaction_journals.bill_id', '=', $bill->id); - - return $this; - } - - /** - * Will include bill name + ID, if any. - * - * @return GroupCollectorInterface - */ - public function withBillInformation(): GroupCollectorInterface - { - if (false === $this->hasBillInformation) { - // join bill table - $this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id'); - // add fields - $this->fields[] = 'bills.id as bill_id'; - $this->fields[] = 'bills.name as bill_name'; - $this->hasBillInformation = true; - } - - return $this; - } - - /** - * Limit the search to a specific budget. - * - * @param Budget $budget - * - * @return GroupCollectorInterface - */ - public function setBudget(Budget $budget): GroupCollectorInterface - { - $this->withBudgetInformation(); - $this->query->where('budgets.id', $budget->id); - - 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 set of budgets. - * - * @param Collection $budgets - * - * @return GroupCollectorInterface - */ - public function setBudgets(Collection $budgets): GroupCollectorInterface - { - if ($budgets->count() > 0) { - $this->withBudgetInformation(); - $this->query->whereIn('budgets.id', $budgets->pluck('id')->toArray()); - } - - return $this; - } - - /** - * Limit the search to a specific category. - * - * @param Category $category - * - * @return GroupCollectorInterface - */ - public function setCategory(Category $category): GroupCollectorInterface - { - $this->withCategoryInformation(); - $this->query->where('categories.id', $category->id); - - 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. - * - * @param TransactionCurrency $currency - * - * @return GroupCollectorInterface - */ - public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface - { - $this->query->where( - function (EloquentBuilder $q) use ($currency) { - $q->where('source.transaction_currency_id', $currency->id); - $q->orWhere('source.foreign_currency_id', $currency->id); - } - ); + $this->query->where('transaction_groups.id', $transactionGroup->id); return $this; } @@ -444,6 +589,58 @@ class GroupCollector implements GroupCollectorInterface return $this; } + /** + * Search for words in descriptions. + * + * @param array $array + * + * @return GroupCollectorInterface + */ + public function setSearchWords(array $array): GroupCollectorInterface + { + $this->query->where( + function (EloquentBuilder $q) use ($array) { + $q->where( + function (EloquentBuilder $q1) use ($array) { + foreach ($array as $word) { + $keyword = sprintf('%%%s%%', $word); + $q1->where('transaction_journals.description', 'LIKE', $keyword); + } + } + ); + $q->orWhere( + function (EloquentBuilder $q2) use ($array) { + foreach ($array as $word) { + $keyword = sprintf('%%%s%%', $word); + $q2->where('transaction_groups.title', 'LIKE', $keyword); + } + } + ); + } + ); + + return $this; + } + + /** + * Define which accounts can be part of the source and destination transactions. + * + * @param Collection $accounts + * + * @return GroupCollectorInterface + */ + public function setSourceAccounts(Collection $accounts): GroupCollectorInterface + { + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $this->query->whereIn('source.account_id', $accountIds); + + app('log')->debug(sprintf('GroupCollector: setSourceAccounts: %s', implode(', ', $accountIds))); + } + + return $this; + } + /** * Limit results to a specific tag. * @@ -459,6 +656,21 @@ class GroupCollector implements GroupCollectorInterface return $this; } + /** + * Limit results to a specific set of tags. + * + * @param Collection $tags + * + * @return GroupCollectorInterface + */ + public function setTags(Collection $tags): GroupCollectorInterface + { + $this->withTagInformation(); + $this->query->whereIn('tag_transaction_journal.tag_id', $tags->pluck('id')->toArray()); + + return $this; + } + /** * Limit the search to one specific transaction group. * @@ -556,33 +768,80 @@ class GroupCollector implements GroupCollectorInterface } /** - * Limit the search to a specific bunch of categories. - * - * @param Collection $categories + * Will include bill name + ID, if any. * * @return GroupCollectorInterface */ - public function setCategories(Collection $categories): GroupCollectorInterface + public function withBillInformation(): GroupCollectorInterface { - if ($categories->count() > 0) { - $this->withCategoryInformation(); - $this->query->whereIn('categories.id', $categories->pluck('id')->toArray()); + if (false === $this->hasBillInformation) { + // join bill table + $this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id'); + // add fields + $this->fields[] = 'bills.id as bill_id'; + $this->fields[] = 'bills.name as bill_name'; + $this->hasBillInformation = true; } return $this; } /** - * Limit results to a specific set of tags. - * - * @param Collection $tags + * Will include budget ID + name, if any. * * @return GroupCollectorInterface */ - public function setTags(Collection $tags): GroupCollectorInterface + public function withBudgetInformation(): GroupCollectorInterface { - $this->withTagInformation(); - $this->query->whereIn('tag_transaction_journal.tag_id', $tags->pluck('id')->toArray()); + 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; + } + + /** + * 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; + } + + /** + * @return GroupCollectorInterface + */ + public function withTagInformation(): GroupCollectorInterface + { + $this->fields[] = 'tags.id as tag_id'; + $this->fields[] = 'tags.tag as tag_name'; + $this->fields[] = 'tags.date as tag_date'; + $this->fields[] = 'tags.description as tag_description'; + $this->fields[] = 'tags.latitude as tag_latitude'; + $this->fields[] = 'tags.longitude as tag_longitude'; + $this->fields[] = 'tags.zoomLevel as tag_zoom_level'; + + $this->joinTagTables(); return $this; } @@ -622,317 +881,22 @@ class GroupCollector implements GroupCollectorInterface } /** - * + * Join table to get tag information. */ - public function dumpQuery(): void + private function joinTagTables(): void { - - echo $this->query->toSql(); - echo '
';
-        print_r($this->query->getBindings());
-        echo '
'; - } - - /** - * Return the sum of all journals. - * TODO ignores the currency. - * - * @return string - */ - public function getSum(): string - { - $journals = $this->getExtractedJournals(); - $sum = '0'; - /** @var array $journal */ - foreach ($journals as $journal) { - $amount = (string)$journal['amount']; - $sum = bcadd($sum, $amount); + 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'); } - - return $sum; } /** - * Return the transaction journals without group information. Is useful in some instances. - * - * @return array - */ - public function getExtractedJournals(): array - { - $selection = $this->getGroups(); - $return = []; - /** @var array $group */ - foreach ($selection as $group) { - $count = count($group['transactions']); - foreach ($group['transactions'] as $journalId => $journal) { - $journal['group_title'] = $group['title']; - $journal['journals_in_group'] = $count; - $return[$journalId] = $journal; - } - } - - return $return; - } - - /** - * Search for words in descriptions. - * - * @param array $array - * - * @return GroupCollectorInterface - */ - public function setSearchWords(array $array): GroupCollectorInterface - { - $this->query->where( - function (EloquentBuilder $q) use ($array) { - $q->where( - function (EloquentBuilder $q1) use ($array) { - foreach ($array as $word) { - $keyword = sprintf('%%%s%%', $word); - $q1->where('transaction_journals.description', 'LIKE', $keyword); - } - } - ); - $q->orWhere( - function (EloquentBuilder $q2) use ($array) { - foreach ($array as $word) { - $keyword = sprintf('%%%s%%', $word); - $q2->where('transaction_groups.title', 'LIKE', $keyword); - } - } - ); - } - ); - - return $this; - } - - /** - * Limit the result to a specific transaction group. - * - * @param TransactionGroup $transactionGroup - * - * @return GroupCollectorInterface - */ - public function setGroup(TransactionGroup $transactionGroup): GroupCollectorInterface - { - $this->query->where('transaction_groups.id', $transactionGroup->id); - - return $this; - } - - /** - * Limit the search to a specific set of bills. - * - * @param Collection $bills - * - * @return GroupCollectorInterface - */ - public function setBills(Collection $bills): GroupCollectorInterface - { - $this->withBillInformation(); - $this->query->whereIn('transaction_journals.bill_id', $bills->pluck('id')->toArray()); - - return $this; - } - - /** - * Get transactions with a specific amount. - * - * @param string $amount - * - * @return GroupCollectorInterface - */ - public function amountIs(string $amount): GroupCollectorInterface - { - $this->query->where( - function (EloquentBuilder $q) use ($amount) { - $q->where('source.amount', app('steam')->negative($amount)); - } - ); - - return $this; - } - - /** - * Get transactions where the amount is less than. - * - * @param string $amount - * - * @return GroupCollectorInterface - */ - public function amountLess(string $amount): GroupCollectorInterface - { - $this->query->where( - function (EloquentBuilder $q) use ($amount) { - $q->where('destination.amount', '<', app('steam')->positive($amount)); - } - ); - - return $this; - } - - /** - * Get transactions where the amount is more than. - * - * @param string $amount - * - * @return GroupCollectorInterface - */ - public function amountMore(string $amount): GroupCollectorInterface - { - $this->query->where( - function (EloquentBuilder $q) use ($amount) { - $q->where('destination.amount', '>', app('steam')->positive($amount)); - } - ); - - return $this; - } - - /** - * Collect transactions before a specific date. - * - * @param Carbon $date - * - * @return GroupCollectorInterface - */ - public function setBefore(Carbon $date): GroupCollectorInterface - { - $beforeStr = $date->format('Y-m-d 00:00:00'); - $this->query->where('transaction_journals.date', '<=', $beforeStr); - Log::debug(sprintf('GroupCollector range is now before %s (inclusive)', $beforeStr)); - - return $this; - } - - /** - * Collect transactions after a specific date. - * - * @param Carbon $date - * - * @return GroupCollectorInterface - */ - public function setAfter(Carbon $date): GroupCollectorInterface - { - $afterStr = $date->format('Y-m-d 00:00:00'); - $this->query->where('transaction_journals.date', '>=', $afterStr); - Log::debug(sprintf('GroupCollector range is now after %s (inclusive)', $afterStr)); - - return $this; - } - - /** - * @return GroupCollectorInterface - */ - public function withTagInformation(): GroupCollectorInterface - { - $this->fields[] = 'tags.id as tag_id'; - $this->fields[] = 'tags.tag as tag_name'; - $this->fields[] = 'tags.date as tag_date'; - $this->fields[] = 'tags.description as tag_description'; - $this->fields[] = 'tags.latitude as tag_latitude'; - $this->fields[] = 'tags.longitude as tag_longitude'; - $this->fields[] = 'tags.zoomLevel as tag_zoom_level'; - - $this->joinTagTables(); - - return $this; - } - - /** - * @param Collection $collection - * - * @return Collection - */ - 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 - $parsedGroup = $this->parseAugmentedGroup($augmentedGroup); - $groupArray = [ - 'id' => $augmentedGroup->transaction_group_id, - 'user_id' => $augmentedGroup->user_id, - 'title' => $augmentedGroup->transaction_group_title, - 'transaction_type' => $parsedGroup['transaction_type_type'], - 'count' => 1, - 'sums' => [], - 'transactions' => [], - ]; - $journalId = (int)$augmentedGroup->transaction_journal_id; - $groupArray['transactions'][$journalId] = $parsedGroup; - $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 - */ - private function parseAugmentedGroup(TransactionGroup $augmentedGroup): array - { - $result = $augmentedGroup->toArray(); - $result['tags'] = []; - try { - $result['date'] = new Carbon($result['date']); - $result['created_at'] = new Carbon($result['created_at']); - $result['updated_at'] = new Carbon($result['updated_at']); - } catch (Exception $e) { - Log::error($e->getMessage()); - } - $result['reconciled'] = 1 === (int)$result['reconciled']; - if (isset($augmentedGroup['tag_id'])) { // assume the other fields are present as well. - $tagId = (int)$augmentedGroup['tag_id']; - $tagDate = null; - try { - $tagDate = Carbon::parse($augmentedGroup['tag_date']); - } catch (InvalidDateException $e) { - Log::debug(sprintf('Could not parse date: %s', $e->getMessage())); - } - - $result['tags'][$tagId] = [ - 'id' => (int)$result['tag_id'], - 'name' => $result['tag_name'], - 'date' => $tagDate, - 'description' => $result['tag_description'], - 'latitude' => $result['tag_latitude'], - 'longitude' => $result['tag_longitude'], - 'zoom_level' => $result['tag_zoom_level'], - ]; - } - - return $result; - } - - /** - * @param array $existingJournal + * @param array $existingJournal * @param TransactionGroup $newGroup + * * @return array */ private function mergeTags(array $existingJournal, TransactionGroup $newGroup): array @@ -962,6 +926,95 @@ class GroupCollector implements GroupCollectorInterface return $existingJournal; } + /** + * @param Collection $collection + * + * @return Collection + */ + private function parseArray(Collection $collection): Collection + { + $groups = []; + /** @var TransactionJournal $augumentedJournal */ + foreach ($collection as $augumentedJournal) { + $groupId = $augumentedJournal->transaction_group_id; + + if (!isset($groups[$groupId])) { + // make new array + $parsedGroup = $this->parseAugmentedGroup($augumentedJournal); + $groupArray = [ + 'id' => $augumentedJournal->transaction_group_id, + 'user_id' => $augumentedJournal->user_id, + 'title' => $augumentedJournal->transaction_group_title, + 'transaction_type' => $parsedGroup['transaction_type_type'], + 'count' => 1, + 'sums' => [], + 'transactions' => [], + ]; + $journalId = (int)$augumentedJournal->transaction_journal_id; + $groupArray['transactions'][$journalId] = $parsedGroup; + $groups[$groupId] = $groupArray; + continue; + } + // or parse the rest. + $journalId = (int)$augumentedJournal->transaction_journal_id; + $groups[$groupId]['count']++; + + if (isset($groups[$groupId]['transactions'][$journalId])) { + $groups[$groupId]['transactions'][$journalId] = $this->mergeTags($groups[$groupId]['transactions'][$journalId], $augumentedJournal); + } + + if (!isset($groups[$groupId]['transactions'][$journalId])) { + $groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedGroup($augumentedJournal); + } + + + } + + $groups = $this->parseSums($groups); + + return new Collection($groups); + } + + /** + * @param TransactionJournal $augumentedJournal + * + * @return array + */ + private function parseAugmentedGroup(TransactionJournal $augumentedJournal): array + { + $result = $augumentedJournal->toArray(); + $result['tags'] = []; + try { + $result['date'] = new Carbon($result['date']); + $result['created_at'] = new Carbon($result['created_at']); + $result['updated_at'] = new Carbon($result['updated_at']); + } catch (Exception $e) { + Log::error($e->getMessage()); + } + $result['reconciled'] = 1 === (int)$result['reconciled']; + if (isset($augumentedJournal['tag_id'])) { // assume the other fields are present as well. + $tagId = (int)$augumentedJournal['tag_id']; + $tagDate = null; + try { + $tagDate = Carbon::parse($augumentedJournal['tag_date']); + } catch (InvalidDateException $e) { + Log::debug(sprintf('Could not parse date: %s', $e->getMessage())); + } + + $result['tags'][$tagId] = [ + 'id' => (int)$result['tag_id'], + 'name' => $result['tag_name'], + 'date' => $tagDate, + 'description' => $result['tag_description'], + 'latitude' => $result['tag_latitude'], + 'longitude' => $result['tag_longitude'], + 'zoom_level' => $result['tag_zoom_level'], + ]; + } + + return $result; + } + /** * @param array $groups * @@ -970,7 +1023,7 @@ class GroupCollector implements GroupCollectorInterface private function parseSums(array $groups): array { /** - * @var int $groudId + * @var int $groudId * @var array $group */ foreach ($groups as $groudId => $group) { @@ -999,7 +1052,9 @@ class GroupCollector implements GroupCollectorInterface $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'] ?? '0'); + $groups[$groudId]['sums'][$currencyId]['amount'] = bcadd( + $groups[$groudId]['sums'][$currencyId]['amount'], $transaction['foreign_amount'] ?? '0' + ); } } } @@ -1007,19 +1062,6 @@ class GroupCollector implements GroupCollectorInterface return $groups; } - /** - * 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'); - } - } - /** * Build the query. */ @@ -1027,8 +1069,11 @@ class GroupCollector implements GroupCollectorInterface { app('log')->debug('GroupCollector::startQuery'); $this->query = $this->user - ->transactionGroups() - ->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id') + //->transactionGroups() + //->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id') + ->transactionJournals() + ->leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id') + // join source transaction. ->leftJoin( 'transactions as source', function (JoinClause $join) { @@ -1057,42 +1102,4 @@ class GroupCollector implements GroupCollectorInterface ->orderBy('transaction_journals.description', 'DESC') ->orderBy('source.amount', 'DESC'); } - - /** - * These accounts must not be source accounts. - * - * @param Collection $accounts - * - * @return GroupCollectorInterface - */ - public function excludeSourceAccounts(Collection $accounts): GroupCollectorInterface - { - if ($accounts->count() > 0) { - $accountIds = $accounts->pluck('id')->toArray(); - $this->query->whereNotIn('source.account_id', $accountIds); - - app('log')->debug(sprintf('GroupCollector: excludeSourceAccounts: %s', implode(', ', $accountIds))); - } - - return $this; - } - - /** - * These accounts must not be destination accounts. - * - * @param Collection $accounts - * - * @return GroupCollectorInterface - */ - public function excludeDestinationAccounts(Collection $accounts): GroupCollectorInterface - { - if ($accounts->count() > 0) { - $accountIds = $accounts->pluck('id')->toArray(); - $this->query->whereNotIn('destination.account_id', $accountIds); - - app('log')->debug(sprintf('GroupCollector: excludeDestinationAccounts: %s', implode(', ', $accountIds))); - } - - return $this; - } } diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index 57964e535c..304aef10d9 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -192,11 +192,12 @@ class BillController extends Controller */ public function index() { - $start = session('start'); - $end = session('end'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $paginator = $this->billRepository->getPaginator($pageSize); - $parameters = new ParameterBag(); + $start = session('start'); + $end = session('end'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $paginator = $this->billRepository->getPaginator($pageSize); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $parameters = new ParameterBag(); $parameters->set('start', $start); $parameters->set('end', $end); @@ -206,9 +207,9 @@ class BillController extends Controller /** @var Collection $bills */ $bills = $paginator->getCollection()->map( - static function (Bill $bill) use ($transformer) { + static function (Bill $bill) use ($transformer, $defaultCurrency) { $return = $transformer->transform($bill); - $return['currency'] = $bill->transactionCurrency; + $return['currency'] = $bill->transactionCurrency ?? $defaultCurrency; return $return; } diff --git a/tests/Feature/Controllers/BillControllerTest.php b/tests/Feature/Controllers/BillControllerTest.php index 9aa46bb6d2..f3c0d41a90 100644 --- a/tests/Feature/Controllers/BillControllerTest.php +++ b/tests/Feature/Controllers/BillControllerTest.php @@ -150,7 +150,6 @@ class BillControllerTest extends TestCase } /** - * @covers \FireflyIII\Http\Controllers\BillController * @covers \FireflyIII\Http\Controllers\BillController */ public function testIndex(): void @@ -158,6 +157,8 @@ class BillControllerTest extends TestCase $this->mockDefaultSession(); $this->mockIntroPreference('shown_demo_bills_index'); + Amount::shouldReceive('getDefaultCurrency')->andReturn($this->getEuro()); + // mock stuff $this->mock(AttachmentHelperInterface::class); $bill = $this->getRandomBill(); diff --git a/tests/Feature/Controllers/CurrencyControllerTest.php b/tests/Feature/Controllers/CurrencyControllerTest.php index 5f26da5340..c9bc8b6001 100644 --- a/tests/Feature/Controllers/CurrencyControllerTest.php +++ b/tests/Feature/Controllers/CurrencyControllerTest.php @@ -267,6 +267,8 @@ class CurrencyControllerTest extends TestCase $userRepos->shouldReceive('hasRole')->atLeast()->once()->andReturn(true); $repository->shouldReceive('currencyInuse')->atLeast()->once()->andReturn(true); + $repository->shouldReceive('currencyInUseAt')->atLeast()->once()->andReturn('accounts'); + $repository->shouldNotReceive('disable'); Preferences::shouldReceive('mark')->atLeast()->once(); diff --git a/tests/Unit/Transformers/BillTransformerTest.php b/tests/Unit/Transformers/BillTransformerTest.php index f32605969c..17ff72b61a 100644 --- a/tests/Unit/Transformers/BillTransformerTest.php +++ b/tests/Unit/Transformers/BillTransformerTest.php @@ -88,8 +88,13 @@ class BillTransformerTest extends TestCase // repos should also receive call for dates: $list = new Collection( - [new Carbon('2018-01-02'), new Carbon('2018-01-09'), new Carbon('2018-01-16'), - new Carbon('2018-01-21'), new Carbon('2018-01-30'), + [ + + (object)['date' => new Carbon('2018-01-02'), 'id' => 1, 'transaction_group_id' => 1,], + (object)['date' => new Carbon('2018-01-09'), 'id' => 1, 'transaction_group_id' => 1,], + (object)['date' => new Carbon('2018-01-16'), 'id' => 1, 'transaction_group_id' => 1,], + (object)['date' => new Carbon('2018-01-21'), 'id' => 1, 'transaction_group_id' => 1,], + (object)['date' => new Carbon('2018-01-30'), 'id' => 1, 'transaction_group_id' => 1,], ] ); $repository->shouldReceive('getPaidDatesInRange')->atLeast()->once()->andReturn($list); @@ -112,7 +117,15 @@ class BillTransformerTest extends TestCase $this->assertEquals('2018-03-01', $result['next_expected_match']); $this->assertEquals(['2018-01-01'], $result['pay_dates']); - $this->assertEquals(['2018-01-02', '2018-01-09', '2018-01-16', '2018-01-21', '2018-01-30',], $result['paid_dates']); + $this->assertEquals( + [ + ['date' => '2018-01-02', 'transaction_group_id' => 1, 'transaction_journal_id' => 1,], + ['date' => '2018-01-09', 'transaction_group_id' => 1, 'transaction_journal_id' => 1,], + ['date' => '2018-01-16', 'transaction_group_id' => 1, 'transaction_journal_id' => 1,], + ['date' => '2018-01-21', 'transaction_group_id' => 1, 'transaction_journal_id' => 1,], + ['date' => '2018-01-30', 'transaction_group_id' => 1, 'transaction_journal_id' => 1,], + ], $result['paid_dates'] + ); } }