Improve mass controller and test controllers.

This commit is contained in:
James Cole 2019-07-20 16:02:50 +02:00
parent 6d34cfb940
commit 889b7e9a18
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
18 changed files with 525 additions and 542 deletions

View File

@ -124,6 +124,37 @@ class AutoCompleteController extends Controller
return response()->json($return);
}
/**
* An auto-complete specifically for expense accounts, used when mass updating mostly.
* @param Request $request
*
* @return JsonResponse
*/
public function expenseAccounts(Request $request): JsonResponse
{
$search = $request->get('search');
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
// filter the account types:
$allowedAccountTypes = [AccountType::EXPENSE];
Log::debug('Now in accounts(). Filtering results.', $allowedAccountTypes);
$return = [];
$result = $repository->searchAccount((string)$search, $allowedAccountTypes);
/** @var Account $account */
foreach ($result as $account) {
$return[] = [
'id' => $account->id,
'name' => $account->name,
'type' => $account->accountType->type,
];
}
return response()->json($return);
}
/**
* Searches in the titles of all transaction journals.
* The result is limited to the top 15 unique results.

View File

@ -63,6 +63,8 @@ class BulkController extends Controller
/**
* Edit a set of journals in bulk.
*
* TODO user wont be able to tell if journal is part of split.
*
* @param Collection $journals
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
@ -71,6 +73,8 @@ class BulkController extends Controller
{
$subTitle = (string)trans('firefly.mass_bulk_journals');
// make amounts positive.
// get list of budgets:
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);

View File

@ -25,23 +25,19 @@ namespace FireflyIII\Http\Controllers\Transaction;
use Carbon\Carbon;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Filter\TransactionViewFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\MassDeleteJournalRequest;
use FireflyIII\Http\Requests\MassEditJournalRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Transformers\TransactionTransformer;
use FireflyIII\User;
use Illuminate\Support\Collection;
use FireflyIII\Services\Internal\Update\JournalUpdateService;
use Illuminate\View\View as IlluminateView;
use Symfony\Component\HttpFoundation\ParameterBag;
use InvalidArgumentException;
use Log;
/**
* Class MassController.
@ -55,6 +51,7 @@ class MassController extends Controller
/**
* MassController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{
@ -65,7 +62,6 @@ class MassController extends Controller
app('view')->share('title', (string)trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-repeat');
$this->repository = app(JournalRepositoryInterface::class);
return $next($request);
}
);
@ -74,11 +70,11 @@ class MassController extends Controller
/**
* Mass delete transactions.
*
* @param Collection $journals
* @param array $journals
*
* @return IlluminateView
*/
public function delete(Collection $journals): IlluminateView
public function delete(array $journals): IlluminateView
{
$subTitle = (string)trans('firefly.mass_delete_journals');
@ -103,10 +99,11 @@ class MassController extends Controller
if (is_array($ids)) {
/** @var string $journalId */
foreach ($ids as $journalId) {
/** @var TransactionJournal $journal */
$journal = $this->repository->findNull((int)$journalId);
if (null !== $journal && (int)$journalId === $journal->id) {
$this->repository->destroy($journal);
$this->repository->destroyJournal($journal);
++$count;
}
}
@ -123,137 +120,69 @@ class MassController extends Controller
/**
* Mass edit of journals.
*
* @param Collection $journals
* @param array $journals
*
* @return IlluminateView
*
* TODO rebuild this feature.
* @throws FireflyException
*/
public function edit(Collection $journals): IlluminateView
public function edit(array $journals): IlluminateView
{
throw new FireflyException(sprintf('The mass-editor is not available in v%s of Firefly III. Sorry about that. It will be back soon.', config('firefly.version')));
/** @var User $user */
$user = auth()->user();
$subTitle = (string)trans('firefly.mass_edit_journals');
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
// valid withdrawal sources:
$array = array_keys(config(sprintf('firefly.source_dests.%s', TransactionType::WITHDRAWAL)));
$withdrawalSources = $repository->getAccountsByType($array);
// valid deposit destinations:
$array = config(sprintf('firefly.source_dests.%s.%s', TransactionType::DEPOSIT, AccountType::REVENUE));
$depositDestinations = $repository->getAccountsByType($array);
/** @var BudgetRepositoryInterface $budgetRepository */
$budgetRepository = app(BudgetRepositoryInterface::class);
$budgets = $budgetRepository->getBudgets();
// reverse amounts
foreach ($journals as $index => $journal) {
$journals[$index]['amount'] = app('steam')->positive($journal['amount']);
$journals[$index]['foreign_amount'] = null === $journal['foreign_amount'] ?
null : app('steam')->positive($journal['foreign_amount']);
}
$this->rememberPreviousUri('transactions.mass-edit.uri');
/** @var TransactionTransformer $transformer */
$transformer = app(TransactionTransformer::class);
$transformer->setParameters(new ParameterBag);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($user);
$collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
$collector->setJournals($journals);
$collector->addFilter(TransactionViewFilter::class);
$collector->addFilter(TransferFilter::class);
$collection = $collector->getTransactions();
$transactions = $collection->map(
function (Transaction $transaction) use ($transformer) {
$transformed = $transformer->transform($transaction);
// make sure amount is positive:
$transformed['amount'] = app('steam')->positive((string)$transformed['amount']);
$transformed['foreign_amount'] = app('steam')->positive((string)$transformed['foreign_amount']);
return $transformed;
}
);
return view('transactions.mass.edit', compact('transactions', 'subTitle', 'accounts', 'budgets'));
return view('transactions.mass.edit', compact('journals', 'subTitle', 'withdrawalSources', 'depositDestinations', 'budgets'));
}
/**
* Mass update of journals.
*
* @param MassEditJournalRequest $request
* @param JournalRepositoryInterface $repository
*
* @return mixed
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws FireflyException
*/
public function update(MassEditJournalRequest $request, JournalRepositoryInterface $repository)
public function update(MassEditJournalRequest $request)
{
throw new FireflyException('Needs refactor');
$journalIds = $request->get('journals');
$count = 0;
if (is_array($journalIds)) {
foreach ($journalIds as $journalId) {
$journal = $repository->findNull((int)$journalId);
if (null !== $journal) {
// get optional fields:
$what = strtolower($this->repository->getTransactionType($journal));
$sourceAccountId = $request->get('source_id')[$journal->id] ?? null;
$currencyId = $request->get('transaction_currency_id')[$journal->id] ?? 1;
$sourceAccountName = $request->get('source_name')[$journal->id] ?? null;
$destAccountId = $request->get('destination_id')[$journal->id] ?? null;
$destAccountName = $request->get('destination_name')[$journal->id] ?? null;
$budgetId = (int)($request->get('budget_id')[$journal->id] ?? 0.0);
$category = $request->get('category')[$journal->id];
$tags = $journal->tags->pluck('tag')->toArray();
$amount = round($request->get('amount')[$journal->id], 12);
$foreignAmount = isset($request->get('foreign_amount')[$journal->id]) ? round($request->get('foreign_amount')[$journal->id], 12) : null;
$foreignCurrencyId = isset($request->get('foreign_currency_id')[$journal->id]) ?
(int)$request->get('foreign_currency_id')[$journal->id] : null;
// build data array
$data = [
'id' => $journal->id,
'what' => $what,
'description' => $request->get('description')[$journal->id],
'date' => new Carbon($request->get('date')[$journal->id]),
'bill_id' => null,
'bill_name' => null,
'notes' => $repository->getNoteText($journal),
'transactions' => [[
'category_id' => null,
'category_name' => $category,
'budget_id' => $budgetId,
'budget_name' => null,
'source_id' => (int)$sourceAccountId,
'source_name' => $sourceAccountName,
'destination_id' => (int)$destAccountId,
'destination_name' => $destAccountName,
'amount' => $amount,
'identifier' => 0,
'reconciled' => false,
'currency_id' => (int)$currencyId,
'currency_code' => null,
'description' => null,
'foreign_amount' => $foreignAmount,
'foreign_currency_id' => $foreignCurrencyId,
'foreign_currency_code' => null,
]],
'currency_id' => $foreignCurrencyId,
'tags' => $tags,
'interest_date' => $journal->interest_date,
'book_date' => $journal->book_date,
'process_date' => $journal->process_date,
];
// call repository update function.
$repository->update($journal, $data);
// trigger rules
event(new UpdatedTransactionGroup($group));
++$count;
}
if (!is_array($journalIds)) {
// TODO something error.
throw new FireflyException('This is not an array.'); // @codeCoverageIgnore
}
$count = 0;
/** @var string $journalId */
foreach ($journalIds as $journalId) {
$integer = (int)$journalId;
try {
$this->updateJournal($integer, $request);
$count++;
} catch (FireflyException $e) { // @codeCoverageIgnore
// do something with error.
//echo $e->getMessage();
//exit;
}
}
app('preferences')->mark();
session()->flash('success', (string)trans('firefly.mass_edited_transactions_success', ['amount' => $count]));
@ -261,4 +190,106 @@ class MassController extends Controller
return redirect($this->getPreviousUri('transactions.mass-edit.uri'));
}
/**
* @param int $journalId
* @param MassEditJournalRequest $request
* @throws FireflyException
*/
private function updateJournal(int $journalId, MassEditJournalRequest $request): void
{
$journal = $this->repository->findNull($journalId);
if (null === $journal) {
throw new FireflyException(sprintf('Trying to edit non-existent or deleted journal #%d', $journalId)); // @codeCoverageIgnore
}
$service = app(JournalUpdateService::class);
// for each field, call the update service.
$service->setTransactionJournal($journal);
$data = [
'date' => $this->getDateFromRequest($request, $journal->id, 'date'),
'description' => $this->getStringFromRequest($request, $journal->id, 'description'),
'source_id' => $this->getIntFromRequest($request, $journal->id, 'source_id'),
'source_name' => $this->getStringFromRequest($request, $journal->id, 'source_name'),
'destination_id' => $this->getIntFromRequest($request, $journal->id, 'destination_id'),
'destination_name' => $this->getStringFromRequest($request, $journal->id, 'destination_name'),
'budget_id' => $this->getIntFromRequest($request, $journal->id, 'budget_id'),
'category_name' => $this->getStringFromRequest($request, $journal->id, 'category'),
'amount' => $this->getStringFromRequest($request, $journal->id, 'amount'),
'foreign_amount' => $this->getStringFromRequest($request, $journal->id, 'foreign_amount'),
];
Log::debug(sprintf('Will update journal #%d with data.', $journal->id), $data);
// call service to update.
$service->setData($data);
$service->update();
// trigger rules
event(new UpdatedTransactionGroup($journal->transactionGroup));
}
/**
* @param MassEditJournalRequest $request
* @param int $journalId
* @param string $string
* @return int|null
* @codeCoverageIgnore
*/
private function getIntFromRequest(MassEditJournalRequest $request, int $journalId, string $string): ?int
{
$value = $request->get($string);
if (!is_array($value)) {
return null;
}
if (!isset($value[$journalId])) {
return null;
}
return (int)$value[$journalId];
}
/**
* @param MassEditJournalRequest $request
* @param int $journalId
* @param string $string
* @return string|null
* @codeCoverageIgnore
*/
private function getStringFromRequest(MassEditJournalRequest $request, int $journalId, string $string): ?string
{
$value = $request->get($string);
if (!is_array($value)) {
return null;
}
if (!isset($value[$journalId])) {
return null;
}
return (string)$value[$journalId];
}
/**
* @param MassEditJournalRequest $request
* @param int $journalId
* @param string $string
* @return Carbon|null
* @codeCoverageIgnore
*/
private function getDateFromRequest(MassEditJournalRequest $request, int $journalId, string $string): ?Carbon
{
$value = $request->get($string);
if (!is_array($value)) {
return null;
}
if (!isset($value[$journalId])) {
return null;
}
try {
$carbon = Carbon::parse($value[$journalId]);
} catch (InvalidArgumentException $e) {
$e->getMessage();
return null;
}
return $carbon;
}
}

View File

@ -41,7 +41,7 @@ class ShowController extends Controller
private $repository;
/**
* ConvertController constructor.
* ShowController constructor.
*/
public function __construct()
{
@ -81,8 +81,29 @@ class ShowController extends Controller
$groupArray = $transformer->transformObject($transactionGroup);
// do some amount calculations:
$amounts = $this->getAmounts($groupArray);
$events = $this->repository->getPiggyEvents($transactionGroup);
$attachments = $this->repository->getAttachments($transactionGroup);
$links = $this->repository->getLinks($transactionGroup);
return view(
'transactions.show', compact(
'transactionGroup', 'amounts', 'first', 'type', 'subTitle', 'splits', 'groupArray',
'events', 'attachments', 'links', 'message'
)
);
}
/**
* @param array $group
* @return array
*/
private function getAmounts(array $group): array
{
$amounts = [];
foreach ($groupArray['transactions'] as $transaction) {
foreach ($group['transactions'] as $transaction) {
$symbol = $transaction['currency_symbol'];
if (!isset($amounts[$symbol])) {
$amounts[$symbol] = [
@ -106,15 +127,6 @@ class ShowController extends Controller
}
}
$events = $this->repository->getPiggyEvents($transactionGroup);
$attachments = $this->repository->getAttachments($transactionGroup);
$links = $this->repository->getLinks($transactionGroup);
return view(
'transactions.show', compact(
'transactionGroup', 'amounts', 'first', 'type', 'subTitle', 'splits', 'groupArray',
'events', 'attachments', 'links', 'message'
)
);
return $amounts;
}
}

View File

@ -53,6 +53,7 @@ class MassEditJournalRequest extends Request
'description.*' => 'required|min:1,max:255',
'source_id.*' => 'numeric|belongsToUser:accounts,id',
'destination_id.*' => 'numeric|belongsToUser:accounts,id',
'journals.*' => 'numeric|belongsToUser:transaction_journals,id',
'revenue_account' => 'max:255',
'expense_account' => 'max:255',
];

View File

@ -131,7 +131,6 @@ class JournalUpdateService
public function update(): void
{
Log::debug(sprintf('Now in JournalUpdateService for journal #%d.', $this->transactionJournal->id));
// can we update account data using the new type?
if ($this->hasValidAccounts()) {
Log::info('-- account info is valid, now update.');
@ -142,7 +141,6 @@ class JournalUpdateService
$this->updateType();
$this->transactionJournal->refresh();
}
// find and update bill, if possible.
$this->updateBill();
@ -276,6 +274,7 @@ class JournalUpdateService
if (null === $this->sourceTransaction) {
$this->sourceTransaction = $this->transactionJournal->transactions()->with(['account'])->where('amount', '<', 0)->first();
}
Log::debug(sprintf('getSourceTransaction: %s', $this->sourceTransaction->amount));
return $this->sourceTransaction;
}
@ -447,9 +446,14 @@ class JournalUpdateService
$sourceTransaction->account()->associate($source);
$sourceTransaction->save();
$destinationTransaction = $this->getDestinationTransaction();
$destinationTransaction->account()->associate($destination);
$destinationTransaction->save();
$destTransaction = $this->getDestinationTransaction();
$destTransaction->account()->associate($destination);
$destTransaction->save();
// refresh transactions.
$this->sourceTransaction->refresh();
$this->destinationTransaction->refresh();
Log::debug(sprintf('Will set source to #%d ("%s")', $source->id, $source->name));
Log::debug(sprintf('Will set dest to #%d ("%s")', $destination->id, $destination->name));
@ -468,14 +472,20 @@ class JournalUpdateService
return;
}
Log::debug(sprintf('Updated amount to %s', $amount));
$sourceTransaction = $this->getSourceTransaction();
$sourceTransaction->amount = app('steam')->negative($value);
$sourceTransaction->amount = app('steam')->negative($amount);
$sourceTransaction->save();
$destinationTransaction = $this->getDestinationTransaction();
$destinationTransaction->amount = app('steam')->positive($value);
$destinationTransaction->save();
$destTransaction = $this->getDestinationTransaction();
$destTransaction->amount = app('steam')->positive($amount);
$destTransaction->save();
// refresh transactions.
$this->sourceTransaction->refresh();
$this->destinationTransaction->refresh();
Log::debug(sprintf('Updated amount to "%s"', $amount));
}
/**
@ -518,6 +528,10 @@ class JournalUpdateService
$dest = $this->getDestinationTransaction();
$dest->transaction_currency_id = $currency->id;
$dest->save();
// refresh transactions.
$this->sourceTransaction->refresh();
$this->destinationTransaction->refresh();
Log::debug(sprintf('Updated currency to #%d (%s)', $currency->id, $currency->code));
}
}
@ -572,6 +586,10 @@ class JournalUpdateService
Log::debug(sprintf('Update foreign info to %s (#%d) %s', $foreignCurrency->code, $foreignCurrency->id, $foreignAmount));
// refresh transactions.
$this->sourceTransaction->refresh();
$this->destinationTransaction->refresh();
return;
}
if ('0' === $amount) {
@ -585,6 +603,10 @@ class JournalUpdateService
Log::debug(sprintf('Foreign amount is "%s" so remove foreign amount info.', $amount));
}
Log::info('Not enough info to update foreign currency info.');
// refresh transactions.
$this->sourceTransaction->refresh();
$this->destinationTransaction->refresh();
}
/**

View File

@ -22,8 +22,9 @@ declare(strict_types=1);
namespace FireflyIII\Support\Binder;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use Illuminate\Routing\Route;
use Illuminate\Support\Collection;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
@ -38,24 +39,38 @@ class JournalList implements BinderInterface
* @return mixed
* @throws \Symfony\Component\HttpKernel\Exception\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()->withAccountInformation();
$collector->setJournalIds($list);
$result = $collector->getExtractedJournals();
if (0 === count($result)) {
throw new NotFoundHttpException;
}
/** @var \Illuminate\Support\Collection $collection */
$collection = auth()->user()->transactionJournals()
->whereIn('transaction_journals.id', $list)
->where('transaction_journals.completed', 1)
->get(['transaction_journals.*']);
if ($collection->count() > 0) {
return $collection;
}
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

@ -1,78 +0,0 @@
<?php
/**
* SimpleJournalList.php
* Copyright (c) 2018 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/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Binder;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use Illuminate\Routing\Route;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class SimpleJournalList
*/
class SimpleJournalList implements BinderInterface
{
/**
* @param string $value
* @param Route $route
*
* @return mixed
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value, Route $route): array
{
if (auth()->check()) {
$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;
}
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

@ -58,7 +58,6 @@ use FireflyIII\Support\Binder\CurrencyCode;
use FireflyIII\Support\Binder\Date;
use FireflyIII\Support\Binder\ImportProvider;
use FireflyIII\Support\Binder\JournalList;
use FireflyIII\Support\Binder\SimpleJournalList;
use FireflyIII\Support\Binder\TagList;
use FireflyIII\Support\Binder\TagOrId;
use FireflyIII\Support\Binder\UnfinishedJournal;
@ -398,7 +397,6 @@ return [
'journalList' => JournalList::class,
'categoryList' => CategoryList::class,
'tagList' => TagList::class,
'simpleJournalList' => SimpleJournalList::class,
// others
'fromCurrencyCode' => CurrencyCode::class,

View File

@ -18,22 +18,20 @@
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
/** global: what */
$(document).ready(function () {
"use strict";
// description
if ($('input[name^="description["]').length > 0) {
console.log('descr');
console.log('Description.');
var journalNames = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'json/transaction-journals/all?uid=' + uid,
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
return $.map(list, function (obj) {
return obj;
});
}
},
@ -41,8 +39,8 @@ $(document).ready(function () {
url: 'json/transaction-journals/all?search=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
return $.map(list, function (obj) {
return obj;
});
}
}
@ -52,15 +50,15 @@ $(document).ready(function () {
}
// destination account names:
if ($('input[name^="destination_name["]').length > 0) {
console.log('Destination.');
var destNames = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'json/expense-accounts?uid=' + uid,
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
return $.map(list, function (obj) {
return obj;
});
}
},
@ -68,8 +66,8 @@ $(document).ready(function () {
url: 'json/expense-accounts?search=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
return $.map(list, function (obj) {
return obj;
});
}
}
@ -80,15 +78,15 @@ $(document).ready(function () {
// source account name
if ($('input[name^="source_name["]').length > 0) {
console.log('Source.');
var sourceNames = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'json/revenue-accounts?uid=' + uid,
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
return $.map(list, function (obj) {
return obj;
});
}
},
@ -96,8 +94,8 @@ $(document).ready(function () {
url: 'json/revenue-accounts?search=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
return $.map(list, function (obj) {
return obj;
});
}
}
@ -113,8 +111,8 @@ $(document).ready(function () {
prefetch: {
url: 'json/categories?uid=' + uid,
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
return $.map(list, function (obj) {
return obj;
});
}
},
@ -122,8 +120,8 @@ $(document).ready(function () {
url: 'json/categories?search=%QUERY&uid=' + uid,
wildcard: '%QUERY',
filter: function (list) {
return $.map(list, function (name) {
return {name: name};
return $.map(list, function (obj) {
return obj;
});
}
}

View File

@ -851,7 +851,7 @@ return [
'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)',
'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious.',
'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.',
'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).',
'mass_edited_transactions_success' => 'Updated :amount transaction(s)',
'opt_group_' => '(no account type)',

View File

@ -23,64 +23,65 @@
declare(strict_types=1);
return [
'buttons' => 'Buttons',
'icon' => 'Icon',
'id' => 'ID',
'create_date' => 'Created at',
'update_date' => 'Updated at',
'updated_at' => 'Updated at',
'balance_before' => 'Balance before',
'balance_after' => 'Balance after',
'name' => 'Name',
'role' => 'Role',
'currentBalance' => 'Current balance',
'linked_to_rules' => 'Relevant rules',
'active' => 'Is active?',
'lastActivity' => 'Last activity',
'balanceDiff' => 'Balance difference',
'matchesOn' => 'Matched on',
'account_type' => 'Account type',
'created_at' => 'Created at',
'account' => 'Account',
'matchingAmount' => 'Amount',
'split_number' => 'Split #',
'destination' => 'Destination',
'source' => 'Source',
'next_expected_match' => 'Next expected match',
'automatch' => 'Auto match?',
'repeat_freq' => 'Repeats',
'description' => 'Description',
'amount' => 'Amount',
'internal_reference' => 'Internal reference',
'date' => 'Date',
'interest_date' => 'Interest date',
'book_date' => 'Book date',
'process_date' => 'Processing date',
'due_date' => 'Due date',
'payment_date' => 'Payment date',
'invoice_date' => 'Invoice date',
'interal_reference' => 'Internal reference',
'notes' => 'Notes',
'from' => 'From',
'piggy_bank' => 'Piggy bank',
'to' => 'To',
'budget' => 'Budget',
'category' => 'Category',
'bill' => 'Bill',
'withdrawal' => 'Withdrawal',
'deposit' => 'Deposit',
'transfer' => 'Transfer',
'type' => 'Type',
'completed' => 'Completed',
'iban' => 'IBAN',
'paid_current_period' => 'Paid this period',
'email' => 'Email',
'registered_at' => 'Registered at',
'is_blocked' => 'Is blocked',
'is_admin' => 'Is admin',
'has_two_factor' => 'Has 2FA',
'blocked_code' => 'Block code',
'source_account' => 'Source account',
'buttons' => 'Buttons',
'icon' => 'Icon',
'id' => 'ID',
'create_date' => 'Created at',
'update_date' => 'Updated at',
'updated_at' => 'Updated at',
'balance_before' => 'Balance before',
'balance_after' => 'Balance after',
'name' => 'Name',
'role' => 'Role',
'currentBalance' => 'Current balance',
'linked_to_rules' => 'Relevant rules',
'active' => 'Is active?',
'transaction_type' => 'Type',
'lastActivity' => 'Last activity',
'balanceDiff' => 'Balance difference',
'matchesOn' => 'Matched on',
'account_type' => 'Account type',
'created_at' => 'Created at',
'account' => 'Account',
'matchingAmount' => 'Amount',
'split_number' => 'Split #',
'destination' => 'Destination',
'source' => 'Source',
'next_expected_match' => 'Next expected match',
'automatch' => 'Auto match?',
'repeat_freq' => 'Repeats',
'description' => 'Description',
'amount' => 'Amount',
'internal_reference' => 'Internal reference',
'date' => 'Date',
'interest_date' => 'Interest date',
'book_date' => 'Book date',
'process_date' => 'Processing date',
'due_date' => 'Due date',
'payment_date' => 'Payment date',
'invoice_date' => 'Invoice date',
'interal_reference' => 'Internal reference',
'notes' => 'Notes',
'from' => 'From',
'piggy_bank' => 'Piggy bank',
'to' => 'To',
'budget' => 'Budget',
'category' => 'Category',
'bill' => 'Bill',
'withdrawal' => 'Withdrawal',
'deposit' => 'Deposit',
'transfer' => 'Transfer',
'type' => 'Type',
'completed' => 'Completed',
'iban' => 'IBAN',
'paid_current_period' => 'Paid this period',
'email' => 'Email',
'registered_at' => 'Registered at',
'is_blocked' => 'Is blocked',
'is_admin' => 'Is admin',
'has_two_factor' => 'Has 2FA',
'blocked_code' => 'Block code',
'source_account' => 'Source account',
'destination_account' => 'Destination account',
'accounts_count' => 'Number of accounts',
'journals_count' => 'Number of transactions',

View File

@ -19,7 +19,6 @@
{{ trans('form.permDeleteWarning') }}
{{ 'perm-delete-many'|_ }}
</p>
<p>
{{ trans('form.mass_journal_are_you_sure') }}
{{ trans('form.mass_make_selection') }}
@ -28,8 +27,9 @@
<table class="table table-striped table-condensed">
<tr>
<th>&nbsp;</th>
<th>{{ trans('list.transaction_type') }}</th>
<th>{{ trans('list.description') }}</th>
<th>{{ trans('list.total_amount') }}</th>
<th>{{ trans('list.amount') }}</th>
<th class="hidden-sm hidden-xs">{{ trans('list.date') }}</th>
<th class="hidden-xs">{{ trans('list.from') }}</th>
<th class="hidden-xs">{{ trans('list.to') }}</th>
@ -37,22 +37,61 @@
{% for journal in journals %}
<tr>
<td>
<input type="checkbox" name="confirm_mass_delete[]" value="{{ journal.id }}" checked/>
<input type="checkbox" name="confirm_mass_delete[]" value="{{ journal.transaction_journal_id }}" checked/>
</td>
<td>
<a href="{{ route('transactions.show',journal.id) }}" title="{{ journal.description }}">{{ journal.description }}</a>
{% if journal.transaction_type_type == 'Withdrawal' %}
<i class="fa fa-long-arrow-left fa-fw" title="{{ trans('firefly.Withdrawal') }}"></i>
{% endif %}
{% if journal.transaction_type_type == 'Deposit' %}
<i class="fa fa-long-arrow-right fa-fw" title="{{ trans('firefly.Deposit') }}"></i>
{% endif %}
{% if journal.transaction_type_type == 'Transfer' %}
<i class="fa fa-exchange fa-fw" title="{{ trans('firefly.Deposit') }}"></i>
{% endif %}
{% if journal.transaction_type_type == 'Reconciliation' %}
<i class="fa-fw fa fa-calculator" title="{{ trans('firefly.reconciliation_transaction') }}"></i>
{% endif %}
{% if journal.transaction_type_type == 'Opening balance' %}
<i class="fa-fw fa fa-star-o" title="{{ trans('firefly.Opening balance') }}"></i>
{% endif %}
</td>
<td>
{{ journal|journalTotalAmount }}
<a href="{{ route('transactions.show',journal.transaction_journal_id) }}"
title="{{ journal.description }}">{{ journal.description }}</a>
</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>
{{ sourceAccount(journal)|raw }}
<a href="{{ route('accounts.show', [journal.source_account_id]) }}"
title="{{ journal.source_account_iban|default(journal.source_account_name) }}">{{ journal.source_account_name }}</a>
</td>
<td>
{{ destinationXAccount(journal)|raw }}
<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>
</tr>
{% endfor %}

View File

@ -1,7 +1,7 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, transactions) }}
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, journals) }}
{% endblock %}
{% block content %}
@ -29,91 +29,107 @@
<th class="col-lg-2 col-md-2 col-sm-2">{{ trans('list.category') }}</th>
<th class="col-lg-2 col-md-2 col-sm-2">{{ trans('list.budget') }}</th>
</tr>
{% for transaction in transactions %}
{% for journal in journals %}
<tr>
<td>
{# LINK TO EDIT FORM #}
<a href="{{ route('transactions.edit', transaction.journal_id) }}" class="btn btn-xs btn-default"><i
<a href="{{ route('transactions.edit', journal.transaction_group_id) }}" class="btn btn-xs btn-default"><i
class="fa fa-fw fa-pencil"></i></a>
<input type="hidden" name="journals[]" value="{{ transaction.journal_id }}"/>
<input type="hidden" name="journals[]" value="{{ journal.transaction_journal_id }}"/>
</td>
<td>
{# DESCRIPTION #}
<input class="form-control input-sm" autocomplete="off"
placeholder="{{ transaction.description }}" name="description[{{ transaction.journal_id }}]"
type="text" value="{{ transaction.description }}">
placeholder="{{ journal.description }}" name="description[{{ journal.transaction_journal_id }}]"
type="text" value="{{ journal.description }}">
</td>
{# AMOUNT #}
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon">{{ transaction.currency_symbol }}</span>
<input name="amount[{{ transaction.journal_id }}]" class="form-control" autocomplete="off"
step="any" type="number" value="{{ transaction.amount }}">
<input type="hidden" name="transaction_currency_id[{{ transaction.journal_id }}]"
value="{{ transaction.currency_id }}">
<span class="input-group-addon">{{ journal.currency_symbol }}</span>
<input name="amount[{{ journal.transaction_journal_id }}]" class="form-control" autocomplete="off"
step="any" type="number" value="{{ journal.amount }}">
<input type="hidden" name="transaction_currency_id[{{ journal.transaction_journal_id }}]"
value="{{ journal.currency_id }}">
</div>
{% if transaction.foreign_amount %}
{% if journal.foreign_amount %}
{# insert foreign data #}
<div class="input-group input-group-sm">
<span class="input-group-addon">{{ transaction.foreign_currency_symbol }}</span>
<input name="foreign_amount[{{ transaction.journal_id }}]" class="form-control" autocomplete="off"
step="any" type="number" value="{{ transaction.foreign_amount }}">
<input type="hidden" name="foreign_currency_id[{{ transaction.journal_id }}]" value="{{ transaction.foreign_currency_id }}">
<span class="input-group-addon">{{ journal.foreign_currency_symbol }}</span>
<input name="foreign_amount[{{ journal.transaction_journal_id }}]" class="form-control" autocomplete="off"
step="any" type="number" value="{{ journal.foreign_amount }}">
<input type="hidden" name="foreign_currency_id[{{ journal.transaction_journal_id }}]"
value="{{ journal.foreign_currency_id }}">
</div>
{% endif %}
</td>
<td>
{# DATE #}
<input class="form-control input-sm" autocomplete="off"
name="date[{{ transaction.journal_id }}]" type="date" value="{{ transaction.date|slice(0,10) }}">
name="date[{{ journal.transaction_journal_id }}]" type="date" value="{{ journal.date|slice(0,10) }}">
</td>
<!-- {{ journal.transaction_type_type }} -->
<!-- Source: {{ journal.source_account_name }} ({{ journal.source_account_id }}) -->
<!-- Destination: {{ journal.destination_account_name }} ({{ journal.destination_account_id }}) -->
<td style="position: relative;">
{# SOURCE ACCOUNT ID FOR TRANSFER OR WITHDRAWAL #}
{% if transaction.type == 'Transfer' or transaction.type == 'Withdrawal' %}
<select class="form-control input-sm" name="source_id[{{ transaction.journal_id }}]">
{% for account in accounts %}
<!-- {{ transaction.type }}: {{ transaction.source_name }} -->
<option value="{{ account.id }}"{% if account.id == transaction.source_id %} selected{% endif %} label="{{ account.name }}">{{ account.name }}</option>
{% endfor %}
</select>
{% else %}
{# SOURCE ACCOUNT NAME FOR DEPOSIT #}
<input class="form-control input-sm" placeholder="{% if transaction.source_type != 'Cash account' %}{{ transaction.source_name }}{% endif %}" autocomplete="off"
name="source_name[{{ transaction.journal_id }}]" type="text" value="{% if transaction.source_type != 'Cash account' %}{{ transaction.source_name }}{% endif %}">
{% endif %}
</td>
<td style="position: relative;">
{% if transaction.type == 'Transfer' or transaction.type == 'Deposit' %}
{# DESTINATION ACCOUNT NAME FOR TRANSFER AND DEPOSIT #}
<select class="form-control input-sm" name="destination_id[{{ transaction.journal_id }}]">
{% for account in accounts %}
<option value="{{ account.id }}"{% if account.id == transaction.destination_id %} selected="selected"{% endif %}
{% if journal.transaction_type_type == 'Transfer' or journal.transaction_type_type == 'Withdrawal' %}
<select class="form-control input-sm" name="source_id[{{ journal.transaction_journal_id }}]">
{% for account in withdrawalSources %}
<option value="{{ account.id }}"{% if account.id == journal.source_account_id %} selected{% endif %}
label="{{ account.name }}">{{ account.name }}</option>
{% endfor %}
</select>
{% else %}
{% endif %}
{# DESTINATION ACCOUNT NAME FOR EXPENSE #}
<input class="form-control input-sm" placeholder="{% if transaction.destination_type != 'Cash account' %}{{ transaction.destination_name }}{% endif %}"
name="destination_name[{{ transaction.journal_id }}]" type="text" autocomplete="off"
value="{% if transaction.destination_type != 'Cash account' %}{{ transaction.destination_name }}{% endif %}">
{# SOURCE ACCOUNT NAME FOR DEPOSIT #}
{% if journal.transaction_type_type == 'Deposit' %}
<input class="form-control input-sm"
placeholder="{% if journal.source_type != 'Cash account' %}{{ journal.source_account_name }}{% endif %}"
autocomplete="off"
name="source_name[{{ journal.transaction_journal_id }}]" type="text"
value="{% if journal.source_type != 'Cash account' %}{{ journal.source_account_name }}{% endif %}">
{% endif %}
</td>
<td style="position: relative;">
{# DESTINATION ACCOUNT NAME FOR TRANSFER AND DEPOSIT #}
{% if journal.transaction_type_type == 'Transfer' or journal.transaction_type_type == 'Deposit' %}
<select class="form-control input-sm" name="destination_id[{{ journal.transaction_journal_id }}]">
{% for account in depositDestinations %}
<option value="{{ account.id }}"{% if account.id == journal.destination_account_id %} selected="selected"{% endif %}
label="{{ account.name }}">{{ account.name }}</option>
{% endfor %}
</select>
{% endif %}
{# DESTINATION ACCOUNT NAME FOR WITHDRAWAL #}
{% if journal.transaction_type_type == 'Withdrawal' %}
<input class="form-control input-sm"
placeholder="{% if journal.destination_type != 'Cash account' %}{{ journal.destination_account_name }}{% endif %}"
name="destination_name[{{ journal.transaction_journal_id }}]" type="text" autocomplete="off"
value="{% if journal.destination_type != 'Cash account' %}{{ journal.destination_account_name }}{% endif %}">
{% endif %}
</td>
{# category #}
<td style="position: relative;">
<input class="form-control input-sm" placeholder="{{ transaction.category_name }}" autocomplete="off"
name="category[{{ transaction.journal_id }}]" type="text" value="{{ transaction.category_name }}">
<input class="form-control input-sm" placeholder="{{ journal.category_name }}" autocomplete="off"
name="category[{{ journal.transaction_journal_id }}]" type="text" value="{{ journal.category_name }}">
</td>
{# budget #}
<td>
{% if transaction.type == 'Withdrawal' %}
<select class="form-control input-sm" name="budget_id[{{ transaction.journal_id }}]">
{% if journal.transaction_type_type == 'Withdrawal' %}
<select class="form-control input-sm" name="budget_id[{{ journal.transaction_journal_id }}]">
<option value="0" label="({{ 'no_budget'|_ }})"
{% if transaction.budget_id == 0 %}selected="selected"{% endif %}
{% if journal.budget_id == 0 %}selected="selected"{% endif %}
>({{ 'no_budget'|_ }})
</option>
{% for budget in budgets %}
<option value="{{ budget.id }}"{% if budget.id == transaction.budget_id %} selected="selected"{% endif %}
<option value="{{ budget.id }}"{% if budget.id == journal.budget_id %} selected="selected"{% endif %}
label="{{ budget.name }}">{{ budget.name }}</option>
{% endfor %}
</select>

View File

@ -41,7 +41,6 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalLink;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
@ -1096,12 +1095,11 @@ try {
// MASS TRANSACTION EDIT / DELETE
Breadcrumbs::register(
'transactions.mass.edit',
function (BreadcrumbsGenerator $breadcrumbs, Collection $journals): void {
if (\count($journals) > 0) {
$journalIds = $journals->pluck('id')->toArray();
$what = strtolower($journals->first()['type']);
$breadcrumbs->parent('transactions.index', $what);
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds));
static function (BreadcrumbsGenerator $breadcrumbs, array $journals): void {
if (count($journals) > 0) {
$objectType = strtolower(reset($journals)['transaction_type_type']);
$breadcrumbs->parent('transactions.index', $objectType);
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', ['']));
return;
}
@ -1111,11 +1109,10 @@ try {
Breadcrumbs::register(
'transactions.mass.delete',
function (BreadcrumbsGenerator $breadcrumbs, Collection $journals) {
$journalIds = $journals->pluck('id')->toArray();
$what = strtolower($journals->first()->transactionType->type);
$breadcrumbs->parent('transactions.index', $what);
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', $journalIds));
static function (BreadcrumbsGenerator $breadcrumbs, array $journals) {
$objectType= strtolower(reset($journals)['transaction_type_type']);
$breadcrumbs->parent('transactions.index', $objectType);
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', ['']));
}
);

View File

@ -922,7 +922,7 @@ Route::group(
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/mass', 'as' => 'transactions.mass.'],
function () {
Route::get('edit/{simpleJournalList}', ['uses' => 'MassController@edit', 'as' => 'edit']);
Route::get('edit/{journalList}', ['uses' => 'MassController@edit', 'as' => 'edit']);
Route::get('delete/{journalList}', ['uses' => 'MassController@delete', 'as' => 'delete']);
Route::post('update', ['uses' => 'MassController@update', 'as' => 'update']);
Route::post('destroy', ['uses' => 'MassController@destroy', 'as' => 'destroy']);
@ -935,7 +935,7 @@ Route::group(
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/bulk', 'as' => 'transactions.bulk.'],
function () {
Route::get('edit/{simpleJournalList}', ['uses' => 'BulkController@edit', 'as' => 'edit']);
Route::get('edit/{journalList}', ['uses' => 'BulkController@edit', 'as' => 'edit']);
Route::post('update', ['uses' => 'BulkController@update', 'as' => 'update']);
}
);

View File

@ -23,17 +23,18 @@ declare(strict_types=1);
namespace Tests\Feature\Controllers\Transaction;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Amount;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Transformers\TransactionTransformer;
use FireflyIII\Services\Internal\Update\JournalUpdateService;
use Illuminate\Support\Collection;
use Log;
use Mockery;
use Preferences;
use Tests\TestCase;
/**
@ -56,26 +57,32 @@ class MassControllerTest extends TestCase
/**
* @covers \FireflyIII\Http\Controllers\Transaction\MassController
* @covers \FireflyIII\Http\Controllers\Transaction\MassController
*/
public function testDelete(): void
{
$this->markTestIncomplete('Needs to be rewritten for v4.8.0');
return;
$journalRepos = $this->mock(JournalRepositoryInterface::class);
$userRepos = $this->mock(UserRepositoryInterface::class);
$this->mockDefaultSession();
$withdrawal = $this->getRandomWithdrawal();
$withdrawalArray = $this->getRandomWithdrawalAsArray();
$userRepos = $this->mock(UserRepositoryInterface::class);
$userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->atLeast()->once()->andReturn(true);
$journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal);
$journalRepos->shouldReceive('getJournalSourceAccounts')->andReturn(new Collection)->once();
$journalRepos->shouldReceive('getJournalDestinationAccounts')->andReturn(new Collection)->once();
$collector = $this->mock(GroupCollectorInterface::class);
$collector->shouldReceive('setTypes')
->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]])->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withCategoryInformation')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withBudgetInformation')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withTagInformation')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withAccountInformation')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('setJournalIds')->withArgs([[$withdrawal->id]])->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('getExtractedJournals')->atLeast()->once()->andReturn([$withdrawalArray]);
Amount::shouldReceive('formatAnything')->atLeast()->once()->andReturn('x');
$withdrawals = TransactionJournal::where('transaction_type_id', 1)->where('user_id', $this->user()->id)->take(2)->get()->pluck('id')->toArray();
$this->be($this->user());
$response = $this->get(route('transactions.mass.delete', $withdrawals));
$response = $this->get(route('transactions.mass.delete', [$withdrawal->id]));
$response->assertStatus(200);
$response->assertSee('Delete a number of transactions');
// has bread crumb
@ -87,26 +94,16 @@ class MassControllerTest extends TestCase
*/
public function testDestroy(): void
{
$this->markTestIncomplete('Needs to be rewritten for v4.8.0');
$repository = $this->mockDefaultSession();
$deposit = $this->getRandomDeposit();
return;
$deposits = TransactionJournal::where('transaction_type_id', 2)->where('user_id', $this->user()->id)->take(2)->get();
$depositIds = $deposits->pluck('id')->toArray();
// mock deletion:
$repository = $this->mock(JournalRepositoryInterface::class);
$userRepos = $this->mock(UserRepositoryInterface::class);
$repository->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal);
$repository->shouldReceive('findNull')->andReturnValues([$deposits[0], $deposits[1]])->times(2);
$repository->shouldReceive('destroy')->times(2);
$repository->shouldReceive('findNull')->atLeast()->once()->andReturn($deposit);
$repository->shouldReceive('destroyJournal')->atLeast()->once();
Preferences::shouldReceive('mark')->atLeast()->once();
$this->session(['transactions.mass-delete.uri' => 'http://localhost']);
$data = [
'confirm_mass_delete' => $depositIds,
];
$data = ['confirm_mass_delete' => [$deposit->id],];
$this->be($this->user());
$response = $this->post(route('transactions.mass.destroy'), $data);
$response->assertSessionHas('success');
@ -118,161 +115,59 @@ class MassControllerTest extends TestCase
*/
public function testEdit(): void
{
$this->markTestIncomplete('Needs to be rewritten for v4.8.0');
$withdrawal = $this->getRandomWithdrawal();
$withdrawalArray = $this->getRandomWithdrawalAsArray();
$asset = $this->getRandomAsset();
$budget = $this->getRandomBudget();
return;
// mock things
$journalRepos = $this->mock(JournalRepositoryInterface::class);
$journalRepos = $this->mockDefaultSession();
$userRepos = $this->mock(UserRepositoryInterface::class);
$transformer = $this->mock(TransactionTransformer::class);
$collector = $this->mock(TransactionCollectorInterface::class);
// data:
$transfers = TransactionJournal::where('transaction_type_id', 3)->where('user_id', $this->user()->id)->take(2)->get();
$transfersArray = $transfers->pluck('id')->toArray();
$source = $this->user()->accounts()->first();
$transaction = new Transaction;
// mock calls:
$transformer->shouldReceive('setParameters')->atLeast()->once();
$transformer->shouldReceive('transform')->atLeast()->once()->andReturn(
[
'amount' => '10',
'foreign_amount' => '',
'type' => 'transfer',
'id' => 3,
'journal_id' => 1,
]
);
$collector->shouldReceive('setUser')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withOpposingAccount')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withCategoryInformation')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withBudgetInformation')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('setJournals')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('addFilter')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('getTransactions')->atLeast()->once()->andReturn(new Collection([new Transaction]));
$userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->atLeast()->once()->andReturn(true);
$journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal);
// mock data for edit page:
$journalRepos->shouldReceive('getJournalSourceAccounts')->andReturn(new Collection([$source]))->atLeast()->once();
$journalRepos->shouldReceive('getJournalDestinationAccounts')->andReturn(new Collection([$source]))->atLeast()->once();
$journalRepos->shouldReceive('getTransactionType')->andReturn('Transfer')->atLeast()->once();
$journalRepos->shouldReceive('isJournalReconciled')->andReturn(false)->atLeast()->once();
// mock stuff:
$repository = $this->mock(AccountRepositoryInterface::class);
$repository->shouldReceive('getAccountsByType')->once()->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->andReturn(new Collection);
// mock more stuff:
$budgetRepos = $this->mock(BudgetRepositoryInterface::class);
$budgetRepos->shouldReceive('getBudgets')->andReturn(new Collection)->atLeast()->once();
$this->be($this->user());
$response = $this->get(route('transactions.mass.edit', $transfersArray));
$response->assertStatus(200);
$response->assertSee('Edit a number of transactions');
// has bread crumb
$response->assertSee('<ol class="breadcrumb">');
}
/**
* @covers \FireflyIII\Http\Controllers\Transaction\MassController
*/
public function testEditMultiple(): void
{
$this->markTestIncomplete('Needs to be rewritten for v4.8.0');
return;
$repository = $this->mock(AccountRepositoryInterface::class);
$budgetRepos = $this->mock(BudgetRepositoryInterface::class);
$userRepos = $this->mock(UserRepositoryInterface::class);
$journalRepos = $this->mock(JournalRepositoryInterface::class);
$transformer = $this->mock(TransactionTransformer::class);
$collector = $this->mock(TransactionCollectorInterface::class);
// mock calls:
$transformer->shouldReceive('setParameters')->atLeast()->once();
$transformer->shouldReceive('transform')->atLeast()->once()->andReturn(
[
'amount' => '10',
'foreign_amount' => '',
'type' => 'transfer',
'id' => 3,
'journal_id' => 1,
]
);
$collector->shouldReceive('setUser')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withOpposingAccount')->atLeast()->once()->andReturnSelf();
$collector = $this->mock(GroupCollectorInterface::class);
$collector->shouldReceive('setTypes')
->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]])->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withCategoryInformation')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withBudgetInformation')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('setJournals')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('addFilter')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('getTransactions')->atLeast()->once()->andReturn(new Collection([new Transaction]));
$collector->shouldReceive('withTagInformation')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('withAccountInformation')->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('setJournalIds')->withArgs([[$withdrawal->id]])->atLeast()->once()->andReturnSelf();
$collector->shouldReceive('getExtractedJournals')->atLeast()->once()->andReturn([$withdrawalArray]);
$repository->shouldReceive('getAccountsByType')->atLeast()->once()->andReturn(new Collection([$asset]));
$budgetRepos->shouldReceive('getBudgets')->atLeast()->once()->andReturn(new Collection([$budget]));
$userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->atLeast()->once()->andReturn(true);
$budgetRepos->shouldReceive('getBudgets')->andReturn(new Collection)->atLeast()->once();
// mock stuff:
$repository = $this->mock(AccountRepositoryInterface::class);
$repository->shouldReceive('getAccountsByType')->once()->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->andReturn(new Collection);
$journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal)->atLeast()->once();
$journalRepos->shouldReceive('getJournalSourceAccounts')
->andReturn(new Collection([1, 2, 3]), new Collection, new Collection, new Collection, new Collection([1]))->atLeast()->once();
$journalRepos->shouldReceive('getJournalDestinationAccounts')
->andReturn(new Collection, new Collection([1, 2, 3]), new Collection, new Collection, new Collection([1]))->atLeast()->once();
$journalRepos->shouldReceive('getTransactionType')
->andReturn('Withdrawal', 'Opening balance', 'Withdrawal', 'Withdrawal', 'Withdrawal')->atLeast()->once();
$journalRepos->shouldReceive('isJournalReconciled')
->andReturn(true, false, false, false, false)->atLeast()->once();
// default transactions
$collection = $this->user()->transactionJournals()->take(5)->get();
$allIds = $collection->pluck('id')->toArray();
$route = route('transactions.mass.edit', implode(',', $allIds));
$this->be($this->user());
$response = $this->get($route);
$response = $this->get(route('transactions.mass.edit', [$withdrawal->id]));
$response->assertStatus(200);
$response->assertSee('Edit a number of transactions');
// has bread crumb
$response->assertSee('<ol class="breadcrumb">');
$response->assertSee('marked as reconciled');
$response->assertSee('multiple source accounts');
$response->assertSee('multiple destination accounts');
}
/**
* @covers \FireflyIII\Http\Controllers\Transaction\MassController
*/
public function testUpdate(): void
{
$this->markTestIncomplete('Needs to be rewritten for v4.8.0');
$deposit = $this->getRandomDeposit();
$repository = $this->mockDefaultSession();
$userRepos = $this->mock(UserRepositoryInterface::class);
$updateService = $this->mock(JournalUpdateService::class);
return;
$deposit = TransactionJournal::where('transaction_type_id', 2)->where('user_id', $this->user()->id)
->whereNull('deleted_at')
->first();
$this->expectsEvents(UpdatedTransactionGroup::class);
// mock stuff
$repository = $this->mock(JournalRepositoryInterface::class);
$userRepos = $this->mock(UserRepositoryInterface::class);
$updateService->shouldReceive('setTransactionJournal')->atLeast()->once();
$updateService->shouldReceive('setData')->atLeast()->once();
$updateService->shouldReceive('update')->atLeast()->once();
Preferences::shouldReceive('mark')->atLeast()->once();
$repository->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal);
$repository->shouldReceive('update')->once();
$repository->shouldReceive('findNull')->once()->andReturn($deposit);
$repository->shouldReceive('getTransactionType')->andReturn('Deposit');
$repository->shouldReceive('getNoteText')->andReturn('Some note');
$repository->shouldReceive('findNull')->atLeast()->once()->andReturn($deposit);
$this->session(['transactions.mass-edit.uri' => 'http://localhost']);
@ -280,7 +175,6 @@ class MassControllerTest extends TestCase
'journals' => [$deposit->id],
'description' => [$deposit->id => 'Updated salary thing'],
'amount' => [$deposit->id => 1600],
'amount_currency_id_amount_' . $deposit->id => 1,
'date' => [$deposit->id => '2014-07-24'],
'source_name' => [$deposit->id => 'Job'],
'destination_id' => [$deposit->id => 1],

View File

@ -159,6 +159,7 @@ abstract class TestCase extends BaseTestCase
}
return [
'transaction_group_id' => $withdrawal->transaction_group_id,
'transaction_journal_id' => $withdrawal->id,
'transaction_type_type' => 'Withdrawal',
'currency_id' => $euro->id,
@ -166,6 +167,7 @@ abstract class TestCase extends BaseTestCase
'date' => $date,
'description' => sprintf('I am descr #%d', $this->randomInt()),
'source_account_id' => 1,
'foreign_amount' => null,
'destination_account_id' => $expense->id,
'destination_account_name' => $expense->name,
'currency_name' => $euro->name,