diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 9af4e98773..436bec9ecb 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -7,8 +7,10 @@ use Config; use ExpandedForm; use FireflyIII\Events\TransactionJournalStored; use FireflyIII\Events\TransactionJournalUpdated; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Requests\JournalFormRequest; +use FireflyIII\Http\Requests\MassJournalRequest; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankEvent; use FireflyIII\Models\Transaction; @@ -222,6 +224,76 @@ class TransactionController extends Controller } + /** + * @param Collection $journals + * + * @return View + */ + public function massDelete(Collection $journals) + { + $subTitle = trans('firefly.mass_delete_journals'); + + // put previous url in session + Session::put('transactions.mass-delete.url', URL::previous()); + Session::flash('gaEventCategory', 'transactions'); + Session::flash('gaEventAction', 'mass-delete'); + + return view('transactions.mass-delete', compact('journals', 'subTitle')); + + } + + /** + * @param MassJournalRequest $request + * @param JournalRepositoryInterface $repository + * + * @return mixed + */ + public function massDestroy(MassJournalRequest $request, JournalRepositoryInterface $repository) + { + $ids = $request->get('confirm_mass_delete'); + $set = new Collection; + if (is_array($ids)) { + /** @var int $journalId */ + foreach ($ids as $journalId) { + /** @var TransactionJournal $journal */ + $journal = $repository->find($journalId); + if (!is_null($journal->id) && $journalId == $journal->id) { + $set->push($journal); + } + } + } + unset($journal); + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $repository->delete($journal); + } + + Preferences::mark(); + + Session::flash('success', trans('firefly.mass_deleted_transactions_success')); + + // redirect to previous URL: + return redirect(session('transactions.mass-delete.url')); + + } + + /** + * @param Collection $journals + */ + public function massEdit(Collection $journals) + { + throw new FireflyException('Mass editing of journals is not yet supported.'); + + } + + /** + * + */ + public function massUpdate() + { + + } + /** * @param JournalRepositoryInterface $repository * diff --git a/app/Http/Requests/MassJournalRequest.php b/app/Http/Requests/MassJournalRequest.php new file mode 100644 index 0000000000..63c9a4caf9 --- /dev/null +++ b/app/Http/Requests/MassJournalRequest.php @@ -0,0 +1,49 @@ + 'required|belongsToUser:transaction_journals,id', + ]; + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 394eec0429..159f1647e7 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -377,6 +377,12 @@ Route::group( Route::post('/transaction/destroy/{tj}', ['uses' => 'TransactionController@destroy', 'as' => 'transactions.destroy']); Route::post('/transaction/reorder', ['uses' => 'TransactionController@reorder', 'as' => 'transactions.reorder']); + // mass edit and mass delete. + Route::get('/transactions/mass-edit/{journalList}', ['uses' => 'TransactionController@massEdit', 'as' => 'transactions.mass-edit']); + Route::get('/transactions/mass-delete/{journalList}', ['uses' => 'TransactionController@massDelete', 'as' => 'transactions.mass-delete']); + Route::post('/transactions/mass-update/{journalList}', ['uses' => 'TransactionController@massUpdate', 'as' => 'transactions.mass-update']); + Route::post('/transactions/mass-destroy', ['uses' => 'TransactionController@massDestroy', 'as' => 'transactions.mass-destroy']); + /** * POPUP Controllers */ diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 5eb7bb32c4..2775226b1f 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -51,6 +51,21 @@ class JournalRepository implements JournalRepositoryInterface return true; } + /** + * @param int $journalId + * + * @return TransactionJournal + */ + public function find(int $journalId) : TransactionJournal + { + $journal = $this->user->transactionjournals()->where('id', $journalId)->first(); + if (is_null($journal)) { + return new TransactionJournal; + } + + return $journal; + } + /** * Get users first transaction journal * @@ -131,7 +146,7 @@ class JournalRepository implements JournalRepositoryInterface public function getJournalsOfTypes(array $types, int $page, int $pageSize = 50): LengthAwarePaginator { $offset = ($page - 1) * $pageSize; - $query = $this->user + $query = $this->user ->transactionJournals() ->expanded() ->transactionTypes($types); diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 47a6c28ffd..e70c40a0f7 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -24,6 +24,13 @@ interface JournalRepositoryInterface */ public function delete(TransactionJournal $journal): bool; + /** + * @param int $journalId + * + * @return TransactionJournal + */ + public function find(int $journalId) : TransactionJournal; + /** * Get users first transaction journal * diff --git a/app/Support/Binder/CategoryList.php b/app/Support/Binder/CategoryList.php index 9d5dd6d459..a33309a852 100644 --- a/app/Support/Binder/CategoryList.php +++ b/app/Support/Binder/CategoryList.php @@ -38,7 +38,7 @@ class CategoryList implements BinderInterface ->where('user_id', Auth::user()->id) ->get(); - // add empty budget if applicable. + // add empty category if applicable. if (in_array('0', $ids)) { $object->push(new Category); } diff --git a/app/Support/Binder/JournalList.php b/app/Support/Binder/JournalList.php new file mode 100644 index 0000000000..f673175db4 --- /dev/null +++ b/app/Support/Binder/JournalList.php @@ -0,0 +1,48 @@ +expanded() + ->where('transaction_journals.user_id', Auth::user()->id) + ->get(TransactionJournal::QUERYFIELDS); + + if ($object->count() > 0) { + return $object; + } + } + throw new NotFoundHttpException; + } +} diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index db638513f3..c11872f66f 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -80,7 +80,7 @@ class FireflyValidator extends Validator $count = DB::table($parameters[0])->where('user_id', Auth::user()->id)->where($field, $value)->count(); - if ($count == 1) { + if ($count === 1) { return true; } diff --git a/config/firefly.php b/config/firefly.php index ec3bb71294..7197e1f7ee 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -173,6 +173,7 @@ return [ // lists 'accountList' => 'FireflyIII\Support\Binder\AccountList', 'budgetList' => 'FireflyIII\Support\Binder\BudgetList', + 'journalList' => 'FireflyIII\Support\Binder\JournalList', 'categoryList' => 'FireflyIII\Support\Binder\CategoryList', // others diff --git a/public/js/ff/transactions/list.js b/public/js/ff/transactions/list.js index c364f19e20..22b4089ddf 100644 --- a/public/js/ff/transactions/list.js +++ b/public/js/ff/transactions/list.js @@ -29,7 +29,6 @@ $(document).ready(function () { function goToMassEdit() { "use strict"; var checkedArray = getCheckboxes(); - console.log('Journals: ' + checkedArray); // go to specially crafted URL: window.location.href = 'transactions/mass-edit/' + checkedArray; @@ -39,7 +38,6 @@ function goToMassEdit() { function goToMassDelete() { "use strict"; var checkedArray = getCheckboxes(); - console.log('Journals: ' + checkedArray); // go to specially crafted URL: window.location.href = 'transactions/mass-delete/' + checkedArray; @@ -63,7 +61,6 @@ function getCheckboxes() { function countChecked() { "use strict"; var checked = $('.select_all_single:checked').length; - console.log("Now " + checked + " selected."); if (checked > 0) { $('.mass_edit span').text(edit_selected_txt + ' (' + checked + ')') $('.mass_delete span').text(delete_selected_txt + ' (' + checked + ')') @@ -88,9 +85,11 @@ function stopMassSelect() { "use strict"; // uncheck all: + $('input[name="select_all"]').prop('checked', false); uncheckAll(); countChecked(); + // hide "select all" box in table header. $('.select_boxes').hide(); diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 6e1182b758..61c2fd4973 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -560,6 +560,10 @@ return [ 'stop_selection' => 'Stop selecting transactions', 'edit_selected' => 'Edit selected', 'delete_selected' => 'Delete selected', + 'mass_delete_journals' => 'Delete a number of transactions', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be catious.', + + // new user: 'welcome' => 'Welcome to Firefly!', 'createNewAsset' => 'Create a new asset account to get started. ' . diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 0d7735421e..5d3a2333e0 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -112,8 +112,11 @@ return [ 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', + 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', + 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', + 'delete_all_permanently' => 'Delete all permanently', 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', diff --git a/resources/views/transactions/mass-delete.twig b/resources/views/transactions/mass-delete.twig new file mode 100644 index 0000000000..c39b4eee43 --- /dev/null +++ b/resources/views/transactions/mass-delete.twig @@ -0,0 +1,78 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, journal) }} +{% endblock %} + +{% block content %} + {{ Form.open({'class' : 'form-horizontal','id' : 'destroy','url' : route('transactions.mass-destroy')}) }} + +
+
+
+
+

{{ 'mass_delete_journals'|_ }}

+
+
+

+ {{ trans('form.permDeleteWarning') }} + {{ 'perm-delete-many'|_ }} +

+ +

+ {{ trans('form.mass_journal_are_you_sure') }} + {{ trans('form.mass_make_selection') }} +

+ + + + + + + + + + + {% for journal in journals %} + + + + + + + + + {% endfor %} +
 {{ trans('list.description') }}{{ trans('list.amount') }}
+ + + {{ journal.description }} + + {{ journal|formatJournal }} + + {{ journal.date.formatLocalized(monthAndDayFormat) }} + + {% if journal.source_account_type == 'Cash account' %} + (cash) + {% else %} + {{ journal.source_account_name }} + {% endif %} + + {% if journal.destination_account_type == 'Cash account' %} + (cash) + {% else %} + {{ journal.destination_account_name }} + {% endif %} +
+ +
+ +
+
+
+ + {{ Form.close|raw }} +{% endblock %}