diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php
index 596385404a..931daa3742 100644
--- a/app/Helpers/Collector/GroupCollector.php
+++ b/app/Helpers/Collector/GroupCollector.php
@@ -409,7 +409,7 @@ class GroupCollector implements GroupCollectorInterface
*/
public function setTag(Tag $tag): GroupCollectorInterface
{
- $this->joinTagTables();
+ $this->withTagInformation();
$this->query->where('tag_transaction_journal.tag_id', $tag->id);
return $this;
@@ -537,7 +537,7 @@ class GroupCollector implements GroupCollectorInterface
*/
public function setTags(Collection $tags): GroupCollectorInterface
{
- $this->joinTagTables();
+ $this->withTagInformation();
$this->query->whereIn('tag_transaction_journal.tag_id', $tags->pluck('id')->toArray());
return $this;
@@ -619,9 +619,11 @@ class GroupCollector implements GroupCollectorInterface
$return = [];
/** @var array $group */
foreach ($selection as $group) {
+ $count = count($group['transactions']);
foreach ($group['transactions'] as $journalId => $journal) {
- $journal['group_title'] = $group['title'];
- $return[$journalId] = $journal;
+ $journal['group_title'] = $group['title'];
+ $journal['journals_in_group'] = $count;
+ $return[$journalId] = $journal;
}
}
@@ -776,6 +778,24 @@ class GroupCollector implements GroupCollectorInterface
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
*
@@ -789,16 +809,18 @@ class GroupCollector implements GroupCollectorInterface
$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,
- 'count' => 1,
- 'sums' => [],
- 'transactions' => [],
+ '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] = $this->parseAugmentedGroup($augmentedGroup);
+ $groupArray['transactions'][$journalId] = $parsedGroup;
$groups[$groupId] = $groupArray;
continue;
}
@@ -948,13 +970,6 @@ class GroupCollector implements GroupCollectorInterface
$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_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';
}
}
diff --git a/app/Helpers/Collector/GroupCollectorInterface.php b/app/Helpers/Collector/GroupCollectorInterface.php
index e9e358f73f..2880576c3c 100644
--- a/app/Helpers/Collector/GroupCollectorInterface.php
+++ b/app/Helpers/Collector/GroupCollectorInterface.php
@@ -53,6 +53,13 @@ interface GroupCollectorInterface
*/
public function getSum(): string;
+ /**
+ * Add tag info.
+ *
+ * @return GroupCollectorInterface
+ */
+ public function withTagInformation(): GroupCollectorInterface;
+
/**
* Return the groups.
*
diff --git a/app/Http/Controllers/Json/AutoCompleteController.php b/app/Http/Controllers/Json/AutoCompleteController.php
index 10fbc664e4..2cd13713ee 100644
--- a/app/Http/Controllers/Json/AutoCompleteController.php
+++ b/app/Http/Controllers/Json/AutoCompleteController.php
@@ -244,7 +244,6 @@ class AutoCompleteController extends Controller
$array[$index]['name'] = $item['tag'];
}
-
return response()->json($array);
}
diff --git a/app/Http/Controllers/Transaction/BulkController.php b/app/Http/Controllers/Transaction/BulkController.php
index a19012351d..bc57f8fe2b 100644
--- a/app/Http/Controllers/Transaction/BulkController.php
+++ b/app/Http/Controllers/Transaction/BulkController.php
@@ -43,6 +43,7 @@ class BulkController extends Controller
/**
* BulkController constructor.
+ * @codeCoverageIgnore
*/
public function __construct()
{
@@ -66,7 +67,7 @@ class BulkController extends Controller
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
- public function edit(Collection $journals)
+ public function edit(array $journals)
{
$subTitle = (string)trans('firefly.mass_bulk_journals');
@@ -74,12 +75,6 @@ class BulkController extends Controller
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$budgetList = app('expandedform')->makeSelectListWithEmpty($repository->getActiveBudgets());
- // collect some useful meta data for the mass edit:
- $journals->each(
- function (TransactionJournal $journal) {
- $journal->transaction_count = $journal->transactions()->count();
- }
- );
return view('transactions.bulk.edit', compact('journals', 'subTitle', 'budgetList'));
}
@@ -104,38 +99,72 @@ class BulkController extends Controller
$count = 0;
foreach ($journalIds as $journalId) {
- $journal = $this->repository->findNull((int)$journalId);
- if (null === $journal) {
- continue;
- }
-
- $count++;
- Log::debug(sprintf('Found journal #%d', $journal->id));
-
- // update category if not told to ignore
- if (false === $ignoreCategory) {
- Log::debug(sprintf('Set category to %s', $request->string('category')));
-
- $this->repository->updateCategory($journal, $request->string('category'));
- }
-
- // update budget if not told to ignore (and is withdrawal)
- if (false === $ignoreBudget) {
- Log::debug(sprintf('Set budget to %d', $request->integer('budget_id')));
- $this->repository->updateBudget($journal, $request->integer('budget_id'));
- }
-
- // update tags:
- if (false === $ignoreTags) {
- Log::debug(sprintf('Set tags to %s', $request->string('budget_id')));
- $this->repository->updateTags($journal, ['tags' => explode(',', $request->string('tags'))]);
+ $journalId = (int)$journalId;
+ $journal = $this->repository->findNull($journalId);
+ if (null !== $journal) {
+ $resultA = $this->updateJournalBudget($journal, $ignoreBudget, $request->integer('budget_id'));
+ $resultB = $this->updateJournalTags($journal, $ignoreTags, explode(',', $request->string('tags')));
+ $resultC = $this->updateJournalCategory($journal, $ignoreCategory, $request->string('category'));
+ if ($resultA || $resultB || $resultC) {
+ $count++;
+ }
}
}
-
app('preferences')->mark();
$request->session()->flash('success', (string)trans('firefly.mass_edited_transactions_success', ['amount' => $count]));
// redirect to previous URL:
return redirect($this->getPreviousUri('transactions.bulk-edit.uri'));
}
+
+ /**
+ * @param TransactionJournal $journal
+ * @param bool $ignoreUpdate
+ * @param array $tags
+ * @return bool
+ */
+ public function updateJournalTags(TransactionJournal $journal, bool $ignoreUpdate, array $tags): bool
+ {
+ if (true === $ignoreUpdate) {
+ return false;
+ }
+ Log::debug(sprintf('Set tags to %s', implode(',', $tags)));
+ $this->repository->updateTags($journal, $tags);
+
+ return true;
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param bool $ignoreUpdate
+ * @param string $category
+ * @return bool
+ */
+ private function updateJournalCategory(TransactionJournal $journal, bool $ignoreUpdate, string $category): bool
+ {
+ if (true === $ignoreUpdate) {
+ return false;
+ }
+ Log::debug(sprintf('Set budget to %s', $category));
+ $this->repository->updateCategory($journal, $category);
+
+ return true;
+ }
+
+ /**
+ * @param TransactionJournal $journal
+ * @param bool $ignoreUpdate
+ * @param int $budgetId
+ * @return bool
+ */
+ private function updateJournalBudget(TransactionJournal $journal, bool $ignoreUpdate, int $budgetId): bool
+ {
+ if (true === $ignoreUpdate) {
+ return false;
+ }
+ Log::debug(sprintf('Set budget to %d', $budgetId));
+ $this->repository->updateBudget($journal, $budgetId);
+
+ return true;
+ }
}
diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php
index 6b1d3a30be..dd1752dc9b 100644
--- a/app/Repositories/Journal/JournalRepository.php
+++ b/app/Repositories/Journal/JournalRepository.php
@@ -778,7 +778,16 @@ class JournalRepository implements JournalRepositoryInterface
/** @var JournalUpdateService $service */
$service = app(JournalUpdateService::class);
- return $service->updateBudget($journal, $budgetId);
+ $service->setTransactionJournal($journal);
+ $service->setData(
+ [
+ 'budget_id' => $budgetId,
+ ]
+ );
+ $service->update();
+ $journal->refresh();
+
+ return $journal;
}
/**
@@ -793,8 +802,16 @@ class JournalRepository implements JournalRepositoryInterface
{
/** @var JournalUpdateService $service */
$service = app(JournalUpdateService::class);
+ $service->setTransactionJournal($journal);
+ $service->setData(
+ [
+ 'category_name' => $category,
+ ]
+ );
+ $service->update();
+ $journal->refresh();
- return $service->updateCategory($journal, $category);
+ return $journal;
}
/**
@@ -809,10 +826,16 @@ class JournalRepository implements JournalRepositoryInterface
{
/** @var JournalUpdateService $service */
$service = app(JournalUpdateService::class);
- $service->connectTags($journal, $tags);
+ $service->setTransactionJournal($journal);
+ $service->setData(
+ [
+ 'tags' => $tags,
+ ]
+ );
+ $service->update();
+ $journal->refresh();
return $journal;
-
}
/**
diff --git a/app/Support/Binder/SimpleJournalList.php b/app/Support/Binder/SimpleJournalList.php
index d5a9ddc12e..a07702a523 100644
--- a/app/Support/Binder/SimpleJournalList.php
+++ b/app/Support/Binder/SimpleJournalList.php
@@ -23,11 +23,9 @@ declare(strict_types=1);
namespace FireflyIII\Support\Binder;
-use FireflyIII\Models\TransactionJournal;
+use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
-use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Illuminate\Routing\Route;
-use Illuminate\Support\Collection;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
@@ -35,73 +33,46 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
*/
class SimpleJournalList implements BinderInterface
{
+
/**
* @param string $value
- * @param Route $route
+ * @param Route $route
*
* @return mixed
- * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @throws NotFoundHttpException
*/
- public static function routeBinder(string $value, Route $route): Collection
+ public static function routeBinder(string $value, Route $route): array
{
if (auth()->check()) {
- $list = array_unique(array_map('\intval', explode(',', $value)));
- if (0 === count($list)) {
- throw new NotFoundHttpException; // @codeCoverageIgnore
+ $list = self::parseList($value);
+
+ // get the journals by using the collector.
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
+ $collector->withCategoryInformation()->withBudgetInformation()->withTagInformation();
+ $collector->setJournalIds($list);
+ $result = $collector->getExtractedJournals();
+ if (0 === count($result)) {
+ throw new NotFoundHttpException;
}
- // prep some vars
- $messages = [];
- $final = new Collection;
- /** @var JournalRepositoryInterface $repository */
- $repository = app(JournalRepositoryInterface::class);
-
- // get all journals:
- /** @var \Illuminate\Support\Collection $collection */
- $collection = auth()->user()->transactionJournals()
- ->whereIn('transaction_journals.id', $list)
- ->where('transaction_journals.completed', 1)
- ->get(['transaction_journals.*']);
-
- // filter the list! Yay!
- /** @var TransactionJournal $journal */
- foreach ($collection as $journal) {
- $sources = $repository->getJournalSourceAccounts($journal);
- $destinations = $repository->getJournalDestinationAccounts($journal);
- if ($sources->count() > 1) {
- $messages[] = (string)trans('firefly.cannot_edit_multiple_source', ['description' => $journal->description, 'id' => $journal->id]);
- continue;
- }
-
- if ($destinations->count() > 1) {
- $messages[] = (string)trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]);
- continue;
- }
- if (TransactionType::OPENING_BALANCE === $repository->getTransactionType($journal)) {
- $messages[] = (string)trans('firefly.cannot_edit_opening_balance');
- continue;
- }
-
- // cannot edit reconciled transactions / journals:
- if ($repository->isJournalReconciled($journal)) {
- $messages[] = (string)trans('firefly.cannot_edit_reconciled', ['description' => $journal->description, 'id' => $journal->id]);
- continue;
- }
-
- $final->push($journal);
- }
-
- if ($final->count() > 0) {
- if (count($messages) > 0) {
- session()->flash('info', $messages);
- }
-
- return $final;
- }
+ return $result;
}
throw new NotFoundHttpException;
}
+
+ /**
+ * @param string $value
+ * @return array
+ */
+ protected static function parseList(string $value): array
+ {
+ $list = array_unique(array_map('\intval', explode(',', $value)));
+ if (0 === count($list)) {
+ throw new NotFoundHttpException; // @codeCoverageIgnore
+ }
+
+ return $list;
+ }
}
diff --git a/public/v1/js/ff/common/autocomplete.js b/public/v1/js/ff/common/autocomplete.js
index 5d8b15a796..ada9f5c524 100644
--- a/public/v1/js/ff/common/autocomplete.js
+++ b/public/v1/js/ff/common/autocomplete.js
@@ -29,8 +29,8 @@ function initTagsAC() {
prefetch: {
url: 'json/tags?uid=' + uid,
filter: function (list) {
- return $.map(list, function (tagTag) {
- return {name: tagTag};
+ return $.map(list, function (item) {
+ return {name: item.name};
});
}
},
@@ -38,8 +38,8 @@ function initTagsAC() {
url: 'json/tags?search=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
- return $.map(list, function (name) {
- return {name: name};
+ return $.map(list, function (item) {
+ return {name: item.name};
});
}
}
@@ -145,8 +145,8 @@ function initCategoryAC() {
prefetch: {
url: 'json/categories?uid=' + uid,
filter: function (list) {
- return $.map(list, function (name) {
- return {name: name};
+ return $.map(list, function (object) {
+ return {name: object.name};
});
}
},
@@ -154,8 +154,8 @@ function initCategoryAC() {
url: 'json/categories?search=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
- return $.map(list, function (name) {
- return {name: name};
+ return $.map(list, function (object) {
+ return {name: object.name};
});
}
}
diff --git a/public/v1/js/ff/list/groups.js b/public/v1/js/ff/list/groups.js
new file mode 100644
index 0000000000..805cbb4cf5
--- /dev/null
+++ b/public/v1/js/ff/list/groups.js
@@ -0,0 +1,120 @@
+/*
+ * groups.js
+ * Copyright (c) 2019 thegrumpydictator@gmail.com
+ *
+ * This file is part of Firefly III.
+ *
+ * Firefly III is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Firefly III is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Firefly III. If not, see