Refactored the bulk edit controller.

This commit is contained in:
James Cole 2019-07-04 17:31:47 +02:00
parent 54623061d8
commit 3c5c14ff5a
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
16 changed files with 432 additions and 170 deletions

View File

@ -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';
}
}

View File

@ -53,6 +53,13 @@ interface GroupCollectorInterface
*/
public function getSum(): string;
/**
* Add tag info.
*
* @return GroupCollectorInterface
*/
public function withTagInformation(): GroupCollectorInterface;
/**
* Return the groups.
*

View File

@ -244,7 +244,6 @@ class AutoCompleteController extends Controller
$array[$index]['name'] = $item['tag'];
}
return response()->json($array);
}

View File

@ -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;
}
}

View File

@ -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;
}
/**

View File

@ -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;
}
}

View File

@ -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};
});
}
}

120
public/v1/js/ff/list/groups.js vendored Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
var count = 0;
$(document).ready(function () {
// top button to select all / deselect all:
$('input[name="select-all"]').change(function () {
if (this.checked) {
checkAll();
countChecked();
updateActionButtons();
} else {
uncheckAll();
countChecked();
updateActionButtons();
}
});
// click the mass edit button:
$('.mass-edit').click(goToMassEdit);
// click the bulk edit button:
$('.bulk-edit').click(goToBulkEdit);
// click the delete button:
$('.mass-delete').click(goToMassDelete);
// click checkbox:
$('.mass-select').unbind('change').change(function () {
countChecked();
updateActionButtons();
});
});
/**
*
* @returns {boolean}
*/
function goToMassEdit() {
console.log(mass_edit_url + '/' + getCheckboxes());
window.location.href = mass_edit_url + '/' + getCheckboxes();
return false;
}
function goToBulkEdit() {
console.log(bulk_edit_url + '/' + getCheckboxes());
window.location.href = bulk_edit_url + '/' + getCheckboxes();
return false;
}
function goToMassDelete() {
console.log(mass_delete_url + '/' + getCheckboxes());
window.location.href = mass_delete_url + '/' + getCheckboxes();
return false;
}
/**
*
* @returns {Array}
*/
function getCheckboxes() {
"use strict";
var list = [];
$.each($('.mass-select'), function (i, v) {
var checkbox = $(v);
if (checkbox.prop('checked')) {
// add to list.
list.push(checkbox.val());
}
});
return list;
}
function countChecked() {
count = $('.mass-select:checked').length;
}
function checkAll() {
$('.mass-select').prop('checked', true);
}
function uncheckAll() {
$('.mass-select').prop('checked', false);
}
function updateActionButtons() {
if (0 !== count) {
$('.action-menu').show();
// also update labels:
$('.mass-edit span').text(edit_selected_txt + ' (' + count + ')');
$('.bulk-edit span').text(edit_bulk_selected_txt + ' (' + count + ')');
$('.mass-delete span').text(delete_selected_txt + ' (' + count + ')');
}
if (0 === count) {
$('.action-menu').hide();
}
}

View File

@ -24,4 +24,22 @@ $(document).ready(function () {
"use strict";
initTagsAC();
initCategoryAC();
// on change, remove the checkbox.
$('input[name="category"]').change(function () {
$('input[name="ignore_category"]').attr('checked', false);
});
$('select[name="budget_id"]').change(function () {
$('input[name="ignore_budget"]').attr('checked', false);
});
$('input[name="tags"]').on('itemAdded', function(event) {
$('input[name="ignore_tags"]').attr('checked', false);
});
});

View File

@ -839,12 +839,15 @@ return [
'mass_delete_journals' => 'Delete a number of transactions',
'mass_edit_journals' => 'Edit a number of transactions',
'mass_bulk_journals' => 'Bulk edit a number of transactions',
'mass_bulk_journals_explain' => 'If you do not want to change your transactions one-by-one using the mass-edit function, you can update them in one go. Simply select the preferred category, tag(s) or budget in the fields below, and all the transactions in the table will be updated.',
'mass_bulk_journals_explain' => 'This form allows you to change properties if the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.',
'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.',
'bulk_set_new_values' => 'Use the inputs below to set new values. If you leave them empty, they will be made empty for all. Also, note that only withdrawals will be given a budget.',
'no_bulk_category' => 'Don\'t update category',
'no_bulk_budget' => 'Don\'t update budget',
'no_bulk_tags' => 'Don\'t update tag(s)',
'bulk_edit' => 'Bulk edit',
'mass_edit' => 'Edit selected individually',
'bulk_edit' => 'Edit selected in bulk',
'mass_delete' => 'Delete selected',
'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' => '(no budget)',
'no_budget_squared' => '(no budget)',

View File

@ -50,9 +50,13 @@ var helpPageTitle = "{{ trans('firefly.help_for_this_page')|escape('js') }}";
var noHelpForPage = "{{ trans('firefly.no_help_could_be_found')|escape('js') }}";
var noHelpForPageTitle = "{{ trans('firefly.no_help_title')|escape('js') }}";
var edit_selected_txt = "{{ trans('firefly.edit')|escape('js') }}";
var edit_selected_txt = "{{ trans('firefly.mass_edit')|escape('js') }}";
var edit_bulk_selected_txt = "{{ trans('firefly.bulk_edit')|escape('js') }}";
var delete_selected_txt = "{{ trans('firefly.delete')|escape('js') }}";
var delete_selected_txt = "{{ trans('firefly.mass_delete')|escape('js') }}";
var mass_edit_url = '{{ route('transactions.mass.edit', ['']) }}';
var bulk_edit_url = '{{ route('transactions.bulk.edit', ['']) }}';
var mass_delete_url = '{{ route('transactions.mass.delete', ['']) }}';
// for demo:
var nextLabel = "{{ trans('firefly.intro_next_label')|escape('js') }}";

View File

@ -8,7 +8,7 @@ TODO: hide and show columns
<td colspan="7" class="no-margin-pagination">{{ groups.render|raw }}</td>
<td colspan="1">
<div class="pull-right">
<input id="list_ALL" value="1" name="all" type="checkbox" class="mass-select-all form-check-inline"/>
<input id="list_ALL" value="1" name="select-all" type="checkbox" class="select-all form-check-inline"/>
</div>
</td>
</tr>
@ -23,8 +23,16 @@ TODO: hide and show columns
</strong></small>
</td>
<td colspan="2" style="border-top:1px #aaa solid;">
{% for sum in group.sums %}
{{ formatAmountBySymbol(sum.amount, sum.currency_symbol, sum.currency_symbol_decimal_places) }}{% if loop.index != group.sums|length %},{% endif %}
{% for sum in group.sums %}
{% if group.transaction_type == 'Deposit' %}
{{ formatAmountBySymbol(sum.amount*-1, sum.currency_symbol, sum.currency_symbol_decimal_places) }}{% if loop.index != group.sums|length %},{% endif %}
{% elseif group.transaction_type == 'Transfer' %}
<span class="text-info">
{{ formatAmountBySymbol(sum.amount*-1, sum.currency_symbol, sum.currency_symbol_decimal_places, false) }}{% if loop.index != group.sums|length %},{% endif %}X
</span>
{% else %}
{{ formatAmountBySymbol(sum.amount, sum.currency_symbol, sum.currency_symbol_decimal_places) }}{% if loop.index != group.sums|length %},{% endif %}
{% endif %}
{% endfor %}
</td>
<td colspan="2" style="border-top:1px #aaa solid;">&nbsp;</td>
@ -75,9 +83,23 @@ TODO: hide and show columns
<a href="{{ route('transactions.show', [group.id]) }}" title="{{ transaction.description }}">{{ transaction.description }}</a>
</td>
<td style=" {{ style|raw }}">
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_symbol_decimal_places) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_symbol_decimal_places) }})
{% if transaction.transaction_type_type == 'Deposit' %}
{{ formatAmountBySymbol(transaction.amount*-1, transaction.currency_symbol, transaction.currency_symbol_decimal_places) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_symbol_decimal_places) }})
{% endif %}
{% elseif transaction.transaction_type_type == 'Transfer' %}
<span class="text-info">
{{ formatAmountBySymbol(transaction.amount*-1, transaction.currency_symbol, transaction.currency_symbol_decimal_places, false) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_symbol_decimal_places, false) }})
{% endif %}
</span>
{% else %}
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_symbol_decimal_places) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_symbol_decimal_places) }})
{% endif %}
{% endif %}
</td>
<td style=" {{ style|raw }}">
@ -106,7 +128,7 @@ TODO: hide and show columns
</td>
<td style="{{ style|raw }}">
<div class="pull-right">
<input id="list_{{ transaction.transaction_journal_id }}" value="1" name="journals[{{ transaction.transaction_journal_id }}]"
<input id="list_{{ transaction.transaction_journal_id }}" value="{{ transaction.transaction_journal_id }}" name="journals[{{ transaction.transaction_journal_id }}]"
type="checkbox" class="mass-select form-check-inline" data-value="{{ transaction.transaction_journal_id }}"/>
</div>
</td>
@ -119,16 +141,14 @@ TODO: hide and show columns
<td colspan="8">
<div class="pull-right">
<!-- Single button -->
<div class="btn-group">
<div class="btn-group action-menu" style="display:none;">
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ 'actions'|_ }} <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="#">Edit individually (x)</a></li>
<li><a href="#">Bulk edit (x)</a></li>
<li><a href="#">Delete (x)</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li><a href="#" class="mass-edit"><i class="fa fa-fw fa-pencil"></i> <span>{{ 'mass_edit'|_ }}</span></a></li>
<li><a href="#" class="bulk-edit"><i class="fa fa-fw fa-pencil-square-o"></i> <span>{{ 'bulk_edit'|_ }}</span></a></li>
<li><a href="#" class="mass-delete"><i class="fa fa-fw fa-trash"></i> <span>{{ 'mass_delete'|_ }}</span></a></li>
</ul>
</div>
</div>

View File

@ -89,13 +89,13 @@
<a href="{{ route('accounts.show', [journal.destination_account_id]) }}" title="{{ journal.destination_account_iban|default(journal.destination_account_name) }}">{{ journal.destination_account_name }}</a>
</td>
<td class="hide-budget">
TODO BUDGET
{# TODO BUDGET #}
</td>
<td class="hide-category">
TODO CATEGORY
{# TODO CATEGORY#}
</td>
<td class="hide-bill">
TODO BILL
{#TODO BILL#}
</td>
<!-- new optional fields (2x) -->

View File

@ -7,16 +7,19 @@
{% block content %}
<form method="POST" action="{{ route('transactions.bulk.update') }}" accept-charset="UTF-8" class="form-horizontal" id="update">
<input name="_token" type="hidden" value="{{ csrf_token() }}">
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">{{ 'mass_bulk_journals'|_ }}</h3>
</div>
<div class="box-body">
<p>
{{ 'mass_bulk_journals_explain'|_ }}
</p>
<div class="row">
<div class="col-lg-8 col-md-12 col-sm-12 col-xs-12">
<table class="table table-striped table-condensed">
@ -28,28 +31,63 @@
<th>{{ trans('list.category') }}</th>
<th>{{ trans('list.budget') }}</th>
<th>{{ trans('list.tags') }}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for journal in journals %}
{% if journal.transaction_count == 2 %}
<input type="hidden" name="journals[]" value="{{ journal.id }}"/>
<input type="hidden" name="journals[]" value="{{ journal.transaction_journal_id }}"/>
<tr>
<td>
<a href="{{ route('transactions.show', [journal.id]) }}">
<a href="{{ route('transactions.show', [journal.transaction_group_id]) }}">
{{ journal.description }}</a></td>
<td>{{ journal|journalTotalAmount }}</td>
<td>
{% if journal.transaction_type_type == 'Deposit' %}
{{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_symbol_decimal_places) }}
{% if null != journal.foreign_amount %}
({{ formatAmountBySymbol(journal.foreign_amount*-1, journal.foreign_currency_symbol, journal.foreign_currency_symbol_decimal_places) }})
{% endif %}
{% elseif journal.transaction_type_type == 'Transfer' %}
<span class="text-info">
{{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_symbol_decimal_places, false) }}
{% if null != journal.foreign_amount %}
({{ formatAmountBySymbol(journal.foreign_amount*-1, journal.foreign_currency_symbol, journal.foreign_currency_symbol_decimal_places, false) }})
{% endif %}
</span>
{% else %}
{{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_symbol_decimal_places) }}
{% if null != journal.foreign_amount %}
({{ formatAmountBySymbol(journal.foreign_amount, journal.foreign_currency_symbol, journal.foreign_currency_symbol_decimal_places) }})
{% endif %}
{% endif %}
</td>
<td>{{ journal.date.formatLocalized(monthAndDayFormat) }}</td>
<td>{{ journalCategories(journal)|raw }}</td>
<td>{{ journalBudgets(journal)|raw }}</td>
<td>
{% if journal.category_id != null %}
<a href="{{ route('categories.show', [journal.category_id]) }}" title="{{ journal.category_name }}">{{ journal.category_name }}</a>
{% endif %}
</td>
<td>
{% if journal.budget_id != null %}
<a href="{{ route('budgets.show', [journal.budget_id]) }}" title="{{ journal.budget_name }}">{{ journal.budget_name }}</a>
{% endif %}
</td>
<td>
{% for tag in journal.tags %}
<a class="label label-success" href="{{ route('tags.show', [tag.id]) }}">
<i class="fa fa-fw fa-tag"></i> {{ tag.tag }}</a>
<span style="display: inline;"><a class="label label-success" href="{{ route('tags.show', [tag.name]) }}">
<i class="fa fa-fw fa-tag"></i>
{{ tag.name }}</a>
</span>
{% endfor %}
</td>
<td>
{% if journal.journals_in_group > 1 %}
<i title="{{ 'part_of_split'|_ }}" class="text-danger fa fa-fw fa-exclamation-triangle"></i>
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
@ -107,21 +145,30 @@
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="box-footer">
{% if journals.count > 0 %}
<input type="submit" name="submit" value="{{ trans('form.update_all_journals') }}" class="btn btn-success pull-right"/>
{% endif %}
<a href="{{ route('index') }}" class="btn-default btn">{{ trans('form.cancel') }}</a>
</div>
<div class="box-footer">
<div class="row">
<div class="col-lg-8">
<input type="submit" name="submit" value="{{ trans('form.update_all_journals') }}" class="btn btn-success pull-right"/>
<a href="{{ route('index') }}" class="btn-default btn">{{ trans('form.cancel') }}</a>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
{% endblock %}
{% block scripts %}

View File

@ -66,5 +66,11 @@
{% endblock %}
{% block scripts %}
<script type="text/javascript" src="v1/js/ff/transactions/list.js?v={{ FF_VERSION }}"></script>
{# old list script #}
{# <script type="text/javascript" src="v1/js/ff/transactions/list.js?v={{ FF_VERSION }}"></script> #}
<script type="text/javascript">
</script>
<script type="text/javascript" src="v1/js/ff/list/groups.js?v={{ FF_VERSION }}"></script>
{% endblock %}

View File

@ -43,6 +43,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalLink;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
if (!function_exists('limitStringLength')) {
@ -302,7 +303,8 @@ try {
$breadcrumbs->push(
trans('firefly.delete_attachment', ['name' => limitStringLength($attachment->filename)]), route('attachments.edit', [$attachment])
);
} else {
}
else {
throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object));
}
}
@ -1120,19 +1122,17 @@ try {
// BULK EDIT
Breadcrumbs::register(
'transactions.bulk.edit',
function (BreadcrumbsGenerator $breadcrumbs, Collection $journals): void {
if ($journals->count() > 0) {
$journalIds = $journals->pluck('id')->toArray();
$what = strtolower($journals->first()->transactionType->type);
$breadcrumbs->parent('transactions.index', $what);
$breadcrumbs->push(trans('firefly.mass_bulk_journals'), route('transactions.bulk.edit', $journalIds));
static function (BreadcrumbsGenerator $breadcrumbs, array $journals): void {
if (count($journals) > 0) {
$ids = Arr::pluck($journals, 'transaction_journal_id');
$first = reset($journals);
$breadcrumbs->parent('transactions.index', strtolower($first['transaction_type_type']));
$breadcrumbs->push(trans('firefly.mass_bulk_journals'), route('transactions.bulk.edit', $ids));
return;
}
$breadcrumbs->parent('index');
return;
}
);