From 805456d032a6b61fc7e51d80db6d3f78cb2abb58 Mon Sep 17 00:00:00 2001
From: Victor Mosin
Date: Sat, 30 Dec 2017 09:21:28 +0100
Subject: [PATCH] Introduced bulk edit endpoint for bulk edit of categories and
tags
---
.../Transaction/MassController.php | 87 ++++++++++++++++++-
.../Requests/MassEditBulkJournalRequest.php | 48 ++++++++++
.../Journal/JournalRepository.php | 43 +++++++++
.../Journal/JournalRepositoryInterface.php | 9 ++
public/js/ff/transactions/list.js | 25 +++++-
public/js/ff/transactions/mass/edit-bulk.js | 43 +++++++++
resources/lang/de_DE/firefly.php | 2 +
resources/lang/de_DE/list.php | 1 +
resources/lang/en_US/firefly.php | 2 +
resources/lang/en_US/list.php | 1 +
resources/views/list/journals.twig | 2 +
.../views/transactions/mass/edit-bulk.twig | 65 ++++++++++++++
resources/views/transactions/mass/edit.twig | 1 -
routes/web.php | 2 +
14 files changed, 328 insertions(+), 3 deletions(-)
create mode 100644 app/Http/Requests/MassEditBulkJournalRequest.php
create mode 100644 public/js/ff/transactions/mass/edit-bulk.js
create mode 100644 resources/views/transactions/mass/edit-bulk.twig
diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php
index 2bb0a44d27..aea0bf9d9d 100644
--- a/app/Http/Controllers/Transaction/MassController.php
+++ b/app/Http/Controllers/Transaction/MassController.php
@@ -26,6 +26,7 @@ use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\MassDeleteJournalRequest;
use FireflyIII\Http\Requests\MassEditJournalRequest;
+use FireflyIII\Http\Requests\MassEditBulkJournalRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
@@ -131,7 +132,7 @@ class MassController extends Controller
// skip transactions that have multiple destinations, multiple sources or are an opening balance.
$filtered = new Collection;
$messages = [];
- // @var TransactionJournal
+ /** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$sources = $journal->sourceAccountList();
$destinations = $journal->destinationAccountList();
@@ -200,6 +201,70 @@ class MassController extends Controller
return view('transactions.mass.edit', compact('journals', 'subTitle', 'accounts', 'budgets'));
}
+ /**
+ * @param Collection $journals
+ *
+ * @return View
+ */
+ public function editBulk(Collection $journals)
+ {
+ $subTitle = trans('firefly.mass_edit_bulk_journals');
+
+ // skip transactions that have multiple destinations, multiple sources or are an opening balance.
+ $filtered = new Collection;
+ $messages = [];
+ /** @var TransactionJournal $journal */
+ foreach ($journals as $journal) {
+ $sources = $journal->sourceAccountList();
+ $destinations = $journal->destinationAccountList();
+ if ($sources->count() > 1) {
+ $messages[] = trans('firefly.cannot_edit_multiple_source', ['description' => $journal->description, 'id' => $journal->id]);
+ continue;
+ }
+
+ if ($destinations->count() > 1) {
+ $messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]);
+ continue;
+ }
+ if (TransactionType::OPENING_BALANCE === $journal->transactionType->type) {
+ $messages[] = trans('firefly.cannot_edit_opening_balance');
+ continue;
+ }
+
+ // cannot edit reconciled transactions / journals:
+ if ($journal->transactions->first()->reconciled) {
+ $messages[] = trans('firefly.cannot_edit_reconciled', ['description' => $journal->description, 'id' => $journal->id]);
+ continue;
+ }
+
+ $filtered->push($journal);
+ }
+
+ if (count($messages) > 0) {
+ Session::flash('info', $messages);
+ }
+
+ // put previous url in session
+ $this->rememberPreviousUri('transactions.mass-edit-bulk.uri');
+ Session::flash('gaEventCategory', 'transactions');
+ Session::flash('gaEventAction', 'mass-edit-bulk');
+
+ // collect some useful meta data for the mass edit:
+ $filtered->each(
+ function (TransactionJournal $journal) {
+ $journal->transaction_count = $journal->transactions()->count();
+ }
+ );
+
+ if (0 === $filtered->count()) {
+ Session::flash('error', trans('firefly.no_edit_multiple_left'));
+ }
+
+ $journals = $filtered;
+
+ return view('transactions.mass.edit-bulk', compact('journals', 'subTitle'));
+ }
+
/**
* @param MassEditJournalRequest $request
* @param JournalRepositoryInterface $repository
@@ -264,4 +329,24 @@ class MassController extends Controller
// redirect to previous URL:
return redirect($this->getPreviousUri('transactions.mass-edit.uri'));
}
+
+ /**
+ * @param MassEditBulkJournalRequest $request
+ * @param JournalRepositoryInterface $repository
+ *
+ * @return mixed
+ */
+ public function updateBulk(MassEditBulkJournalRequest $request, JournalRepositoryInterface $repository)
+ {
+ $journalIds = $request->get('journals');
+ $count = 0;
+ if (is_array($journalIds)) {
+ $repository->updateBulk($journalIds, $request->get('category'), $request->get('tags'));
+ }
+ Preferences::mark();
+ Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count]));
+
+ // redirect to previous URL:
+ return redirect($this->getPreviousUri('transactions.mass-edit-bulk.uri'));
+ }
}
diff --git a/app/Http/Requests/MassEditBulkJournalRequest.php b/app/Http/Requests/MassEditBulkJournalRequest.php
new file mode 100644
index 0000000000..c3024e6729
--- /dev/null
+++ b/app/Http/Requests/MassEditBulkJournalRequest.php
@@ -0,0 +1,48 @@
+.
+ */
+declare(strict_types=1);
+
+namespace FireflyIII\Http\Requests;
+
+/**
+ * Class MassEditBulkJournalRequest.
+ */
+class MassEditBulkJournalRequest extends Request
+{
+ /**
+ * @return bool
+ */
+ public function authorize()
+ {
+ // Only allow logged in users
+ return auth()->check();
+ }
+
+ /**
+ * @return array
+ */
+ public function rules()
+ {
+ // fixed
+
+ return [];
+ }
+}
diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php
index c71afd2e20..43b66715fd 100644
--- a/app/Repositories/Journal/JournalRepository.php
+++ b/app/Repositories/Journal/JournalRepository.php
@@ -24,7 +24,9 @@ namespace FireflyIII\Repositories\Journal;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
+use FireflyIII\Models\Category;
use FireflyIII\Models\Note;
+use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
@@ -432,6 +434,47 @@ class JournalRepository implements JournalRepositoryInterface
return $journal;
}
+ /**
+ * @param array $journals
+ * @param var $category
+ * @param var $tags
+ *
+ * @return int
+ */
+ public function updateBulk(array $journals, $category, $tags): int
+ {
+ $count = 0;
+ foreach ($journals as $journalId) {
+ $journal = $this->find(intval($journalId));
+ if ($journal) {
+ // update category:
+ if (isset($category)) {
+ $categoryToReplace = Category::firstOrCreateEncrypted(['name' => strval($category), 'user_id' => $journal->user->id]);
+ $journal->categories()->sync([$categoryToReplace->id]);
+ /** @var Transaction $transaction */
+ foreach ($journal->transactions()->getResults() as $transaction) {
+ $transaction->categories()->sync([$categoryToReplace->id]);
+ $transaction->touch();
+ }
+ }
+
+ // update tags:
+ if (isset($tags)) {
+ $tagsToReplace = [];
+ foreach (explode(',', strval($tags)) as $tag) {
+ array_push($tagsToReplace, Tag::firstOrCreateEncrypted(['tag' => $tag, 'user_id' => $journal->user->id])->id);
+ }
+ $journal->tags()->sync($tagsToReplace);
+ }
+
+ $journal->touch();
+ ++$count;
+ }
+ }
+
+ return $count;
+ }
+
/**
* Same as above but for transaction journal with multiple transactions.
*
diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php
index a136ed1784..a895e03833 100644
--- a/app/Repositories/Journal/JournalRepositoryInterface.php
+++ b/app/Repositories/Journal/JournalRepositoryInterface.php
@@ -161,6 +161,15 @@ interface JournalRepositoryInterface
*/
public function update(TransactionJournal $journal, array $data): TransactionJournal;
+ /**
+ * @param array $journals
+ * @param var $category
+ * @param var $tags
+ *
+ * @return int
+ */
+ public function updateBulk(array $journals, $category, $tags): int;
+
/**
* @param TransactionJournal $journal
* @param array $data
diff --git a/public/js/ff/transactions/list.js b/public/js/ff/transactions/list.js
index 1a9b5d1230..4f013541ba 100644
--- a/public/js/ff/transactions/list.js
+++ b/public/js/ff/transactions/list.js
@@ -18,7 +18,7 @@
* along with Firefly III. If not, see .
*/
-/** global: edit_selected_txt, delete_selected_txt */
+/** global: edit_selected_txt, edit_bulk_selected_txt, delete_selected_txt */
/**
*
@@ -45,6 +45,8 @@ $(document).ready(function () {
// click the edit button:
$('.mass_edit').click(goToMassEdit);
+ // click the edit button:
+ $('.mass_edit_bulk').click(goToMassBulkEdit);
// click the delete button:
$('.mass_delete').click(goToMassDelete);
// click reconcile button
@@ -100,6 +102,26 @@ function goToMassEdit() {
return false;
}
+/**
+ *
+ * @returns {boolean}
+ */
+function goToMassBulkEdit() {
+ "use strict";
+ var checkedArray = getCheckboxes();
+
+ // go to specially crafted URL:
+ var bases = document.getElementsByTagName('base');
+ var baseHref = null;
+
+ if (bases.length > 0) {
+ baseHref = bases[0].href;
+ }
+
+ window.location.href = baseHref + '/transactions/mass/edit/bulk/' + checkedArray;
+ return false;
+}
+
/**
*
* @returns {boolean}
@@ -144,6 +166,7 @@ function countChecked() {
var checked = $('.select_all_single:checked').length;
if (checked > 0) {
$('.mass_edit span').text(edit_selected_txt + ' (' + checked + ')');
+ $('.mass_edit_bulk span').text(edit_bulk_selected_txt + ' (' + checked + ')');
$('.mass_delete span').text(delete_selected_txt + ' (' + checked + ')');
// get amount for the transactions:
diff --git a/public/js/ff/transactions/mass/edit-bulk.js b/public/js/ff/transactions/mass/edit-bulk.js
new file mode 100644
index 0000000000..0aa1f01a3c
--- /dev/null
+++ b/public/js/ff/transactions/mass/edit-bulk.js
@@ -0,0 +1,43 @@
+/*
+ * edit.js
+ * Copyright (c) 2017 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 .
+ */
+
+/** global: what */
+
+$(document).ready(function () {
+ "use strict";
+
+ $.getJSON('json/categories').done(function (data) {
+ $('input[name="category"]').typeahead({source: data});
+ });
+
+ $.getJSON('json/tags').done(function (data) {
+ var opt = {
+ typeahead: {
+ source: data,
+ afterSelect: function () {
+ this.$element.val("");
+ }
+ }
+ };
+ $('input[name="tags"]').tagsinput(
+ opt
+ );
+ });
+});
\ No newline at end of file
diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php
index 33a6260351..c6e298e9d9 100644
--- a/resources/lang/de_DE/firefly.php
+++ b/resources/lang/de_DE/firefly.php
@@ -713,6 +713,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?',
'reconcile_selected' => 'Ausgleichen',
'mass_delete_journals' => 'Löschen Sie eine Reihe von Überweisungen',
'mass_edit_journals' => 'Bearbeiten Sie eine Reihe von Überweisungen',
+ 'mass_edit_bulk_journals' => 'Bulk-Bearbeitung',
'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.',
'no_budget' => '(kein Budget)',
'no_budget_squared' => '(kein Budget)',
@@ -724,6 +725,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?',
'opt_group_savingAsset' => 'Sparkonten',
'opt_group_sharedAsset' => 'Shared asset accounts',
'opt_group_ccAsset' => 'Kreditkarten',
+ 'mass_edit_bulk' => 'Bulk-Bearbeitung',
// new user:
'welcome' => 'Willkommen bei Firefly!',
diff --git a/resources/lang/de_DE/list.php b/resources/lang/de_DE/list.php
index eb4980e563..2cdeffbc9a 100644
--- a/resources/lang/de_DE/list.php
+++ b/resources/lang/de_DE/list.php
@@ -89,6 +89,7 @@ return [
'budget_count' => 'Anzahl Budgets',
'rule_and_groups_count' => 'Anzahl Regeln und Regelgruppen',
'tags_count' => 'Anzahl Tags',
+ 'tags' => 'Tags',
'inward' => 'Inward description',
'outward' => 'Outward description',
'number_of_transactions' => 'Anzahl der Zahlungsvorgänge',
diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php
index 74ea4fea3f..888950eaa6 100644
--- a/resources/lang/en_US/firefly.php
+++ b/resources/lang/en_US/firefly.php
@@ -728,6 +728,7 @@ return [
'reconcile_selected' => 'Reconcile',
'mass_delete_journals' => 'Delete a number of transactions',
'mass_edit_journals' => 'Edit a number of transactions',
+ 'mass_edit_bulk_journals' => 'Bulk edit',
'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.',
'no_budget' => 'none',
'no_budget_squared' => '(no budget)',
@@ -739,6 +740,7 @@ return [
'opt_group_savingAsset' => 'Savings accounts',
'opt_group_sharedAsset' => 'Shared asset accounts',
'opt_group_ccAsset' => 'Credit cards',
+ 'mass_edit_bulk' => 'Bulk edit',
// new user:
'welcome' => 'Welcome to Firefly!',
diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php
index f7f6d0c8fa..a09af7d39a 100644
--- a/resources/lang/en_US/list.php
+++ b/resources/lang/en_US/list.php
@@ -89,6 +89,7 @@ return [
'budget_count' => 'Number of budgets',
'rule_and_groups_count' => 'Number of rules and rule groups',
'tags_count' => 'Number of tags',
+ 'tags' => 'Tags',
'inward' => 'Inward description',
'outward' => 'Outward description',
'number_of_transactions' => 'Number of transactions',
diff --git a/resources/views/list/journals.twig b/resources/views/list/journals.twig
index 0e3f4ff036..a0b30a4d9b 100644
--- a/resources/views/list/journals.twig
+++ b/resources/views/list/journals.twig
@@ -36,6 +36,7 @@
diff --git a/resources/views/transactions/mass/edit-bulk.twig b/resources/views/transactions/mass/edit-bulk.twig
new file mode 100644
index 0000000000..fdff24348f
--- /dev/null
+++ b/resources/views/transactions/mass/edit-bulk.twig
@@ -0,0 +1,65 @@
+{% extends "./layout/default" %}
+
+{% block breadcrumbs %}
+ {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, journals) }}
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
+{% block scripts %}
+
+
+
+
+{% endblock %}
+{% block styles %}
+
+{% endblock %}
diff --git a/resources/views/transactions/mass/edit.twig b/resources/views/transactions/mass/edit.twig
index 9f834d26e5..5f46ef6e47 100644
--- a/resources/views/transactions/mass/edit.twig
+++ b/resources/views/transactions/mass/edit.twig
@@ -19,7 +19,6 @@
{{ 'cannot_edit_other_fields'|_ }}
-
|
{{ trans('list.description') }} |
diff --git a/routes/web.php b/routes/web.php
index e368b4fc71..03c1c4caeb 100755
--- a/routes/web.php
+++ b/routes/web.php
@@ -784,8 +784,10 @@ Route::group(
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'Transaction', 'prefix' => 'transactions/mass', 'as' => 'transactions.mass.'], function () {
Route::get('edit/{journalList}', ['uses' => 'MassController@edit', 'as' => 'edit']);
+ Route::get('edit/bulk/{journalList}', ['uses' => 'MassController@editBulk', 'as' => 'edit-bulk']);
Route::get('delete/{journalList}', ['uses' => 'MassController@delete', 'as' => 'delete']);
Route::post('update', ['uses' => 'MassController@update', 'as' => 'update']);
+ Route::post('update/bulk', ['uses' => 'MassController@updateBulk', 'as' => 'update-bulk']);
Route::post('destroy', ['uses' => 'MassController@destroy', 'as' => 'destroy']);
}
);