diff --git a/app/Http/Controllers/Json/AutoCompleteController.php b/app/Http/Controllers/Json/AutoCompleteController.php index 902a1cc8b0..40d7b0ddb9 100644 --- a/app/Http/Controllers/Json/AutoCompleteController.php +++ b/app/Http/Controllers/Json/AutoCompleteController.php @@ -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. diff --git a/app/Http/Controllers/Transaction/BulkController.php b/app/Http/Controllers/Transaction/BulkController.php index bc57f8fe2b..dd56aa4a5d 100644 --- a/app/Http/Controllers/Transaction/BulkController.php +++ b/app/Http/Controllers/Transaction/BulkController.php @@ -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); diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index 371794873f..219fd48c1f 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -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; + } } diff --git a/app/Http/Controllers/Transaction/ShowController.php b/app/Http/Controllers/Transaction/ShowController.php index 1f203e4bd8..a8ef038532 100644 --- a/app/Http/Controllers/Transaction/ShowController.php +++ b/app/Http/Controllers/Transaction/ShowController.php @@ -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; } } \ No newline at end of file diff --git a/app/Http/Requests/MassEditJournalRequest.php b/app/Http/Requests/MassEditJournalRequest.php index b171ecfa20..94467f0239 100644 --- a/app/Http/Requests/MassEditJournalRequest.php +++ b/app/Http/Requests/MassEditJournalRequest.php @@ -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', ]; diff --git a/app/Services/Internal/Update/JournalUpdateService.php b/app/Services/Internal/Update/JournalUpdateService.php index 65436f8c57..eb0d477ccc 100644 --- a/app/Services/Internal/Update/JournalUpdateService.php +++ b/app/Services/Internal/Update/JournalUpdateService.php @@ -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(); } /** diff --git a/app/Support/Binder/JournalList.php b/app/Support/Binder/JournalList.php index 9c5b67af3f..8abc85ed24 100644 --- a/app/Support/Binder/JournalList.php +++ b/app/Support/Binder/JournalList.php @@ -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; + } } diff --git a/app/Support/Binder/SimpleJournalList.php b/app/Support/Binder/SimpleJournalList.php deleted file mode 100644 index a07702a523..0000000000 --- a/app/Support/Binder/SimpleJournalList.php +++ /dev/null @@ -1,78 +0,0 @@ -. - */ - -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; - } -} diff --git a/config/firefly.php b/config/firefly.php index 6293af0e59..e062578248 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -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, diff --git a/public/v1/js/ff/transactions/mass/edit.js b/public/v1/js/ff/transactions/mass/edit.js index 41ccf288c7..ccad752a86 100644 --- a/public/v1/js/ff/transactions/mass/edit.js +++ b/public/v1/js/ff/transactions/mass/edit.js @@ -18,22 +18,20 @@ * along with Firefly III. If not, see . */ -/** 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; }); } } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 9140d5815c..fb9567baba 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -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)', diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index bc0e0dcebb..d1556419fc 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -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', diff --git a/resources/views/v1/transactions/mass/delete.twig b/resources/views/v1/transactions/mass/delete.twig index 35946af4a8..f7c96b4463 100644 --- a/resources/views/v1/transactions/mass/delete.twig +++ b/resources/views/v1/transactions/mass/delete.twig @@ -19,7 +19,6 @@ {{ trans('form.permDeleteWarning') }} {{ 'perm-delete-many'|_ }}

-

{{ trans('form.mass_journal_are_you_sure') }} {{ trans('form.mass_make_selection') }} @@ -28,8 +27,9 @@ + - + @@ -37,22 +37,61 @@ {% for journal in journals %} + {% endfor %} diff --git a/resources/views/v1/transactions/mass/edit.twig b/resources/views/v1/transactions/mass/edit.twig index 6bb9fdb046..d715c573b4 100644 --- a/resources/views/v1/transactions/mass/edit.twig +++ b/resources/views/v1/transactions/mass/edit.twig @@ -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 @@ - {% for transaction in transactions %} + {% for journal in journals %} {# AMOUNT #} + + + - + {# category #} {# budget #}
 {{ trans('list.transaction_type') }} {{ trans('list.description') }}{{ trans('list.total_amount') }}{{ trans('list.amount') }}
- + - {{ journal.description }} + {% if journal.transaction_type_type == 'Withdrawal' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Deposit' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Transfer' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Reconciliation' %} + + {% endif %} + {% if journal.transaction_type_type == 'Opening balance' %} + + {% endif %} - {{ journal|journalTotalAmount }} + {{ journal.description }} + + {% 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' %} + {{ 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 %} + + {% 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 %} {{ journal.date.formatLocalized(monthAndDayFormat) }} - {{ sourceAccount(journal)|raw }} + {{ journal.source_account_name }} - {{ destinationXAccount(journal)|raw }} + {{ journal.destination_account_name }}
{{ trans('list.category') }} {{ trans('list.budget') }}
{# LINK TO EDIT FORM #} - - + {# DESCRIPTION #} + placeholder="{{ journal.description }}" name="description[{{ journal.transaction_journal_id }}]" + type="text" value="{{ journal.description }}">
- {{ transaction.currency_symbol }} - - + + + {{ journal.currency_symbol }} + +
- {% if transaction.foreign_amount %} + {% if journal.foreign_amount %} {# insert foreign data #}
- {{ transaction.foreign_currency_symbol }} - - + {{ journal.foreign_currency_symbol }} + +
{% endif %}
{# DATE #} + name="date[{{ journal.transaction_journal_id }}]" type="date" value="{{ journal.date|slice(0,10) }}"> + {# SOURCE ACCOUNT ID FOR TRANSFER OR WITHDRAWAL #} - {% if transaction.type == 'Transfer' or transaction.type == 'Withdrawal' %} - - {% else %} - {# SOURCE ACCOUNT NAME FOR DEPOSIT #} - - {% endif %} - - {% if transaction.type == 'Transfer' or transaction.type == 'Deposit' %} - {# DESTINATION ACCOUNT NAME FOR TRANSFER AND DEPOSIT #} - - {% else %} + {% endif %} - {# DESTINATION ACCOUNT NAME FOR EXPENSE #} - + {# SOURCE ACCOUNT NAME FOR DEPOSIT #} + {% if journal.transaction_type_type == 'Deposit' %} + + {% endif %} + + + {# DESTINATION ACCOUNT NAME FOR TRANSFER AND DEPOSIT #} + {% if journal.transaction_type_type == 'Transfer' or journal.transaction_type_type == 'Deposit' %} + + + {% endif %} + + {# DESTINATION ACCOUNT NAME FOR WITHDRAWAL #} + {% if journal.transaction_type_type == 'Withdrawal' %} + {% endif %} - + - {% if transaction.type == 'Withdrawal' %} - {% for budget in budgets %} - {% endfor %} diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index 3369df4ac6..72b7e53083 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -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', [''])); } ); diff --git a/routes/web.php b/routes/web.php index e41269f88c..f7541a1028 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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']); } ); diff --git a/tests/Feature/Controllers/Transaction/MassControllerTest.php b/tests/Feature/Controllers/Transaction/MassControllerTest.php index c79554aec4..7b03e8ec87 100644 --- a/tests/Feature/Controllers/Transaction/MassControllerTest.php +++ b/tests/Feature/Controllers/Transaction/MassControllerTest.php @@ -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('