From fdc946721825a260af7be5e3ebf56362fe1fd600 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 28 Apr 2018 21:54:48 +0200 Subject: [PATCH 001/182] Fix #1369 --- app/Http/Controllers/PiggyBankController.php | 37 ++++++++----------- .../PiggyBank/PiggyBankRepository.php | 28 ++++++++++---- .../PiggyBankRepositoryInterface.php | 9 ++++- public/js/ff/import/status.js | 5 +++ public/js/ff/piggy-banks/index.js | 33 ++++++++++++++--- resources/views/list/piggy-banks.twig | 2 +- routes/web.php | 3 +- 7 files changed, 79 insertions(+), 38 deletions(-) diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php index 12a56e79c6..e5fdce6c0a 100644 --- a/app/Http/Controllers/PiggyBankController.php +++ b/app/Http/Controllers/PiggyBankController.php @@ -30,6 +30,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Transformers\AccountTransformer; use FireflyIII\Transformers\PiggyBankTransformer; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -215,6 +216,7 @@ class PiggyBankController extends Controller */ public function index(Request $request) { + $this->piggyRepos->correctOrder(); $collection = $this->piggyRepos->getPiggyBanks(); $total = $collection->count(); $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); @@ -261,27 +263,6 @@ class PiggyBankController extends Controller return view('piggy-banks.index', compact('piggyBanks', 'accounts')); } - /** - * @param Request $request - * - * @return \Illuminate\Http\JsonResponse - */ - public function order(Request $request) - { - $data = $request->get('order'); - - // set all users piggy banks to zero: - $this->piggyRepos->reset(); - - if (\is_array($data)) { - foreach ($data as $order => $id) { - $this->piggyRepos->setOrder((int)$id, $order + 1); - } - } - - return response()->json(['result' => 'ok']); - } - /** * @param Request $request * @param PiggyBank $piggyBank @@ -402,6 +383,20 @@ class PiggyBankController extends Controller return view('piggy-banks.remove-mobile', compact('piggyBank', 'repetition', 'currency')); } + /** + * @param Request $request + * @param PiggyBank $piggyBank + * + * @return JsonResponse + */ + public function setOrder(Request $request, PiggyBank $piggyBank): JsonResponse + { + $newOrder = (int)$request->get('order'); + $this->piggyRepos->setOrder($piggyBank, $newOrder); + + return response()->json(['data' => 'OK']); + } + /** * @param PiggyBank $piggyBank * diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 2612cf245c..55b22083ce 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -104,6 +104,22 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return bccomp($amount, $savedSoFar) <= 0; } + /** + * Correct order of piggies in case of issues. + */ + public function correctOrder(): void + { + $set = $this->user->piggyBanks()->orderBy('order', 'ASC')->get(); + $current = 1; + foreach ($set as $piggyBank) { + if ((int)$piggyBank->order !== $current) { + $piggyBank->order = $current; + $piggyBank->save(); + } + $current++; + } + } + /** * @param PiggyBank $piggyBank * @param string $amount @@ -327,7 +343,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** @var PiggyBank $current */ foreach ($piggies as $current) { $repetition = $this->getRepetition($current); - if(null !== $repetition) { + if (null !== $repetition) { $balance = bcsub($balance, $repetition->currentamount); } } @@ -379,14 +395,10 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface * * @return bool */ - public function setOrder(int $piggyBankId, int $order): bool + public function setOrder(PiggyBank $piggyBank, int $order): bool { - $piggyBank = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->where('accounts.user_id', $this->user->id) - ->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); - if ($piggyBank) { - $piggyBank->order = $order; - $piggyBank->save(); - } + $piggyBank->order = $order; + $piggyBank->save(); return true; } diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index 59e217d2f3..ca9ab5ea38 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -86,6 +86,11 @@ interface PiggyBankRepositoryInterface */ public function createEventWithJournal(PiggyBank $piggyBank, string $amount, TransactionJournal $journal): PiggyBankEvent; + /** + * Correct order of piggies in case of issues. + */ + public function correctOrder(): void; + /** * Destroy piggy bank. * @@ -196,12 +201,12 @@ interface PiggyBankRepositoryInterface /** * Set specific piggy bank to specific order. * - * @param int $piggyBankId + * @param PiggyBank $piggyBank * @param int $order * * @return bool */ - public function setOrder(int $piggyBankId, int $order): bool; + public function setOrder(PiggyBank $piggyBank, int $order): bool; /** * @param User $user diff --git a/public/js/ff/import/status.js b/public/js/ff/import/status.js index 73f621239c..46c176e2da 100644 --- a/public/js/ff/import/status.js +++ b/public/js/ff/import/status.js @@ -63,6 +63,10 @@ function checkJobStatus() { */ function reportFailedJob(jqxhr, textStatus, error) { console.log('In reportFailedJob()'); + + // cancel refresh + clearTimeout(timeOutId); + // hide all possible boxes: $('.statusbox').hide(); @@ -133,6 +137,7 @@ function reportOnJobStatus(data) { $('#import-status-more-info').html(data.finishedText); break; case "error": + clearTimeout(timeOutId); console.log('Job reports ERROR.'); // hide all possible boxes: $('.statusbox').hide(); diff --git a/public/js/ff/piggy-banks/index.js b/public/js/ff/piggy-banks/index.js index 292d7f7f7d..6a67810c38 100644 --- a/public/js/ff/piggy-banks/index.js +++ b/public/js/ff/piggy-banks/index.js @@ -83,13 +83,36 @@ function removeMoney(e) { function stopSorting() { "use strict"; $('.loadSpin').addClass('fa fa-refresh fa-spin'); - var order = []; + $.each($('#sortable-piggy>tbody>tr'), function (i, v) { var holder = $(v); + var position = parseInt(holder.data('position')); + var originalOrder = parseInt(holder.data('order')); var id = holder.data('id'); - order.push(id); - }); - $.post('piggy-banks/sort', {order: order, _token: token}).done(function () { - $('.loadSpin').removeClass('fa fa-refresh fa-spin'); + console.log('Now at row ' + i); + var newOrder; + if (position === i) { + return; + } + if (position < i) { + console.log('Row ' + i + ' has moved up!'); + // update position: + holder.data('position', i); + newOrder = originalOrder + 1; + + + } + if (position > i) { + console.log('Row ' + i + ' has moved down!'); + // update position: + holder.data('position', i); + newOrder = originalOrder - 1; + + + } + + $.post('piggy-banks/set-order/' + id, {order: newOrder, _token: token}) }); + $('.loadSpin').removeClass('fa fa-refresh fa-spin'); + } \ No newline at end of file diff --git a/resources/views/list/piggy-banks.twig b/resources/views/list/piggy-banks.twig index 24387cf7b8..2902d639db 100644 --- a/resources/views/list/piggy-banks.twig +++ b/resources/views/list/piggy-banks.twig @@ -14,7 +14,7 @@ {% for piggy in piggyBanks %} - +   diff --git a/routes/web.php b/routes/web.php index 53d0f5054e..80d111977a 100755 --- a/routes/web.php +++ b/routes/web.php @@ -564,7 +564,8 @@ Route::group( Route::post('destroy/{piggyBank}', ['uses' => 'PiggyBankController@destroy', 'as' => 'destroy']); Route::post('add/{piggyBank}', ['uses' => 'PiggyBankController@postAdd', 'as' => 'add']); Route::post('remove/{piggyBank}', ['uses' => 'PiggyBankController@postRemove', 'as' => 'remove']); - Route::post('sort', ['uses' => 'PiggyBankController@order', 'as' => 'order']); + + Route::post('set-order/{piggyBank}', ['uses' => 'PiggyBankController@setOrder', 'as' => 'set-order']); } From 9cbbd581ee279c2f252eb50a122c72f90dfde77f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 07:42:01 +0200 Subject: [PATCH 002/182] Rename template file. --- .../views/form/{multiCheckbox.twig => assetAccountCheckList.twig} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/views/form/{multiCheckbox.twig => assetAccountCheckList.twig} (100%) diff --git a/resources/views/form/multiCheckbox.twig b/resources/views/form/assetAccountCheckList.twig similarity index 100% rename from resources/views/form/multiCheckbox.twig rename to resources/views/form/assetAccountCheckList.twig From 8acb9f405650a23cd0c0a1c919943f816cbfaf8d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 07:42:17 +0200 Subject: [PATCH 003/182] Fix error when un-decryptable files would break the export. --- app/Export/Collector/AttachmentCollector.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/Export/Collector/AttachmentCollector.php b/app/Export/Collector/AttachmentCollector.php index 5405855e3b..244cc919ed 100644 --- a/app/Export/Collector/AttachmentCollector.php +++ b/app/Export/Collector/AttachmentCollector.php @@ -94,7 +94,8 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface */ private function exportAttachment(Attachment $attachment): bool { - $file = $attachment->fileName(); + $file = $attachment->fileName(); + $decrypted = false; if ($this->uploadDisk->exists($file)) { try { $decrypted = Crypt::decrypt($this->uploadDisk->get($file)); @@ -104,6 +105,9 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface return false; } } + if ($decrypted === false) { + return false; + } $exportFile = $this->exportFileName($attachment); $this->exportDisk->put($exportFile, $decrypted); $this->getEntries()->push($exportFile); From bc4e06568d0b2344d514b15ca2c2af3eb84a4e0e Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 07:45:38 +0200 Subject: [PATCH 004/182] Move references to repository --- app/Http/Controllers/BillController.php | 114 +++++++++++------------- 1 file changed, 52 insertions(+), 62 deletions(-) diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index 2e0d35969b..8fcb18777f 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -22,14 +22,11 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; -use ExpandedForm; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Requests\BillFormRequest; use FireflyIII\Models\Bill; -use FireflyIII\Models\Note; use FireflyIII\Repositories\Bill\BillRepositoryInterface; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\TransactionRules\TransactionMatcher; use FireflyIII\Transformers\BillTransformer; @@ -50,6 +47,10 @@ class BillController extends Controller { /** @var AttachmentHelperInterface Helper for attachments. */ private $attachments; + /** @var BillRepositoryInterface */ + private $billRepository; + /** @var RuleGroupRepositoryInterface */ + private $ruleGroupRepos; /** * @@ -67,7 +68,9 @@ class BillController extends Controller function ($request, $next) { app('view')->share('title', trans('firefly.bills')); app('view')->share('mainTitleIcon', 'fa-calendar-o'); - $this->attachments = app(AttachmentHelperInterface::class); + $this->attachments = app(AttachmentHelperInterface::class); + $this->billRepository = app(BillRepositoryInterface::class); + $this->ruleGroupRepos = app(RuleGroupRepositoryInterface::class); return $next($request); } @@ -75,21 +78,20 @@ class BillController extends Controller } /** - * @param Request $request - * - * @param CurrencyRepositoryInterface $repository + * @param Request $request * * @return View */ - public function create(Request $request, CurrencyRepositoryInterface $repository) + public function create(Request $request) { $periods = []; - foreach (config('firefly.bill_periods') as $current) { + /** @var array $billPeriods */ + $billPeriods = config('firefly.bill_periods'); + foreach ($billPeriods as $current) { $periods[$current] = strtolower((string)trans('firefly.repeat_freq_' . $current)); } $subTitle = trans('firefly.create_new_bill'); $defaultCurrency = app('amount')->getDefaultCurrency(); - $currencies = ExpandedForm::makeSelectList($repository->get()); // put previous url in session if not redirect from store (not "create another"). if (true !== session('bills.create.fromStore')) { @@ -97,7 +99,7 @@ class BillController extends Controller } $request->session()->forget('bills.create.fromStore'); - return view('bills.create', compact('periods', 'subTitle', 'currencies', 'defaultCurrency')); + return view('bills.create', compact('periods', 'subTitle', 'defaultCurrency')); } /** @@ -115,16 +117,15 @@ class BillController extends Controller } /** - * @param Request $request - * @param BillRepositoryInterface $repository - * @param Bill $bill + * @param Request $request + * @param Bill $bill * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(Request $request, BillRepositoryInterface $repository, Bill $bill) + public function destroy(Request $request, Bill $bill) { $name = $bill->name; - $repository->destroy($bill); + $this->billRepository->destroy($bill); $request->session()->flash('success', (string)trans('firefly.deleted_bill', ['name' => $name])); Preferences::mark(); @@ -133,18 +134,21 @@ class BillController extends Controller } /** - * @param Request $request - * @param CurrencyRepositoryInterface $repository - * @param Bill $bill + * @param Request $request + * @param Bill $bill * * @return View */ - public function edit(Request $request, CurrencyRepositoryInterface $repository, Bill $bill) + public function edit(Request $request, Bill $bill) { $periods = []; - foreach (config('firefly.bill_periods') as $current) { + /** @var array $billPeriods */ + $billPeriods = config('firefly.bill_periods'); + + foreach ($billPeriods as $current) { $periods[$current] = trans('firefly.' . $current); } + $subTitle = trans('firefly.edit_bill', ['name' => $bill->name]); // put previous url in session if not redirect from store (not "return_to_edit"). @@ -156,36 +160,27 @@ class BillController extends Controller $bill->amount_min = round($bill->amount_min, $currency->decimal_places); $bill->amount_max = round($bill->amount_max, $currency->decimal_places); $defaultCurrency = app('amount')->getDefaultCurrency(); - $currencies = ExpandedForm::makeSelectList($repository->get()); $preFilled = [ - 'notes' => '', + 'notes' => $this->billRepository->getNoteText($bill), + 'transaction_currency_id' => $bill->transaction_currency_id, ]; - /** @var Note $note */ - $note = $bill->notes()->first(); - if (null !== $note) { - $preFilled['notes'] = $note->text; - } - $request->session()->flash('preFilled', $preFilled); - $request->session()->forget('bills.edit.fromUpdate'); - return view('bills.edit', compact('subTitle', 'periods', 'bill', 'defaultCurrency', 'currencies')); + return view('bills.edit', compact('subTitle', 'periods', 'bill', 'defaultCurrency', 'preFilled')); } /** - * @param BillRepositoryInterface $repository - * * @return View */ - public function index(BillRepositoryInterface $repository) + public function index() { $start = session('start'); $end = session('end'); $pageSize = (int)Preferences::get('listPageSize', 50)->data; - $paginator = $repository->getPaginator($pageSize); + $paginator = $this->billRepository->getPaginator($pageSize); $parameters = new ParameterBag(); $parameters->set('start', $start); $parameters->set('end', $end); @@ -203,7 +198,7 @@ class BillController extends Controller ); // add info about rules: - $rules = $repository->getRulesForBills($paginator->getCollection()); + $rules = $this->billRepository->getRulesForBills($paginator->getCollection()); $bills = $bills->map( function (array $bill) use ($rules) { $bill['rules'] = $rules[$bill['id']] ?? []; @@ -218,21 +213,20 @@ class BillController extends Controller } /** - * @param Request $request - * @param BillRepositoryInterface $repository - * @param Bill $bill + * @param Request $request + * @param Bill $bill * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @throws \FireflyIII\Exceptions\FireflyException */ - public function rescan(Request $request, BillRepositoryInterface $repository, Bill $bill) + public function rescan(Request $request, Bill $bill) { if (0 === (int)$bill->active) { $request->session()->flash('warning', (string)trans('firefly.cannot_scan_inactive_bill')); return redirect(URL::previous()); } - $set = $repository->getRulesForBill($bill); + $set = $this->billRepository->getRulesForBill($bill); $total = 0; foreach ($set as $rule) { // simply fire off all rules? @@ -243,7 +237,7 @@ class BillController extends Controller $matcher->setRule($rule); $matchingTransactions = $matcher->findTransactionsByRule(); $total += $matchingTransactions->count(); - $repository->linkCollectionToBill($bill, $matchingTransactions); + $this->billRepository->linkCollectionToBill($bill, $matchingTransactions); } @@ -254,24 +248,23 @@ class BillController extends Controller } /** - * @param Request $request - * @param BillRepositoryInterface $repository - * @param Bill $bill + * @param Request $request + * @param Bill $bill * * @return View */ - public function show(Request $request, BillRepositoryInterface $repository, Bill $bill) + public function show(Request $request, Bill $bill) { // add info about rules: - $rules = $repository->getRulesForBill($bill); + $rules = $this->billRepository->getRulesForBill($bill); $subTitle = $bill->name; $start = session('start'); $end = session('end'); $year = $start->year; $page = (int)$request->get('page'); $pageSize = (int)Preferences::get('listPageSize', 50)->data; - $yearAverage = $repository->getYearAverage($bill, $start); - $overallAverage = $repository->getOverallAverage($bill); + $yearAverage = $this->billRepository->getYearAverage($bill, $start); + $overallAverage = $this->billRepository->getOverallAverage($bill); $manager = new Manager(); $manager->setSerializer(new DataArraySerializer()); $manager->parseIncludes(['attachments', 'notes']); @@ -296,16 +289,14 @@ class BillController extends Controller } /** - * @param BillFormRequest $request - * @param BillRepositoryInterface $repository + * @param BillFormRequest $request * - * @param RuleGroupRepositoryInterface $ruleGroupRepository * @return \Illuminate\Http\RedirectResponse */ - public function store(BillFormRequest $request, BillRepositoryInterface $repository, RuleGroupRepositoryInterface $ruleGroupRepository) + public function store(BillFormRequest $request) { $billData = $request->getBillData(); - $bill = $repository->store($billData); + $bill = $this->billRepository->store($billData); if (null === $bill) { $request->session()->flash('error', (string)trans('firefly.bill_store_error')); @@ -330,16 +321,16 @@ class BillController extends Controller } // find first rule group, or create one: - $count = $ruleGroupRepository->count(); + $count = $this->ruleGroupRepos->count(); if ($count === 0) { $data = [ 'title' => (string)trans('firefly.rulegroup_for_bills_title'), 'description' => (string)trans('firefly.rulegroup_for_bills_description'), ]; - $group = $ruleGroupRepository->store($data); + $group = $this->ruleGroupRepos->store($data); } if ($count > 0) { - $group = $ruleGroupRepository->getActiveGroups(auth()->user())->first(); + $group = $this->ruleGroupRepos->getActiveGroups(auth()->user())->first(); } // redirect to page that will create a new rule. @@ -349,16 +340,15 @@ class BillController extends Controller } /** - * @param BillFormRequest $request - * @param BillRepositoryInterface $repository - * @param Bill $bill + * @param BillFormRequest $request + * @param Bill $bill * * @return \Illuminate\Http\RedirectResponse */ - public function update(BillFormRequest $request, BillRepositoryInterface $repository, Bill $bill) + public function update(BillFormRequest $request, Bill $bill) { $billData = $request->getBillData(); - $bill = $repository->update($bill, $billData); + $bill = $this->billRepository->update($bill, $billData); $request->session()->flash('success', (string)trans('firefly.updated_bill', ['name' => $bill->name])); Preferences::mark(); From a3d0355dddffa75b342fea3235beb5bf89509f1b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 07:45:54 +0200 Subject: [PATCH 005/182] Use new expanded for method. --- app/Http/Controllers/ExportController.php | 10 ++-------- app/Http/Controllers/RuleController.php | 11 +++-------- app/Http/Controllers/RuleGroupController.php | 9 ++------- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 2c19715d42..a6836ba004 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -23,12 +23,10 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; -use ExpandedForm; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Export\ProcessorInterface; use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Http\Requests\ExportFormRequest; -use FireflyIII\Models\AccountType; use FireflyIII\Models\ExportJob; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface; @@ -107,12 +105,11 @@ class ExportController extends Controller } /** - * @param AccountRepositoryInterface $repository * @param ExportJobRepositoryInterface $jobs * * @return View */ - public function index(AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs) + public function index(ExportJobRepositoryInterface $jobs) { // create new export job. $job = $jobs->create(); @@ -120,15 +117,12 @@ class ExportController extends Controller $jobs->cleanup(); // does the user have shared accounts? - $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - $accountList = ExpandedForm::makeSelectList($accounts); - $checked = array_keys($accountList); $formats = array_keys(config('firefly.export_formats')); $defaultFormat = Preferences::get('export_format', config('firefly.default_export_format'))->data; $first = session('first')->format('Y-m-d'); $today = Carbon::create()->format('Y-m-d'); - return view('export.index', compact('job', 'checked', 'accountList', 'formats', 'defaultFormat', 'first', 'today')); + return view('export.index', compact('job', 'formats', 'defaultFormat', 'first', 'today')); } /** diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php index c33c759eb3..9c95b9509a 100644 --- a/app/Http/Controllers/RuleController.php +++ b/app/Http/Controllers/RuleController.php @@ -102,7 +102,6 @@ class RuleController extends Controller $preFilled = [ 'strict' => true, ]; - $groups = ExpandedForm::makeSelectList($this->ruleGroupRepos->get()); $oldTriggers = []; $oldActions = []; $returnToBill = false; @@ -150,7 +149,7 @@ class RuleController extends Controller return view( 'rules.rule.create', compact( - 'subTitleIcon', 'oldTriggers', 'returnToBill', 'groups', 'preFilled', 'bill', 'oldActions', 'triggerCount', 'actionCount', 'ruleGroup', + 'subTitleIcon', 'oldTriggers', 'returnToBill', 'preFilled', 'bill', 'oldActions', 'triggerCount', 'actionCount', 'ruleGroup', 'subTitle' ) ); @@ -212,7 +211,6 @@ class RuleController extends Controller */ public function edit(Request $request, Rule $rule) { - $ruleGroups = ExpandedForm::makeSelectList($this->ruleGroupRepos->get()); $triggerCount = 0; $actionCount = 0; $oldActions = []; @@ -243,7 +241,7 @@ class RuleController extends Controller } session()->forget('rules.edit.fromUpdate'); - return view('rules.rule.edit', compact('rule', 'subTitle', 'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount', 'ruleGroups')); + return view('rules.rule.edit', compact('rule', 'subTitle', 'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount')); } /** @@ -333,14 +331,11 @@ class RuleController extends Controller public function selectTransactions(Rule $rule) { // does the user have shared accounts? - $accounts = $this->accountRepos->getAccountsByType([AccountType::ASSET]); - $accountList = ExpandedForm::makeSelectList($accounts); - $checkedAccounts = array_keys($accountList); $first = session('first')->format('Y-m-d'); $today = Carbon::create()->format('Y-m-d'); $subTitle = (string)trans('firefly.apply_rule_selection', ['title' => $rule->title]); - return view('rules.rule.select-transactions', compact('checkedAccounts', 'accountList', 'first', 'today', 'rule', 'subTitle')); + return view('rules.rule.select-transactions', compact( 'first', 'today', 'rule', 'subTitle')); } /** diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php index 16c44dea94..8aa82f72f7 100644 --- a/app/Http/Controllers/RuleGroupController.php +++ b/app/Http/Controllers/RuleGroupController.php @@ -179,22 +179,17 @@ class RuleGroupController extends Controller } /** - * @param AccountRepositoryInterface $repository * @param RuleGroup $ruleGroup * * @return View */ - public function selectTransactions(AccountRepositoryInterface $repository, RuleGroup $ruleGroup) + public function selectTransactions(RuleGroup $ruleGroup) { - // does the user have shared accounts? - $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - $accountList = ExpandedForm::makeSelectList($accounts); - $checkedAccounts = array_keys($accountList); $first = session('first')->format('Y-m-d'); $today = Carbon::create()->format('Y-m-d'); $subTitle = (string)trans('firefly.apply_rule_group_selection', ['title' => $ruleGroup->title]); - return view('rules.rule-group.select-transactions', compact('checkedAccounts', 'accountList', 'first', 'today', 'ruleGroup', 'subTitle')); + return view('rules.rule-group.select-transactions', compact( 'first', 'today', 'ruleGroup', 'subTitle')); } /** From c0d715c78abf836ad33e18f76638efc245e96ee8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 07:46:03 +0200 Subject: [PATCH 006/182] Add method to collect note text. --- app/Repositories/Bill/BillRepository.php | 20 ++++++++++++++++++- .../Bill/BillRepositoryInterface.php | 10 +++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index a78481ddfb..a9c1e38be2 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -26,6 +26,7 @@ use Carbon\Carbon; use DB; use FireflyIII\Factory\BillFactory; use FireflyIII\Models\Bill; +use FireflyIII\Models\Note; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; @@ -242,6 +243,24 @@ class BillRepository implements BillRepositoryInterface return $sum; } + /** + * Get text or return empty string. + * + * @param Bill $bill + * + * @return string + */ + public function getNoteText(Bill $bill): string + { + /** @var Note $note */ + $note = $bill->notes()->first(); + if (null !== $note) { + return (string)$note->text; + } + + return ''; + } + /** * @param Bill $bill * @@ -554,5 +573,4 @@ class BillRepository implements BillRepositoryInterface return $service->update($bill, $data); } - } diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index c53c8aa6e3..37f7e6abd7 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -24,7 +24,6 @@ namespace FireflyIII\Repositories\Bill; use Carbon\Carbon; use FireflyIII\Models\Bill; -use FireflyIII\Models\TransactionJournal; use FireflyIII\User; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -99,6 +98,15 @@ interface BillRepositoryInterface */ public function getBillsUnpaidInRange(Carbon $start, Carbon $end): string; + /** + * Get text or return empty string. + * + * @param Bill $bill + * + * @return string + */ + public function getNoteText(Bill $bill): string; + /** * @param Bill $bill * From 565cb6d79e8e38f939140f6e7a9fa821c4b475d8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 07:46:14 +0200 Subject: [PATCH 007/182] New select options --- app/Support/ExpandedForm.php | 100 ++++++++++++------ config/twigbridge.php | 2 +- .../views/form/assetAccountCheckList.twig | 26 ++--- 3 files changed, 82 insertions(+), 46 deletions(-) diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index cbfc8c48ac..899b93e8b9 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -27,9 +27,11 @@ use Carbon\Carbon; use Eloquent; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; +use FireflyIII\Models\RuleGroup; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; use RuntimeException; @@ -93,12 +95,51 @@ class ExpandedForm return $this->currencyField($name, 'amount-small', $value, $options); } + /** + * @param string $name + * @param $selected + * @param null $options + * + * @return string + * @throws \Throwable + */ + public function assetAccountCheckList(string $name, $options = null): string + { + $options = $options ?? []; + $label = $this->label($name, $options); + $options = $this->expandOptionArray($name, $label, $options); + $classes = $this->getHolderClasses($name); + $selected = request()->old($name) ?? []; + + // get all asset accounts: + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $assetAccounts = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($assetAccounts as $account) { + $role = $repository->getMetaValue($account, 'accountRole'); + if (null === $role) { + $role = 'no_account_type'; // @codeCoverageIgnore + } + $key = (string)trans('firefly.opt_group_' . $role); + $grouped[$key][$account->id] = $account->name; + } + + unset($options['class']); + $html = view('form.assetAccountCheckList', compact('classes','selected', 'name', 'label', 'options', 'grouped'))->render(); + + return $html; + } + /** * @param string $name * @param null $value * @param array $options * * @return string + * @throws \Throwable */ public function assetAccountList(string $name, $value = null, array $options = []): string { @@ -193,19 +234,10 @@ class ExpandedForm * @param array $options * * @return string + * @throws \Throwable */ public function currencyList(string $name, $value = null, array $options = []): string { - // properties for cache - $cache = new CacheProperties; - $cache->addProperty('exp-form-currency-list'); - $cache->addProperty($name); - $cache->addProperty($value); - $cache->addProperty($options); - - if ($cache->has()) { - return $cache->get(); - } /** @var CurrencyRepositoryInterface $currencyRepos */ $currencyRepos = app(CurrencyRepositoryInterface::class); @@ -217,7 +249,6 @@ class ExpandedForm $array[$currency->id] = $currency->name . ' (' . $currency->symbol . ')'; } $res = $this->select($name, $array, $value, $options); - $cache->store($res); return $res; } @@ -351,28 +382,6 @@ class ExpandedForm return $selectList; } - /** - * @param string $name - * @param array $list - * @param null $selected - * @param array $options - * - * @return string - * @throws \Throwable - */ - public function multiCheckbox(string $name, array $list = [], $selected = null, array $options = []): string - { - $label = $this->label($name, $options); - $options = $this->expandOptionArray($name, $label, $options); - $classes = $this->getHolderClasses($name); - $selected = $this->fillFieldValue($name, $selected); - - unset($options['class']); - $html = view('form.multiCheckbox', compact('classes', 'name', 'label', 'selected', 'options', 'list'))->render(); - - return $html; - } - /** * @param string $name * @param array $list @@ -516,6 +525,31 @@ class ExpandedForm return $html; } + /** + * @param string $name + * @param null $value + * @param array $options + * + * @return string + * @throws \Throwable + */ + public function ruleGroupList(string $name, $value = null, array $options = []): string + { + /** @var RuleGroupRepositoryInterface $groupRepos */ + $groupRepos = app(RuleGroupRepositoryInterface::class); + + // get all currencies: + $list = $groupRepos->get(); + $array = []; + /** @var RuleGroup $group */ + foreach ($list as $group) { + $array[$group->id] = $group->title; + } + $res = $this->select($name, $array, $value, $options); + + return $res; + } + /** * @param string $name * @param array $list diff --git a/config/twigbridge.php b/config/twigbridge.php index 63b6398f22..2e2b1d98c7 100644 --- a/config/twigbridge.php +++ b/config/twigbridge.php @@ -188,7 +188,7 @@ return [ 'is_safe' => [ 'date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location', 'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password', 'nonSelectableBalance', 'nonSelectableAmount', - 'number', 'assetAccountList','amountNoCurrency','currencyList' + 'number', 'assetAccountList','amountNoCurrency','currencyList','ruleGroupList','assetAccountCheckList' ], ], 'Form' => [ diff --git a/resources/views/form/assetAccountCheckList.twig b/resources/views/form/assetAccountCheckList.twig index 39776e2be0..129dc11356 100644 --- a/resources/views/form/assetAccountCheckList.twig +++ b/resources/views/form/assetAccountCheckList.twig @@ -2,18 +2,20 @@
- {% for value,description in list %} - {% set options = options|merge({'id': 'ffInput_'~name~'_'~value}) %} -
- -
+ {% for groupName, accounts in grouped %} + {{ groupName }}
+ {% for id, account in accounts %} +
+ +
+ {% endfor %} {% endfor %} {% include 'form/help' %} {% include 'form/feedback' %} From 7eb56432041e437236e98b993bb155257d2e57b9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 07:46:30 +0200 Subject: [PATCH 008/182] Use new select options. --- resources/views/bills/create.twig | 2 +- resources/views/bills/edit.twig | 2 +- resources/views/export/index.twig | 2 +- resources/views/rules/rule-group/select-transactions.twig | 2 +- resources/views/rules/rule/create.twig | 2 +- resources/views/rules/rule/edit.twig | 2 +- resources/views/rules/rule/select-transactions.twig | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/views/bills/create.twig b/resources/views/bills/create.twig index a2663bff18..9bfde12466 100644 --- a/resources/views/bills/create.twig +++ b/resources/views/bills/create.twig @@ -17,7 +17,7 @@
{{ ExpandedForm.text('name') }} - {{ ExpandedForm.select('transaction_currency_id',currencies, defaultCurrency.id) }} + {{ ExpandedForm.currencyList('transaction_currency_id', defaultCurrency.id) }} {{ ExpandedForm.amountNoCurrency('amount_min') }} {{ ExpandedForm.amountNoCurrency('amount_max') }} {{ ExpandedForm.date('date',phpdate('Y-m-d')) }} diff --git a/resources/views/bills/edit.twig b/resources/views/bills/edit.twig index e64d063905..6302b36198 100644 --- a/resources/views/bills/edit.twig +++ b/resources/views/bills/edit.twig @@ -18,7 +18,7 @@
{{ ExpandedForm.text('name') }} - {{ ExpandedForm.select('transaction_currency_id',currencies) }} + {{ ExpandedForm.currencyList('transaction_currency_id') }} {{ ExpandedForm.amountNoCurrency('amount_min') }} {{ ExpandedForm.amountNoCurrency('amount_max') }} {{ ExpandedForm.date('date',bill.date.format('Y-m-d')) }} diff --git a/resources/views/export/index.twig b/resources/views/export/index.twig index a772feb710..57851bf641 100644 --- a/resources/views/export/index.twig +++ b/resources/views/export/index.twig @@ -74,7 +74,7 @@ {# ACCOUNTS #} - {{ ExpandedForm.multiCheckbox('accounts',accountList, checked, {' class': 'account-checkbox'}) }} + {{ ExpandedForm.assetAccountCheckList('accounts', {' class': 'account-checkbox','select_all': true}) }} {{ ExpandedForm.checkbox('include_attachments','1', true) }} diff --git a/resources/views/rules/rule-group/select-transactions.twig b/resources/views/rules/rule-group/select-transactions.twig index 6ae7769f03..5f13406cc7 100644 --- a/resources/views/rules/rule-group/select-transactions.twig +++ b/resources/views/rules/rule-group/select-transactions.twig @@ -28,7 +28,7 @@
{{ ExpandedForm.date('start_date', first) }} {{ ExpandedForm.date('end_date', today) }} - {{ ExpandedForm.multiCheckbox('accounts',accountList, checkedAccounts, {' class': 'account-checkbox', 'label': trans('firefly.include_transactions_from_accounts') }) }} + {{ ExpandedForm.assetAccountCheckList('accounts', {'select_all': true,'class': 'account-checkbox', 'label': trans('firefly.include_transactions_from_accounts') }) }}
diff --git a/resources/views/rules/rule/create.twig b/resources/views/rules/rule/create.twig index 5c04e33608..569789fadb 100644 --- a/resources/views/rules/rule/create.twig +++ b/resources/views/rules/rule/create.twig @@ -38,7 +38,7 @@
{{ ExpandedForm.text('title') }} {{ ExpandedForm.select('trigger',allJournalTriggers()) }} - {{ ExpandedForm.select('rule_group_id',groups, ruleGroup.id) }} + {{ ExpandedForm.ruleGroupList('rule_group_id', ruleGroup.id) }} {{ ExpandedForm.checkbox('stop_processing',1,null, {helpText: trans('firefly.rule_help_stop_processing')}) }} {{ ExpandedForm.checkbox('strict',1, null,{helpText: trans('firefly.rule_help_strict')}) }}
diff --git a/resources/views/rules/rule/edit.twig b/resources/views/rules/rule/edit.twig index 61226548b3..dad8d5871c 100644 --- a/resources/views/rules/rule/edit.twig +++ b/resources/views/rules/rule/edit.twig @@ -15,7 +15,7 @@
{{ ExpandedForm.text('title') }} - {{ ExpandedForm.select('rule_group_id', ruleGroups) }} + {{ ExpandedForm.ruleGroupList('rule_group_id', ruleGroup.id) }} {{ ExpandedForm.select('trigger',allJournalTriggers(), primaryTrigger) }} {{ ExpandedForm.checkbox('active',1,rule.active, {helpText: trans('firefly.rule_help_active')}) }} {{ ExpandedForm.checkbox('stop_processing',1,rule.stop_processing, {helpText: trans('firefly.rule_help_stop_processing')}) }} diff --git a/resources/views/rules/rule/select-transactions.twig b/resources/views/rules/rule/select-transactions.twig index 39fe00f7de..65df2aabff 100644 --- a/resources/views/rules/rule/select-transactions.twig +++ b/resources/views/rules/rule/select-transactions.twig @@ -28,7 +28,7 @@
{{ ExpandedForm.date('start_date', first) }} {{ ExpandedForm.date('end_date', today) }} - {{ ExpandedForm.multiCheckbox('accounts',accountList, checkedAccounts, {' class': 'account-checkbox', 'label': trans('firefly.include_transactions_from_accounts') }) }} + {{ ExpandedForm.assetAccountCheckList('accounts', {'select_all': true, 'class': 'account-checkbox', 'label': trans('firefly.include_transactions_from_accounts') }) }}
From 88348f59c27ec96068acf24ce54917074377d61b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 09:00:49 +0200 Subject: [PATCH 009/182] Could repair #1389 --- app/TransactionRules/Actions/LinkToBill.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/TransactionRules/Actions/LinkToBill.php b/app/TransactionRules/Actions/LinkToBill.php index 192c01c352..6ffb84cb8e 100644 --- a/app/TransactionRules/Actions/LinkToBill.php +++ b/app/TransactionRules/Actions/LinkToBill.php @@ -57,6 +57,7 @@ class LinkToBill implements ActionInterface { /** @var BillRepositoryInterface $repository */ $repository = app(BillRepositoryInterface::class); + $repository->setUser($this->action->rule->user); $billName = (string)$this->action->action_value; $bill = $repository->findByName($billName); From 71b63bd33bbe1d877c52923a9e058fa01f65121a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 09:48:53 +0200 Subject: [PATCH 010/182] Remove references to ExpandedForm. --- app/Http/Controllers/RuleGroupController.php | 22 +++++------- app/Support/ExpandedForm.php | 36 ++++++++++++++++++-- config/twigbridge.php | 2 +- resources/lang/en_US/firefly.php | 1 + resources/views/rules/rule-group/delete.twig | 2 +- 5 files changed, 44 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php index 8aa82f72f7..3c65632544 100644 --- a/app/Http/Controllers/RuleGroupController.php +++ b/app/Http/Controllers/RuleGroupController.php @@ -23,11 +23,9 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; -use ExpandedForm; use FireflyIII\Http\Requests\RuleGroupFormRequest; use FireflyIII\Http\Requests\SelectTransactionsRequest; use FireflyIII\Jobs\ExecuteRuleGroupOnExistingTransactions; -use FireflyIII\Models\AccountType; use FireflyIII\Models\RuleGroup; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; @@ -75,22 +73,18 @@ class RuleGroupController extends Controller } /** - * @param RuleGroupRepositoryInterface $repository - * @param RuleGroup $ruleGroup + * @param RuleGroup $ruleGroup * * @return View */ - public function delete(RuleGroupRepositoryInterface $repository, RuleGroup $ruleGroup) + public function delete(RuleGroup $ruleGroup) { $subTitle = trans('firefly.delete_rule_group', ['title' => $ruleGroup->title]); - $ruleGroupList = ExpandedForm::makeSelectListWithEmpty($repository->get()); - unset($ruleGroupList[$ruleGroup->id]); - // put previous url in session $this->rememberPreviousUri('rule-groups.delete.uri'); - return view('rules.rule-group.delete', compact('ruleGroup', 'subTitle', 'ruleGroupList')); + return view('rules.rule-group.delete', compact('ruleGroup', 'subTitle')); } /** @@ -179,17 +173,17 @@ class RuleGroupController extends Controller } /** - * @param RuleGroup $ruleGroup + * @param RuleGroup $ruleGroup * * @return View */ public function selectTransactions(RuleGroup $ruleGroup) { - $first = session('first')->format('Y-m-d'); - $today = Carbon::create()->format('Y-m-d'); - $subTitle = (string)trans('firefly.apply_rule_group_selection', ['title' => $ruleGroup->title]); + $first = session('first')->format('Y-m-d'); + $today = Carbon::create()->format('Y-m-d'); + $subTitle = (string)trans('firefly.apply_rule_group_selection', ['title' => $ruleGroup->title]); - return view('rules.rule-group.select-transactions', compact( 'first', 'today', 'ruleGroup', 'subTitle')); + return view('rules.rule-group.select-transactions', compact('first', 'today', 'ruleGroup', 'subTitle')); } /** diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 899b93e8b9..c40e574e5a 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -32,6 +32,7 @@ use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use Form; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; use RuntimeException; @@ -49,6 +50,7 @@ class ExpandedForm * * @return string * @throws \FireflyIII\Exceptions\FireflyException + * @throws \Throwable */ public function amount(string $name, $value = null, array $options = []): string { @@ -128,7 +130,7 @@ class ExpandedForm } unset($options['class']); - $html = view('form.assetAccountCheckList', compact('classes','selected', 'name', 'label', 'options', 'grouped'))->render(); + $html = view('form.assetAccountCheckList', compact('classes', 'selected', 'name', 'label', 'options', 'grouped'))->render(); return $html; } @@ -545,9 +547,37 @@ class ExpandedForm foreach ($list as $group) { $array[$group->id] = $group->title; } - $res = $this->select($name, $array, $value, $options); - return $res; + return $this->select($name, $array, $value, $options); + } + + /** + * @param string $name + * @param null $value + * @param null $options + * + * @return \Illuminate\Support\HtmlString + */ + public function ruleGroupListWithEmpty(string $name, $value = null, $options = null) + { + $options = $options ?? []; + $options['class'] = 'form-control'; + /** @var RuleGroupRepositoryInterface $groupRepos */ + $groupRepos = app(RuleGroupRepositoryInterface::class); + + // get all currencies: + $list = $groupRepos->get(); + $array = [ + 0 => trans('firefly.none_in_select_list'), + ]; + /** @var RuleGroup $group */ + foreach ($list as $group) { + if (isset($options['hidden']) && (int)$options['hidden'] !== $group->id) { + $array[$group->id] = $group->title; + } + } + + return Form::select($name, $array, $value, $options); } /** diff --git a/config/twigbridge.php b/config/twigbridge.php index 2e2b1d98c7..46aa7f0f50 100644 --- a/config/twigbridge.php +++ b/config/twigbridge.php @@ -188,7 +188,7 @@ return [ 'is_safe' => [ 'date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location', 'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password', 'nonSelectableBalance', 'nonSelectableAmount', - 'number', 'assetAccountList','amountNoCurrency','currencyList','ruleGroupList','assetAccountCheckList' + 'number', 'assetAccountList','amountNoCurrency','currencyList','ruleGroupList','assetAccountCheckList','ruleGroupListWithEmpty' ], ], 'Form' => [ diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 19233057ac..1f34455d49 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -179,6 +179,7 @@ return [ 'authorization_request_intro' => ':client is requesting permission to access your financial administration. Would you like to authorize :client to access these records?', 'scopes_will_be_able' => 'This application will be able to:', 'button_authorize' => 'Authorize', + 'none_in_select_list' => '(none)', // check for updates: 'update_check_title' => 'Check for updates', diff --git a/resources/views/rules/rule-group/delete.twig b/resources/views/rules/rule-group/delete.twig index 0d5474343d..4b5618bc9e 100644 --- a/resources/views/rules/rule-group/delete.twig +++ b/resources/views/rules/rule-group/delete.twig @@ -35,7 +35,7 @@

- {{ Form.select('move_rules_before_delete', ruleGroupList, null, {class: 'form-control'}) }} + {{ ExpandedForm.ruleGroupListWithEmpty('move_rules_before_delete',null, {'hidden': ruleGroup.id}) }}

{% else %} From 49138eb03a877526caffc8bdfa92c223cb786ba0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 10:13:33 +0200 Subject: [PATCH 011/182] Can now import and handle external ID field. --- app/Factory/TransactionJournalFactory.php | 2 +- app/Import/Object/ImportJournal.php | 9 +- app/Import/Storage/ImportStorage.php | 146 ++++++++++++---------- resources/lang/en_US/list.php | 1 + resources/views/transactions/show.twig | 2 +- 5 files changed, 90 insertions(+), 70 deletions(-) diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index a4422b7b71..4afdb35465 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -91,7 +91,7 @@ class TransactionJournalFactory // store date meta fields (if present): $fields = ['sepa-cc', 'sepa-ct-op', 'sepa-ct-id', 'sepa-db', 'sepa-country', 'sepa-ep', 'sepa-ci', 'interest_date', 'book_date', 'process_date', - 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'bunq_payment_id','importHash']; + 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'bunq_payment_id', 'importHash', 'external_id']; foreach ($fields as $field) { $this->storeMeta($journal, $data, $field); diff --git a/app/Import/Object/ImportJournal.php b/app/Import/Object/ImportJournal.php index 9fe1d82349..8c2311c09f 100644 --- a/app/Import/Object/ImportJournal.php +++ b/app/Import/Object/ImportJournal.php @@ -158,6 +158,14 @@ class ImportJournal return $this->description; } + /** + * @return string + */ + public function getExternalId(): string + { + return $this->externalId; + } + /** * @return string|null */ @@ -231,7 +239,6 @@ class ImportJournal return null; } - /** * @param string $hash */ diff --git a/app/Import/Storage/ImportStorage.php b/app/Import/Storage/ImportStorage.php index e53c707ea2..116431f3bc 100644 --- a/app/Import/Storage/ImportStorage.php +++ b/app/Import/Storage/ImportStorage.php @@ -199,78 +199,87 @@ class ImportStorage throw new FireflyException($message); } + + /** + * Search for journals with the same external ID. + * + */ + + unset($parameters); $this->addStep(); + $budget = $importJournal->budget->getBudget(); + $category = $importJournal->category->getCategory(); + $bill = $importJournal->bill->getBill(); + $source = $assetAccount; + $destination = $opposingAccount; - try { - $budget = $importJournal->budget->getBudget(); - $category = $importJournal->category->getCategory(); - $bill = $importJournal->bill->getBill(); - $source = $assetAccount; - $destination = $opposingAccount; + // switch account arounds when the transaction type is a deposit. + if ($transactionType === TransactionType::DEPOSIT) { + [$destination, $source] = [$source, $destination]; + } + // switch accounts around when the amount is negative and it's a transfer. + // credits to @NyKoF + if ($transactionType === TransactionType::TRANSFER && -1 === bccomp($amount, '0')) { + [$destination, $source] = [$source, $destination]; + } + Log::debug( + sprintf('Will make #%s (%s) the source and #%s (%s) the destination.', $source->id, $source->name, $destination->id, $destination->name) + ); - // switch account arounds when the transaction type is a deposit. - if ($transactionType === TransactionType::DEPOSIT) { - [$destination, $source] = [$source, $destination]; - } - // switch accounts around when the amount is negative and it's a transfer. - // credits to @NyKoF - if($transactionType === TransactionType::TRANSFER && -1 === bccomp($amount, '0')) { - [$destination, $source] = [$source, $destination]; - } - Log::debug( - sprintf('Will make #%s (%s) the source and #%s (%s) the destination.', $source->id, $source->name, $destination->id, $destination->name) - ); - $data = [ - 'user' => $this->job->user_id, - 'type' => $transactionType, - 'date' => $importJournal->getDate($this->dateFormat), - 'description' => $importJournal->getDescription(), - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'bill_id' => null === $bill ? null : $bill->id, - 'bill_name' => null, - 'tags' => $importJournal->tags, - 'interest_date' => $importJournal->getMetaDate('interest_date'), - 'book_date' => $importJournal->getMetaDate('book_date'), - 'process_date' => $importJournal->getMetaDate('process_date'), - 'due_date' => $importJournal->getMetaDate('due_date'), - 'payment_date' => $importJournal->getMetaDate('payment_date'), - 'invoice_date' => $importJournal->getMetaDate('invoice_date'), - 'internal_reference' => $importJournal->metaFields['internal_reference'] ?? null, - 'notes' => $importJournal->notes, - 'sepa-cc' => $importJournal->getMetaString('sepa-cc'), - 'sepa-ct-op' => $importJournal->getMetaString('sepa-ct-op'), - 'sepa-ct-id' => $importJournal->getMetaString('sepa-ct-id'), - 'sepa-db' => $importJournal->getMetaString('sepa-db'), - 'sepa-country' => $importJournal->getMetaString('sepa-country'), - 'sepa-ep' => $importJournal->getMetaString('sepa-ep'), - 'sepa-ci' => $importJournal->getMetaString('sepa-ci'), - 'importHash' => $importJournal->hash, - 'transactions' => [ - // single transaction: - [ - 'description' => null, - 'amount' => $amount, - 'currency_id' => $currencyId, - 'currency_code' => null, - 'foreign_amount' => $foreignAmount, - 'foreign_currency_id' => $foreignCurrencyId, - 'foreign_currency_code' => null, - 'budget_id' => null === $budget ? null : $budget->id, - 'budget_name' => null, - 'category_id' => null === $category ? null : $category->id, - 'category_name' => null, - 'source_id' => $source->id, - 'source_name' => null, - 'destination_id' => $destination->id, - 'destination_name' => null, - 'reconciled' => false, - 'identifier' => 0, - ], + $data = [ + 'user' => $this->job->user_id, + 'type' => $transactionType, + 'date' => $importJournal->getDate($this->dateFormat), + 'description' => $importJournal->getDescription(), + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'bill_id' => null === $bill ? null : $bill->id, + 'bill_name' => null, + 'tags' => $importJournal->tags, + 'interest_date' => $importJournal->getMetaDate('interest_date'), + 'book_date' => $importJournal->getMetaDate('book_date'), + 'process_date' => $importJournal->getMetaDate('process_date'), + 'due_date' => $importJournal->getMetaDate('due_date'), + 'payment_date' => $importJournal->getMetaDate('payment_date'), + 'invoice_date' => $importJournal->getMetaDate('invoice_date'), + 'internal_reference' => $importJournal->metaFields['internal_reference'] ?? null, + 'notes' => $importJournal->notes, + 'external_id' => $importJournal->getExternalId(), + 'sepa-cc' => $importJournal->getMetaString('sepa-cc'), + 'sepa-ct-op' => $importJournal->getMetaString('sepa-ct-op'), + 'sepa-ct-id' => $importJournal->getMetaString('sepa-ct-id'), + 'sepa-db' => $importJournal->getMetaString('sepa-db'), + 'sepa-country' => $importJournal->getMetaString('sepa-country'), + 'sepa-ep' => $importJournal->getMetaString('sepa-ep'), + 'sepa-ci' => $importJournal->getMetaString('sepa-ci'), + 'importHash' => $importJournal->hash, + 'transactions' => [ + // single transaction: + [ + 'description' => null, + 'amount' => $amount, + 'currency_id' => $currencyId, + 'currency_code' => null, + 'foreign_amount' => $foreignAmount, + 'foreign_currency_id' => $foreignCurrencyId, + 'foreign_currency_code' => null, + 'budget_id' => null === $budget ? null : $budget->id, + 'budget_name' => null, + 'category_id' => null === $category ? null : $category->id, + 'category_name' => null, + 'source_id' => $source->id, + 'source_name' => null, + 'destination_id' => $destination->id, + 'destination_name' => null, + 'reconciled' => false, + 'identifier' => 0, ], - ]; + ], + ]; + $factoryJournal = null; + try { $factoryJournal = $this->factory->create($data); $this->journals->push($factoryJournal); } catch (FireflyException $e) { @@ -278,11 +287,12 @@ class ImportStorage Log::error($e->getTraceAsString()); } + // double add step because "match bills" no longer happens. $this->addStep(); $this->addStep(); // run rules if config calls for it: - if (true === $this->applyRules) { + if (true === $this->applyRules && null !== $factoryJournal) { Log::info('Will apply rules to this journal.'); $this->applyRules($factoryJournal); } @@ -291,6 +301,8 @@ class ImportStorage if (!(true === $this->applyRules)) { Log::info('Will NOT apply rules to this journal.'); } + + // double add step because some other extra thing was removed here. $this->addStep(); $this->addStep(); diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index 05ffa9c72c..27036cd560 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -111,6 +111,7 @@ return [ 'sepa-cc' => 'SEPA Clearing Code', 'sepa-ep' => 'SEPA External Purpose', 'sepa-ci' => 'SEPA Creditor Identifier', + 'external_id' => 'External ID', 'account_at_bunq' => 'Account with bunq', 'file_name' => 'File name', 'file_size' => 'File size', diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index e1627928e6..e66b3ff28d 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -210,7 +210,7 @@ {% endif %} {% endfor %} {# all other meta values #} - {% for metaField in ['internal_reference','sepa-ct-id','sepa-ct-op','sepa-db','sepa-country','sepa-cc','sepa-ep','sepa-ci'] %} + {% for metaField in ['external_id','internal_reference','sepa-ct-id','sepa-ct-op','sepa-db','sepa-country','sepa-cc','sepa-ep','sepa-ci'] %} {% if journalHasMeta(journal, metaField) %} {{ trans('list.'~metaField) }} From ddcf8b892bfb1ff689e8037a54faba33f082dcf1 Mon Sep 17 00:00:00 2001 From: Nico Schreiner Date: Sun, 29 Apr 2018 13:28:49 +0200 Subject: [PATCH 012/182] Fix alignment of sidebar-icons I noticed a minor thing which was bothering me. The Icons in the sidebar weren't align correctly (the fa-repeat icon). This change should fix this. --- resources/views/partials/menu-sidebar.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/partials/menu-sidebar.twig b/resources/views/partials/menu-sidebar.twig index a489fc7a53..4772d605d5 100644 --- a/resources/views/partials/menu-sidebar.twig +++ b/resources/views/partials/menu-sidebar.twig @@ -55,7 +55,7 @@
  • - + {{ 'transactions'|_ }} From f140d2f37a7382a1b3f0b52955a7c15ca8787bc2 Mon Sep 17 00:00:00 2001 From: Paul Sohier Date: Sun, 29 Apr 2018 16:26:19 +0200 Subject: [PATCH 013/182] Select the matching bunq account from the import. Fixes #1398 --- .../Configuration/Bunq/HaveAccounts.php | 36 ++++++++++++++----- resources/views/import/bunq/accounts.twig | 2 +- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/app/Support/Import/Configuration/Bunq/HaveAccounts.php b/app/Support/Import/Configuration/Bunq/HaveAccounts.php index 3291c843e8..cf69af821f 100644 --- a/app/Support/Import/Configuration/Bunq/HaveAccounts.php +++ b/app/Support/Import/Configuration/Bunq/HaveAccounts.php @@ -29,7 +29,6 @@ use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\Import\Configuration\ConfigurationInterface; -use Illuminate\Support\Collection; /** * Class HaveAccounts @@ -74,10 +73,10 @@ class HaveAccounts implements ConfigurationInterface // find accounts with currency code $code = $bunqAccount['currency']; $selection = $this->filterAccounts($dbAccounts, $code); - $config['accounts'][$index]['options'] = app('expandedform')->makeSelectList($selection); + $config['accounts'][$index]['iban'] = $this->getIban($bunqAccount); + $config['accounts'][$index]['options'] = $selection; } - return [ 'config' => $config, ]; @@ -136,17 +135,38 @@ class HaveAccounts implements ConfigurationInterface * @param array $dbAccounts * @param string $code * - * @return Collection + * @return array */ - private function filterAccounts(array $dbAccounts, string $code): Collection + private function filterAccounts(array $dbAccounts, string $code): array { - $collection = new Collection; + $account = []; foreach ($dbAccounts as $accountId => $data) { if ($data['currency']->code === $code) { - $collection->push($data['account']); + $account[$accountId] = [ + 'name' => $data['account']['name'], + 'iban' => $data['account']['iban'], + ]; } } - return $collection; + return $account; + } + + /** + * @param array $bunqAccount + * + * @return string + */ + private function getIban(array $bunqAccount) + { + $iban = ''; + if (count($bunqAccount['alias'])) { + foreach ($bunqAccount['alias'] as $alias) { + if ($alias['type'] === 'IBAN') { + $iban = $alias['value']; + } + } + } + return $iban; } } diff --git a/resources/views/import/bunq/accounts.twig b/resources/views/import/bunq/accounts.twig index 734de2893b..9556218302 100644 --- a/resources/views/import/bunq/accounts.twig +++ b/resources/views/import/bunq/accounts.twig @@ -47,7 +47,7 @@ From 24d8640e9b4e1b7db24a70deb3ed3a877db54392 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 18:03:35 +0200 Subject: [PATCH 014/182] Add logos for new providers. --- public/images/logos/fake.png | Bin 0 -> 2689 bytes public/images/logos/quovo.png | Bin 0 -> 3363 bytes public/images/logos/yodlee.png | Bin 0 -> 2565 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/logos/fake.png create mode 100644 public/images/logos/quovo.png create mode 100644 public/images/logos/yodlee.png diff --git a/public/images/logos/fake.png b/public/images/logos/fake.png new file mode 100644 index 0000000000000000000000000000000000000000..cc22b72065192d16d654cc16a58bc7519500cd76 GIT binary patch literal 2689 zcmZ{m2|UzW8^;Hctz5DUp=qoYvskaeATvmfEs07DW|)|znJFXH$WlhUb+e`{Q8Jdw zS`xiNQcPo~+^L2vHH53WE$YrlH?Oz;pU?T9bDrn-ea?BF|2dz}pXK4^yhc_-761UO z+2(@s681KtCM_;}*W|kC2|F>Gm-AM@HIC+_a3Vu-@uL9%a_dA*43K>h2mnYF5xjlr zzL*_|U@{Sc4Iu~NAPgc!s1`-g)EWF z{mW2M76^AL0Vgc4sEA0o4fOwPEA@_KB87^h(L{RlCH->SO1&43Mvgcn3crg)pd(F| zkQL^Yl;00O((*gA+_)0({TZ;_xC{_|2E<~Wd{G1Fo8EPTI%NLlLh3^o8DMc;;U z@D7ul@(YhpK`Vdx7>^9w6e#%wtZ)L*m&TV{RmQ3{{0+@j`mb}zwiF99r{uw`g1Hh_ zj9!qWiWpitu&Kn;FgvUTm??x^;{nBKe;~Q(HenGPvxEj)c2PXm;}UNEvOQ+_d>F)Jjy<1>O~_ka5bH_ zj+56sY2v-Rphjh&4f{7xJ{g_Y*k~UVyw;rCJDnhT(d=G75&Y!cpbwtv^Au-trr5oEjk2AX@ zK5@Tv&@*Nr6R2}|hwHE;+671e#rmGnYErX4`1fAQY*Q1^LiRn!;nlvO4cD5ESBPW! zauF

    82r5)zr|&_%fXI)JS(q=<|`A6`S1Y!)$r2lUKt3FsYJrzSn&6fcTR=o>XAX zu03&Wu3gRrF30MQsMqsaKjNekhssNophjC~_gB|WtY-I?+xKX)3F%(D<8B`ab?)Y~ zlp^*K@!kU(6(;Ln-nDjS_m$bxGBQ+6o3eq^GUWl*?9n!I(&)^2H4>+h7(8&g$qAp(_4)eokRrst*V4DY+&(o+iR|zU_K4Newa>C; zsJu>{Iba=tIbgPVLtIgzHe9tzEn7O>T~IsYH^1SzKvgNW_zP!uUVoVx2CKMmcGgDU z4W4RELwaqX&Z-@|N9ifYJ64k`92K?M4Qy{^`<}ANwLT4JKJ~0QDuy{v`rK;CW#*r@ zsqKlQ7Z5YGwV;uYE9(t>I9G!4ECIydzFkpsc#C^~VGwG;bPW3$6^{dGIy_O_3b=K8+CsVcU z3_nB$lBvFgV~^w8spa7!zspQNeYxHL#@%UOc4^V$zMxJoWzxZ1AV%NT zs-3|Zxi2U>^w)Ou_&&xyAVnRluiQPH6d@QkRLN5|VL9nr81xcumx!O)ahS#Wa~y0r zUe-J;AD>Xq^ggiBL#m)73}^t)9rL`zWF=Rk`?M@N>s*;C0fGCoa9lqRDgWE7y|ZRA zFMKjadK4;{3MF6DE+%YzcuTkAaZ9zjlJt!9CS|QK9==$hCV4kQPucp|$u)0hc3AfE zOKs2fyi%ZJrN4XxLc`c+&{VQ8#N4#tsr;=Wocl?w zki?pbecjXVJ#KixZ}bn%cRBi|1?hBNt_j{x?n!jkXUIN?=Zo?P_8Lbc7!M;A+qjpMX4{>-UHR+&&whLq1zyT=~pi!lMGw zc4ueL4boNInNKrkMlS9s#-rgFzoD`Vcd{@Q~QG zI!-%&(!Of7Y3WrO<*C0mB9d7(qU(D;bxT+QcyhE6cXZ3ZLKg03VX)EAlQm{_qW%h$Vxhi?%FTJ!G$`V1wFwf4ohyqpUkPm%}gNr++b=U_9|Km>--E4W|isiW+Ws zuFI{sk=dD@`B-jt-*E-s6GfW5Q_S~QRz-%ot#_HCuDk9^a#xIY$&Gc0)3f7?_a=#l zxwQZ*`3e!wKz^~EkM40`E(c@^VbNmUW3I1;Ebqw4)Mg@ikzLSfGZ)^AUrj@q4&C{~TCgo8YclJKq*bMkY>l2KxN z9N)Z83eA0It$gT2Lw?_(m;R6UW1_Fxz3pnq_8CZzw2d~O%K6gk{N4rgRW3H&I^(#% Se|-8*na>4&1>iY literal 0 HcmV?d00001 diff --git a/public/images/logos/quovo.png b/public/images/logos/quovo.png new file mode 100644 index 0000000000000000000000000000000000000000..cf17bef45648234c1a603203585e2355edc48121 GIT binary patch literal 3363 zcmZ{n2|SeB8^=d9*0E&CQr5_F4KwyJM%F?0r7RU=42^~vGlM~dEZLJK=8EKR%ia~q zE{(cMamyaLDIr^SSNw;%_xk(a`+h#>eb0HG-}gD^dCq&@&*zP^wnT#1gxLT90LawD z(3bv$?===6{SMn6BGMlWBwM6Dpt9%0G=0EIFmWOQ0PMVbjR8Q-5d;7ji?H^NWJe2g z7#i;bMtR`fF<^=hfv%=UqQK}~9}F2KPVvF{l3)}a$$bTk-rqATNs8}F$X9eE9WAWI z4e&&axEfd$tSpIO6BieU6Fod(wuZ((aQa9`@-mrBfGH^j1_puyRls+ zkRghUK|qxunu^M*icl?kh#E{;6$Vj*D;?7Oo#8%hK*XTPc%nTXk3(2vDHxp3-cVcx ztnr;Z=s$q&WdXAyVlnjc?iCRMfh+x=?Xcbe??WJBNTfYIePR3hgSNwZTMP-0^WO{a z;EN?Apg)j9=EIcJ-+qL~pUgqyVZia9fP=;Zz}_~%_Uq(mi1oqv?&qSe3|IO`$6?R_ zkHZt~2q-jWuU`Ae0n=gi{xnYiHq8OY4-l@j_a+~Bv%epkeJlOkAlT?T`sW!$u+3O{ zpQWE-0aHUg`^!h?oY<|?2l#i^n&C`*HcUoLVr4*(_=Gf<>)m@9_UPfxVedDVGBVk5 zYx%w2cK%n@5iIAB`Mpeq!Vl9*YVO5sl$f;%rt68r`I!2v6~1}~YU%8zly?K-Rp!#2 z)_WFr*L4FIHWvb0NUyYZjr0w(0Y^3#j4nGJ1DY!x=~GYatLsT1)-%)v6!~zB2-ZQ_ z2QLi&$}$J&q7j(W9JAYK)fqg)49Q6ooH8}gcJ}9lf{fG+Rm~)&QHJns>?dv-c`NqW za|L}w{-`d#{zP#@P7VwPla=8uU@_iP2YmlG* z#KXN>u>$GZ;UJ%(FN1Dq2ZU(zj|Gzi&i&+igxRz8m;SUYJoBT{QdY6@!9=T+oG9mt z$Owi8O|ADCZ4eg0f`}`4uQ&bb;@VT3l^z}i`Yum_Huq}sc~sCHH6dNjk5*>h3Vq6z z;KU7;{rt`$JS*B`iB1?Osal`_xs94*qkhZ0^E?Ha8+KMFfC{`UV?CaqIyuR2EQhp2 zgm&x(*>gC{q_MI#>N2N20bcItaNXhP9{*-hKT4P{v$NT><3Cqr6>eVH+0!hSrKKp$ z$+@0XCIPVo0qBJJFy=pgcPs7( zM@2Ahp0V$?1Hmar z3=%4lKVahg(xtG%BG@0&Zet+k^G+(>+k5tMZ=c%ai!lk32-3D5V7tP8N@JX5Yq`kK z9sT-2h6pP-Y;smNNnXD8cepxW6KILvbTl)EnRf_hHl6;=DlI*Gvte^t=jPT{b#7AL z&0ddVQ*nhDW6!!QG>GPDp3D(56~5!C5KvrjPL4+x&6ap#BFUH+e35@AgmJKx-+FjN zuhN?Ow!}eRR+Fm054EZD9|*e|wxVjE1N!tT?kSDyw)@tkwV-)aMdK~MmCgZ5$dZWQ zWN)61X;&`6DCc$0!wuI?B$0ebqiBe%YwkgGW;M?wF zSNu%OF2s&Ew*q3*>Ls8n7(tg)gMo{hSDTqp?jGEUK;Gm#=GUu_S{4-=Jd;=XAfpdX zd(dkcH%$~3QPlF&3ELfW*+BNG8!pwMPPdQSwM;6GaPSa9FE%?$utr}`ODlNl-Z(z@ z@pD^9mJ|BwD#&8)bU>t2PmEmE8~yb~o++|iQI|1wszmFpMK(9MMBgb}z<+f$Yds-5 zM?T}(w#0Gl@yhqXpL|xPKSa4Ui^evt|C;}sg8>azOf$?{EpfE^(z#TQjC?>;6#d$0 z*wzEK)UOp#JnvAbVMWPJzT~R-$)~zl6E+xU=h9imWE;03ykxrdXoYV^Q2U{?N7ozo z$js}vFu!?UJ7%x<+~v$7JpcakHPh=+hT3WquH<`RR0-vp+4)fti;z>jrrG%qlS6pFjg0|GRtdeZ6H6y4DP_vL zt3nr+vTwRN{t9!hUVKSe(z$dUuI#L0uWH@VV(^FtF)beWuricjqSX;{dw>tx!)4h# z7!v*3!Je4XME``QCrjd69Ut3SpSqD(7WzOV`a)AjX?>tzC~5PtriS0Rgl%I=!OoM@ zuX8rLFWYXr6NTWpTcrG3sTqnpGT&^0LCHCWYohIw5O-)2NAAHRWadXMZx&EtgCviT8@-3$wt@8!tw>uXV0~hebZ$l zVZ-x1a!FsmCde++eAXU42ta3Y>5sJpe+%)fAF{>GCj<`~v^J(%L`xTX2Du=~({-^r zrll7|W2kyvFGGLsYuVIb#8)*n44v7EO&KoAUJ%moP36MJUA*?ki=X}&_sT>ca+;A?(GtE-8yD&rpaHd{;F=n)m9880tyOHWvrNo5iXkcC-4M?%Z}w z+-&{E-4HG9?l#nd(h_*_^Sc>iozKGz)zx(cyW7Wi9{6*yC3JVn$0^`y3C%A}G+ob1 zOf+HNGcp7YjA_x_CMu-4srp(X#E(%X_s!bW^v*LFB=3M=9TLbDUTw>yS^E2gx z?RT@o8ylS`3L>OrgGq1u(?+g=2v$IBgL?k6hbKE-oI8iNtPtpOkkfGrF1%4W@$C&C z4i`|yML-Q?G#XWIj6)B4mwY*LBWVK6fmU-Dd2-GGbNmdBBM>E6#>39xUo3g@Ir9k-1~DeHL^6U)OU;gFJkGhBme*a literal 0 HcmV?d00001 diff --git a/public/images/logos/yodlee.png b/public/images/logos/yodlee.png new file mode 100644 index 0000000000000000000000000000000000000000..fbd5803b40e178cc78eb7b65e43867da801f2159 GIT binary patch literal 2565 zcma)82UJsO77i>m5ScNq3lbnkWI-S=gdRf#5=Eql5E;tK5>iN%kdPu(U_nIcT966o zXh0kR0f(|kvCt7=r8pF&tPV&oGvJCch?@k|EC=`OJLi@E-tYUr``!2MIWNi0)d2$D z1qOjY5XZv=chMayK9v+iSIp{jj_4-Cb$74_l{|$_i5AN2!`@sFNChfBWkA{2)IlIQ z1cT_s^Kx;;`3|}2(i%TW4%ru zgZ4!!n7d(1WDpQ%023q%*osCCV|;}s&U}?dA%Awo=5vB1o+xC18boDMgLzyL&tywE zQExvJ|EtI_5_?@t(!_OdSaIbTcPf_^#22^Vcrb&9Lt`a|Hiy4-WSxa)G1(j{mn)*- z%r_{~vCVj|FETjGPa4v}&4i;IhR7*NTo=w{LqIyV8SlN7fOJrbAZ|UzjS)r-@**%q z4VAdCKw*I`6Pt;6RuGHh!6uQZ;=&{psnBM$M8^ATGExaCB880M@&is%$vmmIC38`V$`0?9`? z66}a{`8jW<8}Yb$^nCQaTBE8cBRc$iEShexi$=3bdegWg8^=3Tj@E0XUF&FJ@bp@< z^J#@4WjaqLDdy09|D;>f9$N%dpHWs&u0NtHaEcbnf{kY9A3T_tFB9I@`29>(;6P;O znT5!a}35;)F@e0%dEvbv=vS$+A5RA{`yWKa$85+9sl$g)vhdd#k^WoXC*!kme#CSQl;+v zs6Rpend!?4-`-Gu%vu_rZ!E~1ZJKVdzW2Ud@gp0w|B-dV^)k%1&ME$OPaCx2wmb6Y?pIKP?yT3@6?-{9OIBde_r7Opy3Mx4{ngduFZ>1Uf_spaLwI`cHY zjF{Isi?Uccf)wql&~w67{TFgj{)_tx&4Y&QhM7jgNHy}z<&@IJ_|9${^|}>@rP9|~ z2PaR3YtZnZtA$G;LIV{$gKOUIy3R-Mr$6cOX?omi`x3Hf>V6!Y97qdLX%2}0kULu2 zmtv`=ow&?V>zgZVSlDmTNYUMHFC$1(=v{c4qU-pRU~)KC=lETl6phOsxyYh8^=W5| zRJrS~|B`(pez_l8@?s!?zdOP9oT0W}y!OdVLZa61mc@s<-wqEf4KrXjcPUeMihj3JY_J1Ct54n*5HLy$OOHI+~TomRD!z>QrN>ye``B;1-4XJGMnp)2-#DtRbZH%Y7#jy zspTJeOqOY=p7h&AuIU>~3}~((HYk zP$cXPmA}#3SI}91!MV_{*>4#byV*FDYL=(hv^#&^N&biL?v6f#-oE+X)_>9cgulK1 zx2JCiEH7_+kT+OWnf?52l4|mP^Sv^FaX33w&DZ$VO0iv2t5>73%`ctvGW%2`_Q={{ z;4P|7?#e4HSbgomN2K;fm$QLHT&;o91H#OuBQo*wjnkyP_0PuRZ-3|#=$k>dkv${8 z`}z(PmuV~YU}XD;_>j&y40hVdyvSFOsCCL|GVe#GePl&ZTtES1Pj$g9?qwK#jc(f8 zUU^9_bSb>*-kj&eD}UYOr?Gg=?}T}9wV{PZ^R$Vs=ff$9oSvWWUg!p&PrWMl##3u7 z$BDNB-Tkv(k%#W6++N6sXcNt>jV=EZ(qj-RFp1Z4cJ8hA@F3k{$y)dTet&Bn zY|&23t~{!FXVr0KHFN0u;_-3N(y`{U zp%3SGr~qblamr8XyLXK1f7>B6FcJu1Fc(t#_IlOqhW|TpEu`RAjdX<&NZ^`$CG;Oe U`N;Mf@xPhlL03YFy?@+)0H(AAC;$Ke literal 0 HcmV?d00001 From 554c63b9c7dd7879d4b8e5363bbbd201ddb33677 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 18:03:55 +0200 Subject: [PATCH 015/182] Expand config to handle new providers --- config/firefly.php | 1 + config/import.php | 31 +++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/config/firefly.php b/config/firefly.php index d86f7cc277..d1a73174cf 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -283,6 +283,7 @@ return [ 'user' => \FireflyIII\User::class, // strings + 'import_source' => \FireflyIII\Support\Binder\ImportSource::class, // dates 'start_date' => \FireflyIII\Support\Binder\Date::class, diff --git a/config/import.php b/config/import.php index 22961bab99..eb79bb716d 100644 --- a/config/import.php +++ b/config/import.php @@ -35,17 +35,40 @@ use FireflyIII\Import\Routine\SpectreRoutine; return [ 'enabled' => [ + 'fake' => false, 'file' => true, 'bunq' => true, 'spectre' => true, 'plaid' => false, + 'quovo' => false, + 'yodlee' => false, + ], + 'has_prereq' => [ + 'fake' => true, + 'file' => false, + 'bunq' => true, + 'spectre' => true, + 'plaid' => true, + 'quovo' => true, + 'yodlee' => true, ], 'prerequisites' => [ + 'fake' => false, 'file' => FilePrerequisites::class, 'bunq' => BunqPrerequisites::class, 'spectre' => SpectrePrerequisites::class, 'plaid' => 'FireflyIII\Import\Prerequisites\PlaidPrerequisites', - + 'quovo' => false, + 'yodlee' => false, + ], + 'has_config' => [ + 'fake' => true, + 'file' => true, + 'bunq' => true, + 'spectre' => true, + 'plaid' => true, + 'quovo' => true, + 'yodlee' => true, ], 'configuration' => [ 'file' => FileConfigurator::class, @@ -61,15 +84,15 @@ return [ ], 'options' => [ - 'file' => [ + 'file' => [ 'import_formats' => ['csv'], // mt940 'default_import_format' => 'csv', 'processors' => [ 'csv' => CsvProcessor::class, ], ], - 'bunq' => [ - 'server' => 'api.bunq.com', // sandbox.public.api.bunq.com - api.bunq.com + 'bunq' => [ + 'server' => 'sandbox.public.api.bunq.com', // sandbox.public.api.bunq.com - api.bunq.com 'version' => 'v1', ], 'spectre' => [ From 9646dc439e5f324d44edbbcdde492c4e55bf3118 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 18:06:31 +0200 Subject: [PATCH 016/182] Big fat reset in import controller to accomodate new routine. --- .../Controllers/Import/IndexController.php | 253 ++++++++++-------- .../Import/PrerequisitesController.php | 15 +- 2 files changed, 142 insertions(+), 126 deletions(-) diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index c340ef8f1e..6e512e78e4 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -25,12 +25,11 @@ namespace FireflyIII\Http\Controllers\Import; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Middleware\IsDemoUser; +use FireflyIII\Import\Prerequisites\PrerequisitesInterface; use FireflyIII\Import\Routine\RoutineInterface; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use Illuminate\Http\Request; -use Illuminate\Http\Response as LaravelResponse; -use Log; use Preferences; use View; @@ -63,62 +62,58 @@ class IndexController extends Controller } /** - * Creates a new import job for $bank with the default (global) job configuration. + * Creates a new import job for $importProvider with the default (global) job configuration. * - * @param string $bank + * @param string $importProvider * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * * @throws FireflyException */ - public function create(string $bank) + public function create(string $importProvider) { - if (true === !config(sprintf('import.enabled.%s', $bank))) { - throw new FireflyException(sprintf('Cannot import from "%s" at this time.', $bank)); // @codeCoverageIgnore - } + $importJob = $this->repository->create($importProvider); - $importJob = $this->repository->create($bank); - - // from here, always go to configure step. - return redirect(route('import.configure', [$importJob->key])); + // redirect to global prerequisites + return redirect(route('import.prerequisites.index', [$importProvider, $importJob->key])); } - /** - * Generate a JSON file of the job's configuration and send it to the user. - * - * @param ImportJob $job - * - * @return LaravelResponse - */ - public function download(ImportJob $job) - { - Log::debug('Now in download()', ['job' => $job->key]); - $config = $job->configuration; - - // This is CSV import specific: - $config['column-roles-complete'] = false; - $config['column-mapping-complete'] = false; - $config['initial-config-complete'] = false; - $config['has-file-upload'] = false; - $config['delimiter'] = "\t" === $config['delimiter'] ? 'tab' : $config['delimiter']; - unset($config['stage']); - - $result = json_encode($config, JSON_PRETTY_PRINT); - $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); - - /** @var LaravelResponse $response */ - $response = response($result, 200); - $response->header('Content-disposition', 'attachment; filename=' . $name) - ->header('Content-Type', 'application/json') - ->header('Content-Description', 'File Transfer') - ->header('Connection', 'Keep-Alive') - ->header('Expires', '0') - ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') - ->header('Pragma', 'public') - ->header('Content-Length', \strlen($result)); - - return $response; - } + // /** + // * Generate a JSON file of the job's configuration and send it to the user. + // * + // * @param ImportJob $job + // * + // * @return LaravelResponse + // */ + // public function download(ImportJob $job) + // { + // Log::debug('Now in download()', ['job' => $job->key]); + // $config = $job->configuration; + // + // // This is CSV import specific: + // $config['column-roles-complete'] = false; + // $config['column-mapping-complete'] = false; + // $config['initial-config-complete'] = false; + // $config['has-file-upload'] = false; + // $config['delimiter'] = "\t" === $config['delimiter'] ? 'tab' : $config['delimiter']; + // unset($config['stage']); + // + // $result = json_encode($config, JSON_PRETTY_PRINT); + // $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); + // + // /** @var LaravelResponse $response */ + // $response = response($result, 200); + // $response->header('Content-disposition', 'attachment; filename=' . $name) + // ->header('Content-Type', 'application/json') + // ->header('Content-Description', 'File Transfer') + // ->header('Connection', 'Keep-Alive') + // ->header('Expires', '0') + // ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + // ->header('Pragma', 'public') + // ->header('Content-Length', \strlen($result)); + // + // return $response; + // } /** * General import index. @@ -127,78 +122,102 @@ class IndexController extends Controller */ public function index() { + // get all import routines: + /** @var array $config */ + $config = config('import.enabled'); + $providers = []; + foreach ($config as $name => $enabled) { + if ($enabled || (bool)config('app.debug')) { + $providers[$name] = []; + } + } + + // has prereq or config? + foreach (array_keys($providers) as $name) { + $providers[$name]['has_prereq'] = (bool)config('import.has_prereq.' . $name); + $providers[$name]['has_config'] = (bool)config('import.has_config.' . $name); + $class = (string)config('import.prerequisites.' . $name); + $result = false; + if ($class !== '' && class_exists($class)) { + /** @var PrerequisitesInterface $object */ + $object = app($class); + $object->setuser(auth()->user()); + $result = $object->isComplete(); + } + $providers[$name]['prereq_complete'] = $result; + } + $subTitle = trans('firefly.import_index_sub_title'); $subTitleIcon = 'fa-home'; - $routines = config('import.enabled'); - return view('import.index', compact('subTitle', 'subTitleIcon', 'routines')); + return view('import.index', compact('subTitle', 'subTitleIcon', 'providers')); } +// +// /** +// * @param Request $request +// * @param string $bank +// * +// * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector +// */ +// public function reset(Request $request, string $bank) +// { +// if ($bank === 'bunq') { +// // remove bunq related preferences. +// Preferences::delete('bunq_api_key'); +// Preferences::delete('bunq_server_public_key'); +// Preferences::delete('bunq_private_key'); +// Preferences::delete('bunq_public_key'); +// Preferences::delete('bunq_installation_token'); +// Preferences::delete('bunq_installation_id'); +// Preferences::delete('bunq_device_server_id'); +// Preferences::delete('external_ip'); +// +// } +// +// if ($bank === 'spectre') { +// // remove spectre related preferences: +// Preferences::delete('spectre_client_id'); +// Preferences::delete('spectre_app_secret'); +// Preferences::delete('spectre_service_secret'); +// Preferences::delete('spectre_app_id'); +// Preferences::delete('spectre_secret'); +// Preferences::delete('spectre_private_key'); +// Preferences::delete('spectre_public_key'); +// Preferences::delete('spectre_customer'); +// } +// +// Preferences::mark(); +// $request->session()->flash('info', (string)trans('firefly.settings_reset_for_' . $bank)); +// +// return redirect(route('import.index')); +// +// } - /** - * @param Request $request - * @param string $bank - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function reset(Request $request, string $bank) - { - if ($bank === 'bunq') { - // remove bunq related preferences. - Preferences::delete('bunq_api_key'); - Preferences::delete('bunq_server_public_key'); - Preferences::delete('bunq_private_key'); - Preferences::delete('bunq_public_key'); - Preferences::delete('bunq_installation_token'); - Preferences::delete('bunq_installation_id'); - Preferences::delete('bunq_device_server_id'); - Preferences::delete('external_ip'); - - } - - if ($bank === 'spectre') { - // remove spectre related preferences: - Preferences::delete('spectre_client_id'); - Preferences::delete('spectre_app_secret'); - Preferences::delete('spectre_service_secret'); - Preferences::delete('spectre_app_id'); - Preferences::delete('spectre_secret'); - Preferences::delete('spectre_private_key'); - Preferences::delete('spectre_public_key'); - Preferences::delete('spectre_customer'); - } - - Preferences::mark(); - $request->session()->flash('info', (string)trans('firefly.settings_reset_for_' . $bank)); - - return redirect(route('import.index')); - - } - - /** - * @param ImportJob $job - * - * @return \Illuminate\Http\JsonResponse - * - * @throws FireflyException - */ - public function start(ImportJob $job) - { - $type = $job->file_type; - $key = sprintf('import.routine.%s', $type); - $className = config($key); - if (null === $className || !class_exists($className)) { - throw new FireflyException(sprintf('Cannot find import routine class for job of type "%s".', $type)); // @codeCoverageIgnore - } - - /** @var RoutineInterface $routine */ - $routine = app($className); - $routine->setJob($job); - $result = $routine->run(); - - if ($result) { - return response()->json(['run' => 'ok']); - } - - throw new FireflyException('Job did not complete successfully. Please review the log files.'); - } +// /** +// * @param ImportJob $job +// * +// * @return \Illuminate\Http\JsonResponse +// * +// * @throws FireflyException +// */ +// public function start(ImportJob $job) +// { +// $type = $job->file_type; +// $key = sprintf('import.routine.%s', $type); +// $className = config($key); +// if (null === $className || !class_exists($className)) { +// throw new FireflyException(sprintf('Cannot find import routine class for job of type "%s".', $type)); // @codeCoverageIgnore +// } +// +// /** @var RoutineInterface $routine */ +// $routine = app($className); +// $routine->setJob($job); +// $result = $routine->run(); +// +// if ($result) { +// return response()->json(['run' => 'ok']); +// } +// +// throw new FireflyException('Job did not complete successfully. Please review the log files.'); +// } } diff --git a/app/Http/Controllers/Import/PrerequisitesController.php b/app/Http/Controllers/Import/PrerequisitesController.php index 76b4973321..e3633cfaab 100644 --- a/app/Http/Controllers/Import/PrerequisitesController.php +++ b/app/Http/Controllers/Import/PrerequisitesController.php @@ -26,6 +26,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Import\Prerequisites\PrerequisitesInterface; +use FireflyIII\Models\ImportJob; use Illuminate\Http\Request; use Log; @@ -58,20 +59,16 @@ class PrerequisitesController extends Controller * redirect the user to a view where this object can be used by a bank specific * class to process. * - * @param string $bank - * - * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse + * @param string $importProvider + * @param ImportJob $importJob * * @throws FireflyException */ - public function index(string $bank) + public function index(string $importProvider, ImportJob $importJob) { - if (true === !config(sprintf('import.enabled.%s', $bank))) { - throw new FireflyException(sprintf('Cannot import from "%s" at this time.', $bank)); // @codeCoverageIgnore - } - $class = (string)config(sprintf('import.prerequisites.%s', $bank)); + $class = (string)config(sprintf('import.prerequisites.%s', $importProvider)); if (!class_exists($class)) { - throw new FireflyException(sprintf('No class to handle "%s".', $bank)); // @codeCoverageIgnore + throw new FireflyException(sprintf('No class to handle configuration for "%s".', $importProvider)); // @codeCoverageIgnore } /** @var PrerequisitesInterface $object */ From 7390e2021873ea3c889e2118af69667abf792c30 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 18:06:47 +0200 Subject: [PATCH 017/182] Add method to indicate completeness. --- .../Prerequisites/BunqPrerequisites.php | 16 +++++++++++++ .../Prerequisites/FilePrerequisites.php | 11 +++++++++ .../Prerequisites/PrerequisitesInterface.php | 7 ++++++ .../Prerequisites/SpectrePrerequisites.php | 23 +++++++++++++++++++ 4 files changed, 57 insertions(+) diff --git a/app/Import/Prerequisites/BunqPrerequisites.php b/app/Import/Prerequisites/BunqPrerequisites.php index 286268b919..feaa23d477 100644 --- a/app/Import/Prerequisites/BunqPrerequisites.php +++ b/app/Import/Prerequisites/BunqPrerequisites.php @@ -91,6 +91,22 @@ class BunqPrerequisites implements PrerequisitesInterface return !$hasApiKey || !$hasServerIP; } + /** + * Indicate if all prerequisites have been met. + * + * @return bool + */ + public function isComplete(): bool + { + // is complete when user has entered both the API key + // and his IP address. + + $hasApiKey = $this->hasApiKey(); + $hasServerIP = $this->hasServerIP(); + + return $hasApiKey && $hasServerIP; + } + /** * Set the user for this Prerequisites-routine. Class is expected to implement and save this. * diff --git a/app/Import/Prerequisites/FilePrerequisites.php b/app/Import/Prerequisites/FilePrerequisites.php index e2177af913..c574cfa05b 100644 --- a/app/Import/Prerequisites/FilePrerequisites.php +++ b/app/Import/Prerequisites/FilePrerequisites.php @@ -75,6 +75,17 @@ class FilePrerequisites implements PrerequisitesInterface return false; } + /** + * Indicate if all prerequisites have been met. + * + * @return bool + */ + public function isComplete(): bool + { + // has no prerequisites, so always return true. + return true; + } + /** * Set the user for this Prerequisites-routine. Class is expected to implement and save this. * diff --git a/app/Import/Prerequisites/PrerequisitesInterface.php b/app/Import/Prerequisites/PrerequisitesInterface.php index 699ea43b31..84519a647a 100644 --- a/app/Import/Prerequisites/PrerequisitesInterface.php +++ b/app/Import/Prerequisites/PrerequisitesInterface.php @@ -53,6 +53,13 @@ interface PrerequisitesInterface */ public function hasPrerequisites(): bool; + /** + * Indicate if all prerequisites have been met. + * + * @return bool + */ + public function isComplete(): bool; + /** * Set the user for this Prerequisites-routine. Class is expected to implement and save this. * diff --git a/app/Import/Prerequisites/SpectrePrerequisites.php b/app/Import/Prerequisites/SpectrePrerequisites.php index a780c3ddd1..7da7d93942 100644 --- a/app/Import/Prerequisites/SpectrePrerequisites.php +++ b/app/Import/Prerequisites/SpectrePrerequisites.php @@ -87,6 +87,29 @@ class SpectrePrerequisites implements PrerequisitesInterface return false; } + /** + * Indicate if all prerequisites have been met. + * + * @return bool + */ + public function isComplete(): bool + { + // return true when user has set the App Id and the Spectre Secret. + $values = [ + Preferences::getForUser($this->user, 'spectre_app_id', false), + Preferences::getForUser($this->user, 'spectre_secret', false), + ]; + $result = true; + /** @var Preference $value */ + foreach ($values as $value) { + if (false === $value->data || null === $value->data) { + $result = false; + } + } + + return $result; + } + /** * Set the user for this Prerequisites-routine. Class is expected to implement and save this. * From f74a6dffca1d68e7fb907101c98a6b8595cefa43 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 18:07:14 +0200 Subject: [PATCH 018/182] Update providers and repositories for new import job fields --- app/Models/ImportJob.php | 99 +++---------------- .../ImportJob/ImportJobRepository.php | 34 +++---- .../ImportJobRepositoryInterface.php | 4 +- app/Support/Binder/ImportProvider.php | 55 +++++++++++ 4 files changed, 85 insertions(+), 107 deletions(-) create mode 100644 app/Support/Binder/ImportProvider.php diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php index a984176a9b..e3744e4368 100644 --- a/app/Models/ImportJob.php +++ b/app/Models/ImportJob.php @@ -43,11 +43,6 @@ class ImportJob extends Model public $validStatus = [ 'new', - 'configuring', - 'configured', - 'running', - 'error', - 'finished', ]; /** * The attributes that should be casted to native types. @@ -56,9 +51,14 @@ class ImportJob extends Model */ protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'configuration' => 'array', + 'extended_status' => 'array', + 'transactions' => 'array', ]; + /** @var array */ + protected $fillable = ['key', 'user_id', 'file_type', 'source', 'status', 'stage', 'configuration', 'extended_status', 'transactions']; /** * @param $value @@ -86,9 +86,11 @@ class ImportJob extends Model } /** + * @deprecated + * * @param int $count */ - public function addTotalSteps(int $count) + public function addTotalSteps(int $count): void { $status = $this->extended_status; $status['steps'] += $count; @@ -97,88 +99,9 @@ class ImportJob extends Model Log::debug(sprintf('Add %d to total steps for job "%s" making total steps %d', $count, $this->key, $status['steps'])); } - /** - * @param string $status - * - * @throws FireflyException - */ - public function change(string $status): void - { - if (\in_array($status, $this->validStatus)) { - Log::debug(sprintf('Job status set (in model) to "%s"', $status)); - $this->status = $status; - $this->save(); - - return; - } - throw new FireflyException(sprintf('Status "%s" is invalid for job "%s".', $status, $this->key)); - - } - - /** - * @param $value - * - * @return mixed - */ - public function getConfigurationAttribute($value) - { - if (null === $value) { - return []; - } - if (0 === \strlen($value)) { - return []; - } - - return json_decode($value, true); - } - - /** - * @param $value - * - * @return mixed - */ - public function getExtendedStatusAttribute($value) - { - if (0 === \strlen((string)$value)) { - return []; - } - - return json_decode($value, true); - } - - /** - * @codeCoverageIgnore - * - * @param $value - */ - public function setConfigurationAttribute($value) - { - $this->attributes['configuration'] = json_encode($value); - } - - /** - * @codeCoverageIgnore - * - * @param $value - */ - public function setExtendedStatusAttribute($value) - { - $this->attributes['extended_status'] = json_encode($value); - } - - /** - * @param $value - */ - public function setStatusAttribute(string $value) - { - if (\in_array($value, $this->validStatus)) { - $this->attributes['status'] = $value; - } - } - /** * @return string - * + * @deprecated * @throws \Illuminate\Contracts\Encryption\DecryptException * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException */ diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index 353c33e9bb..f62f16e21d 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -108,34 +108,34 @@ class ImportJobRepository implements ImportJobRepositoryInterface } /** - * @param string $type + * @param string $importProvider * * @return ImportJob * * @throws FireflyException */ - public function create(string $type): ImportJob + public function create(string $importProvider): ImportJob { - $count = 0; - $type = strtolower($type); + $count = 0; + $importProvider = strtolower($importProvider); while ($count < 30) { $key = Str::random(12); $existing = $this->findByKey($key); if (null === $existing->id) { - $importJob = new ImportJob; - $importJob->user()->associate($this->user); - $importJob->file_type = $type; - $importJob->key = Str::random(12); - $importJob->status = 'new'; - $importJob->configuration = []; - $importJob->extended_status = [ - 'steps' => 0, - 'done' => 0, - 'tag' => 0, - 'errors' => [], - ]; - $importJob->save(); + $importJob = ImportJob::create( + [ + 'user_id' => $this->user->id, + 'source' => $importProvider, + 'file_type' => '', + 'key' => Str::random(12), + 'status' => 'new', + 'stage' => 'new', + 'configuration' => [], + 'extended_status' => [], + 'transactions' => [], + ] + ); // breaks the loop: return $importJob; diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php index 5bb3a905cb..bb56921445 100644 --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php @@ -67,11 +67,11 @@ interface ImportJobRepositoryInterface public function countByHash(string $hash): int; /** - * @param string $type + * @param string $importProvider * * @return ImportJob */ - public function create(string $type): ImportJob; + public function create(string $importProvider): ImportJob; /** * @param string $key diff --git a/app/Support/Binder/ImportProvider.php b/app/Support/Binder/ImportProvider.php new file mode 100644 index 0000000000..3e82d332d5 --- /dev/null +++ b/app/Support/Binder/ImportProvider.php @@ -0,0 +1,55 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Support\Binder; + +use Carbon\Carbon; +use Illuminate\Routing\Route; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Class ImportProvider. + */ +class ImportProvider implements BinderInterface +{ + /** + * @param string $value + * @param Route $route + * + * @return Carbon + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public static function routeBinder(string $value, Route $route): string + { + $providers = (array)config('import.enabled'); + $allowed = []; + foreach ($providers as $name => $enabled) { + if ($enabled || (bool)config('app.debug') === true) { + $allowed[] = $name; + } + } + if (\in_array($value, $allowed, true)) { + return $value; + } + throw new NotFoundHttpException; + } +} From b33883b33480fecbb71a6e986d8cabd59366912e Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 18:07:23 +0200 Subject: [PATCH 019/182] Small code improvements. --- .../Import/Configuration/Bunq/HaveAccounts.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/Support/Import/Configuration/Bunq/HaveAccounts.php b/app/Support/Import/Configuration/Bunq/HaveAccounts.php index cf69af821f..35ec5eae55 100644 --- a/app/Support/Import/Configuration/Bunq/HaveAccounts.php +++ b/app/Support/Import/Configuration/Bunq/HaveAccounts.php @@ -139,17 +139,17 @@ class HaveAccounts implements ConfigurationInterface */ private function filterAccounts(array $dbAccounts, string $code): array { - $account = []; + $accounts = []; foreach ($dbAccounts as $accountId => $data) { if ($data['currency']->code === $code) { - $account[$accountId] = [ + $accounts[$accountId] = [ 'name' => $data['account']['name'], 'iban' => $data['account']['iban'], ]; } } - return $account; + return $accounts; } /** @@ -157,16 +157,17 @@ class HaveAccounts implements ConfigurationInterface * * @return string */ - private function getIban(array $bunqAccount) + private function getIban(array $bunqAccount): string { $iban = ''; - if (count($bunqAccount['alias'])) { + if (\is_array($bunqAccount['alias'])) { foreach ($bunqAccount['alias'] as $alias) { if ($alias['type'] === 'IBAN') { $iban = $alias['value']; } } } + return $iban; } } From fba847dd2845ae0c1a64d8d0dbbf64f34afc947a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 18:07:38 +0200 Subject: [PATCH 020/182] New bindable for import provider --- config/firefly.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/firefly.php b/config/firefly.php index d1a73174cf..57132bc997 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -283,7 +283,7 @@ return [ 'user' => \FireflyIII\User::class, // strings - 'import_source' => \FireflyIII\Support\Binder\ImportSource::class, + 'import_provider' => \FireflyIII\Support\Binder\ImportProvider::class, // dates 'start_date' => \FireflyIII\Support\Binder\Date::class, From 9fb049991f8050c6abc7bc1e23a843f7281c6d50 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 18:07:47 +0200 Subject: [PATCH 021/182] Migration for 4.7.4 --- .../2018_04_29_174524_changes_for_v474.php | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 database/migrations/2018_04_29_174524_changes_for_v474.php diff --git a/database/migrations/2018_04_29_174524_changes_for_v474.php b/database/migrations/2018_04_29_174524_changes_for_v474.php new file mode 100644 index 0000000000..9cdac3f720 --- /dev/null +++ b/database/migrations/2018_04_29_174524_changes_for_v474.php @@ -0,0 +1,37 @@ +string('provider', 50)->after('file_type')->default(''); + $table->string('stage', 50)->after('status')->default(''); + $table->longText('transactions')->after('extended_status'); + } + ); + } +} From b5be1b11d18f74b759cc4ebecd1c235018359893 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 18:07:58 +0200 Subject: [PATCH 022/182] Update index and routes. --- resources/lang/en_US/firefly.php | 24 ++++++ resources/views/import/index.twig | 119 ++++++++++++++++++------------ routes/web.php | 28 ++++--- 3 files changed, 114 insertions(+), 57 deletions(-) diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 1f34455d49..909b03eb26 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1169,6 +1169,30 @@ return [ 'reset_settings_spectre' => 'Remove Spectre secrets and ID\'s. This will also remove your Spectre keypair. Remember to update the new one.', 'settings_reset_for_bunq' => 'Bunq settings reset.', 'settings_reset_for_spectre' => 'Spectre settings reset.', + 'import_need_prereq_title' => 'Import prerequisites', + 'import_need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'import_need_config_title' => 'Import configuration', + 'import_need_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'import_button_fake' => 'Fake an import', + 'import_button_file' => 'Import a file', + 'import_button_bunq' => 'Import from bunq', + 'import_button_spectre' => 'Import using Spectre', + 'import_button_plaid' => 'Import using Plaid', + 'import_button_yodlee' => 'Import using Yodlee', + 'import_button_quovo' => 'Import using Quovo', + 'import_do_prereq_fake' => 'Prerequisites for the fake provider', + 'import_do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'import_do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'import_do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'import_do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'import_do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'import_do_config_fake' => 'Configuration for the fake provider', + 'import_do_config_file' => 'Configuration for file imports', + 'import_do_config_bunq' => 'Configuration for bunq imports', + 'import_do_config_spectre' => 'Configuration for imports from Spectre', + 'import_do_config_plaid' => 'Configuration for imports from Plaid', + 'import_do_config_yodlee' => 'Configuration for imports from Yodlee', + 'import_do_config_quovo' => 'Configuration for imports from Quovo', // sandstorm.io errors and messages: diff --git a/resources/views/import/index.twig b/resources/views/import/index.twig index 02fa364a94..b4d7d19d5c 100644 --- a/resources/views/import/index.twig +++ b/resources/views/import/index.twig @@ -5,65 +5,91 @@ {% endblock %} {% block content %}

    -
    + + +
    +
    +
    +
    +

    {{ 'import_global_config_title'|_ }}

    +
    +
    +

    + {{ 'import_global_config_text'|_ }} +

    +
    +
    +
    +
    +
    +
    +

    {{ 'import_need_prereq_title'|_ }}

    +
    +
    +

    + {{ 'import_need_prereq_intro'|_ }} +

    +
      + {% for name, provider in providers %} + {% if provider.has_prereq %} +
    • + {% if provider.prereq_complete %} + + {% else %} + + {% endif %} + {{ ('import_do_prereq_'~name)|_ }} +
    • + {% endif %} + {% endfor %} +
    +
    +
    +
    +
    +
    +
    +

    {{ 'import_need_config_title'|_ }}

    +
    +
    +

    + {{ 'import_need_config_intro'|_ }} +

    + +
    +
    +
    +
    + {% endblock %} {% block scripts %} {% endblock %} diff --git a/routes/web.php b/routes/web.php index 80d111977a..34255cbad9 100755 --- a/routes/web.php +++ b/routes/web.php @@ -442,29 +442,35 @@ Route::group( Route::group( ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'import', 'as' => 'import.'], function () { + // index Route::get('', ['uses' => 'Import\IndexController@index', 'as' => 'index']); + // create new job + Route::get('create/{import_provider}', ['uses' => 'Import\IndexController@create', 'as' => 'create']); + + // set global prerequisites for an import source, possible with a job already attached. + Route::get('prerequisites/{import_provider}/{importJob}', ['uses' => 'Import\PrerequisitesController@index', 'as' => 'prerequisites.index']); // import method prerequisites: - Route::get('prerequisites/{bank}', ['uses' => 'Import\PrerequisitesController@index', 'as' => 'prerequisites']); - Route::post('prerequisites/{bank}', ['uses' => 'Import\PrerequisitesController@post', 'as' => 'prerequisites.post']); - Route::get('reset/{bank}', ['uses' => 'Import\IndexController@reset', 'as' => 'reset']); + # + # + #Route::get('reset/{bank}', ['uses' => 'Import\IndexController@reset', 'as' => 'reset']); // create the job: - Route::get('create/{bank}', ['uses' => 'Import\IndexController@create', 'as' => 'create-job']); + #Route::get('create/{bank}', ['uses' => 'Import\IndexController@create', 'as' => 'create-job']); // configure the job: - Route::get('configure/{importJob}', ['uses' => 'Import\ConfigurationController@index', 'as' => 'configure']); - Route::post('configure/{importJob}', ['uses' => 'Import\ConfigurationController@post', 'as' => 'configure.post']); + #Route::get('configure/{importJob}', ['uses' => 'Import\ConfigurationController@index', 'as' => 'configure']); + #Route::post('configure/{importJob}', ['uses' => 'Import\ConfigurationController@post', 'as' => 'configure.post']); // get status of any job: - Route::get('status/{importJob}', ['uses' => 'Import\StatusController@index', 'as' => 'status']); - Route::get('json/{importJob}', ['uses' => 'Import\StatusController@json', 'as' => 'status.json']); + #Route::get('status/{importJob}', ['uses' => 'Import\StatusController@index', 'as' => 'status']); + #Route::get('json/{importJob}', ['uses' => 'Import\StatusController@json', 'as' => 'status.json']); // start a job - Route::any('start/{importJob}', ['uses' => 'Import\IndexController@start', 'as' => 'start']); + #Route::any('start/{importJob}', ['uses' => 'Import\IndexController@start', 'as' => 'start']); // download config - Route::get('download/{importJob}', ['uses' => 'Import\IndexController@download', 'as' => 'download']); + #Route::get('download/{importJob}', ['uses' => 'Import\IndexController@download', 'as' => 'download']); } ); @@ -495,7 +501,7 @@ Route::group( Route::group( ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'json', 'as' => 'json.'], function () { - // for auto complete + // for auto complete Route::get('expense-accounts', ['uses' => 'Json\AutoCompleteController@expenseAccounts', 'as' => 'expense-accounts']); Route::get('all-accounts', ['uses' => 'Json\AutoCompleteController@allAccounts', 'as' => 'all-accounts']); Route::get('revenue-accounts', ['uses' => 'Json\AutoCompleteController@revenueAccounts', 'as' => 'revenue-accounts']); From d2bb65bf049d5173049f3671bd3cb07d39b88b20 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 19:07:54 +0200 Subject: [PATCH 023/182] Can now create jobs, and set prerequisites for the fake provider, which will be skipped when they're not necessary. --- .../Controllers/Import/IndexController.php | 19 ++- .../Import/PrerequisitesController.php | 64 +++++---- .../Prerequisites/FakePrerequisites.php | 127 ++++++++++++++++++ .../Prerequisites/PrerequisitesInterface.php | 12 +- config/import.php | 3 +- .../views/import/fake/prerequisites.twig | 43 ++++++ routes/breadcrumbs.php | 6 +- routes/web.php | 3 +- 8 files changed, 237 insertions(+), 40 deletions(-) create mode 100644 app/Import/Prerequisites/FakePrerequisites.php create mode 100644 resources/views/import/fake/prerequisites.twig diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index 6e512e78e4..22508bff9b 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -74,8 +74,23 @@ class IndexController extends Controller { $importJob = $this->repository->create($importProvider); - // redirect to global prerequisites - return redirect(route('import.prerequisites.index', [$importProvider, $importJob->key])); + // if need to set prerequisites, do that first. + $class = (string)config(sprintf('import.prerequisites.%s', $importProvider)); + if (!class_exists($class)) { + throw new FireflyException(sprintf('No class to handle configuration for "%s".', $importProvider)); // @codeCoverageIgnore + } + /** @var PrerequisitesInterface $object */ + $object = app($class); + $object->setUser(auth()->user()); + + if (!$object->isComplete()) { + // redirect to global prerequisites + return redirect(route('import.prerequisites.index', [$importProvider, $importJob->key])); + } + + // Otherwise just redirect to job configuration. + return redirect(route('import.job.configuration.index', [$importJob->key])); + } // /** diff --git a/app/Http/Controllers/Import/PrerequisitesController.php b/app/Http/Controllers/Import/PrerequisitesController.php index e3633cfaab..3ec592f81a 100644 --- a/app/Http/Controllers/Import/PrerequisitesController.php +++ b/app/Http/Controllers/Import/PrerequisitesController.php @@ -27,6 +27,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Import\Prerequisites\PrerequisitesInterface; use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use Illuminate\Http\Request; use Log; @@ -36,6 +37,9 @@ use Log; class PrerequisitesController extends Controller { + /** @var ImportJobRepositoryInterface */ + private $repository; + /** * */ @@ -48,6 +52,8 @@ class PrerequisitesController extends Controller app('view')->share('mainTitleIcon', 'fa-archive'); app('view')->share('title', trans('firefly.import_index_title')); + $this->repository = app(ImportJobRepositoryInterface::class); + return $next($request); } ); @@ -62,29 +68,30 @@ class PrerequisitesController extends Controller * @param string $importProvider * @param ImportJob $importJob * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View * @throws FireflyException */ - public function index(string $importProvider, ImportJob $importJob) + public function index(string $importProvider, ImportJob $importJob = null) { $class = (string)config(sprintf('import.prerequisites.%s', $importProvider)); if (!class_exists($class)) { throw new FireflyException(sprintf('No class to handle configuration for "%s".', $importProvider)); // @codeCoverageIgnore } - /** @var PrerequisitesInterface $object */ $object = app($class); $object->setUser(auth()->user()); - if ($object->hasPrerequisites()) { - $view = $object->getView(); - $parameters = ['title' => (string)trans('firefly.import_index_title'), 'mainTitleIcon' => 'fa-archive']; - $parameters = array_merge($object->getViewParameters(), $parameters); - - return view($view, $parameters); + // TODO if prerequisites have been met and job is not null, just skip this step. + if (null !== $importJob && $object->isComplete()) { + // set job to } - // if no (more) prerequisites, return to create a job: - return redirect(route('import.create-job', [$bank])); + + $view = $object->getView(); + $parameters = ['title' => (string)trans('firefly.import_index_title'), 'mainTitleIcon' => 'fa-archive', 'importJob' => $importJob]; + $parameters = array_merge($object->getViewParameters(), $parameters); + + return view($view, $parameters); } /** @@ -95,33 +102,25 @@ class PrerequisitesController extends Controller * * @see PrerequisitesInterface::storePrerequisites * - * @param Request $request - * @param string $bank + * @param Request $request + * @param string $importProvider + * @param ImportJob $importJob * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * * @throws FireflyException */ - public function post(Request $request, string $bank) + public function post(Request $request, string $importProvider, ImportJob $importJob = null) { - Log::debug(sprintf('Now in postPrerequisites for %s', $bank)); + Log::debug(sprintf('Now in postPrerequisites for %s', $importProvider)); - if (true === !config(sprintf('import.enabled.%s', $bank))) { - throw new FireflyException(sprintf('Cannot import from "%s" at this time.', $bank)); // @codeCoverageIgnore - } - - $class = (string)config(sprintf('import.prerequisites.%s', $bank)); + $class = (string)config(sprintf('import.prerequisites.%s', $importProvider)); if (!class_exists($class)) { throw new FireflyException(sprintf('Cannot find class %s', $class)); // @codeCoverageIgnore } /** @var PrerequisitesInterface $object */ $object = app($class); $object->setUser(auth()->user()); - if (!$object->hasPrerequisites()) { - Log::debug(sprintf('No more prerequisites for %s, move to form.', $bank)); - - return redirect(route('import.create-job', [$bank])); - } Log::debug('Going to store entered prerequisites.'); // store post data $result = $object->storePrerequisites($request); @@ -129,8 +128,23 @@ class PrerequisitesController extends Controller if ($result->count() > 0) { $request->session()->flash('error', $result->first()); + + // redirect back: + return redirect(route('import.prerequisites.index', [$importProvider, $importJob->key]))->withInput(); } - return redirect(route('import.prerequisites', [$bank])); + // session flash! + $request->session()->flash('success', (string)trans('firefly.prerequisites_saved_for_' . $importProvider)); + + // if has job, redirect to global config for provider + // if no job, back to index! + if (null === $importJob) { + return redirect(route('import.index')); + } + + // redirect to global config: + return redirect(route('import.index')); + + } } diff --git a/app/Import/Prerequisites/FakePrerequisites.php b/app/Import/Prerequisites/FakePrerequisites.php new file mode 100644 index 0000000000..e79dd3b337 --- /dev/null +++ b/app/Import/Prerequisites/FakePrerequisites.php @@ -0,0 +1,127 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Import\Prerequisites; + +use FireflyIII\User; +use Illuminate\Http\Request; +use Illuminate\Support\MessageBag; + +/** + * This class contains all the routines necessary for the fake import provider. + * + * Class FakePrerequisites + */ +class FakePrerequisites implements PrerequisitesInterface +{ + /** @var User */ + private $user; + + /** + * Returns view name that allows user to fill in prerequisites. Currently asks for the API key. + * + * @return string + */ + public function getView(): string + { + return 'import.fake.prerequisites'; + } + + /** + * Returns any values required for the prerequisites-view. + * + * @return array + */ + public function getViewParameters(): array + { + $apiKey = ''; + if ($this->hasApiKey()) { + $apiKey = app('preferences')->getForUser($this->user, 'fake_api_key', null)->data; + } + $oldKey = (string)\request()->old('api_key'); + if ($oldKey !== '') { + $apiKey = \request()->old('api_key'); + } + + return ['api_key' => $apiKey]; + } + + /** + * Indicate if all prerequisites have been met. + * + * @return bool + */ + public function isComplete(): bool + { + return $this->hasApiKey(); + } + + /** + * Set the user for this Prerequisites-routine. Class is expected to implement and save this. + * + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + + } + + /** + * @param Request $request + * + * @return MessageBag + */ + public function storePrerequisites(Request $request): MessageBag + { + $apiKey = (string)$request->get('api_key'); + $messageBag = new MessageBag(); + if (32 !== \strlen($apiKey)) { + $messageBag->add('api_key', 'API key must be 32 chars.'); + + return $messageBag; + } + + app('preferences')->setForUser($this->user, 'fake_api_key', $apiKey); + + return $messageBag; + } + + /** + * @return bool + */ + private function hasApiKey(): bool + { + $apiKey = app('preferences')->getForUser($this->user, 'fake_api_key', false); + if (null === $apiKey) { + return false; + } + if (null === $apiKey->data) { + return false; + } + if (\strlen((string)$apiKey->data) === 32) { + return true; + } + + return false; + } +} diff --git a/app/Import/Prerequisites/PrerequisitesInterface.php b/app/Import/Prerequisites/PrerequisitesInterface.php index 84519a647a..962147be49 100644 --- a/app/Import/Prerequisites/PrerequisitesInterface.php +++ b/app/Import/Prerequisites/PrerequisitesInterface.php @@ -45,14 +45,6 @@ interface PrerequisitesInterface */ public function getViewParameters(): array; - /** - * Returns if this import method has any special prerequisites such as config - * variables or other things. - * - * @return bool - */ - public function hasPrerequisites(): bool; - /** * Indicate if all prerequisites have been met. * @@ -68,6 +60,10 @@ interface PrerequisitesInterface public function setUser(User $user): void; /** + * This method responds to the user's submission of an API key. Should do nothing but store the value. + * + * Errors must be returned in the message bag under the field name they are requested by. + * * @param Request $request * * @return MessageBag diff --git a/config/import.php b/config/import.php index eb79bb716d..0c691894c2 100644 --- a/config/import.php +++ b/config/import.php @@ -6,6 +6,7 @@ use FireflyIII\Import\Configuration\FileConfigurator; use FireflyIII\Import\Configuration\SpectreConfigurator; use FireflyIII\Import\FileProcessor\CsvProcessor; use FireflyIII\Import\Prerequisites\BunqPrerequisites; +use FireflyIII\Import\Prerequisites\FakePrerequisites; use FireflyIII\Import\Prerequisites\FilePrerequisites; use FireflyIII\Import\Prerequisites\SpectrePrerequisites; use FireflyIII\Import\Routine\BunqRoutine; @@ -53,7 +54,7 @@ return [ 'yodlee' => true, ], 'prerequisites' => [ - 'fake' => false, + 'fake' => FakePrerequisites::class, 'file' => FilePrerequisites::class, 'bunq' => BunqPrerequisites::class, 'spectre' => SpectrePrerequisites::class, diff --git a/resources/views/import/fake/prerequisites.twig b/resources/views/import/fake/prerequisites.twig new file mode 100644 index 0000000000..a6e926f932 --- /dev/null +++ b/resources/views/import/fake/prerequisites.twig @@ -0,0 +1,43 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render }} +{% endblock %} +{% block content %} +
    +
    + +
    +
    +
    +

    Fake prerequisites

    +
    +
    +
    +
    +

    + Bla bla bla bla +

    +
    +
    + +
    +
    + {{ ExpandedForm.text('api_key', api_key) }} +
    +
    +
    + +
    +
    +
    +
    +{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index 78a2be3167..bddd40a325 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -593,10 +593,10 @@ Breadcrumbs::register( ); Breadcrumbs::register( - 'import.prerequisites', - function (BreadCrumbsGenerator $breadcrumbs, string $bank) { + 'import.prerequisites.index', + function (BreadCrumbsGenerator $breadcrumbs, string $importProvider) { $breadcrumbs->parent('import.index'); - $breadcrumbs->push(trans('import.prerequisites'), route('import.prerequisites', [$bank])); + $breadcrumbs->push(trans('import.prerequisites'), route('import.prerequisites.index', [$importProvider])); } ); diff --git a/routes/web.php b/routes/web.php index 34255cbad9..e4262c26a4 100755 --- a/routes/web.php +++ b/routes/web.php @@ -449,7 +449,8 @@ Route::group( Route::get('create/{import_provider}', ['uses' => 'Import\IndexController@create', 'as' => 'create']); // set global prerequisites for an import source, possible with a job already attached. - Route::get('prerequisites/{import_provider}/{importJob}', ['uses' => 'Import\PrerequisitesController@index', 'as' => 'prerequisites.index']); + Route::get('prerequisites/{import_provider}/{importJob?}', ['uses' => 'Import\PrerequisitesController@index', 'as' => 'prerequisites.index']); + Route::post('prerequisites/{import_provider}/{importJob?}', ['uses' => 'Import\PrerequisitesController@post', 'as' => 'prerequisites.post']); // import method prerequisites: # # From fa41d6df045a5340bb9e5c36d8142764e325c215 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 19:28:07 +0200 Subject: [PATCH 024/182] Clean up translations and strings in import routine. --- .../Controllers/Import/IndexController.php | 2 +- ...ler.php => JobConfigurationController.php} | 6 +- .../Import/PrerequisitesController.php | 9 +- resources/lang/en_US/firefly.php | 46 +-- resources/lang/en_US/import.php | 298 +++++++++++------- resources/views/import/index.twig | 24 +- routes/breadcrumbs.php | 4 +- 7 files changed, 205 insertions(+), 184 deletions(-) rename app/Http/Controllers/Import/{ConfigurationController.php => JobConfigurationController.php} (97%) diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index 22508bff9b..dc19dd40fe 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -162,7 +162,7 @@ class IndexController extends Controller $providers[$name]['prereq_complete'] = $result; } - $subTitle = trans('firefly.import_index_sub_title'); + $subTitle = trans('import.index_breadcrumb'); $subTitleIcon = 'fa-home'; return view('import.index', compact('subTitle', 'subTitleIcon', 'providers')); diff --git a/app/Http/Controllers/Import/ConfigurationController.php b/app/Http/Controllers/Import/JobConfigurationController.php similarity index 97% rename from app/Http/Controllers/Import/ConfigurationController.php rename to app/Http/Controllers/Import/JobConfigurationController.php index 7f6ee65b1c..cbe050420e 100644 --- a/app/Http/Controllers/Import/ConfigurationController.php +++ b/app/Http/Controllers/Import/JobConfigurationController.php @@ -1,6 +1,6 @@ share('mainTitleIcon', 'fa-archive'); app('view')->share('title', trans('firefly.import_index_title')); + app('view')->share('subTitleIcon', 'fa-check'); + $this->repository = app(ImportJobRepositoryInterface::class); return $next($request); @@ -73,6 +75,7 @@ class PrerequisitesController extends Controller */ public function index(string $importProvider, ImportJob $importJob = null) { + app('view')->share('subTitle', trans('import.prerequisites_breadcrumb_' . $importProvider)); $class = (string)config(sprintf('import.prerequisites.%s', $importProvider)); if (!class_exists($class)) { throw new FireflyException(sprintf('No class to handle configuration for "%s".', $importProvider)); // @codeCoverageIgnore @@ -134,7 +137,7 @@ class PrerequisitesController extends Controller } // session flash! - $request->session()->flash('success', (string)trans('firefly.prerequisites_saved_for_' . $importProvider)); + $request->session()->flash('success', (string)trans('import.prerequisites_saved_for_' . $importProvider)); // if has job, redirect to global config for provider // if no job, back to index! @@ -142,8 +145,8 @@ class PrerequisitesController extends Controller return redirect(route('import.index')); } - // redirect to global config: - return redirect(route('import.index')); + // redirect to job config: + return redirect(route('import.job.configuration.index', [$importJob->key])); } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 909b03eb26..2aa4894347 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1149,51 +1149,9 @@ return [ 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', 'cannot_convert_split_journal' => 'Cannot convert a split transaction', - // import bread crumbs and titles: - 'import' => 'Import', - 'import_data' => 'Import data', - 'import_general_index_file' => 'Import a file', - 'import_from_bunq' => 'Import from bunq', - 'import_using_spectre' => 'Import using Spectre', - 'import_using_plaid' => 'Import using Plaid', - 'import_config_bread_crumb' => 'Set up your import', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Import data into Firefly III', - 'import_index_sub_title' => 'Index', - 'import_general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', - 'upload_error' => 'The file you have uploaded could not be processed. Possibly it is of an invalid file type or encoding. The log files will have more information.', - 'reset_import_settings_title' => 'Reset import configuration', - 'reset_import_settings_text' => 'You can use these links to reset your import settings for specific providers. This is useful when bad settings stop you from importing data.', - 'reset_settings_bunq' => 'Remove bunq API key, local external IP address and bunq related RSA keys.', - 'reset_settings_spectre' => 'Remove Spectre secrets and ID\'s. This will also remove your Spectre keypair. Remember to update the new one.', - 'settings_reset_for_bunq' => 'Bunq settings reset.', - 'settings_reset_for_spectre' => 'Spectre settings reset.', - 'import_need_prereq_title' => 'Import prerequisites', - 'import_need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', - 'import_need_config_title' => 'Import configuration', - 'import_need_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', - 'import_button_fake' => 'Fake an import', - 'import_button_file' => 'Import a file', - 'import_button_bunq' => 'Import from bunq', - 'import_button_spectre' => 'Import using Spectre', - 'import_button_plaid' => 'Import using Plaid', - 'import_button_yodlee' => 'Import using Yodlee', - 'import_button_quovo' => 'Import using Quovo', - 'import_do_prereq_fake' => 'Prerequisites for the fake provider', - 'import_do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'import_do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'import_do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'import_do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'import_do_prereq_quovo' => 'Prerequisites for imports using Quovo', - 'import_do_config_fake' => 'Configuration for the fake provider', - 'import_do_config_file' => 'Configuration for file imports', - 'import_do_config_bunq' => 'Configuration for bunq imports', - 'import_do_config_spectre' => 'Configuration for imports from Spectre', - 'import_do_config_plaid' => 'Configuration for imports from Plaid', - 'import_do_config_yodlee' => 'Configuration for imports from Yodlee', - 'import_do_config_quovo' => 'Configuration for imports from Quovo', - + 'import_data' => 'Import data', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 0fdbee1048..a65c67c234 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -23,143 +23,203 @@ declare(strict_types=1); return [ // status of import: - 'status_wait_title' => 'Please hold...', - 'status_wait_text' => 'This box will disappear in a moment.', - 'status_fatal_title' => 'A fatal error occurred', - 'status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', - 'status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', - 'status_ready_title' => 'Import is ready to start', - 'status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'status_ready_noconfig_text' => 'The import is ready to start. All the configuration you needed to do has been done. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'status_ready_config' => 'Download configuration', - 'status_ready_start' => 'Start the import', - 'status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'status_job_new' => 'The job is brand new.', - 'status_job_configuring' => 'The import is being configured.', - 'status_job_configured' => 'The import is configured.', - 'status_job_running' => 'The import is running.. Please wait..', - 'status_job_error' => 'The job has generated an error.', - 'status_job_finished' => 'The import has finished!', - 'status_running_title' => 'The import is running', - 'status_running_placeholder' => 'Please hold for an update...', - 'status_finished_title' => 'Import routine finished', - 'status_finished_text' => 'The import routine has imported your data.', - 'status_errors_title' => 'Errors during the import', - 'status_errors_single' => 'An error has occurred during the import. It does not appear to be fatal.', - 'status_errors_multi' => 'Some errors occurred during the import. These do not appear to be fatal.', - 'status_bread_crumb' => 'Import status', - 'status_sub_title' => 'Import status', - 'config_sub_title' => 'Set up your import', - 'status_finished_job' => 'The :count transactions imported can be found in tag :tag.', - 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', - 'import_with_key' => 'Import with key \':key\'', + 'status_wait_title' => 'Please hold...', + 'status_wait_text' => 'This box will disappear in a moment.', + 'status_fatal_title' => 'A fatal error occurred', + 'status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'status_ready_title' => 'Import is ready to start', + 'status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'status_ready_noconfig_text' => 'The import is ready to start. All the configuration you needed to do has been done. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'status_ready_config' => 'Download configuration', + 'status_ready_start' => 'Start the import', + 'status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'status_job_new' => 'The job is brand new.', + 'status_job_configuring' => 'The import is being configured.', + 'status_job_configured' => 'The import is configured.', + 'status_job_running' => 'The import is running.. Please wait..', + 'status_job_error' => 'The job has generated an error.', + 'status_job_finished' => 'The import has finished!', + 'status_running_title' => 'The import is running', + 'status_running_placeholder' => 'Please hold for an update...', + 'status_finished_title' => 'Import routine finished', + 'status_finished_text' => 'The import routine has imported your data.', + 'status_errors_title' => 'Errors during the import', + 'status_errors_single' => 'An error has occurred during the import. It does not appear to be fatal.', + 'status_errors_multi' => 'Some errors occurred during the import. These do not appear to be fatal.', + 'status_bread_crumb' => 'Import status', + 'status_sub_title' => 'Import status', + 'config_sub_title' => 'Set up your import', + 'status_finished_job' => 'The :count transactions imported can be found in tag :tag.', + 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', + 'import_with_key' => 'Import with key \':key\'', // file, upload something - 'file_upload_title' => 'Import setup (1/4) - Upload your file', - 'file_upload_text' => 'This routine will help you import files from your bank into Firefly III. Please check out the help pages in the top right corner.', - 'file_upload_fields' => 'Fields', - 'file_upload_help' => 'Select your file', - 'file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'file_upload_type_help' => 'Select the type of file you will upload', - 'file_upload_submit' => 'Upload files', + 'file_upload_title' => 'Import setup (1/4) - Upload your file', + 'file_upload_text' => 'This routine will help you import files from your bank into Firefly III. Please check out the help pages in the top right corner.', + 'file_upload_fields' => 'Fields', + 'file_upload_help' => 'Select your file', + 'file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'file_upload_type_help' => 'Select the type of file you will upload', + 'file_upload_submit' => 'Upload files', // file, upload types - 'import_file_type_csv' => 'CSV (comma separated values)', + 'import_file_type_csv' => 'CSV (comma separated values)', // file, initial config for CSV - 'csv_initial_title' => 'Import setup (2/4) - Basic CSV import setup', - 'csv_initial_text' => 'To be able to import your file correctly, please validate the options below.', - 'csv_initial_box' => 'Basic CSV import setup', - 'csv_initial_box_title' => 'Basic CSV import setup options', - 'csv_initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - 'csv_initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'csv_initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'csv_initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'csv_initial_submit' => 'Continue with step 3/4', + 'csv_initial_title' => 'Import setup (2/4) - Basic CSV import setup', + 'csv_initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'csv_initial_box' => 'Basic CSV import setup', + 'csv_initial_box_title' => 'Basic CSV import setup options', + 'csv_initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'csv_initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'csv_initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'csv_initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'csv_initial_submit' => 'Continue with step 3/4', // file, new options: - 'file_apply_rules_title' => 'Apply rules', - 'file_apply_rules_description' => 'Apply your rules. Note that this slows the import significantly.', - 'file_match_bills_title' => 'Match bills', - 'file_match_bills_description' => 'Match your bills to newly created withdrawals. Note that this slows the import significantly.', + 'file_apply_rules_title' => 'Apply rules', + 'file_apply_rules_description' => 'Apply your rules. Note that this slows the import significantly.', + 'file_match_bills_title' => 'Match bills', + 'file_match_bills_description' => 'Match your bills to newly created withdrawals. Note that this slows the import significantly.', // file, roles config - 'csv_roles_title' => 'Import setup (3/4) - Define each column\'s role', - 'csv_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', - 'csv_roles_table' => 'Table', - 'csv_roles_column_name' => 'Name of column', - 'csv_roles_column_example' => 'Column example data', - 'csv_roles_column_role' => 'Column data meaning', - 'csv_roles_do_map_value' => 'Map these values', - 'csv_roles_column' => 'Column', - 'csv_roles_no_example_data' => 'No example data available', - 'csv_roles_submit' => 'Continue with step 4/4', + 'csv_roles_title' => 'Import setup (3/4) - Define each column\'s role', + 'csv_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'csv_roles_table' => 'Table', + 'csv_roles_column_name' => 'Name of column', + 'csv_roles_column_example' => 'Column example data', + 'csv_roles_column_role' => 'Column data meaning', + 'csv_roles_do_map_value' => 'Map these values', + 'csv_roles_column' => 'Column', + 'csv_roles_no_example_data' => 'No example data available', + 'csv_roles_submit' => 'Continue with step 4/4', // not csv, but normal warning - 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', - 'foreign_amount_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'foreign_amount_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', // file, map data - 'file_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', - 'file_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', - 'file_map_field_value' => 'Field value', - 'file_map_field_mapped_to' => 'Mapped to', - 'map_do_not_map' => '(do not map)', - 'file_map_submit' => 'Start the import', - 'file_nothing_to_map' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'file_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'file_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'file_map_field_value' => 'Field value', + 'file_map_field_mapped_to' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'file_map_submit' => 'Start the import', + 'file_nothing_to_map' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', // map things. - 'column__ignore' => '(ignore this column)', - 'column_account-iban' => 'Asset account (IBAN)', - 'column_account-id' => 'Asset account ID (matching FF3)', - 'column_account-name' => 'Asset account (name)', - 'column_amount' => 'Amount', - 'column_amount_foreign' => 'Amount (in foreign currency)', - 'column_amount_debit' => 'Amount (debit column)', - 'column_amount_credit' => 'Amount (credit column)', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching FF3)', - 'column_bill-name' => 'Bill name', - 'column_budget-id' => 'Budget ID (matching FF3)', - 'column_budget-name' => 'Budget name', - 'column_category-id' => 'Category ID (matching FF3)', - 'column_category-name' => 'Category name', - 'column_currency-code' => 'Currency code (ISO 4217)', - 'column_foreign-currency-code' => 'Foreign currency code (ISO 4217)', - 'column_currency-id' => 'Currency ID (matching FF3)', - 'column_currency-name' => 'Currency name (matching FF3)', - 'column_currency-symbol' => 'Currency symbol (matching FF3)', - 'column_date-interest' => 'Interest calculation date', - 'column_date-book' => 'Transaction booking date', - 'column_date-process' => 'Transaction process date', - 'column_date-transaction' => 'Date', - 'column_date-due' => 'Transaction due date', - 'column_date-payment' => 'Transaction payment date', - 'column_date-invoice' => 'Transaction invoice date', - 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-bic' => 'Opposing account (BIC)', - 'column_opposing-id' => 'Opposing account ID (matching FF3)', - 'column_external-id' => 'External ID', - 'column_opposing-name' => 'Opposing account (name)', - 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', - 'column_ing-debit-credit' => 'ING specific debit/credit indicator', - 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', - 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'column_sepa-db' => 'SEPA Mandate Identifier', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country Code', - 'column_tags-comma' => 'Tags (comma separated)', - 'column_tags-space' => 'Tags (space separated)', - 'column_account-number' => 'Asset account (account number)', - 'column_opposing-number' => 'Opposing account (account number)', - 'column_note' => 'Note(s)', - 'column_internal-reference' => 'Internal reference', + 'column__ignore' => '(ignore this column)', + 'column_account-iban' => 'Asset account (IBAN)', + 'column_account-id' => 'Asset account ID (matching FF3)', + 'column_account-name' => 'Asset account (name)', + 'column_amount' => 'Amount', + 'column_amount_foreign' => 'Amount (in foreign currency)', + 'column_amount_debit' => 'Amount (debit column)', + 'column_amount_credit' => 'Amount (credit column)', + 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', + 'column_bill-id' => 'Bill ID (matching FF3)', + 'column_bill-name' => 'Bill name', + 'column_budget-id' => 'Budget ID (matching FF3)', + 'column_budget-name' => 'Budget name', + 'column_category-id' => 'Category ID (matching FF3)', + 'column_category-name' => 'Category name', + 'column_currency-code' => 'Currency code (ISO 4217)', + 'column_foreign-currency-code' => 'Foreign currency code (ISO 4217)', + 'column_currency-id' => 'Currency ID (matching FF3)', + 'column_currency-name' => 'Currency name (matching FF3)', + 'column_currency-symbol' => 'Currency symbol (matching FF3)', + 'column_date-interest' => 'Interest calculation date', + 'column_date-book' => 'Transaction booking date', + 'column_date-process' => 'Transaction process date', + 'column_date-transaction' => 'Date', + 'column_date-due' => 'Transaction due date', + 'column_date-payment' => 'Transaction payment date', + 'column_date-invoice' => 'Transaction invoice date', + 'column_description' => 'Description', + 'column_opposing-iban' => 'Opposing account (IBAN)', + 'column_opposing-bic' => 'Opposing account (BIC)', + 'column_opposing-id' => 'Opposing account ID (matching FF3)', + 'column_external-id' => 'External ID', + 'column_opposing-name' => 'Opposing account (name)', + 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', + 'column_ing-debit-credit' => 'ING specific debit/credit indicator', + 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', + 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', + 'column_sepa-db' => 'SEPA Mandate Identifier', + 'column_sepa-cc' => 'SEPA Clearing Code', + 'column_sepa-ci' => 'SEPA Creditor Identifier', + 'column_sepa-ep' => 'SEPA External Purpose', + 'column_sepa-country' => 'SEPA Country Code', + 'column_tags-comma' => 'Tags (comma separated)', + 'column_tags-space' => 'Tags (space separated)', + 'column_account-number' => 'Asset account (account number)', + 'column_opposing-number' => 'Opposing account (account number)', + 'column_note' => 'Note(s)', + 'column_internal-reference' => 'Internal reference', // prerequisites - 'prerequisites' => 'Prerequisites', + 'prerequisites' => 'Prerequisites', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for fake provider', + 'prerequisites_breadcrumb_file' => 'Prerequisites for file imports', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for Bunq', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_plaid' => 'Prerequisites for Plaid', + 'prerequisites_breadcrumb_quovo' => 'Prerequisites for Quovo', + 'prerequisites_breadcrumb_yodlee' => 'Prerequisites for Yodlee', + // success messages: + 'prerequisites_saved_for_fake' => 'API key stored for fake provider', + 'prerequisites_saved_for_file' => 'Data stored for file imports', + 'prerequisites_saved_for_bunq' => 'API key and IP stored for bunq', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored for Spectre', + 'prerequisites_saved_for_plaid' => 'Data stored for Plaid', + 'prerequisites_saved_for_quovo' => 'Data stored for Quovo', + 'prerequisites_saved_for_yodlee' => 'Data stored for Yodlee', + + // index of import: + 'general_index_title' => 'Import a file', + 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + + // import provider strings (index): + 'button_fake' => 'Fake an import', + 'button_file' => 'Import a file', + 'button_bunq' => 'Import from bunq', + 'button_spectre' => 'Import using Spectre', + 'button_plaid' => 'Import using Plaid', + 'button_yodlee' => 'Import using Yodlee', + 'button_quovo' => 'Import using Quovo', + + // global config box + 'global_config_title' => 'Global import configuration', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + + // prereq box: + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + + // provider config box: + 'can_config_title' => 'Import configuration', + 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'do_config_fake' => 'Configuration for the fake provider', + 'do_config_file' => 'Configuration for file imports', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', + + // import index page: + 'index_breadcrumb' => 'Index', + + 'upload_error' => 'The file you have uploaded could not be processed. Possibly it is of an invalid file type or encoding. The log files will have more information.', + // bunq 'bunq_prerequisites_title' => 'Prerequisites for an import from bunq', diff --git a/resources/views/import/index.twig b/resources/views/import/index.twig index b4d7d19d5c..caeddb4b93 100644 --- a/resources/views/import/index.twig +++ b/resources/views/import/index.twig @@ -8,19 +8,19 @@
    -

    {{ 'import_index_title'|_ }}

    +

    {{ trans('firefly.import_index_title') }}

    - {{ 'import_general_index_intro'|_ }} + {{ trans('import.general_index_intro') }}

    {% for name, provider in providers %} {# button for each import thing: #} {% endfor %} @@ -34,11 +34,11 @@
    -

    {{ 'import_global_config_title'|_ }}

    +

    {{ trans('import.global_config_title') }}

    - {{ 'import_global_config_text'|_ }} + {{ trans('import.global_config_text') }}

    @@ -46,11 +46,11 @@
    -

    {{ 'import_need_prereq_title'|_ }}

    +

    {{ trans('import.need_prereq_title') }}

    - {{ 'import_need_prereq_intro'|_ }} + {{ trans('import.need_prereq_intro') }}

      {% for name, provider in providers %} @@ -61,7 +61,7 @@ {% else %} {% endif %} - {{ ('import_do_prereq_'~name)|_ }} + {{ trans('import.do_prereq_'~name) }} {% endif %} {% endfor %} @@ -72,16 +72,16 @@
      -

      {{ 'import_need_config_title'|_ }}

      +

      {{ trans('import.can_config_title') }}

      - {{ 'import_need_config_intro'|_ }} + {{ trans('import.can_config_title') }}

      diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index bddd40a325..a145e8a9f5 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -580,7 +580,7 @@ Breadcrumbs::register( 'import.index', function (BreadCrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.import'), route('import.index')); + $breadcrumbs->push(trans('firefly.import_index_title'), route('import.index')); } ); @@ -596,7 +596,7 @@ Breadcrumbs::register( 'import.prerequisites.index', function (BreadCrumbsGenerator $breadcrumbs, string $importProvider) { $breadcrumbs->parent('import.index'); - $breadcrumbs->push(trans('import.prerequisites'), route('import.prerequisites.index', [$importProvider])); + $breadcrumbs->push(trans('import.prerequisites_breadcrumb_'.$importProvider), route('import.prerequisites.index', [$importProvider])); } ); From f027d71136ab299d69d329ee12208b0fdcb40c86 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 20:01:03 +0200 Subject: [PATCH 025/182] Fake jobs can be configured and can reach the landing stage. --- .../Controllers/Import/IndexController.php | 2 + .../Import/JobConfigurationController.php | 39 +- .../Import/PrerequisitesController.php | 3 + .../JobConfiguration/FakeJobConfiguration.php | 133 ++ .../JobConfiguratorInterface.php | 73 + app/Models/ImportJob.php | 50 +- .../ImportJob/ImportJobRepository.php | 4 +- config/import.php | 2 + resources/lang/en_US/import.php | 331 +-- resources/views/import/fake/enter-artist.twig | 53 + resources/views/import/fake/enter-song.twig | 53 + routes/breadcrumbs.php | 1894 +++++++++-------- routes/web.php | 7 +- 13 files changed, 1464 insertions(+), 1180 deletions(-) create mode 100644 app/Import/JobConfiguration/FakeJobConfiguration.php create mode 100644 app/Import/JobConfiguration/JobConfiguratorInterface.php create mode 100644 resources/views/import/fake/enter-artist.twig create mode 100644 resources/views/import/fake/enter-song.twig diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index dc19dd40fe..ba3c8913f4 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -87,6 +87,8 @@ class IndexController extends Controller // redirect to global prerequisites return redirect(route('import.prerequisites.index', [$importProvider, $importJob->key])); } + // update job to say "has_prereq". + $this->repository->setStatus($importJob, 'has_prereq'); // Otherwise just redirect to job configuration. return redirect(route('import.job.configuration.index', [$importJob->key])); diff --git a/app/Http/Controllers/Import/JobConfigurationController.php b/app/Http/Controllers/Import/JobConfigurationController.php index cbe050420e..aaa1626ec1 100644 --- a/app/Http/Controllers/Import/JobConfigurationController.php +++ b/app/Http/Controllers/Import/JobConfigurationController.php @@ -25,7 +25,7 @@ namespace FireflyIII\Http\Controllers\Import; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Middleware\IsDemoUser; -use FireflyIII\Import\Configuration\ConfiguratorInterface; +use FireflyIII\Import\JobConfiguration\JobConfiguratorInterface; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use Illuminate\Http\Request; @@ -73,10 +73,10 @@ class JobConfigurationController extends Controller $configurator = $this->makeConfigurator($job); // is the job already configured? - if ($configurator->isJobConfigured()) { - $this->repository->updateStatus($job, 'configured'); + if ($configurator->configurationComplete()) { + $this->repository->updateStatus($job, 'ready_to_run'); - return redirect(route('import.status', [$job->key])); + return redirect(route('import.job.landing', [$job->key])); } $this->repository->updateStatus($job, 'configuring'); @@ -105,40 +105,39 @@ class JobConfigurationController extends Controller $configurator = $this->makeConfigurator($job); // is the job already configured? - if ($configurator->isJobConfigured()) { - return redirect(route('import.status', [$job->key])); + if ($configurator->configurationComplete()) { + $this->repository->updateStatus($job, 'ready_to_run'); + + return redirect(route('import.job.landing', [$job->key])); } - $data = $request->all(); - $configurator->configureJob($data); - // get possible warning from configurator: - $warning = $configurator->getWarningMessage(); + $data = $request->all(); + $messages = $configurator->configureJob($data); - if (\strlen($warning) > 0) { - $request->session()->flash('warning', $warning); + if ($messages->count() > 0) { + $request->session()->flash('warning', $messages->first()); } // return to configure - return redirect(route('import.configure', [$job->key])); + return redirect(route('import.job.configuration.index', [$job->key])); } /** * @param ImportJob $job * - * @return ConfiguratorInterface + * @return JobConfiguratorInterface * * @throws FireflyException */ - private function makeConfigurator(ImportJob $job): ConfiguratorInterface + private function makeConfigurator(ImportJob $job): JobConfiguratorInterface { - $type = $job->file_type; - $key = sprintf('import.configuration.%s', $type); - $className = config($key); + $key = sprintf('import.configuration.%s', $job->provider); + $className = (string)config($key); if (null === $className || !class_exists($className)) { - throw new FireflyException(sprintf('Cannot find configurator class for job of type "%s".', $type)); // @codeCoverageIgnore + throw new FireflyException(sprintf('Cannot find configurator class for job with provider "%s".', $job->provider)); // @codeCoverageIgnore } Log::debug(sprintf('Going to create class "%s"', $className)); - /** @var ConfiguratorInterface $configurator */ + /** @var JobConfiguratorInterface $configurator */ $configurator = app($className); $configurator->setJob($job); diff --git a/app/Http/Controllers/Import/PrerequisitesController.php b/app/Http/Controllers/Import/PrerequisitesController.php index 17283c5d09..ab4607136c 100644 --- a/app/Http/Controllers/Import/PrerequisitesController.php +++ b/app/Http/Controllers/Import/PrerequisitesController.php @@ -145,6 +145,9 @@ class PrerequisitesController extends Controller return redirect(route('import.index')); } + // update job: + $this->repository->setStatus($importJob, 'has_prereq'); + // redirect to job config: return redirect(route('import.job.configuration.index', [$importJob->key])); diff --git a/app/Import/JobConfiguration/FakeJobConfiguration.php b/app/Import/JobConfiguration/FakeJobConfiguration.php new file mode 100644 index 0000000000..9e1af04bc6 --- /dev/null +++ b/app/Import/JobConfiguration/FakeJobConfiguration.php @@ -0,0 +1,133 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Import\JobConfiguration; + +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use Illuminate\Support\MessageBag; + +/** + * Class FakeJobConfiguration + */ +class FakeJobConfiguration implements JobConfiguratorInterface +{ + /** @var ImportJob */ + private $job; + + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * ConfiguratorInterface constructor. + */ + public function __construct() + { + $this->repository = app(ImportJobRepositoryInterface::class); + } + + /** + * Returns true when the initial configuration for this job is complete. + * + * @return bool + */ + public function configurationComplete(): bool + { + // configuration array of job must have two values: + // 'artist' must be 'david bowie', case insensitive + // 'song' must be 'golden years', case insensitive. + $config = $this->job->configuration; + + return (isset($config['artist']) && 'david bowie' === strtolower($config['artist'])) + && (isset($config['song']) && 'golden years' === strtolower($config['song'])); + } + + /** + * Store any data from the $data array into the job. + * + * @param array $data + * + * @return MessageBag + */ + public function configureJob(array $data): MessageBag + { + $artist = strtolower($data['artist'] ?? ''); + $configuration = $this->job->configuration; + if ($artist === 'david bowie') { + // store artist + $configuration['artist'] = $artist; + } + $song = strtolower($data['song'] ?? ''); + if ($song === 'golden years') { + // store artist + $configuration['song'] = $song; + } + $this->repository->setConfiguration($this->job, $configuration); + $messages = new MessageBag(); + + if (\count($configuration) !== 2) { + + $messages->add('some_key', 'Ignore this error'); + } + + return $messages; + } + + /** + * Return the data required for the next step in the job configuration. + * + * @return array + */ + public function getNextData(): array + { + return []; + } + + /** + * Returns the view of the next step in the job configuration. + * + * @return string + */ + public function getNextView(): string + { + // first configure artist: + $config = $this->job->configuration; + $artist = $config['artist'] ?? ''; + $song = $config['song'] ?? ''; + if (strtolower($artist) !== 'david bowie') { + return 'import.fake.enter-artist'; + } + if (strtolower($song) !== 'golden years') { + return 'import.fake.enter-song'; + } + } + + /** + * @param ImportJob $job + */ + public function setJob(ImportJob $job): void + { + $this->job = $job; + $this->repository->setUser($job->user); + } +} \ No newline at end of file diff --git a/app/Import/JobConfiguration/JobConfiguratorInterface.php b/app/Import/JobConfiguration/JobConfiguratorInterface.php new file mode 100644 index 0000000000..7797ed7c31 --- /dev/null +++ b/app/Import/JobConfiguration/JobConfiguratorInterface.php @@ -0,0 +1,73 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Import\JobConfiguration; + +use FireflyIII\Models\ImportJob; +use Illuminate\Support\MessageBag; + +/** + * Interface JobConfiguratorInterface. + */ +interface JobConfiguratorInterface +{ + /** + * ConfiguratorInterface constructor. + */ + public function __construct(); + + /** + * Store any data from the $data array into the job. Anything in the message bag will be flashed + * as an error to the user, regardless of its content. + * + * @param array $data + * + * @return MessageBag + */ + public function configureJob(array $data): MessageBag; + + /** + * Return the data required for the next step in the job configuration. + * + * @return array + */ + public function getNextData(): array; + + /** + * Returns the view of the next step in the job configuration. + * + * @return string + */ + public function getNextView(): string; + + /** + * Returns true when the initial configuration for this job is complete. + * + * @return bool + */ + public function configurationComplete(): bool; + + /** + * @param ImportJob $job + */ + public function setJob(ImportJob $job): void; +} diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php index e3744e4368..ad12a87d35 100644 --- a/app/Models/ImportJob.php +++ b/app/Models/ImportJob.php @@ -22,12 +22,9 @@ declare(strict_types=1); namespace FireflyIII\Models; -use Crypt; use FireflyIII\Exceptions\FireflyException; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; -use Log; -use Storage; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -37,13 +34,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; */ class ImportJob extends Model { - /** - * @var array - */ - public $validStatus - = [ - 'new', - ]; + /** * The attributes that should be casted to native types. * @@ -58,7 +49,7 @@ class ImportJob extends Model 'transactions' => 'array', ]; /** @var array */ - protected $fillable = ['key', 'user_id', 'file_type', 'source', 'status', 'stage', 'configuration', 'extended_status', 'transactions']; + protected $fillable = ['key', 'user_id', 'file_type', 'provider', 'status', 'stage', 'configuration', 'extended_status', 'transactions']; /** * @param $value @@ -74,49 +65,12 @@ class ImportJob extends Model $key = trim($value); $importJob = auth()->user()->importJobs()->where('key', $key)->first(); if (null !== $importJob) { - // must have valid status: - if (!\in_array($importJob->status, $importJob->validStatus)) { - throw new FireflyException(sprintf('ImportJob with key "%s" has invalid status "%s"', $importJob->key, $importJob->status)); - } - return $importJob; } } throw new NotFoundHttpException; } - /** - * @deprecated - * - * @param int $count - */ - public function addTotalSteps(int $count): void - { - $status = $this->extended_status; - $status['steps'] += $count; - $this->extended_status = $status; - $this->save(); - Log::debug(sprintf('Add %d to total steps for job "%s" making total steps %d', $count, $this->key, $status['steps'])); - } - - /** - * @return string - * @deprecated - * @throws \Illuminate\Contracts\Encryption\DecryptException - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException - */ - public function uploadFileContents(): string - { - $fileName = $this->key . '.upload'; - $disk = Storage::disk('upload'); - $encryptedContent = $disk->get($fileName); - $content = Crypt::decrypt($encryptedContent); - $content = trim($content); - Log::debug(sprintf('Content size is %d bytes.', \strlen($content))); - - return $content; - } - /** * @codeCoverageIgnore * @return \Illuminate\Database\Eloquent\Relations\BelongsTo diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index f62f16e21d..8589785f84 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -116,7 +116,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface */ public function create(string $importProvider): ImportJob { - $count = 0; + $count = 0; $importProvider = strtolower($importProvider); while ($count < 30) { @@ -126,7 +126,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface $importJob = ImportJob::create( [ 'user_id' => $this->user->id, - 'source' => $importProvider, + 'provider' => $importProvider, 'file_type' => '', 'key' => Str::random(12), 'status' => 'new', diff --git a/config/import.php b/config/import.php index 0c691894c2..3c87cb2461 100644 --- a/config/import.php +++ b/config/import.php @@ -5,6 +5,7 @@ use FireflyIII\Import\Configuration\BunqConfigurator; use FireflyIII\Import\Configuration\FileConfigurator; use FireflyIII\Import\Configuration\SpectreConfigurator; use FireflyIII\Import\FileProcessor\CsvProcessor; +use FireflyIII\Import\JobConfiguration\FakeJobConfiguration; use FireflyIII\Import\Prerequisites\BunqPrerequisites; use FireflyIII\Import\Prerequisites\FakePrerequisites; use FireflyIII\Import\Prerequisites\FilePrerequisites; @@ -72,6 +73,7 @@ return [ 'yodlee' => true, ], 'configuration' => [ + 'fake' => FakeJobConfiguration::class, 'file' => FileConfigurator::class, 'bunq' => BunqConfigurator::class, 'spectre' => SpectreConfigurator::class, diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index a65c67c234..fbfbcdc6b2 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -23,201 +23,204 @@ declare(strict_types=1); return [ // status of import: - 'status_wait_title' => 'Please hold...', - 'status_wait_text' => 'This box will disappear in a moment.', - 'status_fatal_title' => 'A fatal error occurred', - 'status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', - 'status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', - 'status_ready_title' => 'Import is ready to start', - 'status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'status_ready_noconfig_text' => 'The import is ready to start. All the configuration you needed to do has been done. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'status_ready_config' => 'Download configuration', - 'status_ready_start' => 'Start the import', - 'status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'status_job_new' => 'The job is brand new.', - 'status_job_configuring' => 'The import is being configured.', - 'status_job_configured' => 'The import is configured.', - 'status_job_running' => 'The import is running.. Please wait..', - 'status_job_error' => 'The job has generated an error.', - 'status_job_finished' => 'The import has finished!', - 'status_running_title' => 'The import is running', - 'status_running_placeholder' => 'Please hold for an update...', - 'status_finished_title' => 'Import routine finished', - 'status_finished_text' => 'The import routine has imported your data.', - 'status_errors_title' => 'Errors during the import', - 'status_errors_single' => 'An error has occurred during the import. It does not appear to be fatal.', - 'status_errors_multi' => 'Some errors occurred during the import. These do not appear to be fatal.', - 'status_bread_crumb' => 'Import status', - 'status_sub_title' => 'Import status', - 'config_sub_title' => 'Set up your import', - 'status_finished_job' => 'The :count transactions imported can be found in tag :tag.', - 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', - 'import_with_key' => 'Import with key \':key\'', + 'status_wait_title' => 'Please hold...', + 'status_wait_text' => 'This box will disappear in a moment.', + 'status_fatal_title' => 'A fatal error occurred', + 'status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'status_ready_title' => 'Import is ready to start', + 'status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'status_ready_noconfig_text' => 'The import is ready to start. All the configuration you needed to do has been done. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'status_ready_config' => 'Download configuration', + 'status_ready_start' => 'Start the import', + 'status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'status_job_new' => 'The job is brand new.', + 'status_job_configuring' => 'The import is being configured.', + 'status_job_configured' => 'The import is configured.', + 'status_job_running' => 'The import is running.. Please wait..', + 'status_job_error' => 'The job has generated an error.', + 'status_job_finished' => 'The import has finished!', + 'status_running_title' => 'The import is running', + 'status_running_placeholder' => 'Please hold for an update...', + 'status_finished_title' => 'Import routine finished', + 'status_finished_text' => 'The import routine has imported your data.', + 'status_errors_title' => 'Errors during the import', + 'status_errors_single' => 'An error has occurred during the import. It does not appear to be fatal.', + 'status_errors_multi' => 'Some errors occurred during the import. These do not appear to be fatal.', + 'status_bread_crumb' => 'Import status', + 'status_sub_title' => 'Import status', + 'config_sub_title' => 'Set up your import', + 'status_finished_job' => 'The :count transactions imported can be found in tag :tag.', + 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', + 'import_with_key' => 'Import with key \':key\'', // file, upload something - 'file_upload_title' => 'Import setup (1/4) - Upload your file', - 'file_upload_text' => 'This routine will help you import files from your bank into Firefly III. Please check out the help pages in the top right corner.', - 'file_upload_fields' => 'Fields', - 'file_upload_help' => 'Select your file', - 'file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'file_upload_type_help' => 'Select the type of file you will upload', - 'file_upload_submit' => 'Upload files', + 'file_upload_title' => 'Import setup (1/4) - Upload your file', + 'file_upload_text' => 'This routine will help you import files from your bank into Firefly III. Please check out the help pages in the top right corner.', + 'file_upload_fields' => 'Fields', + 'file_upload_help' => 'Select your file', + 'file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'file_upload_type_help' => 'Select the type of file you will upload', + 'file_upload_submit' => 'Upload files', // file, upload types - 'import_file_type_csv' => 'CSV (comma separated values)', + 'import_file_type_csv' => 'CSV (comma separated values)', // file, initial config for CSV - 'csv_initial_title' => 'Import setup (2/4) - Basic CSV import setup', - 'csv_initial_text' => 'To be able to import your file correctly, please validate the options below.', - 'csv_initial_box' => 'Basic CSV import setup', - 'csv_initial_box_title' => 'Basic CSV import setup options', - 'csv_initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - 'csv_initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'csv_initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'csv_initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'csv_initial_submit' => 'Continue with step 3/4', + 'csv_initial_title' => 'Import setup (2/4) - Basic CSV import setup', + 'csv_initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'csv_initial_box' => 'Basic CSV import setup', + 'csv_initial_box_title' => 'Basic CSV import setup options', + 'csv_initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'csv_initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'csv_initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'csv_initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'csv_initial_submit' => 'Continue with step 3/4', // file, new options: - 'file_apply_rules_title' => 'Apply rules', - 'file_apply_rules_description' => 'Apply your rules. Note that this slows the import significantly.', - 'file_match_bills_title' => 'Match bills', - 'file_match_bills_description' => 'Match your bills to newly created withdrawals. Note that this slows the import significantly.', + 'file_apply_rules_title' => 'Apply rules', + 'file_apply_rules_description' => 'Apply your rules. Note that this slows the import significantly.', + 'file_match_bills_title' => 'Match bills', + 'file_match_bills_description' => 'Match your bills to newly created withdrawals. Note that this slows the import significantly.', // file, roles config - 'csv_roles_title' => 'Import setup (3/4) - Define each column\'s role', - 'csv_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', - 'csv_roles_table' => 'Table', - 'csv_roles_column_name' => 'Name of column', - 'csv_roles_column_example' => 'Column example data', - 'csv_roles_column_role' => 'Column data meaning', - 'csv_roles_do_map_value' => 'Map these values', - 'csv_roles_column' => 'Column', - 'csv_roles_no_example_data' => 'No example data available', - 'csv_roles_submit' => 'Continue with step 4/4', + 'csv_roles_title' => 'Import setup (3/4) - Define each column\'s role', + 'csv_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'csv_roles_table' => 'Table', + 'csv_roles_column_name' => 'Name of column', + 'csv_roles_column_example' => 'Column example data', + 'csv_roles_column_role' => 'Column data meaning', + 'csv_roles_do_map_value' => 'Map these values', + 'csv_roles_column' => 'Column', + 'csv_roles_no_example_data' => 'No example data available', + 'csv_roles_submit' => 'Continue with step 4/4', // not csv, but normal warning - 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', - 'foreign_amount_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'foreign_amount_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', // file, map data - 'file_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', - 'file_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', - 'file_map_field_value' => 'Field value', - 'file_map_field_mapped_to' => 'Mapped to', - 'map_do_not_map' => '(do not map)', - 'file_map_submit' => 'Start the import', - 'file_nothing_to_map' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'file_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'file_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'file_map_field_value' => 'Field value', + 'file_map_field_mapped_to' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'file_map_submit' => 'Start the import', + 'file_nothing_to_map' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', // map things. - 'column__ignore' => '(ignore this column)', - 'column_account-iban' => 'Asset account (IBAN)', - 'column_account-id' => 'Asset account ID (matching FF3)', - 'column_account-name' => 'Asset account (name)', - 'column_amount' => 'Amount', - 'column_amount_foreign' => 'Amount (in foreign currency)', - 'column_amount_debit' => 'Amount (debit column)', - 'column_amount_credit' => 'Amount (credit column)', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching FF3)', - 'column_bill-name' => 'Bill name', - 'column_budget-id' => 'Budget ID (matching FF3)', - 'column_budget-name' => 'Budget name', - 'column_category-id' => 'Category ID (matching FF3)', - 'column_category-name' => 'Category name', - 'column_currency-code' => 'Currency code (ISO 4217)', - 'column_foreign-currency-code' => 'Foreign currency code (ISO 4217)', - 'column_currency-id' => 'Currency ID (matching FF3)', - 'column_currency-name' => 'Currency name (matching FF3)', - 'column_currency-symbol' => 'Currency symbol (matching FF3)', - 'column_date-interest' => 'Interest calculation date', - 'column_date-book' => 'Transaction booking date', - 'column_date-process' => 'Transaction process date', - 'column_date-transaction' => 'Date', - 'column_date-due' => 'Transaction due date', - 'column_date-payment' => 'Transaction payment date', - 'column_date-invoice' => 'Transaction invoice date', - 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-bic' => 'Opposing account (BIC)', - 'column_opposing-id' => 'Opposing account ID (matching FF3)', - 'column_external-id' => 'External ID', - 'column_opposing-name' => 'Opposing account (name)', - 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', - 'column_ing-debit-credit' => 'ING specific debit/credit indicator', - 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', - 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'column_sepa-db' => 'SEPA Mandate Identifier', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country Code', - 'column_tags-comma' => 'Tags (comma separated)', - 'column_tags-space' => 'Tags (space separated)', - 'column_account-number' => 'Asset account (account number)', - 'column_opposing-number' => 'Opposing account (account number)', - 'column_note' => 'Note(s)', - 'column_internal-reference' => 'Internal reference', + 'column__ignore' => '(ignore this column)', + 'column_account-iban' => 'Asset account (IBAN)', + 'column_account-id' => 'Asset account ID (matching FF3)', + 'column_account-name' => 'Asset account (name)', + 'column_amount' => 'Amount', + 'column_amount_foreign' => 'Amount (in foreign currency)', + 'column_amount_debit' => 'Amount (debit column)', + 'column_amount_credit' => 'Amount (credit column)', + 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', + 'column_bill-id' => 'Bill ID (matching FF3)', + 'column_bill-name' => 'Bill name', + 'column_budget-id' => 'Budget ID (matching FF3)', + 'column_budget-name' => 'Budget name', + 'column_category-id' => 'Category ID (matching FF3)', + 'column_category-name' => 'Category name', + 'column_currency-code' => 'Currency code (ISO 4217)', + 'column_foreign-currency-code' => 'Foreign currency code (ISO 4217)', + 'column_currency-id' => 'Currency ID (matching FF3)', + 'column_currency-name' => 'Currency name (matching FF3)', + 'column_currency-symbol' => 'Currency symbol (matching FF3)', + 'column_date-interest' => 'Interest calculation date', + 'column_date-book' => 'Transaction booking date', + 'column_date-process' => 'Transaction process date', + 'column_date-transaction' => 'Date', + 'column_date-due' => 'Transaction due date', + 'column_date-payment' => 'Transaction payment date', + 'column_date-invoice' => 'Transaction invoice date', + 'column_description' => 'Description', + 'column_opposing-iban' => 'Opposing account (IBAN)', + 'column_opposing-bic' => 'Opposing account (BIC)', + 'column_opposing-id' => 'Opposing account ID (matching FF3)', + 'column_external-id' => 'External ID', + 'column_opposing-name' => 'Opposing account (name)', + 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', + 'column_ing-debit-credit' => 'ING specific debit/credit indicator', + 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', + 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', + 'column_sepa-db' => 'SEPA Mandate Identifier', + 'column_sepa-cc' => 'SEPA Clearing Code', + 'column_sepa-ci' => 'SEPA Creditor Identifier', + 'column_sepa-ep' => 'SEPA External Purpose', + 'column_sepa-country' => 'SEPA Country Code', + 'column_tags-comma' => 'Tags (comma separated)', + 'column_tags-space' => 'Tags (space separated)', + 'column_account-number' => 'Asset account (account number)', + 'column_opposing-number' => 'Opposing account (account number)', + 'column_note' => 'Note(s)', + 'column_internal-reference' => 'Internal reference', // prerequisites - 'prerequisites' => 'Prerequisites', - 'prerequisites_breadcrumb_fake' => 'Prerequisites for fake provider', - 'prerequisites_breadcrumb_file' => 'Prerequisites for file imports', - 'prerequisites_breadcrumb_bunq' => 'Prerequisites for Bunq', - 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', - 'prerequisites_breadcrumb_plaid' => 'Prerequisites for Plaid', - 'prerequisites_breadcrumb_quovo' => 'Prerequisites for Quovo', - 'prerequisites_breadcrumb_yodlee' => 'Prerequisites for Yodlee', + 'prerequisites' => 'Prerequisites', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for fake provider', + 'prerequisites_breadcrumb_file' => 'Prerequisites for file imports', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for Bunq', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_plaid' => 'Prerequisites for Plaid', + 'prerequisites_breadcrumb_quovo' => 'Prerequisites for Quovo', + 'prerequisites_breadcrumb_yodlee' => 'Prerequisites for Yodlee', + // success messages: - 'prerequisites_saved_for_fake' => 'API key stored for fake provider', - 'prerequisites_saved_for_file' => 'Data stored for file imports', - 'prerequisites_saved_for_bunq' => 'API key and IP stored for bunq', - 'prerequisites_saved_for_spectre' => 'App ID and secret stored for Spectre', - 'prerequisites_saved_for_plaid' => 'Data stored for Plaid', - 'prerequisites_saved_for_quovo' => 'Data stored for Quovo', - 'prerequisites_saved_for_yodlee' => 'Data stored for Yodlee', + 'prerequisites_saved_for_fake' => 'API key stored for fake provider', + 'prerequisites_saved_for_file' => 'Data stored for file imports', + 'prerequisites_saved_for_bunq' => 'API key and IP stored for bunq', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored for Spectre', + 'prerequisites_saved_for_plaid' => 'Data stored for Plaid', + 'prerequisites_saved_for_quovo' => 'Data stored for Quovo', + 'prerequisites_saved_for_yodlee' => 'Data stored for Yodlee', // index of import: - 'general_index_title' => 'Import a file', - 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + 'general_index_title' => 'Import a file', + 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', // import provider strings (index): - 'button_fake' => 'Fake an import', - 'button_file' => 'Import a file', - 'button_bunq' => 'Import from bunq', - 'button_spectre' => 'Import using Spectre', - 'button_plaid' => 'Import using Plaid', - 'button_yodlee' => 'Import using Yodlee', - 'button_quovo' => 'Import using Quovo', + 'button_fake' => 'Fake an import', + 'button_file' => 'Import a file', + 'button_bunq' => 'Import from bunq', + 'button_spectre' => 'Import using Spectre', + 'button_plaid' => 'Import using Plaid', + 'button_yodlee' => 'Import using Yodlee', + 'button_quovo' => 'Import using Quovo', // global config box - 'global_config_title' => 'Global import configuration', - 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + 'global_config_title' => 'Global import configuration', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', // prereq box: - 'need_prereq_title' => 'Import prerequisites', - 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', - 'do_prereq_fake' => 'Prerequisites for the fake provider', - 'do_prereq_file' => 'Prerequisites for file imports', - 'do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', // provider config box: - 'can_config_title' => 'Import configuration', - 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', - 'do_config_fake' => 'Configuration for the fake provider', - 'do_config_file' => 'Configuration for file imports', - 'do_config_bunq' => 'Configuration for bunq imports', - 'do_config_spectre' => 'Configuration for imports from Spectre', - 'do_config_plaid' => 'Configuration for imports from Plaid', - 'do_config_yodlee' => 'Configuration for imports from Yodlee', - 'do_config_quovo' => 'Configuration for imports from Quovo', + 'can_config_title' => 'Import configuration', + 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'do_config_fake' => 'Configuration for the fake provider', + 'do_config_file' => 'Configuration for file imports', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', + + // job configuration: + 'job_configuration_breadcrumb' => 'Configuration for job ":key"', // import index page: - 'index_breadcrumb' => 'Index', - + 'index_breadcrumb' => 'Index', 'upload_error' => 'The file you have uploaded could not be processed. Possibly it is of an invalid file type or encoding. The log files will have more information.', diff --git a/resources/views/import/fake/enter-artist.twig b/resources/views/import/fake/enter-artist.twig new file mode 100644 index 0000000000..dcd25f50ac --- /dev/null +++ b/resources/views/import/fake/enter-artist.twig @@ -0,0 +1,53 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render }} +{% endblock %} +{% block content %} +
      +
      +
      +
      +

      Enter artist for fake import

      +
      +
      +

      + Enter "David Bowie", no matter the capitalization. +

      +
      +
      + +
      +
      + +
      + +
      +
      +
      +
      +

      Fields be here.

      +
      +
      + {{ ExpandedForm.text('artist') }} +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} diff --git a/resources/views/import/fake/enter-song.twig b/resources/views/import/fake/enter-song.twig new file mode 100644 index 0000000000..1874b386a5 --- /dev/null +++ b/resources/views/import/fake/enter-song.twig @@ -0,0 +1,53 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render }} +{% endblock %} +{% block content %} +
      +
      +
      +
      +

      Enter song for fake import

      +
      +
      +

      + Enter "golden years", no matter the capitalization. +

      +
      +
      + +
      +
      + +
      + +
      +
      +
      +
      +

      Fields be here.

      +
      +
      + {{ ExpandedForm.text('song') }} +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index a145e8a9f5..b8611f7554 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -22,6 +22,7 @@ declare(strict_types=1); use Carbon\Carbon; use DaveJamesMiller\Breadcrumbs\BreadcrumbsGenerator; +use DaveJamesMiller\Breadcrumbs\Exceptions\DuplicateBreadcrumbException; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Attachment; @@ -42,991 +43,994 @@ use FireflyIII\Models\TransactionType; use FireflyIII\User; use Illuminate\Support\Collection; -// HOME -Breadcrumbs::register( - 'home', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->push(trans('breadcrumbs.home'), route('index')); - } -); +try { + // HOME + Breadcrumbs::register( + 'home', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->push(trans('breadcrumbs.home'), route('index')); + } + ); -Breadcrumbs::register( - 'index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->push(trans('breadcrumbs.home'), route('index')); - } -); + Breadcrumbs::register( + 'index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->push(trans('breadcrumbs.home'), route('index')); + } + ); -// ACCOUNTS -Breadcrumbs::register( - 'accounts.index', - function (BreadCrumbsGenerator $breadcrumbs, string $what) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.' . strtolower(e($what)) . '_accounts'), route('accounts.index', [$what])); - } -); + // ACCOUNTS + Breadcrumbs::register( + 'accounts.index', + function (BreadCrumbsGenerator $breadcrumbs, string $what) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.' . strtolower(e($what)) . '_accounts'), route('accounts.index', [$what])); + } + ); -Breadcrumbs::register( - 'accounts.create', - function (BreadCrumbsGenerator $breadcrumbs, string $what) { - $breadcrumbs->parent('accounts.index', $what); - $breadcrumbs->push(trans('firefly.new_' . strtolower(e($what)) . '_account'), route('accounts.create', [$what])); - } -); + Breadcrumbs::register( + 'accounts.create', + function (BreadCrumbsGenerator $breadcrumbs, string $what) { + $breadcrumbs->parent('accounts.index', $what); + $breadcrumbs->push(trans('firefly.new_' . strtolower(e($what)) . '_account'), route('accounts.create', [$what])); + } + ); -Breadcrumbs::register( - 'accounts.show', - function (BreadCrumbsGenerator $breadcrumbs, Account $account, Carbon $start = null, Carbon $end = null) { - $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); + Breadcrumbs::register( + 'accounts.show', + function (BreadCrumbsGenerator $breadcrumbs, Account $account, Carbon $start = null, Carbon $end = null) { + $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); + + $breadcrumbs->parent('accounts.index', $what); + $breadcrumbs->push($account->name, route('accounts.show', [$account->id])); + if (null !== $start && null !== $end) { + $title = trans( + 'firefly.between_dates_breadcrumb', + ['start' => $start ? $start->formatLocalized((string)trans('config.month_and_day')) : '', + 'end' => $end ? $end->formatLocalized((string)trans('config.month_and_day')) : '',] + ); + $breadcrumbs->push($title, route('accounts.show', $account)); + } + } + ); + + Breadcrumbs::register( + 'accounts.show.all', + function (BreadCrumbsGenerator $breadcrumbs, Account $account, Carbon $start = null, Carbon $end = null) { + $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); + + $breadcrumbs->parent('accounts.index', $what); + $breadcrumbs->push($account->name, route('accounts.show', [$account->id])); + } + ); + + Breadcrumbs::register( + 'accounts.reconcile', + function (BreadCrumbsGenerator $breadcrumbs, Account $account) { + $breadcrumbs->parent('accounts.show', $account); + $breadcrumbs->push(trans('firefly.reconcile_account', ['account' => $account->name]), route('accounts.reconcile', [$account->id])); + } + ); + + Breadcrumbs::register( + 'accounts.reconcile.show', + function (BreadCrumbsGenerator $breadcrumbs, Account $account, TransactionJournal $journal) { + $breadcrumbs->parent('accounts.show', $account); + $title = trans('firefly.reconciliation') . ' "' . $journal->description . '"'; + $breadcrumbs->push($title, route('accounts.reconcile.show', [$journal->id])); + } + ); + + Breadcrumbs::register( + 'accounts.delete', + function (BreadCrumbsGenerator $breadcrumbs, Account $account) { + $breadcrumbs->parent('accounts.show', $account); + $breadcrumbs->push(trans('firefly.delete_account', ['name' => $account->name]), route('accounts.delete', [$account->id])); + } + ); + + Breadcrumbs::register( + 'accounts.edit', + function (BreadCrumbsGenerator $breadcrumbs, Account $account) { + $breadcrumbs->parent('accounts.show', $account); + $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); + + $breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => $account->name]), route('accounts.edit', [$account->id])); + } + ); + + // ADMIN + Breadcrumbs::register( + 'admin.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.administration'), route('admin.index')); + } + ); + + Breadcrumbs::register( + 'admin.users', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('admin.index'); + $breadcrumbs->push(trans('firefly.list_all_users'), route('admin.users')); + } + ); + + Breadcrumbs::register( + 'admin.users.show', + function (BreadCrumbsGenerator $breadcrumbs, User $user) { + $breadcrumbs->parent('admin.users'); + $breadcrumbs->push(trans('firefly.single_user_administration', ['email' => $user->email]), route('admin.users.show', [$user->id])); + } + ); + Breadcrumbs::register( + 'admin.users.edit', + function (BreadCrumbsGenerator $breadcrumbs, User $user) { + $breadcrumbs->parent('admin.users'); + $breadcrumbs->push(trans('firefly.edit_user', ['email' => $user->email]), route('admin.users.edit', [$user->id])); + } + ); + Breadcrumbs::register( + 'admin.users.delete', + function (BreadCrumbsGenerator $breadcrumbs, User $user) { + $breadcrumbs->parent('admin.users'); + $breadcrumbs->push(trans('firefly.delete_user', ['email' => $user->email]), route('admin.users.delete', [$user->id])); + } + ); + + Breadcrumbs::register( + 'admin.users.domains', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('admin.index'); + $breadcrumbs->push(trans('firefly.blocked_domains'), route('admin.users.domains')); + } + ); + + Breadcrumbs::register( + 'admin.configuration.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('admin.index'); + $breadcrumbs->push(trans('firefly.instance_configuration'), route('admin.configuration.index')); + } + ); + Breadcrumbs::register( + 'admin.update-check', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('admin.index'); + $breadcrumbs->push(trans('firefly.update_check_title'), route('admin.update-check')); + } + ); + + Breadcrumbs::register( + 'admin.links.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('admin.index'); + $breadcrumbs->push(trans('firefly.journal_link_configuration'), route('admin.links.index')); + } + ); + + Breadcrumbs::register( + 'admin.links.create', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('admin.links.index'); + $breadcrumbs->push(trans('firefly.create_new_link_type'), route('admin.links.create')); + } + ); + + Breadcrumbs::register( + 'admin.links.show', + function (BreadCrumbsGenerator $breadcrumbs, LinkType $linkType) { + $breadcrumbs->parent('admin.links.index'); + $breadcrumbs->push(trans('firefly.overview_for_link', ['name' => $linkType->name]), route('admin.links.show', [$linkType->id])); + } + ); + + Breadcrumbs::register( + 'admin.links.edit', + function (BreadCrumbsGenerator $breadcrumbs, LinkType $linkType) { + $breadcrumbs->parent('admin.links.index'); + $breadcrumbs->push(trans('firefly.edit_link_type', ['name' => $linkType->name]), route('admin.links.edit', [$linkType->id])); + } + ); + + Breadcrumbs::register( + 'admin.links.delete', + function (BreadCrumbsGenerator $breadcrumbs, LinkType $linkType) { + $breadcrumbs->parent('admin.links.index'); + $breadcrumbs->push(trans('firefly.delete_link_type', ['name' => $linkType->name]), route('admin.links.delete', [$linkType->id])); + } + ); + + Breadcrumbs::register( + 'transactions.link.delete', + function (BreadCrumbsGenerator $breadcrumbs, TransactionJournalLink $link) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.delete_journal_link'), route('transactions.link.delete', $link->id)); + } + ); + + // ATTACHMENTS + Breadcrumbs::register( + 'attachments.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.attachments'), route('attachments.index')); + } + ); + + Breadcrumbs::register( + 'attachments.edit', + function (BreadCrumbsGenerator $breadcrumbs, Attachment $attachment) { + $object = $attachment->attachable; + if ($object instanceof TransactionJournal) { + $breadcrumbs->parent('transactions.show', $object); + $breadcrumbs->push($attachment->filename, route('attachments.edit', [$attachment])); + } else { + throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object)); + } + } + ); + Breadcrumbs::register( + 'attachments.delete', + function (BreadCrumbsGenerator $breadcrumbs, Attachment $attachment) { + $object = $attachment->attachable; + if ($object instanceof TransactionJournal) { + $breadcrumbs->parent('transactions.show', $object); + $breadcrumbs->push(trans('firefly.delete_attachment', ['name' => $attachment->filename]), route('attachments.edit', [$attachment])); + } else { + throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object)); + } + } + ); + + // BILLS + Breadcrumbs::register( + 'bills.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.bills'), route('bills.index')); + } + ); + Breadcrumbs::register( + 'bills.create', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('bills.index'); + $breadcrumbs->push(trans('breadcrumbs.newBill'), route('bills.create')); + } + ); + + Breadcrumbs::register( + 'bills.edit', + function (BreadCrumbsGenerator $breadcrumbs, Bill $bill) { + $breadcrumbs->parent('bills.show', $bill); + $breadcrumbs->push(trans('breadcrumbs.edit_bill', ['name' => $bill->name]), route('bills.edit', [$bill->id])); + } + ); + Breadcrumbs::register( + 'bills.delete', + function (BreadCrumbsGenerator $breadcrumbs, Bill $bill) { + $breadcrumbs->parent('bills.show', $bill); + $breadcrumbs->push(trans('breadcrumbs.delete_bill', ['name' => $bill->name]), route('bills.delete', [$bill->id])); + } + ); + + Breadcrumbs::register( + 'bills.show', + function (BreadCrumbsGenerator $breadcrumbs, Bill $bill) { + $breadcrumbs->parent('bills.index'); + $breadcrumbs->push($bill->name, route('bills.show', [$bill->id])); + } + ); + + // BUDGETS + Breadcrumbs::register( + 'budgets.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.budgets'), route('budgets.index')); + } + ); + Breadcrumbs::register( + 'budgets.create', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('budgets.index'); + $breadcrumbs->push(trans('firefly.create_new_budget'), route('budgets.create')); + } + ); + + Breadcrumbs::register( + 'budgets.edit', + function (BreadCrumbsGenerator $breadcrumbs, Budget $budget) { + $breadcrumbs->parent('budgets.show', $budget); + $breadcrumbs->push(trans('firefly.edit_budget', ['name' => $budget->name]), route('budgets.edit', [$budget->id])); + } + ); + Breadcrumbs::register( + 'budgets.delete', + function (BreadCrumbsGenerator $breadcrumbs, Budget $budget) { + $breadcrumbs->parent('budgets.show', $budget); + $breadcrumbs->push(trans('firefly.delete_budget', ['name' => $budget->name]), route('budgets.delete', [$budget->id])); + } + ); + + Breadcrumbs::register( + 'budgets.no-budget', + function (BreadCrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { + $breadcrumbs->parent('budgets.index'); + $breadcrumbs->push(trans('firefly.journals_without_budget'), route('budgets.no-budget')); + + // push when is all: + if ('all' === $moment) { + $breadcrumbs->push(trans('firefly.everything'), route('budgets.no-budget', ['all'])); + } + // when is specific period or when empty: + if ('all' !== $moment && '(nothing)' !== $moment) { + $title = trans( + 'firefly.between_dates_breadcrumb', + ['start' => $start->formatLocalized((string)trans('config.month_and_day')), + 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + ); + $breadcrumbs->push($title, route('budgets.no-budget', [$moment])); + } + } + ); + + Breadcrumbs::register( + 'budgets.show', + function (BreadCrumbsGenerator $breadcrumbs, Budget $budget) { + $breadcrumbs->parent('budgets.index'); + $breadcrumbs->push($budget->name, route('budgets.show', [$budget->id])); + $breadcrumbs->push(trans('firefly.everything'), route('budgets.show', [$budget->id])); + } + ); + + Breadcrumbs::register( + 'budgets.show.limit', + function (BreadCrumbsGenerator $breadcrumbs, Budget $budget, BudgetLimit $budgetLimit) { + $breadcrumbs->parent('budgets.index'); + $breadcrumbs->push($budget->name, route('budgets.show', [$budget->id])); - $breadcrumbs->parent('accounts.index', $what); - $breadcrumbs->push($account->name, route('accounts.show', [$account->id])); - if (null !== $start && null !== $end) { $title = trans( 'firefly.between_dates_breadcrumb', - ['start' => $start ? $start->formatLocalized((string)trans('config.month_and_day')) : '', - 'end' => $end ? $end->formatLocalized((string)trans('config.month_and_day')) : '',] + ['start' => $budgetLimit->start_date->formatLocalized((string)trans('config.month_and_day')), + 'end' => $budgetLimit->end_date->formatLocalized((string)trans('config.month_and_day')),] ); - $breadcrumbs->push($title, route('accounts.show', $account)); - } - } -); -Breadcrumbs::register( - 'accounts.show.all', - function (BreadCrumbsGenerator $breadcrumbs, Account $account, Carbon $start = null, Carbon $end = null) { - $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); - - $breadcrumbs->parent('accounts.index', $what); - $breadcrumbs->push($account->name, route('accounts.show', [$account->id])); - } -); - -Breadcrumbs::register( - 'accounts.reconcile', - function (BreadCrumbsGenerator $breadcrumbs, Account $account) { - $breadcrumbs->parent('accounts.show', $account); - $breadcrumbs->push(trans('firefly.reconcile_account', ['account' => $account->name]), route('accounts.reconcile', [$account->id])); - } -); - -Breadcrumbs::register( - 'accounts.reconcile.show', - function (BreadCrumbsGenerator $breadcrumbs, Account $account, TransactionJournal $journal) { - $breadcrumbs->parent('accounts.show', $account); - $title = trans('firefly.reconciliation') . ' "' . $journal->description . '"'; - $breadcrumbs->push($title, route('accounts.reconcile.show', [$journal->id])); - } -); - -Breadcrumbs::register( - 'accounts.delete', - function (BreadCrumbsGenerator $breadcrumbs, Account $account) { - $breadcrumbs->parent('accounts.show', $account); - $breadcrumbs->push(trans('firefly.delete_account', ['name' => $account->name]), route('accounts.delete', [$account->id])); - } -); - -Breadcrumbs::register( - 'accounts.edit', - function (BreadCrumbsGenerator $breadcrumbs, Account $account) { - $breadcrumbs->parent('accounts.show', $account); - $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); - - $breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => $account->name]), route('accounts.edit', [$account->id])); - } -); - -// ADMIN -Breadcrumbs::register( - 'admin.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.administration'), route('admin.index')); - } -); - -Breadcrumbs::register( - 'admin.users', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('admin.index'); - $breadcrumbs->push(trans('firefly.list_all_users'), route('admin.users')); - } -); - -Breadcrumbs::register( - 'admin.users.show', - function (BreadCrumbsGenerator $breadcrumbs, User $user) { - $breadcrumbs->parent('admin.users'); - $breadcrumbs->push(trans('firefly.single_user_administration', ['email' => $user->email]), route('admin.users.show', [$user->id])); - } -); -Breadcrumbs::register( - 'admin.users.edit', - function (BreadCrumbsGenerator $breadcrumbs, User $user) { - $breadcrumbs->parent('admin.users'); - $breadcrumbs->push(trans('firefly.edit_user', ['email' => $user->email]), route('admin.users.edit', [$user->id])); - } -); -Breadcrumbs::register( - 'admin.users.delete', - function (BreadCrumbsGenerator $breadcrumbs, User $user) { - $breadcrumbs->parent('admin.users'); - $breadcrumbs->push(trans('firefly.delete_user', ['email' => $user->email]), route('admin.users.delete', [$user->id])); - } -); - -Breadcrumbs::register( - 'admin.users.domains', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('admin.index'); - $breadcrumbs->push(trans('firefly.blocked_domains'), route('admin.users.domains')); - } -); - -Breadcrumbs::register( - 'admin.configuration.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('admin.index'); - $breadcrumbs->push(trans('firefly.instance_configuration'), route('admin.configuration.index')); - } -); -Breadcrumbs::register( - 'admin.update-check', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('admin.index'); - $breadcrumbs->push(trans('firefly.update_check_title'), route('admin.update-check')); - } -); - -Breadcrumbs::register( - 'admin.links.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('admin.index'); - $breadcrumbs->push(trans('firefly.journal_link_configuration'), route('admin.links.index')); - } -); - -Breadcrumbs::register( - 'admin.links.create', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('admin.links.index'); - $breadcrumbs->push(trans('firefly.create_new_link_type'), route('admin.links.create')); - } -); - -Breadcrumbs::register( - 'admin.links.show', - function (BreadCrumbsGenerator $breadcrumbs, LinkType $linkType) { - $breadcrumbs->parent('admin.links.index'); - $breadcrumbs->push(trans('firefly.overview_for_link', ['name' => $linkType->name]), route('admin.links.show', [$linkType->id])); - } -); - -Breadcrumbs::register( - 'admin.links.edit', - function (BreadCrumbsGenerator $breadcrumbs, LinkType $linkType) { - $breadcrumbs->parent('admin.links.index'); - $breadcrumbs->push(trans('firefly.edit_link_type', ['name' => $linkType->name]), route('admin.links.edit', [$linkType->id])); - } -); - -Breadcrumbs::register( - 'admin.links.delete', - function (BreadCrumbsGenerator $breadcrumbs, LinkType $linkType) { - $breadcrumbs->parent('admin.links.index'); - $breadcrumbs->push(trans('firefly.delete_link_type', ['name' => $linkType->name]), route('admin.links.delete', [$linkType->id])); - } -); - -Breadcrumbs::register( - 'transactions.link.delete', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournalLink $link) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.delete_journal_link'), route('transactions.link.delete', $link->id)); - } -); - -// ATTACHMENTS -Breadcrumbs::register( - 'attachments.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.attachments'), route('attachments.index')); - } -); - -Breadcrumbs::register( - 'attachments.edit', - function (BreadCrumbsGenerator $breadcrumbs, Attachment $attachment) { - $object = $attachment->attachable; - if ($object instanceof TransactionJournal) { - $breadcrumbs->parent('transactions.show', $object); - $breadcrumbs->push($attachment->filename, route('attachments.edit', [$attachment])); - } else { - throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object)); - } - } -); -Breadcrumbs::register( - 'attachments.delete', - function (BreadCrumbsGenerator $breadcrumbs, Attachment $attachment) { - $object = $attachment->attachable; - if ($object instanceof TransactionJournal) { - $breadcrumbs->parent('transactions.show', $object); - $breadcrumbs->push(trans('firefly.delete_attachment', ['name' => $attachment->filename]), route('attachments.edit', [$attachment])); - } else { - throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object)); - } - } -); - -// BILLS -Breadcrumbs::register( - 'bills.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.bills'), route('bills.index')); - } -); -Breadcrumbs::register( - 'bills.create', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('bills.index'); - $breadcrumbs->push(trans('breadcrumbs.newBill'), route('bills.create')); - } -); - -Breadcrumbs::register( - 'bills.edit', - function (BreadCrumbsGenerator $breadcrumbs, Bill $bill) { - $breadcrumbs->parent('bills.show', $bill); - $breadcrumbs->push(trans('breadcrumbs.edit_bill', ['name' => $bill->name]), route('bills.edit', [$bill->id])); - } -); -Breadcrumbs::register( - 'bills.delete', - function (BreadCrumbsGenerator $breadcrumbs, Bill $bill) { - $breadcrumbs->parent('bills.show', $bill); - $breadcrumbs->push(trans('breadcrumbs.delete_bill', ['name' => $bill->name]), route('bills.delete', [$bill->id])); - } -); - -Breadcrumbs::register( - 'bills.show', - function (BreadCrumbsGenerator $breadcrumbs, Bill $bill) { - $breadcrumbs->parent('bills.index'); - $breadcrumbs->push($bill->name, route('bills.show', [$bill->id])); - } -); - -// BUDGETS -Breadcrumbs::register( - 'budgets.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.budgets'), route('budgets.index')); - } -); -Breadcrumbs::register( - 'budgets.create', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('budgets.index'); - $breadcrumbs->push(trans('firefly.create_new_budget'), route('budgets.create')); - } -); - -Breadcrumbs::register( - 'budgets.edit', - function (BreadCrumbsGenerator $breadcrumbs, Budget $budget) { - $breadcrumbs->parent('budgets.show', $budget); - $breadcrumbs->push(trans('firefly.edit_budget', ['name' => $budget->name]), route('budgets.edit', [$budget->id])); - } -); -Breadcrumbs::register( - 'budgets.delete', - function (BreadCrumbsGenerator $breadcrumbs, Budget $budget) { - $breadcrumbs->parent('budgets.show', $budget); - $breadcrumbs->push(trans('firefly.delete_budget', ['name' => $budget->name]), route('budgets.delete', [$budget->id])); - } -); - -Breadcrumbs::register( - 'budgets.no-budget', - function (BreadCrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { - $breadcrumbs->parent('budgets.index'); - $breadcrumbs->push(trans('firefly.journals_without_budget'), route('budgets.no-budget')); - - // push when is all: - if ('all' === $moment) { - $breadcrumbs->push(trans('firefly.everything'), route('budgets.no-budget', ['all'])); - } - // when is specific period or when empty: - if ('all' !== $moment && '(nothing)' !== $moment) { - $title = trans( - 'firefly.between_dates_breadcrumb', - ['start' => $start->formatLocalized((string)trans('config.month_and_day')), - 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + $breadcrumbs->push( + $title, + route('budgets.show.limit', [$budget->id, $budgetLimit->id]) ); - $breadcrumbs->push($title, route('budgets.no-budget', [$moment])); } - } -); + ); -Breadcrumbs::register( - 'budgets.show', - function (BreadCrumbsGenerator $breadcrumbs, Budget $budget) { - $breadcrumbs->parent('budgets.index'); - $breadcrumbs->push($budget->name, route('budgets.show', [$budget->id])); - $breadcrumbs->push(trans('firefly.everything'), route('budgets.show', [$budget->id])); - } -); - -Breadcrumbs::register( - 'budgets.show.limit', - function (BreadCrumbsGenerator $breadcrumbs, Budget $budget, BudgetLimit $budgetLimit) { - $breadcrumbs->parent('budgets.index'); - $breadcrumbs->push($budget->name, route('budgets.show', [$budget->id])); - - $title = trans( - 'firefly.between_dates_breadcrumb', - ['start' => $budgetLimit->start_date->formatLocalized((string)trans('config.month_and_day')), - 'end' => $budgetLimit->end_date->formatLocalized((string)trans('config.month_and_day')),] - ); - - $breadcrumbs->push( - $title, - route('budgets.show.limit', [$budget->id, $budgetLimit->id]) - ); - } -); - -// CATEGORIES -Breadcrumbs::register( - 'categories.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.categories'), route('categories.index')); - } -); -Breadcrumbs::register( - 'categories.create', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('categories.index'); - $breadcrumbs->push(trans('firefly.new_category'), route('categories.create')); - } -); - -Breadcrumbs::register( - 'categories.edit', - function (BreadCrumbsGenerator $breadcrumbs, Category $category) { - $breadcrumbs->parent('categories.show', $category, '(nothing)', new Carbon, new Carbon); - $breadcrumbs->push(trans('firefly.edit_category', ['name' => $category->name]), route('categories.edit', [$category->id])); - } -); -Breadcrumbs::register( - 'categories.delete', - function (BreadCrumbsGenerator $breadcrumbs, Category $category) { - $breadcrumbs->parent('categories.show', $category, '(nothing)', new Carbon, new Carbon); - $breadcrumbs->push(trans('firefly.delete_category', ['name' => $category->name]), route('categories.delete', [$category->id])); - } -); - -Breadcrumbs::register( - 'categories.show', - function (BreadCrumbsGenerator $breadcrumbs, Category $category, string $moment, Carbon $start, Carbon $end) { - $breadcrumbs->parent('categories.index'); - $breadcrumbs->push($category->name, route('categories.show', [$category->id])); - - // push when is all: - if ('all' === $moment) { - $breadcrumbs->push(trans('firefly.everything'), route('categories.show', [$category->id, 'all'])); + // CATEGORIES + Breadcrumbs::register( + 'categories.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.categories'), route('categories.index')); } - // when is specific period or when empty: - if ('all' !== $moment && '(nothing)' !== $moment) { - $title = trans( - 'firefly.between_dates_breadcrumb', - ['start' => $start->formatLocalized((string)trans('config.month_and_day')), - 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + ); + Breadcrumbs::register( + 'categories.create', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('categories.index'); + $breadcrumbs->push(trans('firefly.new_category'), route('categories.create')); + } + ); + + Breadcrumbs::register( + 'categories.edit', + function (BreadCrumbsGenerator $breadcrumbs, Category $category) { + $breadcrumbs->parent('categories.show', $category, '(nothing)', new Carbon, new Carbon); + $breadcrumbs->push(trans('firefly.edit_category', ['name' => $category->name]), route('categories.edit', [$category->id])); + } + ); + Breadcrumbs::register( + 'categories.delete', + function (BreadCrumbsGenerator $breadcrumbs, Category $category) { + $breadcrumbs->parent('categories.show', $category, '(nothing)', new Carbon, new Carbon); + $breadcrumbs->push(trans('firefly.delete_category', ['name' => $category->name]), route('categories.delete', [$category->id])); + } + ); + + Breadcrumbs::register( + 'categories.show', + function (BreadCrumbsGenerator $breadcrumbs, Category $category, string $moment, Carbon $start, Carbon $end) { + $breadcrumbs->parent('categories.index'); + $breadcrumbs->push($category->name, route('categories.show', [$category->id])); + + // push when is all: + if ('all' === $moment) { + $breadcrumbs->push(trans('firefly.everything'), route('categories.show', [$category->id, 'all'])); + } + // when is specific period or when empty: + if ('all' !== $moment && '(nothing)' !== $moment) { + $title = trans( + 'firefly.between_dates_breadcrumb', + ['start' => $start->formatLocalized((string)trans('config.month_and_day')), + 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + ); + $breadcrumbs->push($title, route('categories.show', [$category->id, $moment])); + } + } + ); + + Breadcrumbs::register( + 'categories.no-category', + function (BreadCrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { + $breadcrumbs->parent('categories.index'); + $breadcrumbs->push(trans('firefly.journals_without_category'), route('categories.no-category')); + + // push when is all: + if ('all' === $moment) { + $breadcrumbs->push(trans('firefly.everything'), route('categories.no-category', ['all'])); + } + // when is specific period or when empty: + if ('all' !== $moment && '(nothing)' !== $moment) { + $title = trans( + 'firefly.between_dates_breadcrumb', + ['start' => $start->formatLocalized((string)trans('config.month_and_day')), + 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + ); + $breadcrumbs->push($title, route('categories.no-category', [$moment])); + } + } + ); + + // CURRENCIES + Breadcrumbs::register( + 'currencies.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.currencies'), route('currencies.index')); + } + ); + + Breadcrumbs::register( + 'currencies.create', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('currencies.index'); + $breadcrumbs->push(trans('firefly.create_currency'), route('currencies.create')); + } + ); + + Breadcrumbs::register( + 'currencies.edit', + function (BreadCrumbsGenerator $breadcrumbs, TransactionCurrency $currency) { + $breadcrumbs->parent('currencies.index'); + $breadcrumbs->push(trans('breadcrumbs.edit_currency', ['name' => $currency->name]), route('currencies.edit', [$currency->id])); + } + ); + Breadcrumbs::register( + 'currencies.delete', + function (BreadCrumbsGenerator $breadcrumbs, TransactionCurrency $currency) { + $breadcrumbs->parent('currencies.index'); + $breadcrumbs->push(trans('breadcrumbs.delete_currency', ['name' => $currency->name]), route('currencies.delete', [$currency->id])); + } + ); + + // EXPORT + Breadcrumbs::register( + 'export.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.export_data'), route('export.index')); + } + ); + + // PIGGY BANKS + Breadcrumbs::register( + 'piggy-banks.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.piggyBanks'), route('piggy-banks.index')); + } + ); + Breadcrumbs::register( + 'piggy-banks.create', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('piggy-banks.index'); + $breadcrumbs->push(trans('breadcrumbs.newPiggyBank'), route('piggy-banks.create')); + } + ); + + Breadcrumbs::register( + 'piggy-banks.edit', + function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { + $breadcrumbs->parent('piggy-banks.show', $piggyBank); + $breadcrumbs->push(trans('breadcrumbs.edit_piggyBank', ['name' => $piggyBank->name]), route('piggy-banks.edit', [$piggyBank->id])); + } + ); + Breadcrumbs::register( + 'piggy-banks.delete', + function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { + $breadcrumbs->parent('piggy-banks.show', $piggyBank); + $breadcrumbs->push(trans('firefly.delete_piggy_bank', ['name' => $piggyBank->name]), route('piggy-banks.delete', [$piggyBank->id])); + } + ); + + Breadcrumbs::register( + 'piggy-banks.show', + function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { + $breadcrumbs->parent('piggy-banks.index'); + $breadcrumbs->push($piggyBank->name, route('piggy-banks.show', [$piggyBank->id])); + } + ); + + Breadcrumbs::register( + 'piggy-banks.add-money-mobile', + function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { + $breadcrumbs->parent('piggy-banks.show', $piggyBank); + $breadcrumbs->push(trans('firefly.add_money_to_piggy', ['name' => $piggyBank->name]), route('piggy-banks.add-money-mobile', [$piggyBank->id])); + } + ); + + Breadcrumbs::register( + 'piggy-banks.remove-money-mobile', + function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { + $breadcrumbs->parent('piggy-banks.show', $piggyBank); + $breadcrumbs->push( + trans('firefly.remove_money_from_piggy_title', ['name' => $piggyBank->name]), + route('piggy-banks.remove-money-mobile', [$piggyBank->id]) ); - $breadcrumbs->push($title, route('categories.show', [$category->id, $moment])); } - } -); + ); -Breadcrumbs::register( - 'categories.no-category', - function (BreadCrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { - $breadcrumbs->parent('categories.index'); - $breadcrumbs->push(trans('firefly.journals_without_category'), route('categories.no-category')); - - // push when is all: - if ('all' === $moment) { - $breadcrumbs->push(trans('firefly.everything'), route('categories.no-category', ['all'])); + // IMPORT + Breadcrumbs::register( + 'import.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.import_index_title'), route('import.index')); } - // when is specific period or when empty: - if ('all' !== $moment && '(nothing)' !== $moment) { - $title = trans( - 'firefly.between_dates_breadcrumb', - ['start' => $start->formatLocalized((string)trans('config.month_and_day')), - 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + ); + + Breadcrumbs::register( + 'import.job.configuration.index', + function (BreadCrumbsGenerator $breadcrumbs, ImportJob $job) { + $breadcrumbs->parent('import.index'); + $breadcrumbs->push(trans('import.job_configuration_breadcrumb', ['key' => $job->key]), route('import.job.configuration.index', [$job->key])); + } + ); + + Breadcrumbs::register( + 'import.prerequisites.index', + function (BreadCrumbsGenerator $breadcrumbs, string $importProvider) { + $breadcrumbs->parent('import.index'); + $breadcrumbs->push(trans('import.prerequisites_breadcrumb_' . $importProvider), route('import.prerequisites.index', [$importProvider])); + } + ); + + + Breadcrumbs::register( + 'import.status', + function (BreadCrumbsGenerator $breadcrumbs, ImportJob $job) { + $breadcrumbs->parent('import.index'); + $breadcrumbs->push(trans('import.status_bread_crumb', ['key' => $job->key]), route('import.status', [$job->key])); + } + ); + + + // PREFERENCES + Breadcrumbs::register( + 'preferences.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.preferences'), route('preferences.index')); + } + ); + + Breadcrumbs::register( + 'profile.code', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.profile'), route('profile.index')); + } + ); + + // PROFILE + Breadcrumbs::register( + 'profile.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.profile'), route('profile.index')); + } + ); + Breadcrumbs::register( + 'profile.change-password', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('profile.index'); + $breadcrumbs->push(trans('breadcrumbs.changePassword'), route('profile.change-password')); + } + ); + + Breadcrumbs::register( + 'profile.change-email', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('profile.index'); + $breadcrumbs->push(trans('breadcrumbs.change_email'), route('profile.change-email')); + } + ); + + Breadcrumbs::register( + 'profile.delete-account', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('profile.index'); + $breadcrumbs->push(trans('firefly.delete_account'), route('profile.delete-account')); + } + ); + + // REPORTS + Breadcrumbs::register( + 'reports.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.reports'), route('reports.index')); + } + ); + + Breadcrumbs::register( + 'reports.report.audit', + function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, Carbon $start, Carbon $end) { + $breadcrumbs->parent('reports.index'); + + $monthFormat = (string)trans('config.month_and_day'); + $startString = $start->formatLocalized($monthFormat); + $endString = $end->formatLocalized($monthFormat); + $title = (string)trans('firefly.report_audit', ['start' => $startString, 'end' => $endString]); + + $breadcrumbs->push($title, route('reports.report.audit', [$accountIds, $start->format('Ymd'), $end->format('Ymd')])); + } + ); + Breadcrumbs::register( + 'reports.report.budget', + function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $budgetIds, Carbon $start, Carbon $end) { + $breadcrumbs->parent('reports.index'); + + $monthFormat = (string)trans('config.month_and_day'); + $startString = $start->formatLocalized($monthFormat); + $endString = $end->formatLocalized($monthFormat); + $title = (string)trans('firefly.report_budget', ['start' => $startString, 'end' => $endString]); + + $breadcrumbs->push($title, route('reports.report.budget', [$accountIds, $budgetIds, $start->format('Ymd'), $end->format('Ymd')])); + } + ); + + Breadcrumbs::register( + 'reports.report.tag', + function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $tagTags, Carbon $start, Carbon $end) { + $breadcrumbs->parent('reports.index'); + + $monthFormat = (string)trans('config.month_and_day'); + $startString = $start->formatLocalized($monthFormat); + $endString = $end->formatLocalized($monthFormat); + $title = (string)trans('firefly.report_tag', ['start' => $startString, 'end' => $endString]); + + $breadcrumbs->push($title, route('reports.report.tag', [$accountIds, $tagTags, $start->format('Ymd'), $end->format('Ymd')])); + } + ); + + Breadcrumbs::register( + 'reports.report.category', + function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $categoryIds, Carbon $start, Carbon $end) { + $breadcrumbs->parent('reports.index'); + + $monthFormat = (string)trans('config.month_and_day'); + $startString = $start->formatLocalized($monthFormat); + $endString = $end->formatLocalized($monthFormat); + $title = (string)trans('firefly.report_category', ['start' => $startString, 'end' => $endString]); + + $breadcrumbs->push($title, route('reports.report.category', [$accountIds, $categoryIds, $start->format('Ymd'), $end->format('Ymd')])); + } + ); + + Breadcrumbs::register( + 'reports.report.account', + function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $expenseIds, Carbon $start, Carbon $end) { + $breadcrumbs->parent('reports.index'); + + $monthFormat = (string)trans('config.month_and_day'); + $startString = $start->formatLocalized($monthFormat); + $endString = $end->formatLocalized($monthFormat); + $title = (string)trans('firefly.report_account', ['start' => $startString, 'end' => $endString]); + + $breadcrumbs->push($title, route('reports.report.account', [$accountIds, $expenseIds, $start->format('Ymd'), $end->format('Ymd')])); + } + ); + + Breadcrumbs::register( + 'reports.report.default', + function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, Carbon $start, Carbon $end) { + $breadcrumbs->parent('reports.index'); + + $monthFormat = (string)trans('config.month_and_day'); + $startString = $start->formatLocalized($monthFormat); + $endString = $end->formatLocalized($monthFormat); + $title = (string)trans('firefly.report_default', ['start' => $startString, 'end' => $endString]); + + $breadcrumbs->push($title, route('reports.report.default', [$accountIds, $start->format('Ymd'), $end->format('Ymd')])); + } + ); + + // New user Controller + Breadcrumbs::register( + 'new-user.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.getting_started'), route('new-user.index')); + } + ); + + // Rules + Breadcrumbs::register( + 'rules.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.rules'), route('rules.index')); + } + ); + + Breadcrumbs::register( + 'rules.create', + function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.make_new_rule', ['title' => $ruleGroup->title]), route('rules.create', [$ruleGroup])); + } + ); + Breadcrumbs::register( + 'rules.edit', + function (BreadCrumbsGenerator $breadcrumbs, Rule $rule) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.edit_rule', ['title' => $rule->title]), route('rules.edit', [$rule])); + } + ); + Breadcrumbs::register( + 'rules.delete', + function (BreadCrumbsGenerator $breadcrumbs, Rule $rule) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.delete_rule', ['title' => $rule->title]), route('rules.delete', [$rule])); + } + ); + Breadcrumbs::register( + 'rule-groups.create', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.make_new_rule_group'), route('rule-groups.create')); + } + ); + Breadcrumbs::register( + 'rule-groups.edit', + function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.edit_rule_group', ['title' => $ruleGroup->title]), route('rule-groups.edit', [$ruleGroup])); + } + ); + Breadcrumbs::register( + 'rule-groups.delete', + function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push(trans('firefly.delete_rule_group', ['title' => $ruleGroup->title]), route('rule-groups.delete', [$ruleGroup])); + } + ); + + Breadcrumbs::register( + 'rules.select-transactions', + function (BreadCrumbsGenerator $breadcrumbs, Rule $rule) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push( + trans('firefly.rule_select_transactions', ['title' => $rule->title]), route('rules.select-transactions', [$rule]) ); - $breadcrumbs->push($title, route('categories.no-category', [$moment])); } - } -); + ); -// CURRENCIES -Breadcrumbs::register( - 'currencies.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.currencies'), route('currencies.index')); - } -); - -Breadcrumbs::register( - 'currencies.create', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('currencies.index'); - $breadcrumbs->push(trans('firefly.create_currency'), route('currencies.create')); - } -); - -Breadcrumbs::register( - 'currencies.edit', - function (BreadCrumbsGenerator $breadcrumbs, TransactionCurrency $currency) { - $breadcrumbs->parent('currencies.index'); - $breadcrumbs->push(trans('breadcrumbs.edit_currency', ['name' => $currency->name]), route('currencies.edit', [$currency->id])); - } -); -Breadcrumbs::register( - 'currencies.delete', - function (BreadCrumbsGenerator $breadcrumbs, TransactionCurrency $currency) { - $breadcrumbs->parent('currencies.index'); - $breadcrumbs->push(trans('breadcrumbs.delete_currency', ['name' => $currency->name]), route('currencies.delete', [$currency->id])); - } -); - -// EXPORT -Breadcrumbs::register( - 'export.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.export_data'), route('export.index')); - } -); - -// PIGGY BANKS -Breadcrumbs::register( - 'piggy-banks.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.piggyBanks'), route('piggy-banks.index')); - } -); -Breadcrumbs::register( - 'piggy-banks.create', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('piggy-banks.index'); - $breadcrumbs->push(trans('breadcrumbs.newPiggyBank'), route('piggy-banks.create')); - } -); - -Breadcrumbs::register( - 'piggy-banks.edit', - function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { - $breadcrumbs->parent('piggy-banks.show', $piggyBank); - $breadcrumbs->push(trans('breadcrumbs.edit_piggyBank', ['name' => $piggyBank->name]), route('piggy-banks.edit', [$piggyBank->id])); - } -); -Breadcrumbs::register( - 'piggy-banks.delete', - function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { - $breadcrumbs->parent('piggy-banks.show', $piggyBank); - $breadcrumbs->push(trans('firefly.delete_piggy_bank', ['name' => $piggyBank->name]), route('piggy-banks.delete', [$piggyBank->id])); - } -); - -Breadcrumbs::register( - 'piggy-banks.show', - function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { - $breadcrumbs->parent('piggy-banks.index'); - $breadcrumbs->push($piggyBank->name, route('piggy-banks.show', [$piggyBank->id])); - } -); - -Breadcrumbs::register( - 'piggy-banks.add-money-mobile', - function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { - $breadcrumbs->parent('piggy-banks.show', $piggyBank); - $breadcrumbs->push(trans('firefly.add_money_to_piggy', ['name' => $piggyBank->name]), route('piggy-banks.add-money-mobile', [$piggyBank->id])); - } -); - -Breadcrumbs::register( - 'piggy-banks.remove-money-mobile', - function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { - $breadcrumbs->parent('piggy-banks.show', $piggyBank); - $breadcrumbs->push( - trans('firefly.remove_money_from_piggy_title', ['name' => $piggyBank->name]), - route('piggy-banks.remove-money-mobile', [$piggyBank->id]) - ); - } -); - -// IMPORT -Breadcrumbs::register( - 'import.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.import_index_title'), route('import.index')); - } -); - -Breadcrumbs::register( - 'import.configure', - function (BreadCrumbsGenerator $breadcrumbs, ImportJob $job) { - $breadcrumbs->parent('import.index'); - $breadcrumbs->push(trans('import.config_sub_title', ['key' => $job->key]), route('import.configure', [$job->key])); - } -); - -Breadcrumbs::register( - 'import.prerequisites.index', - function (BreadCrumbsGenerator $breadcrumbs, string $importProvider) { - $breadcrumbs->parent('import.index'); - $breadcrumbs->push(trans('import.prerequisites_breadcrumb_'.$importProvider), route('import.prerequisites.index', [$importProvider])); - } -); - - -Breadcrumbs::register( - 'import.status', - function (BreadCrumbsGenerator $breadcrumbs, ImportJob $job) { - $breadcrumbs->parent('import.index'); - $breadcrumbs->push(trans('import.status_bread_crumb', ['key' => $job->key]), route('import.status', [$job->key])); - } -); - - -// PREFERENCES -Breadcrumbs::register( - 'preferences.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.preferences'), route('preferences.index')); - } -); - -Breadcrumbs::register( - 'profile.code', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.profile'), route('profile.index')); - } -); - -// PROFILE -Breadcrumbs::register( - 'profile.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.profile'), route('profile.index')); - } -); -Breadcrumbs::register( - 'profile.change-password', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('profile.index'); - $breadcrumbs->push(trans('breadcrumbs.changePassword'), route('profile.change-password')); - } -); - -Breadcrumbs::register( - 'profile.change-email', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('profile.index'); - $breadcrumbs->push(trans('breadcrumbs.change_email'), route('profile.change-email')); - } -); - -Breadcrumbs::register( - 'profile.delete-account', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('profile.index'); - $breadcrumbs->push(trans('firefly.delete_account'), route('profile.delete-account')); - } -); - -// REPORTS -Breadcrumbs::register( - 'reports.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.reports'), route('reports.index')); - } -); - -Breadcrumbs::register( - 'reports.report.audit', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, Carbon $start, Carbon $end) { - $breadcrumbs->parent('reports.index'); - - $monthFormat = (string)trans('config.month_and_day'); - $startString = $start->formatLocalized($monthFormat); - $endString = $end->formatLocalized($monthFormat); - $title = (string)trans('firefly.report_audit', ['start' => $startString, 'end' => $endString]); - - $breadcrumbs->push($title, route('reports.report.audit', [$accountIds, $start->format('Ymd'), $end->format('Ymd')])); - } -); -Breadcrumbs::register( - 'reports.report.budget', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $budgetIds, Carbon $start, Carbon $end) { - $breadcrumbs->parent('reports.index'); - - $monthFormat = (string)trans('config.month_and_day'); - $startString = $start->formatLocalized($monthFormat); - $endString = $end->formatLocalized($monthFormat); - $title = (string)trans('firefly.report_budget', ['start' => $startString, 'end' => $endString]); - - $breadcrumbs->push($title, route('reports.report.budget', [$accountIds, $budgetIds, $start->format('Ymd'), $end->format('Ymd')])); - } -); - -Breadcrumbs::register( - 'reports.report.tag', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $tagTags, Carbon $start, Carbon $end) { - $breadcrumbs->parent('reports.index'); - - $monthFormat = (string)trans('config.month_and_day'); - $startString = $start->formatLocalized($monthFormat); - $endString = $end->formatLocalized($monthFormat); - $title = (string)trans('firefly.report_tag', ['start' => $startString, 'end' => $endString]); - - $breadcrumbs->push($title, route('reports.report.tag', [$accountIds, $tagTags, $start->format('Ymd'), $end->format('Ymd')])); - } -); - -Breadcrumbs::register( - 'reports.report.category', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $categoryIds, Carbon $start, Carbon $end) { - $breadcrumbs->parent('reports.index'); - - $monthFormat = (string)trans('config.month_and_day'); - $startString = $start->formatLocalized($monthFormat); - $endString = $end->formatLocalized($monthFormat); - $title = (string)trans('firefly.report_category', ['start' => $startString, 'end' => $endString]); - - $breadcrumbs->push($title, route('reports.report.category', [$accountIds, $categoryIds, $start->format('Ymd'), $end->format('Ymd')])); - } -); - -Breadcrumbs::register( - 'reports.report.account', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $expenseIds, Carbon $start, Carbon $end) { - $breadcrumbs->parent('reports.index'); - - $monthFormat = (string)trans('config.month_and_day'); - $startString = $start->formatLocalized($monthFormat); - $endString = $end->formatLocalized($monthFormat); - $title = (string)trans('firefly.report_account', ['start' => $startString, 'end' => $endString]); - - $breadcrumbs->push($title, route('reports.report.account', [$accountIds, $expenseIds, $start->format('Ymd'), $end->format('Ymd')])); - } -); - -Breadcrumbs::register( - 'reports.report.default', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, Carbon $start, Carbon $end) { - $breadcrumbs->parent('reports.index'); - - $monthFormat = (string)trans('config.month_and_day'); - $startString = $start->formatLocalized($monthFormat); - $endString = $end->formatLocalized($monthFormat); - $title = (string)trans('firefly.report_default', ['start' => $startString, 'end' => $endString]); - - $breadcrumbs->push($title, route('reports.report.default', [$accountIds, $start->format('Ymd'), $end->format('Ymd')])); - } -); - -// New user Controller -Breadcrumbs::register( - 'new-user.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.getting_started'), route('new-user.index')); - } -); - -// Rules -Breadcrumbs::register( - 'rules.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.rules'), route('rules.index')); - } -); - -Breadcrumbs::register( - 'rules.create', - function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { - $breadcrumbs->parent('rules.index'); - $breadcrumbs->push(trans('firefly.make_new_rule', ['title' => $ruleGroup->title]), route('rules.create', [$ruleGroup])); - } -); -Breadcrumbs::register( - 'rules.edit', - function (BreadCrumbsGenerator $breadcrumbs, Rule $rule) { - $breadcrumbs->parent('rules.index'); - $breadcrumbs->push(trans('firefly.edit_rule', ['title' => $rule->title]), route('rules.edit', [$rule])); - } -); -Breadcrumbs::register( - 'rules.delete', - function (BreadCrumbsGenerator $breadcrumbs, Rule $rule) { - $breadcrumbs->parent('rules.index'); - $breadcrumbs->push(trans('firefly.delete_rule', ['title' => $rule->title]), route('rules.delete', [$rule])); - } -); -Breadcrumbs::register( - 'rule-groups.create', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('rules.index'); - $breadcrumbs->push(trans('firefly.make_new_rule_group'), route('rule-groups.create')); - } -); -Breadcrumbs::register( - 'rule-groups.edit', - function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { - $breadcrumbs->parent('rules.index'); - $breadcrumbs->push(trans('firefly.edit_rule_group', ['title' => $ruleGroup->title]), route('rule-groups.edit', [$ruleGroup])); - } -); -Breadcrumbs::register( - 'rule-groups.delete', - function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { - $breadcrumbs->parent('rules.index'); - $breadcrumbs->push(trans('firefly.delete_rule_group', ['title' => $ruleGroup->title]), route('rule-groups.delete', [$ruleGroup])); - } -); - -Breadcrumbs::register( - 'rules.select-transactions', - function (BreadCrumbsGenerator $breadcrumbs, Rule $rule) { - $breadcrumbs->parent('rules.index'); - $breadcrumbs->push( - trans('firefly.rule_select_transactions', ['title' => $rule->title]), route('rules.select-transactions', [$rule]) - ); - } -); - -Breadcrumbs::register( - 'rule-groups.select-transactions', - function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { - $breadcrumbs->parent('rules.index'); - $breadcrumbs->push( - trans('firefly.rule_group_select_transactions', ['title' => $ruleGroup->title]), route('rule-groups.select-transactions', [$ruleGroup]) - ); - } -); - -// SEARCH -Breadcrumbs::register( - 'search.index', - function (BreadCrumbsGenerator $breadcrumbs, $query) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.search_result', ['query' => $query]), route('search.index')); - } -); - -// TAGS -Breadcrumbs::register( - 'tags.index', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.tags'), route('tags.index')); - } -); - -Breadcrumbs::register( - 'tags.create', - function (BreadCrumbsGenerator $breadcrumbs) { - $breadcrumbs->parent('tags.index'); - $breadcrumbs->push(trans('breadcrumbs.createTag'), route('tags.create')); - } -); - -Breadcrumbs::register( - 'tags.edit', - function (BreadCrumbsGenerator $breadcrumbs, Tag $tag) { - $breadcrumbs->parent('tags.show', $tag, '(nothing)', new Carbon, new Carbon); - $breadcrumbs->push(trans('breadcrumbs.edit_tag', ['tag' => $tag->tag]), route('tags.edit', [$tag->id])); - } -); - -Breadcrumbs::register( - 'tags.delete', - function (BreadCrumbsGenerator $breadcrumbs, Tag $tag) { - $breadcrumbs->parent('tags.show', $tag, '(nothing)', new Carbon, new Carbon); - $breadcrumbs->push(trans('breadcrumbs.delete_tag', ['tag' => $tag->tag]), route('tags.delete', [$tag->id])); - } -); - -Breadcrumbs::register( - 'tags.show', - function (BreadCrumbsGenerator $breadcrumbs, Tag $tag, string $moment, Carbon $start, Carbon $end) { - $breadcrumbs->parent('tags.index'); - $breadcrumbs->push($tag->tag, route('tags.show', [$tag->id, $moment])); - if ('all' === $moment) { - $breadcrumbs->push(trans('firefly.everything'), route('tags.show', [$tag->id, $moment])); - } - // when is specific period or when empty: - if ('all' !== $moment && '(nothing)' !== $moment) { - $title = trans( - 'firefly.between_dates_breadcrumb', - ['start' => $start->formatLocalized((string)trans('config.month_and_day')), - 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + Breadcrumbs::register( + 'rule-groups.select-transactions', + function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { + $breadcrumbs->parent('rules.index'); + $breadcrumbs->push( + trans('firefly.rule_group_select_transactions', ['title' => $ruleGroup->title]), route('rule-groups.select-transactions', [$ruleGroup]) ); - $breadcrumbs->push($title, route('tags.show', [$tag->id, $moment])); } - } -); + ); -// TRANSACTIONS - -Breadcrumbs::register( - 'transactions.index', - function (BreadCrumbsGenerator $breadcrumbs, string $what, Carbon $start = null, Carbon $end = null) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); - - if (null !== $start && null !== $end) { - // add date range: - $title = trans( - 'firefly.between_dates_breadcrumb', - ['start' => $start->formatLocalized((string)trans('config.month_and_day')), - 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] - ); - $breadcrumbs->push($title, route('transactions.index', [$what, $start, $end])); + // SEARCH + Breadcrumbs::register( + 'search.index', + function (BreadCrumbsGenerator $breadcrumbs, $query) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.search_result', ['query' => $query]), route('search.index')); } - } -); + ); -Breadcrumbs::register( - 'transactions.index.all', - function (BreadCrumbsGenerator $breadcrumbs, string $what, Carbon $start = null, Carbon $end = null) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); - } -); + // TAGS + Breadcrumbs::register( + 'tags.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.tags'), route('tags.index')); + } + ); -Breadcrumbs::register( - 'transactions.create', - function (BreadCrumbsGenerator $breadcrumbs, string $what) { - $breadcrumbs->parent('transactions.index', $what); - $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what])); - } -); + Breadcrumbs::register( + 'tags.create', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('tags.index'); + $breadcrumbs->push(trans('breadcrumbs.createTag'), route('tags.create')); + } + ); -Breadcrumbs::register( - 'transactions.edit', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { - $breadcrumbs->parent('transactions.show', $journal); - $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.edit', [$journal->id])); - } -); + Breadcrumbs::register( + 'tags.edit', + function (BreadCrumbsGenerator $breadcrumbs, Tag $tag) { + $breadcrumbs->parent('tags.show', $tag, '(nothing)', new Carbon, new Carbon); + $breadcrumbs->push(trans('breadcrumbs.edit_tag', ['tag' => $tag->tag]), route('tags.edit', [$tag->id])); + } + ); -// also edit reconciliations: -Breadcrumbs::register( - 'accounts.reconcile.edit', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { - $breadcrumbs->parent('transactions.show', $journal); - $breadcrumbs->push( - trans('breadcrumbs.edit_reconciliation', ['description' => $journal->description]), route('accounts.reconcile.edit', [$journal->id]) - ); - } -); + Breadcrumbs::register( + 'tags.delete', + function (BreadCrumbsGenerator $breadcrumbs, Tag $tag) { + $breadcrumbs->parent('tags.show', $tag, '(nothing)', new Carbon, new Carbon); + $breadcrumbs->push(trans('breadcrumbs.delete_tag', ['tag' => $tag->tag]), route('tags.delete', [$tag->id])); + } + ); -Breadcrumbs::register( - 'transactions.delete', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { - $breadcrumbs->parent('transactions.show', $journal); - $breadcrumbs->push(trans('breadcrumbs.delete_journal', ['description' => $journal->description]), route('transactions.delete', [$journal->id])); - } -); + Breadcrumbs::register( + 'tags.show', + function (BreadCrumbsGenerator $breadcrumbs, Tag $tag, string $moment, Carbon $start, Carbon $end) { + $breadcrumbs->parent('tags.index'); + $breadcrumbs->push($tag->tag, route('tags.show', [$tag->id, $moment])); + if ('all' === $moment) { + $breadcrumbs->push(trans('firefly.everything'), route('tags.show', [$tag->id, $moment])); + } + // when is specific period or when empty: + if ('all' !== $moment && '(nothing)' !== $moment) { + $title = trans( + 'firefly.between_dates_breadcrumb', + ['start' => $start->formatLocalized((string)trans('config.month_and_day')), + 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + ); + $breadcrumbs->push($title, route('tags.show', [$tag->id, $moment])); + } + } + ); -Breadcrumbs::register( - 'transactions.show', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { - $what = strtolower($journal->transactionType->type); - $breadcrumbs->parent('transactions.index', $what); - $breadcrumbs->push($journal->description, route('transactions.show', [$journal->id])); - } -); + // TRANSACTIONS -Breadcrumbs::register( - 'transactions.convert.index', - function (BreadCrumbsGenerator $breadcrumbs, TransactionType $destinationType, TransactionJournal $journal) { - $breadcrumbs->parent('transactions.show', $journal); - $breadcrumbs->push( - trans('firefly.convert_to_' . $destinationType->type, ['description' => $journal->description]), - route('transactions.convert.index', [strtolower($destinationType->type), $journal->id]) - ); - } -); + Breadcrumbs::register( + 'transactions.index', + function (BreadCrumbsGenerator $breadcrumbs, string $what, Carbon $start = null, Carbon $end = null) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); -// 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']); + if (null !== $start && null !== $end) { + // add date range: + $title = trans( + 'firefly.between_dates_breadcrumb', + ['start' => $start->formatLocalized((string)trans('config.month_and_day')), + 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + ); + $breadcrumbs->push($title, route('transactions.index', [$what, $start, $end])); + } + } + ); + + Breadcrumbs::register( + 'transactions.index.all', + function (BreadCrumbsGenerator $breadcrumbs, string $what, Carbon $start = null, Carbon $end = null) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); + } + ); + + Breadcrumbs::register( + 'transactions.create', + function (BreadCrumbsGenerator $breadcrumbs, string $what) { $breadcrumbs->parent('transactions.index', $what); - $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds)); - - return; + $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what])); } - $breadcrumbs->parent('index'); - } -); + ); -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)); - } -); + Breadcrumbs::register( + 'transactions.edit', + function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { + $breadcrumbs->parent('transactions.show', $journal); + $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.edit', [$journal->id])); + } + ); -// BULK EDIT -Breadcrumbs::register( - 'transactions.bulk.edit', - function (BreadCrumbsGenerator $breadcrumbs, Collection $journals): void { - if ($journals->count() > 0) { + // also edit reconciliations: + Breadcrumbs::register( + 'accounts.reconcile.edit', + function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { + $breadcrumbs->parent('transactions.show', $journal); + $breadcrumbs->push( + trans('breadcrumbs.edit_reconciliation', ['description' => $journal->description]), route('accounts.reconcile.edit', [$journal->id]) + ); + } + ); + + Breadcrumbs::register( + 'transactions.delete', + function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { + $breadcrumbs->parent('transactions.show', $journal); + $breadcrumbs->push(trans('breadcrumbs.delete_journal', ['description' => $journal->description]), route('transactions.delete', [$journal->id])); + } + ); + + Breadcrumbs::register( + 'transactions.show', + function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { + $what = strtolower($journal->transactionType->type); + $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->push($journal->description, route('transactions.show', [$journal->id])); + } + ); + + Breadcrumbs::register( + 'transactions.convert.index', + function (BreadCrumbsGenerator $breadcrumbs, TransactionType $destinationType, TransactionJournal $journal) { + $breadcrumbs->parent('transactions.show', $journal); + $breadcrumbs->push( + trans('firefly.convert_to_' . $destinationType->type, ['description' => $journal->description]), + route('transactions.convert.index', [strtolower($destinationType->type), $journal->id]) + ); + } + ); + + // 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)); + + return; + } + $breadcrumbs->parent('index'); + } + ); + + 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_bulk_journals'), route('transactions.bulk.edit', $journalIds)); + $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', $journalIds)); + } + ); + + // BULK EDIT + Breadcrumbs::register( + 'transactions.bulk.edit', + function (BreadCrumbsGenerator $breadcrumbs, Collection $journals): void { + if ($journals->count() > 0) { + $journalIds = $journals->pluck('id')->toArray(); + $what = strtolower($journals->first()->transactionType->type); + $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->push(trans('firefly.mass_bulk_journals'), route('transactions.bulk.edit', $journalIds)); + + return; + } + + $breadcrumbs->parent('index'); return; } + ); - $breadcrumbs->parent('index'); - - return; - } -); - -// SPLIT -Breadcrumbs::register( - 'transactions.split.edit', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { - $breadcrumbs->parent('transactions.show', $journal); - $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.split.edit', [$journal->id])); - } -); + // SPLIT + Breadcrumbs::register( + 'transactions.split.edit', + function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { + $breadcrumbs->parent('transactions.show', $journal); + $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.split.edit', [$journal->id])); + } + ); +} catch (DuplicateBreadcrumbException $e) { +} \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index e4262c26a4..5249864f04 100755 --- a/routes/web.php +++ b/routes/web.php @@ -451,6 +451,11 @@ Route::group( // set global prerequisites for an import source, possible with a job already attached. Route::get('prerequisites/{import_provider}/{importJob?}', ['uses' => 'Import\PrerequisitesController@index', 'as' => 'prerequisites.index']); Route::post('prerequisites/{import_provider}/{importJob?}', ['uses' => 'Import\PrerequisitesController@post', 'as' => 'prerequisites.post']); + + // configure a job: + Route::get('job/configuration/{importJob}', ['uses' => 'Import\JobConfigurationController@index', 'as' => 'job.configuration.index']); + Route::post('job/configuration/{importJob}', ['uses' => 'Import\JobConfigurationController@post', 'as' => 'job.configuration.post']); + // import method prerequisites: # # @@ -460,7 +465,7 @@ Route::group( #Route::get('create/{bank}', ['uses' => 'Import\IndexController@create', 'as' => 'create-job']); // configure the job: - #Route::get('configure/{importJob}', ['uses' => 'Import\ConfigurationController@index', 'as' => 'configure']); + #Route::post('configure/{importJob}', ['uses' => 'Import\ConfigurationController@post', 'as' => 'configure.post']); // get status of any job: From f2b71bc2800faf3c5b2ab20073368627e8cd274e Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Apr 2018 21:20:06 +0200 Subject: [PATCH 026/182] Fake jobs can be started and will crash. --- .../Controllers/Import/IndexController.php | 159 +++---- .../Import/JobConfigurationController.php | 13 +- .../Import/JobStatusController.php | 196 +++++++++ .../Controllers/Import/StatusController.php | 125 ------ app/Import/Routine/RoutineInterface.php | 20 +- config/import.php | 6 +- public/js/ff/import/status_v2.js | 388 ++++++++++++++++++ resources/views/import/status.twig | 30 +- routes/breadcrumbs.php | 21 +- routes/web.php | 7 + 10 files changed, 724 insertions(+), 241 deletions(-) create mode 100644 app/Http/Controllers/Import/JobStatusController.php delete mode 100644 app/Http/Controllers/Import/StatusController.php create mode 100644 public/js/ff/import/status_v2.js diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index ba3c8913f4..9dd0691f07 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -26,11 +26,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Import\Prerequisites\PrerequisitesInterface; -use FireflyIII\Import\Routine\RoutineInterface; -use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use Illuminate\Http\Request; -use Preferences; use View; @@ -74,6 +70,24 @@ class IndexController extends Controller { $importJob = $this->repository->create($importProvider); + // if job provider has no prerequisites: + if (!(bool)config(sprintf('import.has_prereq.%s', $importProvider))) { + + + // if job provider also has no configuration: + if (!(bool)config(sprintf('import.has_config.%s', $importProvider))) { + $this->repository->updateStatus($importJob, 'ready_to_run'); + + return redirect(route('import.job.status.index', [$importJob->key])); + } + + // update job to say "has_prereq". + $this->repository->setStatus($importJob, 'has_prereq'); + + // redirect to job configuration. + return redirect(route('import.job.configuration.index', [$importJob->key])); + } + // if need to set prerequisites, do that first. $class = (string)config(sprintf('import.prerequisites.%s', $importProvider)); if (!class_exists($class)) { @@ -87,6 +101,7 @@ class IndexController extends Controller // redirect to global prerequisites return redirect(route('import.prerequisites.index', [$importProvider, $importJob->key])); } + // update job to say "has_prereq". $this->repository->setStatus($importJob, 'has_prereq'); @@ -158,7 +173,7 @@ class IndexController extends Controller if ($class !== '' && class_exists($class)) { /** @var PrerequisitesInterface $object */ $object = app($class); - $object->setuser(auth()->user()); + $object->setUser(auth()->user()); $result = $object->isComplete(); } $providers[$name]['prereq_complete'] = $result; @@ -169,72 +184,72 @@ class IndexController extends Controller return view('import.index', compact('subTitle', 'subTitleIcon', 'providers')); } -// -// /** -// * @param Request $request -// * @param string $bank -// * -// * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector -// */ -// public function reset(Request $request, string $bank) -// { -// if ($bank === 'bunq') { -// // remove bunq related preferences. -// Preferences::delete('bunq_api_key'); -// Preferences::delete('bunq_server_public_key'); -// Preferences::delete('bunq_private_key'); -// Preferences::delete('bunq_public_key'); -// Preferences::delete('bunq_installation_token'); -// Preferences::delete('bunq_installation_id'); -// Preferences::delete('bunq_device_server_id'); -// Preferences::delete('external_ip'); -// -// } -// -// if ($bank === 'spectre') { -// // remove spectre related preferences: -// Preferences::delete('spectre_client_id'); -// Preferences::delete('spectre_app_secret'); -// Preferences::delete('spectre_service_secret'); -// Preferences::delete('spectre_app_id'); -// Preferences::delete('spectre_secret'); -// Preferences::delete('spectre_private_key'); -// Preferences::delete('spectre_public_key'); -// Preferences::delete('spectre_customer'); -// } -// -// Preferences::mark(); -// $request->session()->flash('info', (string)trans('firefly.settings_reset_for_' . $bank)); -// -// return redirect(route('import.index')); -// -// } + // + // /** + // * @param Request $request + // * @param string $bank + // * + // * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + // */ + // public function reset(Request $request, string $bank) + // { + // if ($bank === 'bunq') { + // // remove bunq related preferences. + // Preferences::delete('bunq_api_key'); + // Preferences::delete('bunq_server_public_key'); + // Preferences::delete('bunq_private_key'); + // Preferences::delete('bunq_public_key'); + // Preferences::delete('bunq_installation_token'); + // Preferences::delete('bunq_installation_id'); + // Preferences::delete('bunq_device_server_id'); + // Preferences::delete('external_ip'); + // + // } + // + // if ($bank === 'spectre') { + // // remove spectre related preferences: + // Preferences::delete('spectre_client_id'); + // Preferences::delete('spectre_app_secret'); + // Preferences::delete('spectre_service_secret'); + // Preferences::delete('spectre_app_id'); + // Preferences::delete('spectre_secret'); + // Preferences::delete('spectre_private_key'); + // Preferences::delete('spectre_public_key'); + // Preferences::delete('spectre_customer'); + // } + // + // Preferences::mark(); + // $request->session()->flash('info', (string)trans('firefly.settings_reset_for_' . $bank)); + // + // return redirect(route('import.index')); + // + // } -// /** -// * @param ImportJob $job -// * -// * @return \Illuminate\Http\JsonResponse -// * -// * @throws FireflyException -// */ -// public function start(ImportJob $job) -// { -// $type = $job->file_type; -// $key = sprintf('import.routine.%s', $type); -// $className = config($key); -// if (null === $className || !class_exists($className)) { -// throw new FireflyException(sprintf('Cannot find import routine class for job of type "%s".', $type)); // @codeCoverageIgnore -// } -// -// /** @var RoutineInterface $routine */ -// $routine = app($className); -// $routine->setJob($job); -// $result = $routine->run(); -// -// if ($result) { -// return response()->json(['run' => 'ok']); -// } -// -// throw new FireflyException('Job did not complete successfully. Please review the log files.'); -// } + // /** + // * @param ImportJob $job + // * + // * @return \Illuminate\Http\JsonResponse + // * + // * @throws FireflyException + // */ + // public function start(ImportJob $job) + // { + // $type = $job->file_type; + // $key = sprintf('import.routine.%s', $type); + // $className = config($key); + // if (null === $className || !class_exists($className)) { + // throw new FireflyException(sprintf('Cannot find import routine class for job of type "%s".', $type)); // @codeCoverageIgnore + // } + // + // /** @var RoutineInterface $routine */ + // $routine = app($className); + // $routine->setJob($job); + // $result = $routine->run(); + // + // if ($result) { + // return response()->json(['run' => 'ok']); + // } + // + // throw new FireflyException('Job did not complete successfully. Please review the log files.'); + // } } diff --git a/app/Http/Controllers/Import/JobConfigurationController.php b/app/Http/Controllers/Import/JobConfigurationController.php index aaa1626ec1..7261da9bc1 100644 --- a/app/Http/Controllers/Import/JobConfigurationController.php +++ b/app/Http/Controllers/Import/JobConfigurationController.php @@ -69,6 +69,15 @@ class JobConfigurationController extends Controller */ public function index(ImportJob $job) { + // if provider has no config, just push it through + $importProvider = $job->provider; + if (!(bool)config(sprintf('import.has_config.%s', $importProvider))) { + $this->repository->updateStatus($job, 'ready_to_run'); + + return redirect(route('import.job.status.index', [$job->key])); + } + + // create configuration class: $configurator = $this->makeConfigurator($job); @@ -76,7 +85,7 @@ class JobConfigurationController extends Controller if ($configurator->configurationComplete()) { $this->repository->updateStatus($job, 'ready_to_run'); - return redirect(route('import.job.landing', [$job->key])); + return redirect(route('import.job.status.index', [$job->key])); } $this->repository->updateStatus($job, 'configuring'); @@ -108,7 +117,7 @@ class JobConfigurationController extends Controller if ($configurator->configurationComplete()) { $this->repository->updateStatus($job, 'ready_to_run'); - return redirect(route('import.job.landing', [$job->key])); + return redirect(route('import.job.status.index', [$job->key])); } $data = $request->all(); diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php new file mode 100644 index 0000000000..ece8a4cdea --- /dev/null +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -0,0 +1,196 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Import; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Http\Middleware\IsDemoUser; +use FireflyIII\Import\Routine\RoutineInterface; +use FireflyIII\Models\ImportJob; +use Illuminate\Http\JsonResponse; +use Log; + +/** + * Class JobStatusController + */ +class JobStatusController extends Controller +{ + /** + * + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-archive'); + app('view')->share('title', trans('firefly.import_index_title')); + + return $next($request); + } + ); + $this->middleware(IsDemoUser::class); + } + + /** + * @param ImportJob $importJob + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function index(ImportJob $importJob) + { + // jump away depending on job status: + if ($importJob->status === 'has_prereq') { + // TODO back to configuration. + } + + if ($importJob->status === 'errored') { + // TODO to error screen + } + + if ($importJob->status === 'finished') { + // TODO to finished screen. + } + + return view('import.status', compact('importJob')); + } + + /** + * @param ImportJob $job + * + * @return JsonResponse + */ + public function json(ImportJob $job): JsonResponse + { + $json = [ + 'status' => $job->status, + ]; + + return response()->json($json); + } + + /** + * @param ImportJob $job + * + * @return JsonResponse + * @throws FireflyException + */ + public function start(ImportJob $job): JsonResponse + { + $importProvider = $job->provider; + $key = sprintf('import.routine.%s', $importProvider); + $className = config($key); + if (null === $className || !class_exists($className)) { + return response()->json(['status' => 'NOK', 'message' => sprintf('Cannot find import routine class for job of type "%s".', $importProvider)]); + } + + /** @var RoutineInterface $routine */ + $routine = app($className); + $routine->setJob($job); + try { + $routine->run(); + } catch (FireflyException $e) { + $message = 'The import routine crashed: ' . $e->getMessage(); + Log::error($message); + Log::error($e->getTraceAsString()); + + return response()->json(['status' => 'NOK', 'message' => $message]); + } + + // expect nothing from routine, just return OK to user. + return response()->json(['status' => 'OK', 'message' => 'finished']); + } + + // /** + // * @param ImportJob $job + // * + // * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View + // */ + // public function index(ImportJob $job) + // { + // $statuses = ['configured', 'running', 'finished', 'error']; + // if (!\in_array($job->status, $statuses)) { + // return redirect(route('import.configure', [$job->key])); + // } + // $subTitle = trans('import.status_sub_title'); + // $subTitleIcon = 'fa-star'; + // + // return view('import.status', compact('job', 'subTitle', 'subTitleIcon')); + // } + // + // /** + // * Show status of import job in JSON. + // * + // * @param ImportJob $job + // * + // * @return \Illuminate\Http\JsonResponse + // */ + // public function json(ImportJob $job) + // { + // $result = [ + // 'started' => false, + // 'finished' => false, + // 'running' => false, + // 'errors' => array_values($job->extended_status['errors']), + // 'percentage' => 0, + // 'show_percentage' => false, + // 'steps' => $job->extended_status['steps'], + // 'done' => $job->extended_status['done'], + // 'statusText' => trans('import.status_job_' . $job->status), + // 'status' => $job->status, + // 'finishedText' => '', + // ]; + // + // if (0 !== $job->extended_status['steps']) { + // $result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0); + // $result['show_percentage'] = true; + // } + // if ('finished' === $job->status) { + // $result['finished'] = true; + // $tagId = (int)$job->extended_status['tag']; + // if ($tagId !== 0) { + // /** @var TagRepositoryInterface $repository */ + // $repository = app(TagRepositoryInterface::class); + // $tag = $repository->find($tagId); + // $count = $tag->transactionJournals()->count(); + // $result['finishedText'] = trans( + // 'import.status_finished_job', ['count' => $count, 'link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag] + // ); + // } + // + // if ($tagId === 0) { + // $result['finishedText'] = trans('import.status_finished_no_tag'); // @codeCoverageIgnore + // } + // } + // + // if ('running' === $job->status) { + // $result['started'] = true; + // $result['running'] = true; + // } + // $result['percentage'] = $result['percentage'] > 100 ? 100 : $result['percentage']; + // Log::debug(sprintf('JOB STATUS: %d/%d', $result['done'], $result['steps'])); + // + // return response()->json($result); + // } +} diff --git a/app/Http/Controllers/Import/StatusController.php b/app/Http/Controllers/Import/StatusController.php deleted file mode 100644 index 4c95ec77a3..0000000000 --- a/app/Http/Controllers/Import/StatusController.php +++ /dev/null @@ -1,125 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Controllers\Import; - -use FireflyIII\Http\Controllers\Controller; -use FireflyIII\Http\Middleware\IsDemoUser; -use FireflyIII\Models\ImportJob; -use FireflyIII\Repositories\Tag\TagRepositoryInterface; -use Log; - -/** - * Class StatusController - */ -class StatusController extends Controller -{ - /** - * - */ - public function __construct() - { - parent::__construct(); - - $this->middleware( - function ($request, $next) { - app('view')->share('mainTitleIcon', 'fa-archive'); - app('view')->share('title', trans('firefly.import_index_title')); - - return $next($request); - } - ); - $this->middleware(IsDemoUser::class); - } - - /** - * @param ImportJob $job - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View - */ - public function index(ImportJob $job) - { - $statuses = ['configured', 'running', 'finished', 'error']; - if (!\in_array($job->status, $statuses)) { - return redirect(route('import.configure', [$job->key])); - } - $subTitle = trans('import.status_sub_title'); - $subTitleIcon = 'fa-star'; - - return view('import.status', compact('job', 'subTitle', 'subTitleIcon')); - } - - /** - * Show status of import job in JSON. - * - * @param ImportJob $job - * - * @return \Illuminate\Http\JsonResponse - */ - public function json(ImportJob $job) - { - $result = [ - 'started' => false, - 'finished' => false, - 'running' => false, - 'errors' => array_values($job->extended_status['errors']), - 'percentage' => 0, - 'show_percentage' => false, - 'steps' => $job->extended_status['steps'], - 'done' => $job->extended_status['done'], - 'statusText' => trans('import.status_job_' . $job->status), - 'status' => $job->status, - 'finishedText' => '', - ]; - - if (0 !== $job->extended_status['steps']) { - $result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0); - $result['show_percentage'] = true; - } - if ('finished' === $job->status) { - $result['finished'] = true; - $tagId = (int)$job->extended_status['tag']; - if ($tagId !== 0) { - /** @var TagRepositoryInterface $repository */ - $repository = app(TagRepositoryInterface::class); - $tag = $repository->find($tagId); - $count = $tag->transactionJournals()->count(); - $result['finishedText'] = trans( - 'import.status_finished_job', ['count' => $count, 'link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag] - ); - } - - if ($tagId === 0) { - $result['finishedText'] = trans('import.status_finished_no_tag'); // @codeCoverageIgnore - } - } - - if ('running' === $job->status) { - $result['started'] = true; - $result['running'] = true; - } - $result['percentage'] = $result['percentage'] > 100 ? 100 : $result['percentage']; - Log::debug(sprintf('JOB STATUS: %d/%d', $result['done'], $result['steps'])); - - return response()->json($result); - } -} diff --git a/app/Import/Routine/RoutineInterface.php b/app/Import/Routine/RoutineInterface.php index ef2d25515b..32ee378874 100644 --- a/app/Import/Routine/RoutineInterface.php +++ b/app/Import/Routine/RoutineInterface.php @@ -22,33 +22,19 @@ declare(strict_types=1); namespace FireflyIII\Import\Routine; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; -use Illuminate\Support\Collection; /** * Interface RoutineInterface */ interface RoutineInterface { - /** - * @return Collection - */ - public function getErrors(): Collection; - - /** - * @return Collection - */ - public function getJournals(): Collection; - - /** - * @return int - */ - public function getLines(): int; - /** * @return bool + * @throws FireflyException */ - public function run(): bool; + public function run(): void; /** * @param ImportJob $job diff --git a/config/import.php b/config/import.php index 3c87cb2461..390edfab10 100644 --- a/config/import.php +++ b/config/import.php @@ -46,8 +46,8 @@ return [ 'yodlee' => false, ], 'has_prereq' => [ - 'fake' => true, - 'file' => false, + 'fake' => false, + 'file' => true, 'bunq' => true, 'spectre' => true, 'plaid' => true, @@ -64,7 +64,7 @@ return [ 'yodlee' => false, ], 'has_config' => [ - 'fake' => true, + 'fake' => false, 'file' => true, 'bunq' => true, 'spectre' => true, diff --git a/public/js/ff/import/status_v2.js b/public/js/ff/import/status_v2.js new file mode 100644 index 0000000000..b1a2d00a8f --- /dev/null +++ b/public/js/ff/import/status_v2.js @@ -0,0 +1,388 @@ +/* + * status_v2.js + * 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 . + */ + +/** global: jobStatusUri */ + +var timeOutId; +var hasStartedJob = false; +var checkInitialInterval = 1000; +var checkNextInterval = 500; +var maxLoops = 20; +var totalLoops = 0; + +$(function () { + "use strict"; + timeOutId = setTimeout(checkJobStatus, checkInitialInterval); +}); + +/** + * Downloads some JSON and responds to its content to see what the status is of the current import job. + */ +function checkJobStatus() { + console.log('In checkJobStatus()'); + $.getJSON(jobStatusUri).done(reportOnJobStatus).fail(reportFailure); +} + +/** + * Reports to the user what the state is of the current job. + * + * @param data + */ +function reportOnJobStatus(data) { + console.log('In reportOnJobStatus()'); + console.log(data); + switch (data.status) { + case "ready_to_run": + startJob(); + checkOnJob(); + + + break; + default: + console.error('Cannot handle status ' + data.status); + } +} + +/** + * Will refresh and get job status. + */ +function checkOnJob() { + if (maxLoops !== 0 && totalLoops < maxLoops) { + timeOutId = setTimeout(checkJobStatus, checkNextInterval); + } + if (maxLoops !== 0) { + console.log('max: ' + maxLoops + ' current: ' + totalLoops); + } + totalLoops++; +} + +/** + * Start the job. + */ +function startJob() { + console.log('In startJob()'); + if (hasStartedJob) { + console.log('Job already started!'); + return; + } + console.log('JOB STARTED!'); + hasStartedJob = true; + $.post(jobStartUri, {_token: token}).fail(reportOnSubmitError).done(reportOnSubmit) +} + +/** + * Function is called when the JSON array could not be retrieved. + * + * @param xhr + * @param status + * @param error + */ +function reportFailure(xhr, status, error) { + // cancel checking again for job status: + clearTimeout(timeOutId); + + // hide status boxes: + $('.statusbox').hide(); + + // show fatal error box: + $('.fatal_error').show(); + + $('.fatal_error_txt').text('Cannot get status of current job: ' + status + ': ' + error); + // show error box. +} + +/** + * Function is called when the job could not be started. + * + * @param xhr + * @param status + * @param error + */ +function reportOnSubmitError(xhr, status, error) { + // cancel checking again for job status: + clearTimeout(timeOutId); + + // hide status boxes: + $('.statusbox').hide(); + + // show fatal error box: + $('.fatal_error').show(); + + $('.fatal_error_txt').text('Job could not be started or crashed: ' + status + ': ' + error); + // show error box. +} + +function reportOnSubmit(data) { + if (data.status === 'NOK') { + // cancel checking again for job status: + clearTimeout(timeOutId); + + // hide status boxes: + $('.statusbox').hide(); + + // show fatal error box: + $('.fatal_error').show(); + + $('.fatal_error_txt').text('Job could not be started or crashed: ' + data.message); + // show error box. + } +} + +// /** +// * This method is called when the JSON query returns an error. If possible, this error is relayed to the user. +// */ +// function reportFailedJob(jqxhr, textStatus, error) { +// console.log('In reportFailedJob()'); +// +// // cancel refresh +// clearTimeout(timeOutId); +// +// // hide all possible boxes: +// $('.statusbox').hide(); +// +// // fill in some details: +// var errorMessage = textStatus + " " + error; +// +// $('.fatal_error_txt').text(errorMessage); +// +// // show the fatal error box: +// $('.fatal_error').show(); +// } +// +// /** +// * This method is called when the job enquiry (JSON) returns some info. +// * It also decides whether or not to check again. +// * +// * @param data +// */ +// function reportOnJobStatus(data) { +// console.log('In reportOnJobStatus()'); +// switch (data.status) { +// case "configured": +// console.log('Job reports configured.'); +// // job is ready. Do not check again, just show the start-box. Hide the rest. +// if (!job.configuration['auto-start']) { +// $('.statusbox').hide(); +// $('.status_configured').show(); +// } +// if (job.configuration['auto-start']) { +// timeOutId = setTimeout(checkJobStatus, interval); +// } +// if (pressedStart) { +// // do a time out just in case. Could be that job is running or is even done already. +// timeOutId = setTimeout(checkJobStatus, 2000); +// pressedStart = false; +// } +// break; +// case "running": +// console.log('Job reports running.'); +// // job is running! Show the running box: +// $('.statusbox').hide(); +// $('.status_running').show(); +// +// // update the bar +// updateBar(data); +// +// // update the status text: +// updateStatusText(data); +// +// // report on detected errors: +// reportOnErrors(data); +// +// if (jobIsStalled(data)) { +// // do something +// showStalledBox(); +// } else { +// // check again in 500ms +// timeOutId = setTimeout(checkJobStatus, interval); +// } +// break; +// case "finished": +// console.log('Job reports finished.'); +// $('.statusbox').hide(); +// $('.status_finished').show(); +// // report on detected errors: +// reportOnErrors(data); +// // show text: +// $('#import-status-more-info').html(data.finishedText); +// break; +// case "error": +// clearTimeout(timeOutId); +// console.log('Job reports ERROR.'); +// // hide all possible boxes: +// $('.statusbox').hide(); +// +// // fill in some details: +// var errorMessage = data.errors.join(", "); +// +// $('.fatal_error_txt').text(errorMessage); +// +// // show the fatal error box: +// $('.fatal_error').show(); +// break; +// case "configuring": +// console.log('Job reports configuring.'); +// // redirect back to configure screen. +// window.location = jobConfigureUri; +// break; +// default: +// console.error('Cannot handle job status ' + data.status); +// break; +// +// } +// } +// +// /** +// * Shows a fatal error when the job seems to be stalled. +// */ +// function showStalledBox() { +// console.log('In showStalledBox().'); +// $('.statusbox').hide(); +// $('.fatal_error').show(); +// $('.fatal_error_txt').text(langImportTimeOutError); +// } +// +// /** +// * Detects if a job is frozen. +// * +// * @param data +// */ +// function jobIsStalled(data) { +// console.log('In jobIsStalled().'); +// if (data.done === numberOfSteps) { +// numberOfReports++; +// console.log('Number of reports ' + numberOfReports); +// } +// if (data.done !== numberOfSteps) { +// numberOfReports = 0; +// console.log('Number of reports ' + numberOfReports); +// } +// if (numberOfReports > 20) { +// console.log('Number of reports > 20! ' + numberOfReports); +// return true; +// } +// numberOfSteps = data.done; +// console.log('Number of steps ' + numberOfSteps); +// return false; +// } +// +// /** +// * This function tells Firefly start the job. It will also initialize a re-check in 500ms time. +// * Only when job is in "configured" state. +// */ +// function startJob() { +// console.log('In startJob().'); +// if (job.status === "configured") { +// console.log('Job status = configured.'); +// // disable the button, add loading thing. +// $('.start-job').prop('disabled', true).text('...'); +// $.post(jobStartUri, {_token: token}).fail(reportOnSubmitError); +// +// // check status, every 500 ms. +// timeOutId = setTimeout(checkJobStatus, startInterval); +// return; +// } +// console.log('Job.status = ' + job.status); +// } +// +// /** +// * When the start button fails (returns error code) this function reports. It assumes a time out. +// */ +// function reportOnSubmitError(jqxhr, textStatus, error) { +// console.log('In reportOnSubmitError().'); +// // stop the refresh thing +// clearTimeout(timeOutId); +// +// // hide all possible boxes: +// $('.statusbox').hide(); +// +// // fill in some details: +// var errorMessage = "Submitting the job returned an error: " + textStatus + ' ' + error; +// +// $('.fatal_error_txt').text(errorMessage); +// +// // show the fatal error box: +// $('.fatal_error').show(); +// jobFailed = true; +// +// } +// +// /** +// * This method updates the percentage bar thing if the job is running! +// */ +// function updateBar(data) { +// console.log('In updateBar().'); +// var bar = $('#import-status-bar'); +// if (data.show_percentage) { +// bar.addClass('progress-bar-success').removeClass('progress-bar-info'); +// bar.attr('aria-valuenow', data.percentage); +// bar.css('width', data.percentage + '%'); +// $('#import-status-bar').text(data.done + '/' + data.steps); +// return true; +// } +// // dont show percentage: +// bar.removeClass('progress-bar-success').addClass('progress-bar-info'); +// bar.attr('aria-valuenow', 100); +// bar.css('width', '100%'); +// return true; +// } +// +// /** +// * Add text with current import status. +// * @param data +// */ +// function updateStatusText(data) { +// "use strict"; +// console.log('In updateStatusText().'); +// $('#import-status-txt').removeClass('text-danger').text(data.statusText); +// } +// +// /** +// * Report on errors found in import: +// * @param data +// */ +// function reportOnErrors(data) { +// console.log('In reportOnErrors().'); +// if (knownErrors === data.errors.length) { +// return; +// } +// if (data.errors.length === 0) { +// return; +// } +// +// if (data.errors.length === 1) { +// $('#import-status-error-intro').text(langImportSingleError); +// //'An error has occured during the import. The import can continue, however.' +// } +// if (data.errors.length > 1) { +// // 'Errors have occured during the import. The import can continue, however.' +// $('#import-status-error-intro').text(langImportMultiError); +// } +// $('.info_errors').show(); +// // fill the list with error texts +// $('#import-status-error-list').empty(); +// for (var i = 0; i < data.errors.length; i++) { +// var errorSet = data.errors[i]; +// for (var j = 0; j < errorSet.length; j++) { +// var item = $('
    • ').html(errorSet[j]); +// $('#import-status-error-list').append(item); +// } +// } +// } \ No newline at end of file diff --git a/resources/views/import/status.twig b/resources/views/import/status.twig index 26a543b61d..3d2832f2c0 100644 --- a/resources/views/import/status.twig +++ b/resources/views/import/status.twig @@ -43,7 +43,7 @@
    • - {# Box for when the job is ready to start #} + {# Box for when the job is ready to start + #} - {# Box for when the job is running! #} + {# Box for when the job is running! - - {# displays the finished status of the import #} + #} + {# displays the finished status of the import + #} {# box to show error information. #} + {# + #} {% endblock %} {% block scripts %} - + {% endblock %} {% block styles %} {% endblock %} diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index b8611f7554..cba44bd866 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -586,6 +586,14 @@ try { } ); + Breadcrumbs::register( + 'import.prerequisites.index', + function (BreadCrumbsGenerator $breadcrumbs, string $importProvider) { + $breadcrumbs->parent('import.index'); + $breadcrumbs->push(trans('import.prerequisites_breadcrumb_' . $importProvider), route('import.prerequisites.index', [$importProvider])); + } + ); + Breadcrumbs::register( 'import.job.configuration.index', function (BreadCrumbsGenerator $breadcrumbs, ImportJob $job) { @@ -595,19 +603,10 @@ try { ); Breadcrumbs::register( - 'import.prerequisites.index', - function (BreadCrumbsGenerator $breadcrumbs, string $importProvider) { - $breadcrumbs->parent('import.index'); - $breadcrumbs->push(trans('import.prerequisites_breadcrumb_' . $importProvider), route('import.prerequisites.index', [$importProvider])); - } - ); - - - Breadcrumbs::register( - 'import.status', + 'import.job.status.index', function (BreadCrumbsGenerator $breadcrumbs, ImportJob $job) { $breadcrumbs->parent('import.index'); - $breadcrumbs->push(trans('import.status_bread_crumb', ['key' => $job->key]), route('import.status', [$job->key])); + $breadcrumbs->push(trans('import.job_status_breadcrumb', ['key' => $job->key]), route('import.job.status.index', [$job->key])); } ); diff --git a/routes/web.php b/routes/web.php index 5249864f04..0fe2713fc4 100755 --- a/routes/web.php +++ b/routes/web.php @@ -456,6 +456,13 @@ Route::group( Route::get('job/configuration/{importJob}', ['uses' => 'Import\JobConfigurationController@index', 'as' => 'job.configuration.index']); Route::post('job/configuration/{importJob}', ['uses' => 'Import\JobConfigurationController@post', 'as' => 'job.configuration.post']); + // get status of a job. This is also the landing page of a job after job config is complete. + Route::get('job/status/{importJob}', ['uses' => 'Import\JobStatusController@index', 'as' => 'job.status.index']); + Route::get('job/json/{importJob}', ['uses' => 'Import\JobStatusController@json', 'as' => 'job.status.json']); + + // start the job! + Route::post('job/start/{importJob}', ['uses' => 'Import\JobStatusController@start', 'as' => 'job.start']); + // import method prerequisites: # # From 3ead4d4587bc801bcb5032bc5577a6bbe0b36b71 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 30 Apr 2018 06:12:55 +0200 Subject: [PATCH 027/182] List rules as inactive if relevant #1392 --- app/Repositories/Bill/BillRepository.php | 4 ++-- resources/lang/en_US/firefly.php | 1 + resources/views/bills/show.twig | 4 +++- resources/views/list/bills.twig | 7 ++++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index a9c1e38be2..670f984f53 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -393,11 +393,11 @@ class BillRepository implements BillRepositoryInterface $rules = $this->user->rules() ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id') ->where('rule_actions.action_type', 'link_to_bill') - ->get(['rules.id', 'rules.title', 'rule_actions.action_value']); + ->get(['rules.id', 'rules.title', 'rule_actions.action_value','rules.active']); $array = []; foreach ($rules as $rule) { $array[$rule->action_value] = $array[$rule->action_value] ?? []; - $array[$rule->action_value][] = ['id' => $rule->id, 'title' => $rule->title]; + $array[$rule->action_value][] = ['id' => $rule->id, 'title' => $rule->title,'active' => $rule->active]; } $return = []; foreach ($collection as $bill) { diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 2aa4894347..cb1a2848f4 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -667,6 +667,7 @@ return [ 'bill_will_automatch' => 'Bill will automatically linked to matching transactions', 'skips_over' => 'skips over', 'bill_store_error' => 'An unexpected error occurred while storing your new bill. Please check the log files', + 'list_inactive_rule' => 'inactive rule', // accounts: 'details_for_asset' => 'Details for asset account ":name"', diff --git a/resources/views/bills/show.twig b/resources/views/bills/show.twig index 7f61c3d402..96603a4171 100644 --- a/resources/views/bills/show.twig +++ b/resources/views/bills/show.twig @@ -73,7 +73,9 @@ {% if rules.count > 0 %} {% endif %} diff --git a/resources/views/list/bills.twig b/resources/views/list/bills.twig index f56763283d..33ab891135 100644 --- a/resources/views/list/bills.twig +++ b/resources/views/list/bills.twig @@ -46,7 +46,12 @@ {% if entry.rules|length > 0 %} {% endif %} From 1c2089b8a32499f91f54aaa3d44739e371e7976d Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 30 Apr 2018 06:17:27 +0200 Subject: [PATCH 028/182] Fixes #1400 --- resources/lang/en_US/firefly.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index cb1a2848f4..3062b2aeab 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -803,6 +803,7 @@ return [ 'opt_group_savingAsset' => 'Savings accounts', 'opt_group_sharedAsset' => 'Shared asset accounts', 'opt_group_ccAsset' => 'Credit cards', + 'opt_group_cashWalletAsset' => 'Cash wallets', 'notes' => 'Notes', 'unknown_journal_error' => 'Could not store the transaction. Please check the log files.', From cd75224cdddaab995eb6ae66923faab638dc3111 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 30 Apr 2018 06:37:29 +0200 Subject: [PATCH 029/182] Create a fake routine, check for its progress. --- .../Import/JobStatusController.php | 13 +++ app/Import/Routine/FakeRoutine.php | 83 +++++++++++++++++++ .../ImportJob/ImportJobRepository.php | 14 ++++ .../ImportJobRepositoryInterface.php | 8 ++ .../Import/Routine/Fake/StageNewHandler.php | 44 ++++++++++ config/import.php | 12 ++- public/js/ff/import/status_v2.js | 17 +++- resources/views/import/status.twig | 20 +++++ routes/web.php | 2 +- 9 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 app/Import/Routine/FakeRoutine.php create mode 100644 app/Support/Import/Routine/Fake/StageNewHandler.php diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index ece8a4cdea..53307c51da 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -27,6 +27,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Import\Routine\RoutineInterface; use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use Illuminate\Http\JsonResponse; use Log; @@ -35,6 +36,9 @@ use Log; */ class JobStatusController extends Controller { + /** @var ImportJobRepositoryInterface */ + private $repository; + /** * */ @@ -46,6 +50,7 @@ class JobStatusController extends Controller function ($request, $next) { app('view')->share('mainTitleIcon', 'fa-archive'); app('view')->share('title', trans('firefly.import_index_title')); + $this->repository = app(ImportJobRepositoryInterface::class); return $next($request); } @@ -104,6 +109,8 @@ class JobStatusController extends Controller if (null === $className || !class_exists($className)) { return response()->json(['status' => 'NOK', 'message' => sprintf('Cannot find import routine class for job of type "%s".', $importProvider)]); } + // set job to be running: + $this->repository->setStatus($job, 'running'); /** @var RoutineInterface $routine */ $routine = app($className); @@ -115,9 +122,15 @@ class JobStatusController extends Controller Log::error($message); Log::error($e->getTraceAsString()); + // set job errored out: + $this->repository->setStatus($job, 'errored'); + return response()->json(['status' => 'NOK', 'message' => $message]); } + // set job finished this step: + $this->repository->setStatus($job, 'stage_finished'); + // expect nothing from routine, just return OK to user. return response()->json(['status' => 'OK', 'message' => 'finished']); } diff --git a/app/Import/Routine/FakeRoutine.php b/app/Import/Routine/FakeRoutine.php new file mode 100644 index 0000000000..6fef517d3c --- /dev/null +++ b/app/Import/Routine/FakeRoutine.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Import\Routine; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Support\Import\Routine\Fake\StageNewHandler; + + +/** + * Class FakeRoutine + */ +class FakeRoutine implements RoutineInterface +{ + /** @var ImportJob */ + private $job; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * FakeRoutine constructor. + */ + public function __construct() + { + $this->repository = app(ImportJobRepositoryInterface::class); + } + + + /** + * Fake import routine has three stages: + * + * "new": will quietly log gibberish for 15 seconds, then switch to stage "ahoy" + * unless "ahoy" has been done already. If so, jump to stage "final". + * "ahoy": will log some nonsense and then drop job into "need_extra_config" to force it back to the job config routine. + * "final": will do some logging, sleep for 10 seconds and then finish. Generates 5 random transactions. + * + * @return bool + * @throws FireflyException + */ + public function run(): void + { + switch ($this->job->stage) { + default: + throw new FireflyException(sprintf('Fake routine cannot handle stage "%s".', $this->job->stage)); + case 'new': + $handler = new StageNewHandler; + $handler->run(); + } + } + + /** + * @param ImportJob $job + * + * @return mixed + */ + public function setJob(ImportJob $job) + { + $this->job = $job; + $this->repository->setUser($job->user); + } +} \ No newline at end of file diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index 8589785f84..8bb8af480e 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -334,6 +334,20 @@ class ImportJobRepository implements ImportJobRepositoryInterface return $job; } + /** + * @param ImportJob $job + * @param string $stage + * + * @return ImportJob + */ + public function setStage(ImportJob $job, string $stage): ImportJob + { + $job->stage = $stage; + $job->save(); + + return $job; + } + /** * @param ImportJob $job * @param string $status diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php index bb56921445..e72e6ce752 100644 --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php @@ -145,6 +145,14 @@ interface ImportJobRepositoryInterface */ public function setStatus(ImportJob $job, string $status): ImportJob; + /** + * @param ImportJob $job + * @param string $stage + * + * @return ImportJob + */ + public function setStage(ImportJob $job, string $stage): ImportJob; + /** * @param ImportJob $job * @param int $steps diff --git a/app/Support/Import/Routine/Fake/StageNewHandler.php b/app/Support/Import/Routine/Fake/StageNewHandler.php new file mode 100644 index 0000000000..fbbfbc4dfd --- /dev/null +++ b/app/Support/Import/Routine/Fake/StageNewHandler.php @@ -0,0 +1,44 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Routine\Fake; + +use Log; + +/** + * Class StageNewHandler + */ +class StageNewHandler +{ + /** + * + */ + public function run(): void + { + for ($i = 0; $i < 15; $i++) { + Log::debug(sprintf('Am now in stage new hander, sleeping... (%d)', $i)); + sleep(1); + } + } + +} \ No newline at end of file diff --git a/config/import.php b/config/import.php index 390edfab10..a5f56fdb78 100644 --- a/config/import.php +++ b/config/import.php @@ -11,6 +11,7 @@ use FireflyIII\Import\Prerequisites\FakePrerequisites; use FireflyIII\Import\Prerequisites\FilePrerequisites; use FireflyIII\Import\Prerequisites\SpectrePrerequisites; use FireflyIII\Import\Routine\BunqRoutine; +use FireflyIII\Import\Routine\FakeRoutine; use FireflyIII\Import\Routine\FileRoutine; use FireflyIII\Import\Routine\SpectreRoutine; @@ -59,7 +60,7 @@ return [ 'file' => FilePrerequisites::class, 'bunq' => BunqPrerequisites::class, 'spectre' => SpectrePrerequisites::class, - 'plaid' => 'FireflyIII\Import\Prerequisites\PlaidPrerequisites', + 'plaid' => false, 'quovo' => false, 'yodlee' => false, ], @@ -77,13 +78,18 @@ return [ 'file' => FileConfigurator::class, 'bunq' => BunqConfigurator::class, 'spectre' => SpectreConfigurator::class, - 'plaid' => 'FireflyIII\Import\Configuration\PlaidConfigurator', + 'plaid' => false, + 'quovo' => false, + 'yodlee' => false, ], 'routine' => [ + 'fake' => FakeRoutine::class, 'file' => FileRoutine::class, 'bunq' => BunqRoutine::class, 'spectre' => SpectreRoutine::class, - 'plaid' => 'FireflyIII\Import\Routine\PlaidRoutine', + 'plaid' => false, + 'quovo' => false, + 'yodlee' => false, ], 'options' => [ diff --git a/public/js/ff/import/status_v2.js b/public/js/ff/import/status_v2.js index b1a2d00a8f..421627de6f 100644 --- a/public/js/ff/import/status_v2.js +++ b/public/js/ff/import/status_v2.js @@ -52,8 +52,10 @@ function reportOnJobStatus(data) { case "ready_to_run": startJob(); checkOnJob(); - - + break; + case "running": + showProgressBox(); + checkOnJob(); break; default: console.error('Cannot handle status ' + data.status); @@ -108,6 +110,17 @@ function reportFailure(xhr, status, error) { // show error box. } +function showProgressBox() { + // hide fatal error box: + $('.fatal_error').hide(); + + // hide initial status box: + $('.status_initial').hide(); + + // show running box: + $('.status_running').show(); +} + /** * Function is called when the job could not be started. * diff --git a/resources/views/import/status.twig b/resources/views/import/status.twig index 3d2832f2c0..288a290f70 100644 --- a/resources/views/import/status.twig +++ b/resources/views/import/status.twig @@ -42,6 +42,26 @@
    + {# box to show when job is running ... #} + {# Box for when the job is ready to start
    -
    +
    diff --git a/resources/views/import/fake/enter-song.twig b/resources/views/import/fake/enter-song.twig index 1874b386a5..6c8e168f3e 100644 --- a/resources/views/import/fake/enter-song.twig +++ b/resources/views/import/fake/enter-song.twig @@ -20,7 +20,7 @@
    - +
    diff --git a/resources/views/import/status.twig b/resources/views/import/status.twig index 288a290f70..d40a9ad4f7 100644 --- a/resources/views/import/status.twig +++ b/resources/views/import/status.twig @@ -170,7 +170,7 @@ var jobStatusUri = '{{ route('import.job.status.json', [importJob.key]) }}'; var jobStartUri = '{{ route('import.job.start', [importJob.key]) }}'; - + var jobConfigurationUri = '{{ route('import.job.configuration.index', [importJob.key]) }}'; // some useful translations. {#var langImportTimeOutError = '(time out thing)';#} @@ -178,7 +178,7 @@ {#var langImportMultiError = '{{ trans('import.status_errors_multi')|escape('js') }}';#} - {#var jobConfigureUri = '#}{#{{ route('import.configure', [job.key]) }}#}{#';#} + var token = '{{ csrf_token() }}'; var job = {{ job|json_encode|raw }}; From fe10955eb95e9fc1f1314b250173054fc5b6e579 Mon Sep 17 00:00:00 2001 From: Shashank M Chakravarthy Date: Thu, 3 May 2018 10:56:09 +0530 Subject: [PATCH 031/182] Adding Indian rupee Adding Indian rupee under asian currencies. --- database/seeds/TransactionCurrencySeeder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/database/seeds/TransactionCurrencySeeder.php b/database/seeds/TransactionCurrencySeeder.php index 1fc5dce66d..764dd26469 100644 --- a/database/seeds/TransactionCurrencySeeder.php +++ b/database/seeds/TransactionCurrencySeeder.php @@ -58,6 +58,7 @@ class TransactionCurrencySeeder extends Seeder $currencies[] = ['code' => 'JPY', 'name' => 'Japanese yen', 'symbol' => '¥', 'decimal_places' => 2]; $currencies[] = ['code' => 'RMB', 'name' => 'Chinese yuan', 'symbol' => '元', 'decimal_places' => 2]; $currencies[] = ['code' => 'RUB', 'name' => 'Russian ruble ', 'symbol' => '₽', 'decimal_places' => 2]; + $currencies[] = ['code' => 'INR', 'name' => 'Indian rupee', 'symbol' => '₹', 'decimal_places' => 2]; // international currencies $currencies[] = ['code' => 'XBT', 'name' => 'Bitcoin', 'symbol' => '₿', 'decimal_places' => 8]; From 38d9f10672839b78639fe7c16fa658f960921aa4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 3 May 2018 16:53:52 +0200 Subject: [PATCH 032/182] New issue templates. --- .github/ISSUE_TEMPLATE/Bug_report.md | 22 ++++++++++++++++++++ .github/ISSUE_TEMPLATE/Custom.md | 25 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/Feature_request.md | 20 ++++++++++++++++++ .github/issue_template.md | 11 ---------- 4 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/Custom.md create mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md delete mode 100644 .github/issue_template.md diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 0000000000..1312de943a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,22 @@ +--- +name: Bug report +about: Create a report to help Firefly III improve + +--- + +**Bug description** +I am running Firefly III version x.x.x + +(please give a clear and concise description of what the bug is) + +**Steps to reproduce** +What do you need to do to trigger this bug? + +**Extra info** +Please add extra info here, such as OS, browser, and the output from the `/debug`-page of your Firefly III installation (click the version at the bottom). + +**Bonus points** +Earn bonus points by: + +- Post a stacktrace from your log files +- Add a screenshot \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/Custom.md b/.github/ISSUE_TEMPLATE/Custom.md new file mode 100644 index 0000000000..f943c935cb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Custom.md @@ -0,0 +1,25 @@ +--- +name: I have a question or a problem +about: Ask away + +--- + +I am running Firefly III version x.x.x + +**Description of my issue:** + + +**Steps to reproduce** + +(please include if this problem also exists on the demo site: https://demo.firefly-iii.org/ ) + +**Extra info** + +Please add extra info here, such as OS, browser, and the output from the `/debug`-page of your Firefly III installation (click the version at the bottom). + +**Bonus points** +Earn bonus points by: + +- Post a stacktrace from your log files +- Add a screenshot +- Post nginx or Apache configuration \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 0000000000..7dfae270ae --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea or feature for Firefly III + +--- + +**Description** +Please describe your feature request: + +- I would like Firefly III to do X. +- What if you would add Y? + +**Solution** +Describe what your feature would add to Firefly III. + +**What are alternatives?** +Please describe what alternatives currently exist. + +**Additional context** +Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/issue_template.md b/.github/issue_template.md deleted file mode 100644 index d1640a0f27..0000000000 --- a/.github/issue_template.md +++ /dev/null @@ -1,11 +0,0 @@ -I am running Firefly III version x.x.x - -#### Description of my issue: - -#### Steps to reproduce - -(please include if this problem also exists on the demo site) - -#### Other important details (log files, system info): - -Please click the version number in the right corner of any Firefly III page to get debug information. \ No newline at end of file From 6bddb63b459faff8aa2f5c38e83df36f2202d337 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 3 May 2018 17:23:16 +0200 Subject: [PATCH 033/182] New code for updated import routine. --- .../Import/JobConfigurationController.php | 2 +- .../Import/JobStatusController.php | 100 ++++- .../JobConfiguration/FakeJobConfiguration.php | 26 +- app/Import/Routine/FakeRoutine.php | 2 + app/Import/Storage/ImportArrayStorage.php | 361 ++++++++++++++++++ app/Models/ImportJob.php | 12 +- .../ImportJob/ImportJobRepository.php | 49 +++ .../ImportJobRepositoryInterface.php | 27 ++ app/Repositories/Tag/TagRepository.php | 11 + .../Tag/TagRepositoryInterface.php | 7 + .../Import/Routine/Fake/StageAhoyHandler.php | 2 +- .../Import/Routine/Fake/StageFinalHandler.php | 96 ++++- .../Import/Routine/Fake/StageNewHandler.php | 2 +- config/import.php | 2 +- .../2018_04_29_174524_changes_for_v474.php | 4 + public/js/ff/import/status_v2.js | 64 +++- resources/lang/en_US/import.php | 52 +-- resources/views/import/fake/enter-album.twig | 53 +++ resources/views/import/status.twig | 17 +- routes/web.php | 1 + 20 files changed, 843 insertions(+), 47 deletions(-) create mode 100644 app/Import/Storage/ImportArrayStorage.php create mode 100644 resources/views/import/fake/enter-album.twig diff --git a/app/Http/Controllers/Import/JobConfigurationController.php b/app/Http/Controllers/Import/JobConfigurationController.php index cd27eb7420..cb736d19ac 100644 --- a/app/Http/Controllers/Import/JobConfigurationController.php +++ b/app/Http/Controllers/Import/JobConfigurationController.php @@ -85,7 +85,7 @@ class JobConfigurationController extends Controller Log::debug('Job needs no config, is ready to run!'); $this->repository->updateStatus($importJob ,'ready_to_run'); - return redirect(route('import.job.status.index', [$importProvider->key])); + return redirect(route('import.job.status.index', [$importJob->key])); } // create configuration class: diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index f97854b807..a804735a73 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -22,14 +22,19 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Import; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Import\Routine\RoutineInterface; +use FireflyIII\Import\Storage\ImportArrayStorage; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Repositories\Tag\TagRepositoryInterface; use Illuminate\Http\JsonResponse; +use Illuminate\Http\RedirectResponse; use Log; +use Symfony\Component\Debug\Exception\FatalThrowableError; /** * Class JobStatusController @@ -82,14 +87,21 @@ class JobStatusController extends Controller } /** - * @param ImportJob $job + * @param ImportJob $importJob * * @return JsonResponse */ public function json(ImportJob $importJob): JsonResponse { - $json = [ - 'status' => $importJob->status, + $extendedStatus = $importJob->extended_status; + $json = [ + 'status' => $importJob->status, + 'errors' => $importJob->errors, + 'count' => count($importJob->transactions), + 'tag_id' => $importJob->tag_id, + 'tag_name' => null === $importJob->tag_id ? null : $importJob->tag->tag, + 'journals' => $extendedStatus['count'] ?? 0, + 'journals_text' => trans_choice('import.status_with_count', $extendedStatus['count'] ?? 0), ]; return response()->json($json); @@ -104,11 +116,11 @@ class JobStatusController extends Controller public function start(ImportJob $importJob): JsonResponse { // catch impossible status: - $allowed = ['ready_to_run']; + $allowed = ['ready_to_run', 'need_job_config']; if (null !== $importJob && !in_array($importJob->status, $allowed)) { Log::error('Job is not ready.'); - session()->flash('error', trans('import.bad_job_status')); - return redirect(route('import.index')); + + return response()->json(['status' => 'NOK', 'message' => 'JobStatusController::start expects state "ready_to_run".']); } $importProvider = $importJob->provider; @@ -122,6 +134,21 @@ class JobStatusController extends Controller // if the job is set to "provider_finished", we should be able to store transactions // generated by the provider. // otherwise, just continue. + if ($importJob->status === 'provider_finished') { + try { + $this->importFromJob($importJob); + } catch (FireflyException $e) { + $message = 'The import storage routine crashed: ' . $e->getMessage(); + Log::error($message); + Log::error($e->getTraceAsString()); + + // set job errored out: + $this->repository->setStatus($importJob, 'error'); + + return response()->json(['status' => 'NOK', 'message' => $message]); + } + } + // set job to be running: $this->repository->setStatus($importJob, 'running'); @@ -144,6 +171,67 @@ class JobStatusController extends Controller // expect nothing from routine, just return OK to user. return response()->json(['status' => 'OK', 'message' => 'stage_finished']); + } + + /** + * @param ImportJob $job + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(ImportJob $importJob): JsonResponse + { + // catch impossible status: + $allowed = ['provider_finished', 'storing_data']; // todo remove storing data. + if (null !== $importJob && !in_array($importJob->status, $allowed)) { + Log::error('Job is not ready.'); + + return response()->json(['status' => 'NOK', 'message' => 'JobStatusController::start expects state "provider_finished".']); + } + + // set job to be storing data: + $this->repository->setStatus($importJob, 'storing_data'); + + try { + $this->importFromJob($importJob); + } catch (FireflyException $e) { + $message = 'The import storage routine crashed: ' . $e->getMessage(); + Log::error($message); + Log::error($e->getTraceAsString()); + + // set job errored out: + $this->repository->setStatus($importJob, 'error'); + + return response()->json(['status' => 'NOK', 'message' => $message]); + } + + // set job to be finished. + $this->repository->setStatus($importJob, 'finished'); + + // expect nothing from routine, just return OK to user. + return response()->json(['status' => 'OK', 'message' => 'storage_finished']); + } + + /** + * @param ImportJob $importJob + * + * @throws FireflyException + */ + private function importFromJob(ImportJob $importJob): void + { + try { + $storage = new ImportArrayStorage($importJob); + $journals = $storage->store(); + $extendedStatus = $importJob->extended_status; + $extendedStatus['count'] = $journals->count(); + $this->repository->setExtendedStatus($importJob, $extendedStatus); + } catch (FireflyException|Exception|FatalThrowableError $e) { + throw new FireflyException($e->getMessage()); + } + + + + } // /** diff --git a/app/Import/JobConfiguration/FakeJobConfiguration.php b/app/Import/JobConfiguration/FakeJobConfiguration.php index 9e1af04bc6..509e0caf2a 100644 --- a/app/Import/JobConfiguration/FakeJobConfiguration.php +++ b/app/Import/JobConfiguration/FakeJobConfiguration.php @@ -56,10 +56,16 @@ class FakeJobConfiguration implements JobConfiguratorInterface // configuration array of job must have two values: // 'artist' must be 'david bowie', case insensitive // 'song' must be 'golden years', case insensitive. + // if stage is not "new", then album must be 'station to station' $config = $this->job->configuration; + if ($this->job->stage === 'new') { + return (isset($config['artist']) && 'david bowie' === strtolower($config['artist'])) + && (isset($config['song']) && 'golden years' === strtolower($config['song'])); + } + + return isset($config['album']) && 'station to station' === strtolower($config['album']); + - return (isset($config['artist']) && 'david bowie' === strtolower($config['artist'])) - && (isset($config['song']) && 'golden years' === strtolower($config['song'])); } /** @@ -72,16 +78,24 @@ class FakeJobConfiguration implements JobConfiguratorInterface public function configureJob(array $data): MessageBag { $artist = strtolower($data['artist'] ?? ''); + $song = strtolower($data['song'] ?? ''); + $album = strtolower($data['album'] ?? ''); $configuration = $this->job->configuration; if ($artist === 'david bowie') { // store artist $configuration['artist'] = $artist; } - $song = strtolower($data['song'] ?? ''); + if ($song === 'golden years') { - // store artist + // store song $configuration['song'] = $song; } + + if ($album=== 'station to station') { + // store album + $configuration['album'] = $album; + } + $this->repository->setConfiguration($this->job, $configuration); $messages = new MessageBag(); @@ -114,12 +128,16 @@ class FakeJobConfiguration implements JobConfiguratorInterface $config = $this->job->configuration; $artist = $config['artist'] ?? ''; $song = $config['song'] ?? ''; + $album = $config['album'] ?? ''; if (strtolower($artist) !== 'david bowie') { return 'import.fake.enter-artist'; } if (strtolower($song) !== 'golden years') { return 'import.fake.enter-song'; } + if (strtolower($album) !== 'station to station' && $this->job->stage !== 'new') { + return 'import.fake.enter-album'; + } } /** diff --git a/app/Import/Routine/FakeRoutine.php b/app/Import/Routine/FakeRoutine.php index a79f9ce76f..1894ebaac5 100644 --- a/app/Import/Routine/FakeRoutine.php +++ b/app/Import/Routine/FakeRoutine.php @@ -87,9 +87,11 @@ class FakeRoutine implements RoutineInterface break; case 'final': $handler = new StageFinalHandler; + $handler->setJob($this->job); $transactions = $handler->getTransactions(); $this->repository->setStatus($this->job, 'provider_finished'); $this->repository->setStage($this->job, 'final'); + $this->repository->setTransactions($this->job, $transactions); } } diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php new file mode 100644 index 0000000000..2d08c9631a --- /dev/null +++ b/app/Import/Storage/ImportArrayStorage.php @@ -0,0 +1,361 @@ +importJob = $importJob; + $this->countTransfers(); + + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + + Log::debug('Constructed ImportArrayStorage()'); + } + + /** + * Actually does the storing. + * + * @return Collection + * @throws FireflyException + */ + public function store(): Collection + { + $count = count($this->importJob->transactions); + Log::debug(sprintf('Now in store(). Count of items is %d', $count)); + $toStore = []; + foreach ($this->importJob->transactions as $index => $transaction) { + Log::debug(sprintf('Now at item %d out of %d', ($index + 1), $count)); + $existingId = $this->hashExists($transaction); + if (null !== $existingId) { + $this->logDuplicateObject($transaction, $existingId); + $this->repository->addErrorMessage( + $this->importJob, sprintf( + 'Entry #%d ("%s") could not be imported. It already exists.', + $index, $transaction['description'] + ) + ); + continue; + } + if ($this->checkForTransfers) { + if ($this->transferExists($transaction)) { + $this->logDuplicateTransfer($transaction); + $this->repository->addErrorMessage( + $this->importJob, sprintf( + 'Entry #%d ("%s") could not be imported. Such a transfer already exists.', + $index, + $transaction['description'] + ) + ); + continue; + } + } + $toStore[] = $transaction; + } + + if (count($toStore) === 0) { + Log::info('No transactions to store left!'); + + return new Collection; + } + Log::debug('Going to store...'); + // now actually store them: + $collection = new Collection; + /** @var TransactionJournalFactory $factory */ + $factory = app(TransactionJournalFactory::class); + $factory->setUser($this->importJob->user); + foreach ($toStore as $store) { + // convert the date to an object: + $store['date'] = Carbon::createFromFormat('Y-m-d', $store['date']); + + // store the journal. + $collection->push($factory->create($store)); + } + Log::debug('DONE storing!'); + + + // create tag and append journals: + $this->createTag($collection); + + return $collection; + } + + /** + * @param Collection $collection + */ + private function createTag(Collection $collection): void + { + + /** @var TagRepositoryInterface $repository */ + $repository = app(TagRepositoryInterface::class); + $repository->setUser($this->importJob->user); + $data = [ + 'tag' => trans('import.import_with_key', ['key' => $this->importJob->key]), + 'date' => new Carbon, + 'description' => null, + 'latitude' => null, + 'longitude' => null, + 'zoomLevel' => null, + 'tagMode' => 'nothing', + ]; + $tag = $repository->store($data); + + Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); + Log::debug('Looping journals...'); + $journalIds = $collection->pluck('id')->toArray(); + $tagId = $tag->id; + foreach ($journalIds as $journalId) { + Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); + DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); + } + Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $collection->count(), $tag->id, $tag->tag)); + + $this->repository->setTag($this->importJob, $tag); + + } + + /** + * Count the number of transfers in the array. If this is zero, don't bother checking for double transfers. + */ + private function countTransfers(): void + { + $count = 0; + foreach ($this->importJob->transactions as $transaction) { + if (strtolower(TransactionType::TRANSFER) === $transaction['type']) { + $count++; + } + } + $count = 1; + if ($count > 0) { + $this->checkForTransfers = true; + + // get users transfers. Needed for comparison. + $this->getTransfers(); + } + + } + + /** + * Get the users transfers, so they can be compared to whatever the user is trying to import. + */ + private function getTransfers(): void + { + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts() + ->setTypes([TransactionType::TRANSFER]) + ->withOpposingAccount(); + $collector->removeFilter(InternalTransferFilter::class); + $this->transfers = $collector->getJournals(); + + } + + + /** + * @param array $transaction + * + * @return int|null + * @throws FireflyException + */ + private function hashExists(array $transaction): ?int + { + $json = json_encode($transaction); + if ($json === false) { + throw new FireflyException('Could not encode import array. Please see the logs.', $transaction); + } + $hash = hash('sha256', $json, false); + + // find it! + /** @var TransactionJournalMeta $entry */ + $entry = TransactionJournalMeta + ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') + ->where('data', $hash) + ->where('name', 'importHashV2') + ->first(['journal_meta.*']); + if (null === $entry) { + return null; + } + Log::info(sprintf('Found a transaction journal with an existing hash: %s', $hash)); + + return (int)$entry->transaction_journal_id; + } + + /** + * @param array $transaction + * @param int $existingId + */ + private function logDuplicateObject(array $transaction, int $existingId): void + { + Log::info( + 'Transaction is a duplicate, and will not be imported (the hash exists).', + [ + 'existing' => $existingId, + 'description' => $transaction['description'] ?? '', + 'amount' => $transaction['transactions'][0]['amount'] ?? 0, + 'date' => isset($transaction['date']) ? $transaction['date'] : '', + ] + ); + + } + + /** + * @param array $transaction + */ + private function logDuplicateTransfer(array $transaction): void + { + Log::info( + 'Transaction is a duplicate transfer, and will not be imported (such a transfer exists already).', + [ + 'description' => $transaction['description'] ?? '', + 'amount' => $transaction['transactions'][0]['amount'] ?? 0, + 'date' => isset($transaction['date']) ? $transaction['date'] : '', + ] + ); + } + + /** + * Check if a transfer exists. + * + * @param $transaction + * + * @return bool + */ + private function transferExists(array $transaction): bool + { + Log::debug('Check if is a double transfer.'); + if (strtolower(TransactionType::TRANSFER) !== $transaction['type']) { + Log::debug(sprintf('Is a %s, not a transfer so no.', $transaction['type'])); + + return false; + } + + // how many hits do we need? + $requiredHits = count($transaction['transactions']) * 4; + $totalHits = 0; + Log::debug(sprintf('Required hits for transfer comparison is %d', $requiredHits)); + + // loop over each split: + foreach ($transaction['transactions'] as $current) { + + // get the amount: + $amount = (string)($current['amount'] ?? '0'); + if (bccomp($amount, '0') === -1) { + $amount = bcmul($amount, '-1'); + } + + // get the description: + $description = strlen((string)$current['description']) === 0 ? $transaction['description'] : $current['description']; + + // get the source and destination ID's: + $currentSourceIDs = [(int)$current['source_id'], (int)$current['destination_id']]; + sort($currentSourceIDs); + + // get the source and destination names: + $currentSourceNames = [(string)$current['source_name'], (string)$current['destination_name']]; + sort($currentSourceNames); + + // then loop all transfers: + /** @var Transaction $transfer */ + foreach ($this->transfers as $transfer) { + // number of hits for this split-transfer combination: + $hits = 0; + Log::debug(sprintf('Now looking at transaction journal #%d', $transfer->journal_id)); + // compare amount: + Log::debug(sprintf('Amount %s compared to %s', $amount, $transfer->transaction_amount)); + if (0 !== bccomp($amount, $transfer->transaction_amount)) { + continue; + } + ++$hits; + Log::debug(sprintf('Comparison is a hit! (%s)', $hits)); + + // compare description: + Log::debug(sprintf('Comparing "%s" to "%s"', $description, $transfer->description)); + if ($description !== $transfer->description) { + continue; + } + ++$hits; + Log::debug(sprintf('Comparison is a hit! (%s)', $hits)); + + // compare date: + $transferDate = $transfer->date->format('Y-m-d'); + Log::debug(sprintf('Comparing dates "%s" to "%s"', $transaction['date'], $transferDate)); + if ($transaction['date'] !== $transferDate) { + continue; + } + ++$hits; + Log::debug(sprintf('Comparison is a hit! (%s)', $hits)); + + // compare source and destination id's + $transferSourceIDs = [(int)$transfer->account_id, (int)$transfer->opposing_account_id]; + sort($transferSourceIDs); + Log::debug('Comparing current transaction source+dest IDs', $currentSourceIDs); + Log::debug('.. with current transfer source+dest IDs', $transferSourceIDs); + if ($currentSourceIDs === $transferSourceIDs) { + ++$hits; + Log::debug(sprintf('Source IDs are the same! (%d)', $hits)); + } + unset($transferSourceIDs); + + // compare source and destination names + $transferSource = [(string)$transfer->account_name, (int)$transfer->opposing_account_name]; + sort($transferSource); + Log::debug('Comparing current transaction source+dest names', $currentSourceNames); + Log::debug('.. with current transfer source+dest names', $transferSource); + if ($currentSourceNames === $transferSource) { + Log::debug(sprintf('Source names are the same! (%d)', $hits)); + ++$hits; + } + $totalHits += $hits; + if ($totalHits >= $requiredHits) { + return true; + } + } + } + Log::debug(sprintf('Total hits: %d, required: %d', $totalHits, $requiredHits)); + + return $totalHits >= $requiredHits; + } + +} \ No newline at end of file diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php index ad12a87d35..81fa39a74f 100644 --- a/app/Models/ImportJob.php +++ b/app/Models/ImportJob.php @@ -47,9 +47,10 @@ class ImportJob extends Model 'configuration' => 'array', 'extended_status' => 'array', 'transactions' => 'array', + 'errors' => 'array', ]; /** @var array */ - protected $fillable = ['key', 'user_id', 'file_type', 'provider', 'status', 'stage', 'configuration', 'extended_status', 'transactions']; + protected $fillable = ['key', 'user_id', 'file_type', 'provider', 'status', 'stage', 'configuration', 'extended_status', 'transactions', 'errors']; /** * @param $value @@ -79,4 +80,13 @@ class ImportJob extends Model { return $this->belongsTo(User::class); } + + /** + * @codeCoverageIgnore + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function tag() + { + return $this->belongsTo(Tag::class); + } } diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index 55c8c54cd5..2e0bb62de0 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\ImportJob; use Crypt; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; +use FireflyIII\Models\Tag; use FireflyIII\Models\TransactionJournalMeta; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\User; @@ -126,6 +127,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface $importJob = ImportJob::create( [ 'user_id' => $this->user->id, + 'tag_id' => null, 'provider' => $importProvider, 'file_type' => '', 'key' => Str::random(12), @@ -134,6 +136,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface 'configuration' => [], 'extended_status' => [], 'transactions' => [], + 'errors' => [], ] ); @@ -427,4 +430,50 @@ class ImportJobRepository implements ImportJobRepositoryInterface { return $job->uploadFileContents(); } + + /** + * @param ImportJob $job + * @param array $transactions + * + * @return ImportJob + */ + public function setTransactions(ImportJob $job, array $transactions): ImportJob + { + $job->transactions = $transactions; + $job->save(); + + return $job; + } + + /** + * Add message to job. + * + * @param ImportJob $job + * @param string $error + * + * @return ImportJob + */ + public function addErrorMessage(ImportJob $job, string $error): ImportJob + { + $errors = $job->errors; + $errors[] = $error; + $job->errors = $errors; + $job->save(); + + return $job; + } + + /** + * @param ImportJob $job + * @param Tag $tag + * + * @return ImportJob + */ + public function setTag(ImportJob $job, Tag $tag): ImportJob + { + $job->tag()->associate($tag); + $job->save(); + + return $job; + } } diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php index e72e6ce752..ce1256d8d8 100644 --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\ImportJob; use FireflyIII\Models\ImportJob; +use FireflyIII\Models\Tag; use FireflyIII\User; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -32,6 +33,32 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; interface ImportJobRepositoryInterface { + /** + * @param ImportJob $job + * @param array $transactions + * + * @return ImportJob + */ + public function setTransactions(ImportJob $job, array $transactions): ImportJob; + + /** + * @param ImportJob $job + * @param Tag $tag + * + * @return ImportJob + */ + public function setTag(ImportJob $job, Tag $tag): ImportJob; + + /** + * Add message to job. + * + * @param ImportJob $job + * @param string $error + * + * @return ImportJob + */ + public function addErrorMessage(ImportJob $job, string $error): ImportJob; + /** * @param ImportJob $job * @param int $index diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index e893ec152e..0bd3980bf9 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -105,6 +105,7 @@ class TagRepository implements TagRepositoryInterface /** * @param int $tagId * + * @deprecated * @return Tag */ public function find(int $tagId): Tag @@ -453,4 +454,14 @@ class TagRepository implements TagRepositoryInterface return (int)($range[0] + $extra); } + + /** + * @param int $tagId + * + * @return Tag|null + */ + public function findNull(int $tagId): ?Tag + { + return $this->user->tags()->find($tagId); + } } diff --git a/app/Repositories/Tag/TagRepositoryInterface.php b/app/Repositories/Tag/TagRepositoryInterface.php index f17b57118b..ab5c537575 100644 --- a/app/Repositories/Tag/TagRepositoryInterface.php +++ b/app/Repositories/Tag/TagRepositoryInterface.php @@ -69,6 +69,13 @@ interface TagRepositoryInterface /** * @param int $tagId * + * @return Tag|null + */ + public function findNull(int $tagId): ?Tag; + + /** + * @param int $tagId + * @deprecated * @return Tag */ public function find(int $tagId): Tag; diff --git a/app/Support/Import/Routine/Fake/StageAhoyHandler.php b/app/Support/Import/Routine/Fake/StageAhoyHandler.php index 851b61ce06..3ccd4cac2c 100644 --- a/app/Support/Import/Routine/Fake/StageAhoyHandler.php +++ b/app/Support/Import/Routine/Fake/StageAhoyHandler.php @@ -36,7 +36,7 @@ class StageAhoyHandler */ public function run(): void { - for ($i = 0; $i < 15; $i++) { + for ($i = 0; $i < 2; $i++) { Log::debug(sprintf('Am now in stage AHOY hander, sleeping... (%d)', $i)); sleep(1); } diff --git a/app/Support/Import/Routine/Fake/StageFinalHandler.php b/app/Support/Import/Routine/Fake/StageFinalHandler.php index 6900573de5..c849eb538a 100644 --- a/app/Support/Import/Routine/Fake/StageFinalHandler.php +++ b/app/Support/Import/Routine/Fake/StageFinalHandler.php @@ -2,6 +2,8 @@ namespace FireflyIII\Support\Import\Routine\Fake; +use Carbon\Carbon; + /** * Class StageFinalHandler * @@ -9,6 +11,18 @@ namespace FireflyIII\Support\Import\Routine\Fake; */ class StageFinalHandler { + + private $job; + + /** + * @param mixed $job + */ + public function setJob($job): void + { + $this->job = $job; + } + + /** * @return array */ @@ -17,12 +31,92 @@ class StageFinalHandler $transactions = []; for ($i = 0; $i < 5; $i++) { - $transaction = []; + $transaction = [ + 'type' => 'withdrawal', + 'date' => Carbon::create()->format('Y-m-d'), + 'tags' => '', + 'user' => $this->job->user_id, + // all custom fields: + 'internal_reference' => null, + 'notes' => null, + + // journal data: + 'description' => 'Some random description #' . random_int(1, 10000), + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'bill_id' => null, + 'bill_name' => null, + + // transaction data: + 'transactions' => [ + [ + 'currency_id' => null, + 'currency_code' => 'EUR', + 'description' => null, + 'amount' => random_int(500, 5000) / 100, + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + 'source_id' => null, + 'source_name' => 'Checking Account', + 'destination_id' => null, + 'destination_name' => 'Random expense account #' . random_int(1, 10000), + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'reconciled' => false, + 'identifier' => 0, + ], + ], + ]; $transactions[] = $transaction; } + // add a transfer I know exists already + $transactions[] = [ + 'type' => 'transfer', + 'date' => '2017-02-28', + 'tags' => '', + 'user' => $this->job->user_id, + + // all custom fields: + 'internal_reference' => null, + 'notes' => null, + + // journal data: + 'description' => 'Saving money for February', + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'bill_id' => null, + 'bill_name' => null, + + // transaction data: + 'transactions' => [ + [ + 'currency_id' => null, + 'currency_code' => 'EUR', + 'description' => null, + 'amount' => '140', + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + 'source_id' => 1, + 'source_name' => 'Checking Account', + 'destination_id' => 2, + 'destination_name' => null, + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'reconciled' => false, + 'identifier' => 0, + ], + ], + ]; + return $transactions; diff --git a/app/Support/Import/Routine/Fake/StageNewHandler.php b/app/Support/Import/Routine/Fake/StageNewHandler.php index d12ef19f4f..231baae778 100644 --- a/app/Support/Import/Routine/Fake/StageNewHandler.php +++ b/app/Support/Import/Routine/Fake/StageNewHandler.php @@ -36,7 +36,7 @@ class StageNewHandler */ public function run(): void { - for ($i = 0; $i < 15; $i++) { + for ($i = 0; $i < 2; $i++) { Log::debug(sprintf('Am now in stage new hander, sleeping... (%d)', $i)); sleep(1); } diff --git a/config/import.php b/config/import.php index 171f77ffdf..89887682c3 100644 --- a/config/import.php +++ b/config/import.php @@ -65,7 +65,7 @@ return [ 'yodlee' => false, ], 'has_config' => [ - 'fake' => true, + 'fake' => false, 'file' => true, 'bunq' => true, 'spectre' => true, diff --git a/database/migrations/2018_04_29_174524_changes_for_v474.php b/database/migrations/2018_04_29_174524_changes_for_v474.php index 9cdac3f720..4b527e5ea7 100644 --- a/database/migrations/2018_04_29_174524_changes_for_v474.php +++ b/database/migrations/2018_04_29_174524_changes_for_v474.php @@ -31,6 +31,10 @@ class ChangesForV474 extends Migration $table->string('provider', 50)->after('file_type')->default(''); $table->string('stage', 50)->after('status')->default(''); $table->longText('transactions')->after('extended_status'); + $table->longText('errors')->after('transactions'); + + $table->integer('tag_id', false, true)->nullable()->after('user_id'); + $table->foreign('tag_id')->references('id')->on('tags')->onDelete('set null'); } ); } diff --git a/public/js/ff/import/status_v2.js b/public/js/ff/import/status_v2.js index 92ab649dba..fe8dc6bb04 100644 --- a/public/js/ff/import/status_v2.js +++ b/public/js/ff/import/status_v2.js @@ -22,6 +22,7 @@ var timeOutId; var hasStartedJob = false; +var jobStorageStarted = false; var checkInitialInterval = 1000; var checkNextInterval = 500; var maxLoops = 60; @@ -59,7 +60,8 @@ function reportOnJobStatus(data) { checkOnJob(); break; case "running": - showProgressBox(); + case "storing_data": + showProgressBox(data.ttatus); checkOnJob(); break; @@ -67,12 +69,48 @@ function reportOnJobStatus(data) { // redirect user to configuration for this job. window.location.replace(jobConfigurationUri); break; + case 'provider_finished': + // call routine to store stuff: + storeJobData(); + checkOnJob(); + break; + case "finished": + showJobResults(data); + break; default: console.error('Cannot handle status ' + data.status); } } +/** + * + * @param data + */ +function showJobResults(data) { + // hide all boxes. +// hide status boxes: + $('.statusbox').hide(); + + // render the count: + $('#import-status-more-info').append($('').text(data.journals_text)); + + + // render relevant data from JSON thing. + if (data.errors.length > 0) { + $('#import-status-error-txt').show(); + data.errors.forEach(function (element) { + $('#import-status-errors').append($('
  • ').text(element)); + }); + + + } + + // show success box. + $('.status_finished').show(); + +} + /** * Will refresh and get job status. */ @@ -100,6 +138,20 @@ function startJob() { $.post(jobStartUri, {_token: token}).fail(reportOnSubmitError).done(reportOnSubmit) } +/** + * Start the storage routine for this job. + */ +function storeJobData() { + console.log('In storeJobData()'); + if (jobStorageStarted) { + console.log('Store job already started!'); + return; + } + console.log('STORAGE JOB STARTED!'); + jobStorageStarted = true; + $.post(jobStorageStartUri, {_token: token}).fail(reportOnSubmitError).done(reportOnSubmit) +} + /** * Function is called when the JSON array could not be retrieved. * @@ -121,12 +173,20 @@ function reportFailure(xhr, status, error) { // show error box. } -function showProgressBox() { +/** + * + */ +function showProgressBox(status) { // hide fatal error box: $('.fatal_error').hide(); // hide initial status box: $('.status_initial').hide(); + if(status === 'running') { + $('#import-status-txt').text(langImportRunning); + } else { + $('#import-status-txt').text(langImportStoring); + } // show running box: $('.status_running').show(); diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 46bfd04366..c9a4c01524 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -23,36 +23,40 @@ declare(strict_types=1); return [ // status of import: - 'status_wait_title' => 'Please hold...', - 'status_wait_text' => 'This box will disappear in a moment.', - 'status_fatal_title' => 'A fatal error occurred', - 'status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', - 'status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', - 'status_ready_title' => 'Import is ready to start', - 'status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'status_ready_noconfig_text' => 'The import is ready to start. All the configuration you needed to do has been done. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'status_ready_config' => 'Download configuration', - 'status_ready_start' => 'Start the import', - 'status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'status_job_new' => 'The job is brand new.', - 'status_job_configuring' => 'The import is being configured.', - 'status_job_configured' => 'The import is configured.', - 'status_job_running' => 'The import is running.. Please wait..', - 'status_job_error' => 'The job has generated an error.', - 'status_job_finished' => 'The import has finished!', - 'status_running_title' => 'The import is running', - 'status_running_placeholder' => 'Please hold for an update...', - 'status_finished_title' => 'Import routine finished', - 'status_finished_text' => 'The import routine has imported your data.', - 'status_errors_title' => 'Errors during the import', - 'status_errors_single' => 'An error has occurred during the import. It does not appear to be fatal.', - 'status_errors_multi' => 'Some errors occurred during the import. These do not appear to be fatal.', + 'status_wait_title' => 'Please hold...', + 'status_wait_text' => 'This box will disappear in a moment.', + 'status_fatal_title' => 'A fatal error occurred', + 'status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'status_ready_title' => 'Import is ready to start', + 'status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'status_ready_noconfig_text' => 'The import is ready to start. All the configuration you needed to do has been done. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'status_ready_config' => 'Download configuration', + 'status_ready_start' => 'Start the import', + 'status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'status_job_new' => 'The job is brand new.', + 'status_job_configuring' => 'The import is being configured.', + 'status_job_configured' => 'The import is configured.', + 'status_job_running' => 'The import is running.. Please wait..', + 'status_job_storing' => 'The import is storing your data.. Please wait..', + 'status_job_error' => 'The job has generated an error.', + 'status_job_finished' => 'The import has finished!', + 'status_running_title' => 'The import is running', + 'status_running_placeholder' => 'Please hold for an update...', + 'status_finished_title' => 'Import routine finished', + 'status_finished_text' => 'The import routine has imported your data.', + 'status_errors_title' => 'Errors during the import', + 'status_errors_single' => 'An error has occurred during the import. It does not appear to be fatal.', + 'status_errors_multi' => 'Some errors occurred during the import. These do not appear to be fatal.', + 'status_with_count' => 'One transaction has been imported|:count transactions have been imported.', + 'status_bread_crumb' => 'Import status', 'status_sub_title' => 'Import status', 'config_sub_title' => 'Set up your import', 'status_finished_job' => 'The :count transactions imported can be found in tag :tag.', 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', 'import_with_key' => 'Import with key \':key\'', + 'finished_with_errors' => 'The import reported some problems.', // file, upload something 'file_upload_title' => 'Import setup (1/4) - Upload your file', diff --git a/resources/views/import/fake/enter-album.twig b/resources/views/import/fake/enter-album.twig new file mode 100644 index 0000000000..b2a7a95517 --- /dev/null +++ b/resources/views/import/fake/enter-album.twig @@ -0,0 +1,53 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render }} +{% endblock %} +{% block content %} +
    +
    +
    +
    +

    Enter station for fake import

    +
    +
    +

    + Enter "station to station", no matter the capitalization. +

    +
    +
    + +
    +
    + + + +
    +
    +
    +
    +

    Fields be here.

    +
    +
    + {{ ExpandedForm.text('album') }} +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
  • +{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} diff --git a/resources/views/import/status.twig b/resources/views/import/status.twig index d40a9ad4f7..6272a60ecd 100644 --- a/resources/views/import/status.twig +++ b/resources/views/import/status.twig @@ -56,7 +56,7 @@ Running...
    -

    Some text here

    +

    @@ -128,7 +128,7 @@
    #} - {# displays the finished status of the import + {# displays the finished status of the import #} - #} - {# box to show error information. #} {#
    -
    + From 6e84326583b9ec0f311caf0181ccc2b7cd584cb9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 4 May 2018 06:00:16 +0200 Subject: [PATCH 036/182] Fix #1418 --- resources/views/bills/show.twig | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/resources/views/bills/show.twig b/resources/views/bills/show.twig index 96603a4171..c912bc5583 100644 --- a/resources/views/bills/show.twig +++ b/resources/views/bills/show.twig @@ -32,8 +32,9 @@ {{ trans('firefly.repeat_freq_' ~object.data.repeat_freq) }}. + - {{ 'bill_is_active'|_ }} + {{ 'bill_is_active'|_ }} {% if object.data.active %} {{ 'yes'|_ }} @@ -62,6 +63,12 @@
    +
    @@ -84,6 +91,17 @@ {{ 'rescan_old'|_ }}
    + {% if object.data.notes != '' %} +
    +
    +

    {{ 'notes'|_ }}

    +
    +
    + {{ object.data.notes }} +
    +
    + {% endif %} + {% if object.data.attachments_count > 0 %}
    From b541f7b944321811de2524cc0708b1a8b83ba55a Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 4 May 2018 06:59:14 +0200 Subject: [PATCH 037/182] Improve fake import. --- .../Import/JobConfigurationController.php | 6 +- .../Import/JobStatusController.php | 40 +- .../Import/PrerequisitesController.php | 6 +- .../JobConfiguration/FakeJobConfiguration.php | 17 +- .../Import/Routine/Fake/StageAhoyHandler.php | 2 +- .../Import/Routine/Fake/StageNewHandler.php | 2 +- public/js/ff/import/status_v2.js | 310 ++--------- resources/lang/en_US/form.php | 7 + resources/lang/en_US/import.php | 524 ++++++++++-------- resources/views/import/fake/apply-rules.twig | 16 +- resources/views/import/fake/enter-album.twig | 6 +- resources/views/import/fake/enter-artist.twig | 6 +- resources/views/import/fake/enter-song.twig | 6 +- .../views/import/fake/prerequisites.twig | 4 +- 14 files changed, 387 insertions(+), 565 deletions(-) diff --git a/app/Http/Controllers/Import/JobConfigurationController.php b/app/Http/Controllers/Import/JobConfigurationController.php index 31bcfa954c..b2878f59ef 100644 --- a/app/Http/Controllers/Import/JobConfigurationController.php +++ b/app/Http/Controllers/Import/JobConfigurationController.php @@ -73,7 +73,7 @@ class JobConfigurationController extends Controller $allowed = ['has_prereq', 'need_job_config', 'has_config']; if (null !== $importJob && !in_array($importJob->status, $allowed)) { Log::error('Job is not new but wants to do prerequisites'); - session()->flash('error', trans('import.bad_job_status')); + session()->flash('error', trans('import.bad_job_status', ['status' => $importJob->status])); return redirect(route('import.index')); } @@ -101,7 +101,7 @@ class JobConfigurationController extends Controller $view = $configurator->getNextView(); $data = $configurator->getNextData(); - $subTitle = trans('firefly.import_config_bread_crumb'); + $subTitle = trans('import.job_configuration_breadcrumb', ['key' => $importJob->key]); $subTitleIcon = 'fa-wrench'; return view($view, compact('data', 'importJob', 'subTitle', 'subTitleIcon')); @@ -123,7 +123,7 @@ class JobConfigurationController extends Controller $allowed = ['has_prereq', 'need_job_config', 'has_config']; if (null !== $importJob && !in_array($importJob->status, $allowed)) { Log::error('Job is not new but wants to do prerequisites'); - session()->flash('error', trans('import.bad_job_status')); + session()->flash('error', trans('import.bad_job_status', ['status' => $importJob->status])); return redirect(route('import.index')); } diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index fe30e59af2..d091b50200 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -30,9 +30,7 @@ use FireflyIII\Import\Routine\RoutineInterface; use FireflyIII\Import\Storage\ImportArrayStorage; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Repositories\Tag\TagRepositoryInterface; use Illuminate\Http\JsonResponse; -use Illuminate\Http\RedirectResponse; use Log; use Symfony\Component\Debug\Exception\FatalThrowableError; @@ -83,7 +81,10 @@ class JobStatusController extends Controller // TODO to finished screen. } - return view('import.status', compact('importJob')); + $subTitleIcon = 'fa-gear'; + $subTitle = trans('import.job_status_breadcrumb', ['key' => $importJob->key]); + + return view('import.status', compact('importJob', 'subTitle', 'subTitleIcon')); } /** @@ -94,24 +95,25 @@ class JobStatusController extends Controller public function json(ImportJob $importJob): JsonResponse { $extendedStatus = $importJob->extended_status; + $count = \count($importJob->transactions); $json = [ - 'status' => $importJob->status, - 'errors' => $importJob->errors, - 'count' => count($importJob->transactions), - 'tag_id' => $importJob->tag_id, - 'tag_name' => null === $importJob->tag_id ? null : $importJob->tag->tag, - 'journals' => $extendedStatus['count'] ?? 0, - 'journals_text' => trans_choice('import.status_with_count', $extendedStatus['count'] ?? 0), - 'tag_text' => '', + 'status' => $importJob->status, + 'errors' => $importJob->errors, + 'count' => $count, + 'tag_id' => $importJob->tag_id, + 'tag_name' => null === $importJob->tag_id ? null : $importJob->tag->tag, + 'journals' => $extendedStatus['count'] ?? 0, + 'report_txt' => trans('import.unknown_import_result'), ]; - if (null !== $importJob->tag_id) { - $json['tag_text'] = trans( - 'import.status_finished_job', - ['count' => $extendedStatus['count'], - 'link' => route('tags.show', [$importJob->tag_id]), - 'tag' => $importJob->tag->tag, - ] - ); + // if count is zero: + if ($count === 0) { + $json['report_txt'] = trans('import.result_no_transactions'); + } + if ($count === 1 && null !== $importJob->tag_id) { + $json['report_txt'] = trans('import.result_one_transaction', ['route' => route('tags.show', [$importJob->tag_id]), 'tag' => $importJob->tag->tag ]); + } + if ($count > 1 && null !== $importJob->tag_id) { + $json['report_txt'] = trans('import.result_many_transactions', ['count' => $count,'route' => route('tags.show', [$importJob->tag_id]), 'tag' => $importJob->tag->tag ]); } return response()->json($json); diff --git a/app/Http/Controllers/Import/PrerequisitesController.php b/app/Http/Controllers/Import/PrerequisitesController.php index cc44c5b310..e7d52de775 100644 --- a/app/Http/Controllers/Import/PrerequisitesController.php +++ b/app/Http/Controllers/Import/PrerequisitesController.php @@ -78,7 +78,8 @@ class PrerequisitesController extends Controller $allowed = ['new']; if (null !== $importJob && !in_array($importJob->status, $allowed)) { Log::error('Job is not new but wants to do prerequisites'); - session()->flash('error', trans('import.bad_job_status')); + session()->flash('error', trans('import.bad_job_status', ['status' => $importJob->status])); + return redirect(route('import.index')); } @@ -131,7 +132,8 @@ class PrerequisitesController extends Controller $allowed = ['new']; if (null !== $importJob && !in_array($importJob->status, $allowed)) { Log::error('Job is not new but wants to do prerequisites'); - session()->flash('error', trans('import.bad_job_status')); + session()->flash('error', trans('import.bad_job_status', ['status' => $importJob->status])); + return redirect(route('import.index')); } diff --git a/app/Import/JobConfiguration/FakeJobConfiguration.php b/app/Import/JobConfiguration/FakeJobConfiguration.php index f1f6582612..25d7c445d3 100644 --- a/app/Import/JobConfiguration/FakeJobConfiguration.php +++ b/app/Import/JobConfiguration/FakeJobConfiguration.php @@ -81,7 +81,7 @@ class FakeJobConfiguration implements JobConfigurationInterface $artist = strtolower($data['artist'] ?? ''); $song = strtolower($data['song'] ?? ''); $album = strtolower($data['album'] ?? ''); - $applyRules = isset($data['apply-rules']) ? (int)$data['apply-rules'] === 1 : null; + $applyRules = isset($data['apply_rules']) ? (int)$data['apply_rules'] === 1 : null; $configuration = $this->job->configuration; if ($artist === 'david bowie') { // store artist @@ -119,7 +119,12 @@ class FakeJobConfiguration implements JobConfigurationInterface */ public function getNextData(): array { - return []; + return [ + 'rulesOptions' => [ + 1 => trans('firefly.yes'), + 0 => trans('firefly.no'), + ], + ]; } /** @@ -130,10 +135,10 @@ class FakeJobConfiguration implements JobConfigurationInterface public function getNextView(): string { // first configure artist: - $config = $this->job->configuration; - $artist = $config['artist'] ?? ''; - $song = $config['song'] ?? ''; - $album = $config['album'] ?? ''; + $config = $this->job->configuration; + $artist = $config['artist'] ?? ''; + $song = $config['song'] ?? ''; + $album = $config['album'] ?? ''; $applyRules = $config['apply-rules'] ?? null; if (null === $applyRules) { return 'import.fake.apply-rules'; diff --git a/app/Support/Import/Routine/Fake/StageAhoyHandler.php b/app/Support/Import/Routine/Fake/StageAhoyHandler.php index 3ccd4cac2c..851b61ce06 100644 --- a/app/Support/Import/Routine/Fake/StageAhoyHandler.php +++ b/app/Support/Import/Routine/Fake/StageAhoyHandler.php @@ -36,7 +36,7 @@ class StageAhoyHandler */ public function run(): void { - for ($i = 0; $i < 2; $i++) { + for ($i = 0; $i < 15; $i++) { Log::debug(sprintf('Am now in stage AHOY hander, sleeping... (%d)', $i)); sleep(1); } diff --git a/app/Support/Import/Routine/Fake/StageNewHandler.php b/app/Support/Import/Routine/Fake/StageNewHandler.php index 231baae778..d12ef19f4f 100644 --- a/app/Support/Import/Routine/Fake/StageNewHandler.php +++ b/app/Support/Import/Routine/Fake/StageNewHandler.php @@ -36,7 +36,7 @@ class StageNewHandler */ public function run(): void { - for ($i = 0; $i < 2; $i++) { + for ($i = 0; $i < 15; $i++) { Log::debug(sprintf('Am now in stage new hander, sleeping... (%d)', $i)); sleep(1); } diff --git a/public/js/ff/import/status_v2.js b/public/js/ff/import/status_v2.js index c467e1f0ee..512aaf5ccb 100644 --- a/public/js/ff/import/status_v2.js +++ b/public/js/ff/import/status_v2.js @@ -31,15 +31,15 @@ var startCount = 0; $(function () { "use strict"; - timeOutId = setTimeout(checkJobStatus, checkInitialInterval); + timeOutId = setTimeout(checkJobJSONStatus, checkInitialInterval); }); /** * Downloads some JSON and responds to its content to see what the status is of the current import job. */ -function checkJobStatus() { - console.log('In checkJobStatus()'); - $.getJSON(jobStatusUri).done(reportOnJobStatus).fail(reportFailure); +function checkJobJSONStatus() { + console.log('In checkJobJSONStatus()'); + $.getJSON(jobStatusUri).done(reportJobJSONDone).fail(reportJobJSONFailure); } /** @@ -47,8 +47,8 @@ function checkJobStatus() { * * @param data */ -function reportOnJobStatus(data) { - console.log('In reportOnJobStatus()'); +function reportJobJSONDone(data) { + console.log('In reportJobJSONDone() with status "' + data.status + '"'); console.log(data); switch (data.status) { case "ready_to_run": @@ -56,13 +56,13 @@ function reportOnJobStatus(data) { hasStartedJob = false; } startCount++; - startJob(); - checkOnJob(); + sendJobPOSTStart(); + recheckJobJSONStatus(); break; case "running": case "storing_data": showProgressBox(data.status); - checkOnJob(); + recheckJobJSONStatus(); break; case "need_job_config": @@ -71,8 +71,8 @@ function reportOnJobStatus(data) { break; case 'provider_finished': // call routine to store stuff: - storeJobData(); - checkOnJob(); + sendJobPOSTStore(); + recheckJobJSONStatus(); break; case "finished": showJobResults(data); @@ -88,16 +88,12 @@ function reportOnJobStatus(data) { * @param data */ function showJobResults(data) { + console.log('In showJobResults()'); // hide all boxes. -// hide status boxes: $('.statusbox').hide(); // render the count: - $('#import-status-more-info').append($('').text(data.journals_text)); - - if(data.tag_id) { - $('#import-status-more-info').append($('
    ')).append($('').html(data.tag_text)); - } + $('#import-status-more-info').append($('').html(data.report_txt)); // render relevant data from JSON thing. if (data.errors.length > 0) { @@ -117,9 +113,10 @@ function showJobResults(data) { /** * Will refresh and get job status. */ -function checkOnJob() { +function recheckJobJSONStatus() { + console.log('In recheckJobJSONStatus()'); if (maxLoops !== 0 && totalLoops < maxLoops) { - timeOutId = setTimeout(checkJobStatus, checkNextInterval); + timeOutId = setTimeout(checkJobJSONStatus, checkNextInterval); } if (maxLoops !== 0) { console.log('max: ' + maxLoops + ' current: ' + totalLoops); @@ -130,31 +127,32 @@ function checkOnJob() { /** * Start the job. */ -function startJob() { - console.log('In startJob()'); +function sendJobPOSTStart() { + console.log('In sendJobPOSTStart()'); if (hasStartedJob) { - console.log('Job already started!'); + console.log('Import job already started!'); return; } - console.log('JOB STARTED!'); + console.log('Job was started'); hasStartedJob = true; - $.post(jobStartUri, {_token: token}).fail(reportOnSubmitError).done(reportOnSubmit) + $.post(jobStartUri, {_token: token}).fail(reportJobPOSTFailure).done(reportJobPOSTDone) } /** * Start the storage routine for this job. */ -function storeJobData() { - console.log('In storeJobData()'); +function sendJobPOSTStore() { + console.log('In sendJobPOSTStore()'); if (jobStorageStarted) { console.log('Store job already started!'); return; } - console.log('STORAGE JOB STARTED!'); + console.log('Storage job has started!'); jobStorageStarted = true; - $.post(jobStorageStartUri, {_token: token}).fail(reportOnSubmitError).done(reportOnSubmit) + $.post(jobStorageStartUri, {_token: token}).fail(reportJobPOSTFailure).done(reportJobPOSTDone) } + /** * Function is called when the JSON array could not be retrieved. * @@ -162,7 +160,8 @@ function storeJobData() { * @param status * @param error */ -function reportFailure(xhr, status, error) { +function reportJobJSONFailure(xhr, status, error) { + console.log('In reportJobJSONFailure()'); // cancel checking again for job status: clearTimeout(timeOutId); @@ -180,12 +179,13 @@ function reportFailure(xhr, status, error) { * */ function showProgressBox(status) { + console.log('In showProgressBox()'); // hide fatal error box: $('.fatal_error').hide(); // hide initial status box: $('.status_initial').hide(); - if(status === 'running' || status === 'ready_to_run') { + if (status === 'running' || status === 'ready_to_run') { $('#import-status-txt').text(langImportRunning); } else { $('#import-status-txt').text(langImportStoring); @@ -202,7 +202,8 @@ function showProgressBox(status) { * @param status * @param error */ -function reportOnSubmitError(xhr, status, error) { +function reportJobPOSTFailure(xhr, status, error) { + console.log('In reportJobPOSTFailure()'); // cancel checking again for job status: clearTimeout(timeOutId); @@ -216,7 +217,8 @@ function reportOnSubmitError(xhr, status, error) { // show error box. } -function reportOnSubmit(data) { +function reportJobPOSTDone(data) { + console.log('In function reportJobPOSTDone() with status "' + data.status + '"'); if (data.status === 'NOK') { // cancel checking again for job status: clearTimeout(timeOutId); @@ -230,246 +232,4 @@ function reportOnSubmit(data) { $('.fatal_error_txt').text('Job could not be started or crashed: ' + data.message); // show error box. } -} - -// /** -// * This method is called when the JSON query returns an error. If possible, this error is relayed to the user. -// */ -// function reportFailedJob(jqxhr, textStatus, error) { -// console.log('In reportFailedJob()'); -// -// // cancel refresh -// clearTimeout(timeOutId); -// -// // hide all possible boxes: -// $('.statusbox').hide(); -// -// // fill in some details: -// var errorMessage = textStatus + " " + error; -// -// $('.fatal_error_txt').text(errorMessage); -// -// // show the fatal error box: -// $('.fatal_error').show(); -// } -// -// /** -// * This method is called when the job enquiry (JSON) returns some info. -// * It also decides whether or not to check again. -// * -// * @param data -// */ -// function reportOnJobStatus(data) { -// console.log('In reportOnJobStatus()'); -// switch (data.status) { -// case "configured": -// console.log('Job reports configured.'); -// // job is ready. Do not check again, just show the start-box. Hide the rest. -// if (!job.configuration['auto-start']) { -// $('.statusbox').hide(); -// $('.status_configured').show(); -// } -// if (job.configuration['auto-start']) { -// timeOutId = setTimeout(checkJobStatus, interval); -// } -// if (pressedStart) { -// // do a time out just in case. Could be that job is running or is even done already. -// timeOutId = setTimeout(checkJobStatus, 2000); -// pressedStart = false; -// } -// break; -// case "running": -// console.log('Job reports running.'); -// // job is running! Show the running box: -// $('.statusbox').hide(); -// $('.status_running').show(); -// -// // update the bar -// updateBar(data); -// -// // update the status text: -// updateStatusText(data); -// -// // report on detected errors: -// reportOnErrors(data); -// -// if (jobIsStalled(data)) { -// // do something -// showStalledBox(); -// } else { -// // check again in 500ms -// timeOutId = setTimeout(checkJobStatus, interval); -// } -// break; -// case "finished": -// console.log('Job reports finished.'); -// $('.statusbox').hide(); -// $('.status_finished').show(); -// // report on detected errors: -// reportOnErrors(data); -// // show text: -// $('#import-status-more-info').html(data.finishedText); -// break; -// case "error": -// clearTimeout(timeOutId); -// console.log('Job reports ERROR.'); -// // hide all possible boxes: -// $('.statusbox').hide(); -// -// // fill in some details: -// var errorMessage = data.errors.join(", "); -// -// $('.fatal_error_txt').text(errorMessage); -// -// // show the fatal error box: -// $('.fatal_error').show(); -// break; -// case "configuring": -// console.log('Job reports configuring.'); -// // redirect back to configure screen. -// window.location = jobConfigureUri; -// break; -// default: -// console.error('Cannot handle job status ' + data.status); -// break; -// -// } -// } -// -// /** -// * Shows a fatal error when the job seems to be stalled. -// */ -// function showStalledBox() { -// console.log('In showStalledBox().'); -// $('.statusbox').hide(); -// $('.fatal_error').show(); -// $('.fatal_error_txt').text(langImportTimeOutError); -// } -// -// /** -// * Detects if a job is frozen. -// * -// * @param data -// */ -// function jobIsStalled(data) { -// console.log('In jobIsStalled().'); -// if (data.done === numberOfSteps) { -// numberOfReports++; -// console.log('Number of reports ' + numberOfReports); -// } -// if (data.done !== numberOfSteps) { -// numberOfReports = 0; -// console.log('Number of reports ' + numberOfReports); -// } -// if (numberOfReports > 20) { -// console.log('Number of reports > 20! ' + numberOfReports); -// return true; -// } -// numberOfSteps = data.done; -// console.log('Number of steps ' + numberOfSteps); -// return false; -// } -// -// /** -// * This function tells Firefly start the job. It will also initialize a re-check in 500ms time. -// * Only when job is in "configured" state. -// */ -// function startJob() { -// console.log('In startJob().'); -// if (job.status === "configured") { -// console.log('Job status = configured.'); -// // disable the button, add loading thing. -// $('.start-job').prop('disabled', true).text('...'); -// $.post(jobStartUri, {_token: token}).fail(reportOnSubmitError); -// -// // check status, every 500 ms. -// timeOutId = setTimeout(checkJobStatus, startInterval); -// return; -// } -// console.log('Job.status = ' + job.status); -// } -// -// /** -// * When the start button fails (returns error code) this function reports. It assumes a time out. -// */ -// function reportOnSubmitError(jqxhr, textStatus, error) { -// console.log('In reportOnSubmitError().'); -// // stop the refresh thing -// clearTimeout(timeOutId); -// -// // hide all possible boxes: -// $('.statusbox').hide(); -// -// // fill in some details: -// var errorMessage = "Submitting the job returned an error: " + textStatus + ' ' + error; -// -// $('.fatal_error_txt').text(errorMessage); -// -// // show the fatal error box: -// $('.fatal_error').show(); -// jobFailed = true; -// -// } -// -// /** -// * This method updates the percentage bar thing if the job is running! -// */ -// function updateBar(data) { -// console.log('In updateBar().'); -// var bar = $('#import-status-bar'); -// if (data.show_percentage) { -// bar.addClass('progress-bar-success').removeClass('progress-bar-info'); -// bar.attr('aria-valuenow', data.percentage); -// bar.css('width', data.percentage + '%'); -// $('#import-status-bar').text(data.done + '/' + data.steps); -// return true; -// } -// // dont show percentage: -// bar.removeClass('progress-bar-success').addClass('progress-bar-info'); -// bar.attr('aria-valuenow', 100); -// bar.css('width', '100%'); -// return true; -// } -// -// /** -// * Add text with current import status. -// * @param data -// */ -// function updateStatusText(data) { -// "use strict"; -// console.log('In updateStatusText().'); -// $('#import-status-txt').removeClass('text-danger').text(data.statusText); -// } -// -// /** -// * Report on errors found in import: -// * @param data -// */ -// function reportOnErrors(data) { -// console.log('In reportOnErrors().'); -// if (knownErrors === data.errors.length) { -// return; -// } -// if (data.errors.length === 0) { -// return; -// } -// -// if (data.errors.length === 1) { -// $('#import-status-error-intro').text(langImportSingleError); -// //'An error has occured during the import. The import can continue, however.' -// } -// if (data.errors.length > 1) { -// // 'Errors have occured during the import. The import can continue, however.' -// $('#import-status-error-intro').text(langImportMultiError); -// } -// $('.info_errors').show(); -// // fill the list with error texts -// $('#import-status-error-list').empty(); -// for (var i = 0; i < data.errors.length; i++) { -// var errorSet = data.errors[i]; -// for (var j = 0; j < errorSet.length; j++) { -// var item = $('
  • ').html(errorSet[j]); -// $('#import-status-error-list').append(item); -// } -// } -// } \ No newline at end of file +} \ No newline at end of file diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 1b134859b0..5f8eb6d92f 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -184,6 +184,13 @@ return [ 'blocked' => 'Is blocked?', 'blocked_code' => 'Reason for block', + // import + 'apply_rules' => 'Apply rules', + 'artist' => 'Artist', + 'album' => 'Album', + 'song' => 'Song', + + // admin 'domain' => 'Domain', 'single_user_mode' => 'Disable user registration', diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index c3e85568fc..6eb7ad6bdb 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -22,254 +22,300 @@ declare(strict_types=1); */ return [ - // status of import: - 'status_wait_title' => 'Please hold...', - 'status_wait_text' => 'This box will disappear in a moment.', - 'status_fatal_title' => 'A fatal error occurred', - 'status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', - 'status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', - 'status_ready_title' => 'Import is ready to start', - 'status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'status_ready_noconfig_text' => 'The import is ready to start. All the configuration you needed to do has been done. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'status_ready_config' => 'Download configuration', - 'status_ready_start' => 'Start the import', - 'status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'status_job_new' => 'The job is brand new.', - 'status_job_configuring' => 'The import is being configured.', - 'status_job_configured' => 'The import is configured.', - 'status_job_running' => 'The import is running.. Please wait..', - 'status_job_storing' => 'The import is storing your data.. Please wait..', - 'status_job_error' => 'The job has generated an error.', - 'status_job_finished' => 'The import has finished!', - 'status_running_title' => 'The import is running', - 'status_running_placeholder' => 'Please hold for an update...', - 'status_finished_title' => 'Import routine finished', - 'status_finished_text' => 'The import routine has imported your data.', - 'status_errors_title' => 'Errors during the import', - 'status_errors_single' => 'An error has occurred during the import. It does not appear to be fatal.', - 'status_errors_multi' => 'Some errors occurred during the import. These do not appear to be fatal.', - 'status_with_count' => 'One transaction has been imported|:count transactions have been imported.', - 'job_status_breadcrumb' => 'Import job state', - - 'status_bread_crumb' => 'Import status', - 'status_sub_title' => 'Import status', - 'config_sub_title' => 'Set up your import', - 'import_config_bread_crumb' => 'Import configuration', - 'status_finished_job' => 'The :count transactions imported can be found in tag :tag.', - 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', - 'import_with_key' => 'Import with key \':key\'', - 'finished_with_errors' => 'The import reported some problems.', - - // file, upload something - 'file_upload_title' => 'Import setup (1/4) - Upload your file', - 'file_upload_text' => 'This routine will help you import files from your bank into Firefly III. Please check out the help pages in the top right corner.', - 'file_upload_fields' => 'Fields', - 'file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', - 'file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'file_upload_type_help' => 'Select the type of file you will upload', - 'file_upload_submit' => 'Upload files', - - // file, upload types - 'import_file_type_csv' => 'CSV (comma separated values)', - - // file, initial config for CSV - 'csv_initial_title' => 'Import setup (2/4) - Basic CSV import setup', - 'csv_initial_text' => 'To be able to import your file correctly, please validate the options below.', - 'csv_initial_box' => 'Basic CSV import setup', - 'csv_initial_box_title' => 'Basic CSV import setup options', - 'csv_initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - 'csv_initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'csv_initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'csv_initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'csv_initial_submit' => 'Continue with step 3/4', - - // file, new options: - 'file_apply_rules_title' => 'Apply rules', - 'file_apply_rules_description' => 'Apply your rules. Note that this slows the import significantly.', - 'file_match_bills_title' => 'Match bills', - 'file_match_bills_description' => 'Match your bills to newly created withdrawals. Note that this slows the import significantly.', - - // file, roles config - 'csv_roles_title' => 'Import setup (3/4) - Define each column\'s role', - 'csv_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', - 'csv_roles_table' => 'Table', - 'csv_roles_column_name' => 'Name of column', - 'csv_roles_column_example' => 'Column example data', - 'csv_roles_column_role' => 'Column data meaning', - 'csv_roles_do_map_value' => 'Map these values', - 'csv_roles_column' => 'Column', - 'csv_roles_no_example_data' => 'No example data available', - 'csv_roles_submit' => 'Continue with step 4/4', - - // not csv, but normal warning - 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', - 'foreign_amount_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', - - // file, map data - 'file_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', - 'file_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', - 'file_map_field_value' => 'Field value', - 'file_map_field_mapped_to' => 'Mapped to', - 'map_do_not_map' => '(do not map)', - 'file_map_submit' => 'Start the import', - 'file_nothing_to_map' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', - - // map things. - 'column__ignore' => '(ignore this column)', - 'column_account-iban' => 'Asset account (IBAN)', - 'column_account-id' => 'Asset account ID (matching FF3)', - 'column_account-name' => 'Asset account (name)', - 'column_amount' => 'Amount', - 'column_amount_foreign' => 'Amount (in foreign currency)', - 'column_amount_debit' => 'Amount (debit column)', - 'column_amount_credit' => 'Amount (credit column)', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching FF3)', - 'column_bill-name' => 'Bill name', - 'column_budget-id' => 'Budget ID (matching FF3)', - 'column_budget-name' => 'Budget name', - 'column_category-id' => 'Category ID (matching FF3)', - 'column_category-name' => 'Category name', - 'column_currency-code' => 'Currency code (ISO 4217)', - 'column_foreign-currency-code' => 'Foreign currency code (ISO 4217)', - 'column_currency-id' => 'Currency ID (matching FF3)', - 'column_currency-name' => 'Currency name (matching FF3)', - 'column_currency-symbol' => 'Currency symbol (matching FF3)', - 'column_date-interest' => 'Interest calculation date', - 'column_date-book' => 'Transaction booking date', - 'column_date-process' => 'Transaction process date', - 'column_date-transaction' => 'Date', - 'column_date-due' => 'Transaction due date', - 'column_date-payment' => 'Transaction payment date', - 'column_date-invoice' => 'Transaction invoice date', - 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-bic' => 'Opposing account (BIC)', - 'column_opposing-id' => 'Opposing account ID (matching FF3)', - 'column_external-id' => 'External ID', - 'column_opposing-name' => 'Opposing account (name)', - 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', - 'column_ing-debit-credit' => 'ING specific debit/credit indicator', - 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', - 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'column_sepa-db' => 'SEPA Mandate Identifier', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country Code', - 'column_tags-comma' => 'Tags (comma separated)', - 'column_tags-space' => 'Tags (space separated)', - 'column_account-number' => 'Asset account (account number)', - 'column_opposing-number' => 'Opposing account (account number)', - 'column_note' => 'Note(s)', - 'column_internal-reference' => 'Internal reference', - - // prerequisites - 'prerequisites' => 'Prerequisites', - 'prerequisites_breadcrumb_fake' => 'Prerequisites for fake provider', - 'prerequisites_breadcrumb_file' => 'Prerequisites for file imports', - 'prerequisites_breadcrumb_bunq' => 'Prerequisites for Bunq', - 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', - 'prerequisites_breadcrumb_plaid' => 'Prerequisites for Plaid', - 'prerequisites_breadcrumb_quovo' => 'Prerequisites for Quovo', - 'prerequisites_breadcrumb_yodlee' => 'Prerequisites for Yodlee', - - // success messages: - 'prerequisites_saved_for_fake' => 'API key stored for fake provider', - 'prerequisites_saved_for_file' => 'Data stored for file imports', - 'prerequisites_saved_for_bunq' => 'API key and IP stored for bunq', - 'prerequisites_saved_for_spectre' => 'App ID and secret stored for Spectre', - 'prerequisites_saved_for_plaid' => 'Data stored for Plaid', - 'prerequisites_saved_for_quovo' => 'Data stored for Quovo', - 'prerequisites_saved_for_yodlee' => 'Data stored for Yodlee', - - // index of import: - 'general_index_title' => 'Import a file', - 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', - 'bad_job_status' => 'You cannot access this page when the job is at this point. Sorry!', + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Import data into Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', + 'job_configuration_breadcrumb' => 'Configuration for ":key"', + 'job_status_breadcrumb' => 'Import status for ":key"', + // index page: + 'general_index_title' => 'Import a file', + 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', // import provider strings (index): - 'button_fake' => 'Fake an import', - 'button_file' => 'Import a file', - 'button_bunq' => 'Import from bunq', - 'button_spectre' => 'Import using Spectre', - 'button_plaid' => 'Import using Plaid', - 'button_yodlee' => 'Import using Yodlee', - 'button_quovo' => 'Import using Quovo', + 'button_fake' => 'Fake an import', + 'button_file' => 'Import a file', + 'button_bunq' => 'Import from bunq', + 'button_spectre' => 'Import using Spectre', + 'button_plaid' => 'Import using Plaid', + 'button_yodlee' => 'Import using Yodlee', + 'button_quovo' => 'Import using Quovo', + // global config box (index) + 'global_config_title' => 'Global import configuration', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + // prerequisites box (index) + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + // provider config box (index) + 'can_config_title' => 'Import configuration', + 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'do_config_fake' => 'Configuration for the fake provider', + 'do_config_file' => 'Configuration for file imports', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', - // global config box - 'global_config_title' => 'Global import configuration', - 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', - - // prereq box: - 'need_prereq_title' => 'Import prerequisites', - 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', - 'do_prereq_fake' => 'Prerequisites for the fake provider', - 'do_prereq_file' => 'Prerequisites for file imports', - 'do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', - - // provider config box: - 'can_config_title' => 'Import configuration', - 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', - 'do_config_fake' => 'Configuration for the fake provider', - 'do_config_file' => 'Configuration for file imports', - 'do_config_bunq' => 'Configuration for bunq imports', - 'do_config_spectre' => 'Configuration for imports from Spectre', - 'do_config_plaid' => 'Configuration for imports from Plaid', - 'do_config_yodlee' => 'Configuration for imports from Yodlee', - 'do_config_quovo' => 'Configuration for imports from Quovo', + // prerequisites: + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', // job configuration: - 'job_configuration_breadcrumb' => 'Configuration for job ":key"', + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. In this case, enter "station to station" to continue.', - // import index page: - 'index_breadcrumb' => 'Index', - 'upload_error' => 'The file you have uploaded could not be processed. Possibly it is of an invalid file type or encoding. The log files will have more information.', + // import status page: + 'import_with_key' => 'Import with key \':key\'', + 'status_wait_title' => 'Please hold...', + 'status_wait_text' => 'This box will disappear in a moment.', + 'status_running_title' => 'The import is running', + 'status_job_running' => 'Please wait, running the import...', + 'status_job_storing' => 'Please wait, storing data...', + 'status_fatal_title' => 'Fatal error', + 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', + 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', + 'status_finished_title' => 'Import finished', + 'status_finished_text' => 'The import has finished.', + 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', + 'unknown_import_result' => 'Unknown import result', + 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the error message below can tell you what happened.', + 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', + 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', - // bunq - 'bunq_prerequisites_title' => 'Prerequisites for an import from bunq', - 'bunq_prerequisites_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'bunq_prerequisites_text_ip' => 'Bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', - 'bunq_do_import' => 'Yes, import from this account', - 'bunq_accounts_title' => 'Bunq accounts', - 'bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', + // general errors and warnings: + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', - // Spectre - 'spectre_title' => 'Import using Spectre', - 'spectre_prerequisites_title' => 'Prerequisites for an import using Spectre', - 'spectre_prerequisites_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your secrets page.', - 'spectre_accounts_title' => 'Select accounts to import from', - 'spectre_accounts_text' => 'Each account on the left below has been found by Spectre and can be imported into Firefly III. Please select the asset account that should hold any given transactions. If you do not wish to import from any particular account, remove the check from the checkbox.', - 'spectre_do_import' => 'Yes, import from this account', - 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'SWIFT', - 'spectre_extra_key_status' => 'Status', - 'spectre_extra_key_card_type' => 'Card type', - 'spectre_extra_key_account_name' => 'Account name', - 'spectre_extra_key_client_name' => 'Client name', - 'spectre_extra_key_account_number' => 'Account number', - 'spectre_extra_key_blocked_amount' => 'Blocked amount', - 'spectre_extra_key_available_amount' => 'Available amount', - 'spectre_extra_key_credit_limit' => 'Credit limit', - 'spectre_extra_key_interest_rate' => 'Interest rate', - 'spectre_extra_key_expiry_date' => 'Expiry date', - 'spectre_extra_key_open_date' => 'Open date', - 'spectre_extra_key_current_time' => 'Current time', - 'spectre_extra_key_current_date' => 'Current date', - 'spectre_extra_key_cards' => 'Cards', - 'spectre_extra_key_units' => 'Units', - 'spectre_extra_key_unit_price' => 'Unit price', - 'spectre_extra_key_transactions_count' => 'Transaction count', + // status of import: + // 'status_wait_title' => 'Please hold...', + // 'status_wait_text' => 'This box will disappear in a moment.', + // 'status_fatal_title' => 'A fatal error occurred', + // 'status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + // 'status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + // 'status_ready_title' => 'Import is ready to start', + // 'status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + // 'status_ready_noconfig_text' => 'The import is ready to start. All the configuration you needed to do has been done. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + // 'status_ready_config' => 'Download configuration', + // 'status_ready_start' => 'Start the import', + // 'status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + // 'status_job_new' => 'The job is brand new.', + // 'status_job_configuring' => 'The import is being configured.', + // 'status_job_configured' => 'The import is configured.', + // 'status_job_running' => 'The import is running.. Please wait..', + // 'status_job_storing' => 'The import is storing your data.. Please wait..', + // 'status_job_error' => 'The job has generated an error.', + // 'status_job_finished' => 'The import has finished!', + // 'status_running_title' => 'The import is running', + // 'status_running_placeholder' => 'Please hold for an update...', + // 'status_finished_title' => 'Import routine finished', + // 'status_finished_text' => 'The import routine has imported your data.', + // 'status_errors_title' => 'Errors during the import', + // 'status_errors_single' => 'An error has occurred during the import. It does not appear to be fatal.', + // 'status_errors_multi' => 'Some errors occurred during the import. These do not appear to be fatal.', + // 'status_with_count' => 'One transaction has been imported|:count transactions have been imported.', + // 'job_status_breadcrumb' => 'Import job state', + // + // 'status_bread_crumb' => 'Import status', + // 'status_sub_title' => 'Import status', + // 'config_sub_title' => 'Set up your import', + // 'import_config_bread_crumb' => 'Import configuration', + // 'status_finished_job' => 'The :count transactions imported can be found in tag :tag.', + // 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', + // 'import_with_key' => 'Import with key \':key\'', + // 'finished_with_errors' => 'The import reported some problems.', + // + // // file, upload something + // 'file_upload_title' => 'Import setup (1/4) - Upload your file', + // 'file_upload_text' => 'This routine will help you import files from your bank into Firefly III. Please check out the help pages in the top right corner.', + // 'file_upload_fields' => 'Fields', + // 'file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + // 'file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + // 'file_upload_type_help' => 'Select the type of file you will upload', + // 'file_upload_submit' => 'Upload files', + // + // // file, upload types + // 'import_file_type_csv' => 'CSV (comma separated values)', + // + // // file, initial config for CSV + // 'csv_initial_title' => 'Import setup (2/4) - Basic CSV import setup', + // 'csv_initial_text' => 'To be able to import your file correctly, please validate the options below.', + // 'csv_initial_box' => 'Basic CSV import setup', + // 'csv_initial_box_title' => 'Basic CSV import setup options', + // 'csv_initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + // 'csv_initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + // 'csv_initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + // 'csv_initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + // 'csv_initial_submit' => 'Continue with step 3/4', + // + // // file, new options: + // 'file_apply_rules_title' => 'Apply rules', + // 'file_apply_rules_description' => 'Apply your rules. Note that this slows the import significantly.', + // 'file_match_bills_title' => 'Match bills', + // 'file_match_bills_description' => 'Match your bills to newly created withdrawals. Note that this slows the import significantly.', + // + // // file, roles config + // 'csv_roles_title' => 'Import setup (3/4) - Define each column\'s role', + // 'csv_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + // 'csv_roles_table' => 'Table', + // 'csv_roles_column_name' => 'Name of column', + // 'csv_roles_column_example' => 'Column example data', + // 'csv_roles_column_role' => 'Column data meaning', + // 'csv_roles_do_map_value' => 'Map these values', + // 'csv_roles_column' => 'Column', + // 'csv_roles_no_example_data' => 'No example data available', + // 'csv_roles_submit' => 'Continue with step 4/4', + // + // // not csv, but normal warning + // 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + // 'foreign_amount_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + // + // // file, map data + // 'file_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + // 'file_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + // 'file_map_field_value' => 'Field value', + // 'file_map_field_mapped_to' => 'Mapped to', + // 'map_do_not_map' => '(do not map)', + // 'file_map_submit' => 'Start the import', + // 'file_nothing_to_map' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + // + // // map things. + // 'column__ignore' => '(ignore this column)', + // 'column_account-iban' => 'Asset account (IBAN)', + // 'column_account-id' => 'Asset account ID (matching FF3)', + // 'column_account-name' => 'Asset account (name)', + // 'column_amount' => 'Amount', + // 'column_amount_foreign' => 'Amount (in foreign currency)', + // 'column_amount_debit' => 'Amount (debit column)', + // 'column_amount_credit' => 'Amount (credit column)', + // 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', + // 'column_bill-id' => 'Bill ID (matching FF3)', + // 'column_bill-name' => 'Bill name', + // 'column_budget-id' => 'Budget ID (matching FF3)', + // 'column_budget-name' => 'Budget name', + // 'column_category-id' => 'Category ID (matching FF3)', + // 'column_category-name' => 'Category name', + // 'column_currency-code' => 'Currency code (ISO 4217)', + // 'column_foreign-currency-code' => 'Foreign currency code (ISO 4217)', + // 'column_currency-id' => 'Currency ID (matching FF3)', + // 'column_currency-name' => 'Currency name (matching FF3)', + // 'column_currency-symbol' => 'Currency symbol (matching FF3)', + // 'column_date-interest' => 'Interest calculation date', + // 'column_date-book' => 'Transaction booking date', + // 'column_date-process' => 'Transaction process date', + // 'column_date-transaction' => 'Date', + // 'column_date-due' => 'Transaction due date', + // 'column_date-payment' => 'Transaction payment date', + // 'column_date-invoice' => 'Transaction invoice date', + // 'column_description' => 'Description', + // 'column_opposing-iban' => 'Opposing account (IBAN)', + // 'column_opposing-bic' => 'Opposing account (BIC)', + // 'column_opposing-id' => 'Opposing account ID (matching FF3)', + // 'column_external-id' => 'External ID', + // 'column_opposing-name' => 'Opposing account (name)', + // 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', + // 'column_ing-debit-credit' => 'ING specific debit/credit indicator', + // 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', + // 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', + // 'column_sepa-db' => 'SEPA Mandate Identifier', + // 'column_sepa-cc' => 'SEPA Clearing Code', + // 'column_sepa-ci' => 'SEPA Creditor Identifier', + // 'column_sepa-ep' => 'SEPA External Purpose', + // 'column_sepa-country' => 'SEPA Country Code', + // 'column_tags-comma' => 'Tags (comma separated)', + // 'column_tags-space' => 'Tags (space separated)', + // 'column_account-number' => 'Asset account (account number)', + // 'column_opposing-number' => 'Opposing account (account number)', + // 'column_note' => 'Note(s)', + // 'column_internal-reference' => 'Internal reference', + // + // // prerequisites + // 'prerequisites' => 'Prerequisites', + // 'prerequisites_breadcrumb_fake' => 'Prerequisites for fake provider', + // 'prerequisites_breadcrumb_file' => 'Prerequisites for file imports', + // 'prerequisites_breadcrumb_bunq' => 'Prerequisites for Bunq', + // 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + // 'prerequisites_breadcrumb_plaid' => 'Prerequisites for Plaid', + // 'prerequisites_breadcrumb_quovo' => 'Prerequisites for Quovo', + // 'prerequisites_breadcrumb_yodlee' => 'Prerequisites for Yodlee', + // + // // success messages: + // 'prerequisites_saved_for_fake' => 'API key stored for fake provider', + // 'prerequisites_saved_for_file' => 'Data stored for file imports', + // 'prerequisites_saved_for_bunq' => 'API key and IP stored for bunq', + // 'prerequisites_saved_for_spectre' => 'App ID and secret stored for Spectre', + // 'prerequisites_saved_for_plaid' => 'Data stored for Plaid', + // 'prerequisites_saved_for_quovo' => 'Data stored for Quovo', + // 'prerequisites_saved_for_yodlee' => 'Data stored for Yodlee', + // - // various other strings: - 'imported_from_account' => 'Imported from ":account"', + // + + // + // // job configuration: + // 'job_configuration_breadcrumb' => 'Configuration for job ":key"', + // + // // import index page: + // 'index_breadcrumb' => 'Index', + // 'upload_error' => 'The file you have uploaded could not be processed. Possibly it is of an invalid file type or encoding. The log files will have more information.', + // + // + // // bunq + // 'bunq_prerequisites_title' => 'Prerequisites for an import from bunq', + // 'bunq_prerequisites_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + // 'bunq_prerequisites_text_ip' => 'Bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + // 'bunq_do_import' => 'Yes, import from this account', + // 'bunq_accounts_title' => 'Bunq accounts', + // 'bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', + // + // // Spectre + // 'spectre_title' => 'Import using Spectre', + // 'spectre_prerequisites_title' => 'Prerequisites for an import using Spectre', + // 'spectre_prerequisites_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + // 'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your secrets page.', + // 'spectre_accounts_title' => 'Select accounts to import from', + // 'spectre_accounts_text' => 'Each account on the left below has been found by Spectre and can be imported into Firefly III. Please select the asset account that should hold any given transactions. If you do not wish to import from any particular account, remove the check from the checkbox.', + // 'spectre_do_import' => 'Yes, import from this account', + // 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', + // + // // keys from "extra" array: + // 'spectre_extra_key_iban' => 'IBAN', + // 'spectre_extra_key_swift' => 'SWIFT', + // 'spectre_extra_key_status' => 'Status', + // 'spectre_extra_key_card_type' => 'Card type', + // 'spectre_extra_key_account_name' => 'Account name', + // 'spectre_extra_key_client_name' => 'Client name', + // 'spectre_extra_key_account_number' => 'Account number', + // 'spectre_extra_key_blocked_amount' => 'Blocked amount', + // 'spectre_extra_key_available_amount' => 'Available amount', + // 'spectre_extra_key_credit_limit' => 'Credit limit', + // 'spectre_extra_key_interest_rate' => 'Interest rate', + // 'spectre_extra_key_expiry_date' => 'Expiry date', + // 'spectre_extra_key_open_date' => 'Open date', + // 'spectre_extra_key_current_time' => 'Current time', + // 'spectre_extra_key_current_date' => 'Current date', + // 'spectre_extra_key_cards' => 'Cards', + // 'spectre_extra_key_units' => 'Units', + // 'spectre_extra_key_unit_price' => 'Unit price', + // 'spectre_extra_key_transactions_count' => 'Transaction count', + // + // // various other strings: + // 'imported_from_account' => 'Imported from ":account"', ]; diff --git a/resources/views/import/fake/apply-rules.twig b/resources/views/import/fake/apply-rules.twig index 6cf883fa41..922ce43bd0 100644 --- a/resources/views/import/fake/apply-rules.twig +++ b/resources/views/import/fake/apply-rules.twig @@ -8,11 +8,11 @@
    -

    Rules

    +

    {{ trans('import.job_config_apply_rules_title') }}

    - Should apply rules? + {{ trans('import.job_config_apply_rules_text') }}

    @@ -20,19 +20,19 @@
    - +
    -

    Fields be here.

    +

    + {{ trans('import.job_config_input') }} +

    - + {{ ExpandedForm.select('apply_rules', data.rulesOptions) }}
    diff --git a/resources/views/import/fake/enter-album.twig b/resources/views/import/fake/enter-album.twig index b2a7a95517..01ee24a3b3 100644 --- a/resources/views/import/fake/enter-album.twig +++ b/resources/views/import/fake/enter-album.twig @@ -8,11 +8,11 @@
    -

    Enter station for fake import

    +

    {{ trans('import.job_config_fake_album_title') }}

    - Enter "station to station", no matter the capitalization. + {{ trans('import.job_config_fake_album_text') }}

    @@ -26,7 +26,7 @@
    -

    Fields be here.

    +

    {{ trans('import.job_config_input') }}

    {{ ExpandedForm.text('album') }} diff --git a/resources/views/import/fake/enter-artist.twig b/resources/views/import/fake/enter-artist.twig index 824621b1af..a422a31552 100644 --- a/resources/views/import/fake/enter-artist.twig +++ b/resources/views/import/fake/enter-artist.twig @@ -8,11 +8,11 @@
    -

    Enter artist for fake import

    +

    {{ trans('import.job_config_fake_artist_title') }}

    - Enter "David Bowie", no matter the capitalization. + {{ trans('import.job_config_fake_artist_text') }}

    @@ -26,7 +26,7 @@
    -

    Fields be here.

    +

    {{ trans('import.job_config_input') }}

    {{ ExpandedForm.text('artist') }} diff --git a/resources/views/import/fake/enter-song.twig b/resources/views/import/fake/enter-song.twig index 6c8e168f3e..dc1de31a2b 100644 --- a/resources/views/import/fake/enter-song.twig +++ b/resources/views/import/fake/enter-song.twig @@ -8,11 +8,11 @@
    -

    Enter song for fake import

    +

    {{ trans('import.job_config_fake_song_title') }}

    - Enter "golden years", no matter the capitalization. + {{ trans('import.job_config_fake_song_text') }}

    @@ -26,7 +26,7 @@
    -

    Fields be here.

    +

    {{ trans('import.job_config_input') }}

    {{ ExpandedForm.text('song') }} diff --git a/resources/views/import/fake/prerequisites.twig b/resources/views/import/fake/prerequisites.twig index a6e926f932..39af64e195 100644 --- a/resources/views/import/fake/prerequisites.twig +++ b/resources/views/import/fake/prerequisites.twig @@ -10,13 +10,13 @@
    -

    Fake prerequisites

    +

    {{ trans('import.prereq_fake_title') }}

    - Bla bla bla bla + {{ trans('import.prereq_fake_text') }}

    From 1c0da454db198c8bf65967849d37084529a3e948 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 4 May 2018 20:21:27 +0200 Subject: [PATCH 038/182] Improved code for new import + some tests. --- .../Controllers/Import/IndexController.php | 121 +- .../Import/JobConfigurationController.php | 16 +- .../Import/JobStatusController.php | 169 +- .../Import/PrerequisitesController.php | 2 - app/Import/Routine/BunqRoutine.php | 1650 +++++++++-------- app/Import/Routine/FileRoutine.php | 535 +++--- app/Import/Routine/SpectreRoutine.php | 1070 +++++------ app/Import/Storage/ImportArrayStorage.php | 268 ++- .../Import/Routine/Fake/StageAhoyHandler.php | 2 +- .../Import/Routine/Fake/StageNewHandler.php | 2 +- config/import.php | 2 +- .../2018_04_29_174524_changes_for_v474.php | 4 +- public/js/ff/import/status_v2.js | 47 +- resources/lang/en_US/import.php | 3 +- resources/views/import/status.twig | 1 + .../Controllers/ExportControllerTest.php | 2 +- .../Import/ConfigurationControllerTest.php | 131 -- .../Import/IndexControllerTest.php | 123 +- .../Import/JobConfigurationControllerTest.php | 228 +++ .../Import/JobStatusControllerTest.php | 375 ++++ .../Import/PrerequisitesControllerTest.php | 204 +- .../Import/StatusControllerTest.php | 96 - 22 files changed, 2770 insertions(+), 2281 deletions(-) delete mode 100644 tests/Feature/Controllers/Import/ConfigurationControllerTest.php create mode 100644 tests/Feature/Controllers/Import/JobConfigurationControllerTest.php create mode 100644 tests/Feature/Controllers/Import/JobStatusControllerTest.php delete mode 100644 tests/Feature/Controllers/Import/StatusControllerTest.php diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index 639b5bb152..4dad6d55ca 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -24,7 +24,6 @@ namespace FireflyIII\Http\Controllers\Import; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; -use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Import\Prerequisites\PrerequisitesInterface; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use View; @@ -54,7 +53,6 @@ class IndexController extends Controller return $next($request); } ); - $this->middleware(IsDemoUser::class)->except(['index']); } /** @@ -68,11 +66,19 @@ class IndexController extends Controller */ public function create(string $importProvider) { + if ( + !(bool)config('app.debug') + && !(bool)config(sprintf('import.enabled.%s', $importProvider)) === true + && !\in_array(config('app.env'), ['demo', 'testing']) + ) { + throw new FireflyException(sprintf('Import using provider "%s" is currently not available.', $importProvider)); // @codeCoverageIgnore + } + $importJob = $this->repository->create($importProvider); // if job provider has no prerequisites: if (!(bool)config(sprintf('import.has_prereq.%s', $importProvider))) { - + // @codeCoverageIgnoreStart // if job provider also has no configuration: if (!(bool)config(sprintf('import.has_config.%s', $importProvider))) { $this->repository->updateStatus($importJob, 'ready_to_run'); @@ -85,6 +91,7 @@ class IndexController extends Controller // redirect to job configuration. return redirect(route('import.job.configuration.index', [$importJob->key])); + // @codeCoverageIgnoreEnd } // if need to set prerequisites, do that first. @@ -122,7 +129,7 @@ class IndexController extends Controller $config = config('import.enabled'); $providers = []; foreach ($config as $name => $enabled) { - if ($enabled || (bool)config('app.debug')) { + if ($enabled || (bool)config('app.debug') || \in_array(config('app.env'), ['demo', 'testing'])) { $providers[$name] = []; } } @@ -147,110 +154,4 @@ class IndexController extends Controller return view('import.index', compact('subTitle', 'subTitleIcon', 'providers')); } - // - // /** - // * @param Request $request - // * @param string $bank - // * - // * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - // */ - // public function reset(Request $request, string $bank) - // { - // if ($bank === 'bunq') { - // // remove bunq related preferences. - // Preferences::delete('bunq_api_key'); - // Preferences::delete('bunq_server_public_key'); - // Preferences::delete('bunq_private_key'); - // Preferences::delete('bunq_public_key'); - // Preferences::delete('bunq_installation_token'); - // Preferences::delete('bunq_installation_id'); - // Preferences::delete('bunq_device_server_id'); - // Preferences::delete('external_ip'); - // - // } - // - // if ($bank === 'spectre') { - // // remove spectre related preferences: - // Preferences::delete('spectre_client_id'); - // Preferences::delete('spectre_app_secret'); - // Preferences::delete('spectre_service_secret'); - // Preferences::delete('spectre_app_id'); - // Preferences::delete('spectre_secret'); - // Preferences::delete('spectre_private_key'); - // Preferences::delete('spectre_public_key'); - // Preferences::delete('spectre_customer'); - // } - // - // Preferences::mark(); - // $request->session()->flash('info', (string)trans('firefly.settings_reset_for_' . $bank)); - // - // return redirect(route('import.index')); - // - // } - - // /** - // * @param ImportJob $job - // * - // * @return \Illuminate\Http\JsonResponse - // * - // * @throws FireflyException - // */ - // public function start(ImportJob $job) - // { - // $type = $job->file_type; - // $key = sprintf('import.routine.%s', $type); - // $className = config($key); - // if (null === $className || !class_exists($className)) { - // throw new FireflyException(sprintf('Cannot find import routine class for job of type "%s".', $type)); // @codeCoverageIgnore - // } - // - // /** @var RoutineInterface $routine */ - // $routine = app($className); - // $routine->setJob($job); - // $result = $routine->run(); - // - // if ($result) { - // return response()->json(['run' => 'ok']); - // } - // - // throw new FireflyException('Job did not complete successfully. Please review the log files.'); - // } - - - // /** - // * Generate a JSON file of the job's configuration and send it to the user. - // * - // * @param ImportJob $job - // * - // * @return LaravelResponse - // */ - // public function download(ImportJob $job) - // { - // Log::debug('Now in download()', ['job' => $job->key]); - // $config = $job->configuration; - // - // // This is CSV import specific: - // $config['column-roles-complete'] = false; - // $config['column-mapping-complete'] = false; - // $config['initial-config-complete'] = false; - // $config['has-file-upload'] = false; - // $config['delimiter'] = "\t" === $config['delimiter'] ? 'tab' : $config['delimiter']; - // unset($config['stage']); - // - // $result = json_encode($config, JSON_PRETTY_PRINT); - // $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); - // - // /** @var LaravelResponse $response */ - // $response = response($result, 200); - // $response->header('Content-disposition', 'attachment; filename=' . $name) - // ->header('Content-Type', 'application/json') - // ->header('Content-Description', 'File Transfer') - // ->header('Connection', 'Keep-Alive') - // ->header('Expires', '0') - // ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') - // ->header('Pragma', 'public') - // ->header('Content-Length', \strlen($result)); - // - // return $response; - // } } diff --git a/app/Http/Controllers/Import/JobConfigurationController.php b/app/Http/Controllers/Import/JobConfigurationController.php index b2878f59ef..1d61ae5612 100644 --- a/app/Http/Controllers/Import/JobConfigurationController.php +++ b/app/Http/Controllers/Import/JobConfigurationController.php @@ -70,10 +70,10 @@ class JobConfigurationController extends Controller public function index(ImportJob $importJob) { // catch impossible status: - $allowed = ['has_prereq', 'need_job_config', 'has_config']; - if (null !== $importJob && !in_array($importJob->status, $allowed)) { - Log::error('Job is not new but wants to do prerequisites'); + $allowed = ['has_prereq', 'need_job_config']; + if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { session()->flash('error', trans('import.bad_job_status', ['status' => $importJob->status])); + return redirect(route('import.index')); } @@ -82,10 +82,12 @@ class JobConfigurationController extends Controller // if provider has no config, just push it through: $importProvider = $importJob->provider; if (!(bool)config(sprintf('import.has_config.%s', $importProvider))) { + // @codeCoverageIgnoreStart Log::debug('Job needs no config, is ready to run!'); - $this->repository->updateStatus($importJob ,'ready_to_run'); + $this->repository->updateStatus($importJob, 'ready_to_run'); return redirect(route('import.job.status.index', [$importJob->key])); + // @codeCoverageIgnoreEnd } // create configuration class: @@ -120,10 +122,10 @@ class JobConfigurationController extends Controller public function post(Request $request, ImportJob $importJob) { // catch impossible status: - $allowed = ['has_prereq', 'need_job_config', 'has_config']; - if (null !== $importJob && !in_array($importJob->status, $allowed)) { - Log::error('Job is not new but wants to do prerequisites'); + $allowed = ['has_prereq', 'need_job_config']; + if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { session()->flash('error', trans('import.bad_job_status', ['status' => $importJob->status])); + return redirect(route('import.index')); } diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index d091b50200..7faeae86e5 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -32,7 +32,6 @@ use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use Illuminate\Http\JsonResponse; use Log; -use Symfony\Component\Debug\Exception\FatalThrowableError; /** * Class JobStatusController @@ -68,19 +67,6 @@ class JobStatusController extends Controller */ public function index(ImportJob $importJob) { - // jump away depending on job status: - if ($importJob->status === 'has_prereq') { - // TODO back to configuration. - } - - if ($importJob->status === 'errored') { - // TODO to error screen - } - - if ($importJob->status === 'finished') { - // TODO to finished screen. - } - $subTitleIcon = 'fa-gear'; $subTitle = trans('import.job_status_breadcrumb', ['key' => $importJob->key]); @@ -94,42 +80,44 @@ class JobStatusController extends Controller */ public function json(ImportJob $importJob): JsonResponse { - $extendedStatus = $importJob->extended_status; - $count = \count($importJob->transactions); - $json = [ + $count = \count($importJob->transactions); + $json = [ 'status' => $importJob->status, 'errors' => $importJob->errors, 'count' => $count, 'tag_id' => $importJob->tag_id, 'tag_name' => null === $importJob->tag_id ? null : $importJob->tag->tag, - 'journals' => $extendedStatus['count'] ?? 0, 'report_txt' => trans('import.unknown_import_result'), ]; // if count is zero: + if (null !== $importJob->tag_id) { + $count = $importJob->tag->transactionJournals->count(); + } if ($count === 0) { $json['report_txt'] = trans('import.result_no_transactions'); } - if ($count === 1 && null !== $importJob->tag_id) { - $json['report_txt'] = trans('import.result_one_transaction', ['route' => route('tags.show', [$importJob->tag_id]), 'tag' => $importJob->tag->tag ]); + if ($count === 1) { + $json['report_txt'] = trans('import.result_one_transaction', ['route' => route('tags.show', [$importJob->tag_id]), 'tag' => $importJob->tag->tag]); } if ($count > 1 && null !== $importJob->tag_id) { - $json['report_txt'] = trans('import.result_many_transactions', ['count' => $count,'route' => route('tags.show', [$importJob->tag_id]), 'tag' => $importJob->tag->tag ]); + $json['report_txt'] = trans( + 'import.result_many_transactions', ['count' => $count, 'route' => route('tags.show', [$importJob->tag_id]), 'tag' => $importJob->tag->tag] + ); } return response()->json($json); } /** - * @param ImportJob $job + * @param ImportJob $importJob * * @return JsonResponse - * @throws FireflyException */ public function start(ImportJob $importJob): JsonResponse { // catch impossible status: $allowed = ['ready_to_run', 'need_job_config']; - if (null !== $importJob && !in_array($importJob->status, $allowed)) { + if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { Log::error('Job is not ready.'); return response()->json(['status' => 'NOK', 'message' => 'JobStatusController::start expects state "ready_to_run".']); @@ -139,29 +127,13 @@ class JobStatusController extends Controller $key = sprintf('import.routine.%s', $importProvider); $className = config($key); if (null === $className || !class_exists($className)) { - return response()->json(['status' => 'NOK', 'message' => sprintf('Cannot find import routine class for job of type "%s".', $importProvider)]); + // @codeCoverageIgnoreStart + return response()->json( + ['status' => 'NOK', 'message' => sprintf('Cannot find import routine class for job of type "%s".', $importProvider)] + ); + // @codeCoverageIgnoreEnd } - - // if the job is set to "provider_finished", we should be able to store transactions - // generated by the provider. - // otherwise, just continue. - if ($importJob->status === 'provider_finished') { - try { - $this->importFromJob($importJob); - } catch (FireflyException $e) { - $message = 'The import storage routine crashed: ' . $e->getMessage(); - Log::error($message); - Log::error($e->getTraceAsString()); - - // set job errored out: - $this->repository->setStatus($importJob, 'error'); - - return response()->json(['status' => 'NOK', 'message' => $message]); - } - } - - // set job to be running: $this->repository->setStatus($importJob, 'running'); @@ -170,7 +142,7 @@ class JobStatusController extends Controller $routine->setJob($importJob); try { $routine->run(); - } catch (FireflyException $e) { + } catch (FireflyException|Exception $e) { $message = 'The import routine crashed: ' . $e->getMessage(); Log::error($message); Log::error($e->getTraceAsString()); @@ -186,16 +158,20 @@ class JobStatusController extends Controller } /** - * @param ImportJob $job + * Store does three things: + * + * - Store the transactions. + * - Add them to a tag. + * + * @param ImportJob $importJob * * @return JsonResponse - * @throws FireflyException */ public function store(ImportJob $importJob): JsonResponse { // catch impossible status: - $allowed = ['provider_finished', 'storing_data']; // todo remove storing data. - if (null !== $importJob && !in_array($importJob->status, $allowed)) { + $allowed = ['provider_finished', 'storing_data']; + if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { Log::error('Job is not ready.'); return response()->json(['status' => 'NOK', 'message' => 'JobStatusController::start expects state "provider_finished".']); @@ -205,7 +181,7 @@ class JobStatusController extends Controller $this->repository->setStatus($importJob, 'storing_data'); try { - $this->importFromJob($importJob); + $this->storeTransactions($importJob); } catch (FireflyException $e) { $message = 'The import storage routine crashed: ' . $e->getMessage(); Log::error($message); @@ -216,9 +192,9 @@ class JobStatusController extends Controller return response()->json(['status' => 'NOK', 'message' => $message]); } + // set storage to be finished: + $this->repository->setStatus($importJob, 'storage_finished'); - // set job to be finished. - $this->repository->setStatus($importJob, 'finished'); // expect nothing from routine, just return OK to user. return response()->json(['status' => 'OK', 'message' => 'storage_finished']); @@ -229,90 +205,15 @@ class JobStatusController extends Controller * * @throws FireflyException */ - private function importFromJob(ImportJob $importJob): void + private function storeTransactions(ImportJob $importJob): void { + /** @var ImportArrayStorage $storage */ + $storage = app(ImportArrayStorage::class); + $storage->setJob($importJob); try { - $storage = new ImportArrayStorage($importJob); - $journals = $storage->store(); - $extendedStatus = $importJob->extended_status; - $extendedStatus['count'] = $journals->count(); - $this->repository->setExtendedStatus($importJob, $extendedStatus); - } catch (FireflyException|Exception|FatalThrowableError $e) { + $storage->store(); + } catch (FireflyException|Exception $e) { throw new FireflyException($e->getMessage()); } - - } - - // /** - // * @param ImportJob $job - // * - // * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View - // */ - // public function index(ImportJob $job) - // { - // $statuses = ['configured', 'running', 'finished', 'error']; - // if (!\in_array($job->status, $statuses)) { - // return redirect(route('import.configure', [$job->key])); - // } - // $subTitle = trans('import.status_sub_title'); - // $subTitleIcon = 'fa-star'; - // - // return view('import.status', compact('job', 'subTitle', 'subTitleIcon')); - // } - // - // /** - // * Show status of import job in JSON. - // * - // * @param ImportJob $job - // * - // * @return \Illuminate\Http\JsonResponse - // */ - // public function json(ImportJob $job) - // { - // $result = [ - // 'started' => false, - // 'finished' => false, - // 'running' => false, - // 'errors' => array_values($job->extended_status['errors']), - // 'percentage' => 0, - // 'show_percentage' => false, - // 'steps' => $job->extended_status['steps'], - // 'done' => $job->extended_status['done'], - // 'statusText' => trans('import.status_job_' . $job->status), - // 'status' => $job->status, - // 'finishedText' => '', - // ]; - // - // if (0 !== $job->extended_status['steps']) { - // $result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0); - // $result['show_percentage'] = true; - // } - // if ('finished' === $job->status) { - // $result['finished'] = true; - // $tagId = (int)$job->extended_status['tag']; - // if ($tagId !== 0) { - // /** @var TagRepositoryInterface $repository */ - // $repository = app(TagRepositoryInterface::class); - // $tag = $repository->find($tagId); - // $count = $tag->transactionJournals()->count(); - // $result['finishedText'] = trans( - // 'import.status_finished_job', ['count' => $count, 'link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag] - // ); - // } - // - // if ($tagId === 0) { - // $result['finishedText'] = trans('import.status_finished_no_tag'); // @codeCoverageIgnore - // } - // } - // - // if ('running' === $job->status) { - // $result['started'] = true; - // $result['running'] = true; - // } - // $result['percentage'] = $result['percentage'] > 100 ? 100 : $result['percentage']; - // Log::debug(sprintf('JOB STATUS: %d/%d', $result['done'], $result['steps'])); - // - // return response()->json($result); - // } } diff --git a/app/Http/Controllers/Import/PrerequisitesController.php b/app/Http/Controllers/Import/PrerequisitesController.php index e7d52de775..6d7fc3a25a 100644 --- a/app/Http/Controllers/Import/PrerequisitesController.php +++ b/app/Http/Controllers/Import/PrerequisitesController.php @@ -51,7 +51,6 @@ class PrerequisitesController extends Controller function ($request, $next) { app('view')->share('mainTitleIcon', 'fa-archive'); app('view')->share('title', trans('firefly.import_index_title')); - app('view')->share('subTitleIcon', 'fa-check'); $this->repository = app(ImportJobRepositoryInterface::class); @@ -59,7 +58,6 @@ class PrerequisitesController extends Controller return $next($request); } ); - $this->middleware(IsDemoUser::class); } /** diff --git a/app/Import/Routine/BunqRoutine.php b/app/Import/Routine/BunqRoutine.php index 98156f9d7e..ff4b79112e 100644 --- a/app/Import/Routine/BunqRoutine.php +++ b/app/Import/Routine/BunqRoutine.php @@ -85,830 +85,854 @@ use Preferences; */ class BunqRoutine implements RoutineInterface { - /** @var Collection */ - public $errors; - /** @var Collection */ - public $journals; - /** @var int */ - public $lines = 0; - /** @var AccountFactory */ - private $accountFactory; - /** @var AccountRepositoryInterface */ - private $accountRepository; - /** @var ImportJob */ - private $job; - /** @var TransactionJournalFactory */ - private $journalFactory; - /** @var ImportJobRepositoryInterface */ - private $repository; - +// /** @var Collection */ +// public $errors; +// /** @var Collection */ +// public $journals; +// /** @var int */ +// public $lines = 0; +// /** @var AccountFactory */ +// private $accountFactory; +// /** @var AccountRepositoryInterface */ +// private $accountRepository; +// /** @var ImportJob */ +// private $job; +// /** @var TransactionJournalFactory */ +// private $journalFactory; +// /** @var ImportJobRepositoryInterface */ +// private $repository; +// +// /** +// * ImportRoutine constructor. +// */ +// public function __construct() +// { +// $this->journals = new Collection; +// $this->errors = new Collection; +// } +// +// /** +// * @return Collection +// */ +// public function getErrors(): Collection +// { +// return $this->errors; +// } +// +// /** +// * @return Collection +// */ +// public function getJournals(): Collection +// { +// return $this->journals; +// } +// +// /** +// * @return int +// */ +// public function getLines(): int +// { +// return $this->lines; +// } +// +// /** +// * @return bool +// * +// * @throws FireflyException +// */ +// public function run(): bool +// { +// Log::info(sprintf('Start with import job %s using Bunq.', $this->job->key)); +// set_time_limit(0); +// // this method continues with the job and is called by whenever a stage is +// // finished +// $this->continueJob(); +// +// return true; +// } +// +// /** +// * @param ImportJob $job +// */ +// public function setJob(ImportJob $job) +// { +// $this->job = $job; +// $this->repository = app(ImportJobRepositoryInterface::class); +// $this->accountRepository = app(AccountRepositoryInterface::class); +// $this->accountFactory = app(AccountFactory::class); +// $this->journalFactory = app(TransactionJournalFactory::class); +// $this->repository->setUser($job->user); +// $this->accountRepository->setUser($job->user); +// $this->accountFactory->setUser($job->user); +// $this->journalFactory->setUser($job->user); +// } +// +// /** +// * @throws FireflyException +// */ +// protected function continueJob() +// { +// // if in "configuring" +// if ('configuring' === $this->getStatus()) { +// Log::debug('Job is in configuring stage, will do nothing.'); +// +// return; +// } +// $stage = $this->getConfig()['stage'] ?? 'unknown'; +// Log::debug(sprintf('Now in continueJob() for stage %s', $stage)); +// switch ($stage) { +// case 'initial': +// // register device and get tokens. +// $this->runStageInitial(); +// $this->continueJob(); +// break; +// case 'registered': +// // get all bank accounts of user. +// $this->runStageRegistered(); +// $this->continueJob(); +// break; +// case 'logged-in': +// $this->runStageLoggedIn(); +// break; +// case 'have-accounts': +// // do nothing in this stage. Job should revert to config routine. +// break; +// case 'have-account-mapping': +// $this->setStatus('running'); +// $this->runStageHaveAccountMapping(); +// +// break; +// default: +// throw new FireflyException(sprintf('No action for stage %s!', $stage)); +// break; +// } +// } +// +// /** +// * @throws FireflyException +// */ +// protected function runStageInitial(): void +// { +// $this->addStep(); +// Log::debug('In runStageInitial()'); +// $this->setStatus('running'); +// +// // register the device at Bunq: +// $serverId = $this->registerDevice(); +// Log::debug(sprintf('Found device server with id %d', $serverId->getId())); +// +// $config = $this->getConfig(); +// $config['stage'] = 'registered'; +// $this->setConfig($config); +// $this->addStep(); +// } +// +// /** +// * Get a session token + userperson + usercompany. Store it in the job. +// * +// * @throws FireflyException +// */ +// protected function runStageRegistered(): void +// { +// $this->addStep(); +// Log::debug('Now in runStageRegistered()'); +// $apiKey = (string)Preferences::getForUser($this->job->user, 'bunq_api_key')->data; +// $serverPublicKey = new ServerPublicKey(Preferences::getForUser($this->job->user, 'bunq_server_public_key', [])->data); +// $installationToken = $this->getInstallationToken(); +// $request = new DeviceSessionRequest; +// $request->setInstallationToken($installationToken); +// $request->setPrivateKey($this->getPrivateKey()); +// $request->setServerPublicKey($serverPublicKey); +// $request->setSecret($apiKey); +// $request->call(); +// $this->addStep(); +// +// Log::debug('Requested new session.'); +// +// $deviceSession = $request->getDeviceSessionId(); +// $userPerson = $request->getUserPerson(); +// $userCompany = $request->getUserCompany(); +// $sessionToken = $request->getSessionToken(); +// +// $config = $this->getConfig(); +// $config['device_session_id'] = $deviceSession->toArray(); +// $config['user_person'] = $userPerson->toArray(); +// $config['user_company'] = $userCompany->toArray(); +// $config['session_token'] = $sessionToken->toArray(); +// $config['stage'] = 'logged-in'; +// $this->setConfig($config); +// $this->addStep(); +// +// Log::debug('Session stored in job.'); +// } +// +// /** +// * Shorthand method. +// */ +// private function addStep(): void +// { +// $this->addSteps(1); +// } +// +// /** +// * Shorthand method. +// * +// * @param int $count +// */ +// private function addSteps(int $count): void +// { +// $this->repository->addStepsDone($this->job, $count); +// } +// +// /** +// * Shorthand method +// * +// * @param int $steps +// */ +// private function addTotalSteps(int $steps): void +// { +// $this->repository->addTotalSteps($this->job, $steps); +// } +// +// /** +// * @param int $paymentId +// * +// * @return bool +// */ +// private function alreadyImported(int $paymentId): bool +// { +// $count = TransactionJournalMeta::where('name', 'bunq_payment_id') +// ->where('data', json_encode($paymentId))->count(); +// +// Log::debug(sprintf('Transaction #%d is %d time(s) in the database.', $paymentId, $count)); +// +// return $count > 0; +// } +// +// /** +// * @param LabelMonetaryAccount $party +// * @param string $expectedType +// * +// * @return Account +// */ +// private function convertToAccount(LabelMonetaryAccount $party, string $expectedType): Account +// { +// Log::debug('in convertToAccount()'); +// +// if ($party->getIban() !== null) { +// // find opposing party by IBAN first. +// $result = $this->accountRepository->findByIbanNull($party->getIban(), [$expectedType]); +// if (null !== $result) { +// Log::debug(sprintf('Search for %s resulted in account %s (#%d)', $party->getIban(), $result->name, $result->id)); +// +// return $result; +// } +// +// // try to find asset account just in case: +// if ($expectedType !== AccountType::ASSET) { +// $result = $this->accountRepository->findByIbanNull($party->getIban(), [AccountType::ASSET]); +// if (null !== $result) { +// Log::debug(sprintf('Search for Asset "%s" resulted in account %s (#%d)', $party->getIban(), $result->name, $result->id)); +// +// return $result; +// } +// } +// } +// +// // create new account: +// $data = [ +// 'user_id' => $this->job->user_id, +// 'iban' => $party->getIban(), +// 'name' => $party->getLabelUser()->getDisplayName(), +// 'account_type_id' => null, +// 'accountType' => $expectedType, +// 'virtualBalance' => null, +// 'active' => true, +// +// ]; +// $account = $this->accountFactory->create($data); +// Log::debug( +// sprintf( +// 'Converted label monetary account %s to %s account %s (#%d)', +// $party->getLabelUser()->getDisplayName(), +// $expectedType, +// $account->name, $account->id +// ) +// ); +// +// return $account; +// } +// +// /** +// * This method creates a new public/private keypair for the user. This isn't really secure, since the key is generated on the fly with +// * no regards for HSM's, smart cards or other things. It would require some low level programming to get this right. But the private key +// * is stored encrypted in the database so it's something. +// */ +// private function createKeyPair(): void +// { +// Log::debug('Now in createKeyPair()'); +// $private = Preferences::getForUser($this->job->user, 'bunq_private_key', null); +// $public = Preferences::getForUser($this->job->user, 'bunq_public_key', null); +// +// if (!(null === $private && null === $public)) { +// Log::info('Already have public and private key, return NULL.'); +// +// return; +// } +// +// Log::debug('Generate new key pair for user.'); +// $keyConfig = [ +// 'digest_alg' => 'sha512', +// 'private_key_bits' => 2048, +// 'private_key_type' => OPENSSL_KEYTYPE_RSA, +// ]; +// // Create the private and public key +// $res = openssl_pkey_new($keyConfig); +// +// // Extract the private key from $res to $privKey +// $privKey = ''; +// openssl_pkey_export($res, $privKey); +// +// // Extract the public key from $res to $pubKey +// $pubKey = openssl_pkey_get_details($res); +// +// Preferences::setForUser($this->job->user, 'bunq_private_key', $privKey); +// Preferences::setForUser($this->job->user, 'bunq_public_key', $pubKey['key']); +// Log::debug('Created and stored key pair'); +// } +// +// /** +// * Shorthand method. +// * +// * @return array +// */ +// private function getConfig(): array +// { +// return $this->repository->getConfiguration($this->job); +// } +// +// /** +// * Try to detect the current device ID (in case this instance has been registered already. +// * +// * @return DeviceServerId +// * +// * @throws FireflyException +// */ +// private function getExistingDevice(): ?DeviceServerId +// { +// Log::debug('Now in getExistingDevice()'); +// $installationToken = $this->getInstallationToken(); +// $serverPublicKey = $this->getServerPublicKey(); +// $request = new ListDeviceServerRequest; +// $remoteIp = $this->getRemoteIp(); +// $request->setInstallationToken($installationToken); +// $request->setServerPublicKey($serverPublicKey); +// $request->setPrivateKey($this->getPrivateKey()); +// $request->call(); +// $devices = $request->getDevices(); +// /** @var DeviceServer $device */ +// foreach ($devices as $device) { +// if ($device->getIp() === $remoteIp) { +// Log::debug(sprintf('This instance is registered as device #%s', $device->getId()->getId())); +// +// return $device->getId(); +// } +// } +// Log::info('This instance is not yet registered.'); +// +// return null; +// } +// +// /** +// * Shorthand method. +// * +// * @return array +// */ +// private function getExtendedStatus(): array +// { +// return $this->repository->getExtendedStatus($this->job); +// } +// +// /** +// * Get the installation token, either from the users preferences or from Bunq. +// * +// * @return InstallationToken +// * +// * @throws FireflyException +// */ +// private function getInstallationToken(): InstallationToken +// { +// Log::debug('Now in getInstallationToken().'); +// $token = Preferences::getForUser($this->job->user, 'bunq_installation_token', null); +// if (null !== $token) { +// Log::debug('Have installation token, return it.'); +// +// return new InstallationToken($token->data); +// } +// Log::debug('Have no installation token, request one.'); +// +// // verify bunq api code: +// $publicKey = $this->getPublicKey(); +// $request = new InstallationTokenRequest; +// $request->setPublicKey($publicKey); +// $request->call(); +// Log::debug('Sent request for installation token.'); +// +// $installationToken = $request->getInstallationToken(); +// $installationId = $request->getInstallationId(); +// $serverPublicKey = $request->getServerPublicKey(); +// +// Log::debug('Have all values from InstallationTokenRequest'); +// +// +// Preferences::setForUser($this->job->user, 'bunq_installation_token', $installationToken->toArray()); +// Preferences::setForUser($this->job->user, 'bunq_installation_id', $installationId->toArray()); +// Preferences::setForUser($this->job->user, 'bunq_server_public_key', $serverPublicKey->toArray()); +// +// Log::debug('Stored token, ID and pub key.'); +// +// return $installationToken; +// } +// +// /** +// * Get the private key from the users preferences. +// * +// * @return string +// */ +// private function getPrivateKey(): string +// { +// Log::debug('In getPrivateKey()'); +// $preference = Preferences::getForUser($this->job->user, 'bunq_private_key', null); +// if (null === $preference) { +// Log::debug('private key is null'); +// // create key pair +// $this->createKeyPair(); +// } +// $preference = Preferences::getForUser($this->job->user, 'bunq_private_key', null); +// Log::debug('Return private key for user'); +// +// return (string)$preference->data; +// } +// +// /** +// * Get a public key from the users preferences. +// * +// * @return string +// */ +// private function getPublicKey(): string +// { +// Log::debug('Now in getPublicKey()'); +// $preference = Preferences::getForUser($this->job->user, 'bunq_public_key', null); +// if (null === $preference) { +// Log::debug('public key is NULL.'); +// // create key pair +// $this->createKeyPair(); +// } +// $preference = Preferences::getForUser($this->job->user, 'bunq_public_key', null); +// Log::debug('Return public key for user'); +// +// return (string)$preference->data; +// } +// +// /** +// * Request users server remote IP. Let's assume this value will not change any time soon. +// * +// * @return string +// * +// */ +// private function getRemoteIp(): ?string +// { +// +// $preference = Preferences::getForUser($this->job->user, 'external_ip', null); +// if (null === $preference) { +// +// /** @var IPRetrievalInterface $service */ +// $service = app(IPRetrievalInterface::class); +// $serverIp = $service->getIP(); +// if (null !== $serverIp) { +// Preferences::setForUser($this->job->user, 'external_ip', $serverIp); +// } +// +// return $serverIp; +// } +// +// return $preference->data; +// } +// +// /** +// * Get the public key of the server, necessary to verify server signature. +// * +// * @return ServerPublicKey +// * +// * @throws FireflyException +// */ +// private function getServerPublicKey(): ServerPublicKey +// { +// $pref = Preferences::getForUser($this->job->user, 'bunq_server_public_key', null)->data; +// if (null === $pref) { +// throw new FireflyException('Cannot determine bunq server public key, but should have it at this point.'); +// } +// +// return new ServerPublicKey($pref); +// } +// +// /** +// * Shorthand method. +// * +// * @return string +// */ +// private function getStatus(): string +// { +// return $this->repository->getStatus($this->job); +// } +// +// /** +// * Import the transactions that were found. +// * +// * @param array $payments +// * +// * @throws FireflyException +// */ +// private function importPayments(array $payments): void +// { +// Log::debug('Going to run importPayments()'); +// $journals = new Collection; +// $config = $this->getConfig(); +// foreach ($payments as $accountId => $data) { +// Log::debug(sprintf('Now running for bunq account #%d with %d payment(s).', $accountId, \count($data['payments']))); +// /** @var Payment $payment */ +// foreach ($data['payments'] as $index => $payment) { +// Log::debug(sprintf('Now at payment #%d with ID #%d', $index, $payment->getId())); +// // store or find counter party: +// $counterParty = $payment->getCounterParty(); +// $amount = $payment->getAmount(); +// $paymentId = $payment->getId(); +// if ($this->alreadyImported($paymentId)) { +// Log::error(sprintf('Already imported bunq payment with id #%d', $paymentId)); +// +// // add three steps to keep up +// $this->addSteps(3); +// continue; +// } +// Log::debug(sprintf('Amount is %s %s', $amount->getCurrency(), $amount->getValue())); +// $expected = AccountType::EXPENSE; +// if (bccomp($amount->getValue(), '0') === 1) { +// // amount + means that its a deposit. +// $expected = AccountType::REVENUE; +// Log::debug('Will make opposing account revenue.'); +// } +// $opposing = $this->convertToAccount($counterParty, $expected); +// $account = $this->accountRepository->findNull($config['accounts-mapped'][$accountId]); +// $type = TransactionType::WITHDRAWAL; +// +// $this->addStep(); +// +// Log::debug(sprintf('Will store withdrawal between "%s" (%d) and "%s" (%d)', $account->name, $account->id, $opposing->name, $opposing->id)); +// +// // start storing stuff: +// $source = $account; +// $destination = $opposing; +// if (bccomp($amount->getValue(), '0') === 1) { +// // its a deposit: +// $source = $opposing; +// $destination = $account; +// $type = TransactionType::DEPOSIT; +// Log::debug('Will make it a deposit.'); +// } +// if ($account->accountType->type === AccountType::ASSET && $opposing->accountType->type === AccountType::ASSET) { +// $type = TransactionType::TRANSFER; +// Log::debug('Both are assets, will make transfer.'); +// } +// +// $storeData = [ +// 'user' => $this->job->user_id, +// 'type' => $type, +// 'date' => $payment->getCreated(), +// 'description' => $payment->getDescription(), +// 'piggy_bank_id' => null, +// 'piggy_bank_name' => null, +// 'bill_id' => null, +// 'bill_name' => null, +// 'tags' => [$payment->getType(), $payment->getSubType()], +// 'internal_reference' => $payment->getId(), +// 'notes' => null, +// 'bunq_payment_id' => $payment->getId(), +// 'transactions' => [ +// // single transaction: +// [ +// 'description' => null, +// 'amount' => $amount->getValue(), +// 'currency_id' => null, +// 'currency_code' => $amount->getCurrency(), +// 'foreign_amount' => null, +// 'foreign_currency_id' => null, +// 'foreign_currency_code' => null, +// 'budget_id' => null, +// 'budget_name' => null, +// 'category_id' => null, +// 'category_name' => null, +// 'source_id' => $source->id, +// 'source_name' => null, +// 'destination_id' => $destination->id, +// 'destination_name' => null, +// 'reconciled' => false, +// 'identifier' => 0, +// ], +// ], +// ]; +// $journal = $this->journalFactory->create($storeData); +// Log::debug(sprintf('Stored journal with ID #%d', $journal->id)); +// $this->addStep(); +// $journals->push($journal); +// +// } +// } +// if ($journals->count() > 0) { +// // link to tag +// /** @var TagRepositoryInterface $repository */ +// $repository = app(TagRepositoryInterface::class); +// $repository->setUser($this->job->user); +// $data = [ +// 'tag' => trans('import.import_with_key', ['key' => $this->job->key]), +// 'date' => new Carbon, +// 'description' => null, +// 'latitude' => null, +// 'longitude' => null, +// 'zoomLevel' => null, +// 'tagMode' => 'nothing', +// ]; +// $tag = $repository->store($data); +// $extended = $this->getExtendedStatus(); +// $extended['tag'] = $tag->id; +// $this->setExtendedStatus($extended); +// +// Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); +// Log::debug('Looping journals...'); +// $tagId = $tag->id; +// +// foreach ($journals as $journal) { +// Log::debug(sprintf('Linking journal #%d to tag #%d...', $journal->id, $tagId)); +// DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journal->id, 'tag_id' => $tagId]); +// $this->addStep(); +// } +// Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $journals->count(), $tag->id, $tag->tag)); +// } +// +// // set status to "finished"? +// // update job: +// $this->setStatus('finished'); +// } +// +// /** +// * To install Firefly III as a new device: +// * - Send an installation token request. +// * - Use this token to send a device server request +// * - Store the installation token +// * - Use the installation token each time we need a session. +// * +// * @throws FireflyException +// */ +// private function registerDevice(): DeviceServerId +// { +// Log::debug('Now in registerDevice()'); +// $deviceServerId = Preferences::getForUser($this->job->user, 'bunq_device_server_id', null); +// $serverIp = $this->getRemoteIp(); +// if (null !== $deviceServerId) { +// Log::debug('Already have device server ID.'); +// +// return new DeviceServerId($deviceServerId->data); +// } +// +// Log::debug('Device server ID is null, we have to find an existing one or register a new one.'); +// $installationToken = $this->getInstallationToken(); +// $serverPublicKey = $this->getServerPublicKey(); +// $apiKey = Preferences::getForUser($this->job->user, 'bunq_api_key', ''); +// $this->addStep(); +// +// // try get the current from a list: +// $deviceServerId = $this->getExistingDevice(); +// $this->addStep(); +// if (null !== $deviceServerId) { +// Log::debug('Found device server ID in existing devices list.'); +// +// return $deviceServerId; +// } +// +// Log::debug('Going to create new DeviceServerRequest() because nothing found in existing list.'); +// $request = new DeviceServerRequest; +// $request->setPrivateKey($this->getPrivateKey()); +// $request->setDescription('Firefly III v' . config('firefly.version') . ' for ' . $this->job->user->email); +// $request->setSecret($apiKey->data); +// $request->setPermittedIps([$serverIp]); +// $request->setInstallationToken($installationToken); +// $request->setServerPublicKey($serverPublicKey); +// $deviceServerId = null; +// // try to register device: +// try { +// $request->call(); +// $deviceServerId = $request->getDeviceServerId(); +// } catch (FireflyException $e) { +// Log::error($e->getMessage()); +// // we really have to quit at this point :( +// throw new FireflyException($e->getMessage()); +// } +// if (null === $deviceServerId) { +// throw new FireflyException('Was not able to register server with bunq. Please see the log files.'); +// } +// +// Preferences::setForUser($this->job->user, 'bunq_device_server_id', $deviceServerId->toArray()); +// Log::debug(sprintf('Server ID: %s', json_encode($deviceServerId))); +// +// return $deviceServerId; +// } +// +// /** +// * Will download the transactions for each account that is selected to be imported from. +// * Will of course also update the number of steps and what-not. +// * +// * @throws FireflyException +// */ +// private function runStageHaveAccountMapping(): void +// { +// $config = $this->getConfig(); +// $user = new UserPerson($config['user_person']); +// $mapping = $config['accounts-mapped']; +// $token = new SessionToken($config['session_token']); +// $count = 0; +// $all = []; +// if (0 === $user->getId()) { +// $user = new UserCompany($config['user_company']); +// Log::debug(sprintf('Will try to get transactions for company #%d', $user->getId())); +// } +// +// $this->addTotalSteps(\count($config['accounts']) * 2); +// +// foreach ($config['accounts'] as $accountData) { +// $this->addStep(); +// $account = new MonetaryAccountBank($accountData); +// $importId = $account->getId(); +// if (isset($mapping[$importId])) { +// Log::debug(sprintf('Will grab payments for account %s', $account->getDescription())); +// $request = new ListPaymentRequest(); +// $request->setPrivateKey($this->getPrivateKey()); +// $request->setServerPublicKey($this->getServerPublicKey()); +// $request->setSessionToken($token); +// $request->setUserId($user->getId()); +// $request->setAccount($account); +// $request->call(); +// $payments = $request->getPayments(); +// +// // store in array +// $all[$account->getId()] = [ +// 'account' => $account, +// 'import_id' => $importId, +// 'payments' => $payments, +// ]; +// $count += \count($payments); +// } +// Log::debug(sprintf('Total number of payments: %d', $count)); +// $this->addStep(); +// // add steps for import: +// $this->addTotalSteps($count * 3); +// $this->importPayments($all); +// } +// +// // update job to be complete, I think? +// } +// +// /** +// * @throws FireflyException +// */ +// private function runStageLoggedIn(): void +// { +// $this->addStep(); +// // grab new session token: +// $config = $this->getConfig(); +// $token = new SessionToken($config['session_token']); +// $user = new UserPerson($config['user_person']); +// if (0 === $user->getId()) { +// $user = new UserCompany($config['user_company']); +// } +// +// // list accounts request +// $request = new ListMonetaryAccountRequest(); +// $request->setServerPublicKey($this->getServerPublicKey()); +// $request->setPrivateKey($this->getPrivateKey()); +// $request->setUserId($user->getId()); +// $request->setSessionToken($token); +// $request->call(); +// $accounts = $request->getMonetaryAccounts(); +// $arr = []; +// Log::debug(sprintf('Get monetary accounts, found %d accounts.', $accounts->count())); +// $this->addStep(); +// +// /** @var MonetaryAccountBank $account */ +// foreach ($accounts as $account) { +// $arr[] = $account->toArray(); +// } +// +// $config = $this->getConfig(); +// $config['accounts'] = $arr; +// $config['stage'] = 'have-accounts'; +// $this->setConfig($config); +// +// // once the accounts are stored, go to configuring stage: +// // update job, set status to "configuring". +// $this->setStatus('configuring'); +// $this->addStep(); +// } +// +// /** +// * Shorthand. +// * +// * @param array $config +// */ +// private function setConfig(array $config): void +// { +// $this->repository->setConfiguration($this->job, $config); +// } +// +// /** +// * Shorthand method. +// * +// * @param array $extended +// */ +// private function setExtendedStatus(array $extended): void +// { +// $this->repository->setExtendedStatus($this->job, $extended); +// } +// +// /** +// * Shorthand. +// * +// * @param string $status +// */ +// private function setStatus(string $status): void +// { +// $this->repository->setStatus($this->job, $status); +// } /** - * ImportRoutine constructor. - */ - public function __construct() - { - $this->journals = new Collection; - $this->errors = new Collection; - } - - /** - * @return Collection - */ - public function getErrors(): Collection - { - return $this->errors; - } - - /** - * @return Collection - */ - public function getJournals(): Collection - { - return $this->journals; - } - - /** - * @return int - */ - public function getLines(): int - { - return $this->lines; - } - - /** - * @return bool + * At the end of each run(), the import routine must set the job to the expected status. * + * The final status of the routine must be "provider_finished". + * + * @return bool * @throws FireflyException */ - public function run(): bool + public function run(): void { - Log::info(sprintf('Start with import job %s using Bunq.', $this->job->key)); - set_time_limit(0); - // this method continues with the job and is called by whenever a stage is - // finished - $this->continueJob(); - - return true; + // TODO: Implement run() method. + throw new NotImplementedException; } /** * @param ImportJob $job + * + * @return mixed */ public function setJob(ImportJob $job) { - $this->job = $job; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->accountRepository = app(AccountRepositoryInterface::class); - $this->accountFactory = app(AccountFactory::class); - $this->journalFactory = app(TransactionJournalFactory::class); - $this->repository->setUser($job->user); - $this->accountRepository->setUser($job->user); - $this->accountFactory->setUser($job->user); - $this->journalFactory->setUser($job->user); - } - - /** - * @throws FireflyException - */ - protected function continueJob() - { - // if in "configuring" - if ('configuring' === $this->getStatus()) { - Log::debug('Job is in configuring stage, will do nothing.'); - - return; - } - $stage = $this->getConfig()['stage'] ?? 'unknown'; - Log::debug(sprintf('Now in continueJob() for stage %s', $stage)); - switch ($stage) { - case 'initial': - // register device and get tokens. - $this->runStageInitial(); - $this->continueJob(); - break; - case 'registered': - // get all bank accounts of user. - $this->runStageRegistered(); - $this->continueJob(); - break; - case 'logged-in': - $this->runStageLoggedIn(); - break; - case 'have-accounts': - // do nothing in this stage. Job should revert to config routine. - break; - case 'have-account-mapping': - $this->setStatus('running'); - $this->runStageHaveAccountMapping(); - - break; - default: - throw new FireflyException(sprintf('No action for stage %s!', $stage)); - break; - } - } - - /** - * @throws FireflyException - */ - protected function runStageInitial(): void - { - $this->addStep(); - Log::debug('In runStageInitial()'); - $this->setStatus('running'); - - // register the device at Bunq: - $serverId = $this->registerDevice(); - Log::debug(sprintf('Found device server with id %d', $serverId->getId())); - - $config = $this->getConfig(); - $config['stage'] = 'registered'; - $this->setConfig($config); - $this->addStep(); - } - - /** - * Get a session token + userperson + usercompany. Store it in the job. - * - * @throws FireflyException - */ - protected function runStageRegistered(): void - { - $this->addStep(); - Log::debug('Now in runStageRegistered()'); - $apiKey = (string)Preferences::getForUser($this->job->user, 'bunq_api_key')->data; - $serverPublicKey = new ServerPublicKey(Preferences::getForUser($this->job->user, 'bunq_server_public_key', [])->data); - $installationToken = $this->getInstallationToken(); - $request = new DeviceSessionRequest; - $request->setInstallationToken($installationToken); - $request->setPrivateKey($this->getPrivateKey()); - $request->setServerPublicKey($serverPublicKey); - $request->setSecret($apiKey); - $request->call(); - $this->addStep(); - - Log::debug('Requested new session.'); - - $deviceSession = $request->getDeviceSessionId(); - $userPerson = $request->getUserPerson(); - $userCompany = $request->getUserCompany(); - $sessionToken = $request->getSessionToken(); - - $config = $this->getConfig(); - $config['device_session_id'] = $deviceSession->toArray(); - $config['user_person'] = $userPerson->toArray(); - $config['user_company'] = $userCompany->toArray(); - $config['session_token'] = $sessionToken->toArray(); - $config['stage'] = 'logged-in'; - $this->setConfig($config); - $this->addStep(); - - Log::debug('Session stored in job.'); - } - - /** - * Shorthand method. - */ - private function addStep(): void - { - $this->addSteps(1); - } - - /** - * Shorthand method. - * - * @param int $count - */ - private function addSteps(int $count): void - { - $this->repository->addStepsDone($this->job, $count); - } - - /** - * Shorthand method - * - * @param int $steps - */ - private function addTotalSteps(int $steps): void - { - $this->repository->addTotalSteps($this->job, $steps); - } - - /** - * @param int $paymentId - * - * @return bool - */ - private function alreadyImported(int $paymentId): bool - { - $count = TransactionJournalMeta::where('name', 'bunq_payment_id') - ->where('data', json_encode($paymentId))->count(); - - Log::debug(sprintf('Transaction #%d is %d time(s) in the database.', $paymentId, $count)); - - return $count > 0; - } - - /** - * @param LabelMonetaryAccount $party - * @param string $expectedType - * - * @return Account - */ - private function convertToAccount(LabelMonetaryAccount $party, string $expectedType): Account - { - Log::debug('in convertToAccount()'); - - if ($party->getIban() !== null) { - // find opposing party by IBAN first. - $result = $this->accountRepository->findByIbanNull($party->getIban(), [$expectedType]); - if (null !== $result) { - Log::debug(sprintf('Search for %s resulted in account %s (#%d)', $party->getIban(), $result->name, $result->id)); - - return $result; - } - - // try to find asset account just in case: - if ($expectedType !== AccountType::ASSET) { - $result = $this->accountRepository->findByIbanNull($party->getIban(), [AccountType::ASSET]); - if (null !== $result) { - Log::debug(sprintf('Search for Asset "%s" resulted in account %s (#%d)', $party->getIban(), $result->name, $result->id)); - - return $result; - } - } - } - - // create new account: - $data = [ - 'user_id' => $this->job->user_id, - 'iban' => $party->getIban(), - 'name' => $party->getLabelUser()->getDisplayName(), - 'account_type_id' => null, - 'accountType' => $expectedType, - 'virtualBalance' => null, - 'active' => true, - - ]; - $account = $this->accountFactory->create($data); - Log::debug( - sprintf( - 'Converted label monetary account %s to %s account %s (#%d)', - $party->getLabelUser()->getDisplayName(), - $expectedType, - $account->name, $account->id - ) - ); - - return $account; - } - - /** - * This method creates a new public/private keypair for the user. This isn't really secure, since the key is generated on the fly with - * no regards for HSM's, smart cards or other things. It would require some low level programming to get this right. But the private key - * is stored encrypted in the database so it's something. - */ - private function createKeyPair(): void - { - Log::debug('Now in createKeyPair()'); - $private = Preferences::getForUser($this->job->user, 'bunq_private_key', null); - $public = Preferences::getForUser($this->job->user, 'bunq_public_key', null); - - if (!(null === $private && null === $public)) { - Log::info('Already have public and private key, return NULL.'); - - return; - } - - Log::debug('Generate new key pair for user.'); - $keyConfig = [ - 'digest_alg' => 'sha512', - 'private_key_bits' => 2048, - 'private_key_type' => OPENSSL_KEYTYPE_RSA, - ]; - // Create the private and public key - $res = openssl_pkey_new($keyConfig); - - // Extract the private key from $res to $privKey - $privKey = ''; - openssl_pkey_export($res, $privKey); - - // Extract the public key from $res to $pubKey - $pubKey = openssl_pkey_get_details($res); - - Preferences::setForUser($this->job->user, 'bunq_private_key', $privKey); - Preferences::setForUser($this->job->user, 'bunq_public_key', $pubKey['key']); - Log::debug('Created and stored key pair'); - } - - /** - * Shorthand method. - * - * @return array - */ - private function getConfig(): array - { - return $this->repository->getConfiguration($this->job); - } - - /** - * Try to detect the current device ID (in case this instance has been registered already. - * - * @return DeviceServerId - * - * @throws FireflyException - */ - private function getExistingDevice(): ?DeviceServerId - { - Log::debug('Now in getExistingDevice()'); - $installationToken = $this->getInstallationToken(); - $serverPublicKey = $this->getServerPublicKey(); - $request = new ListDeviceServerRequest; - $remoteIp = $this->getRemoteIp(); - $request->setInstallationToken($installationToken); - $request->setServerPublicKey($serverPublicKey); - $request->setPrivateKey($this->getPrivateKey()); - $request->call(); - $devices = $request->getDevices(); - /** @var DeviceServer $device */ - foreach ($devices as $device) { - if ($device->getIp() === $remoteIp) { - Log::debug(sprintf('This instance is registered as device #%s', $device->getId()->getId())); - - return $device->getId(); - } - } - Log::info('This instance is not yet registered.'); - - return null; - } - - /** - * Shorthand method. - * - * @return array - */ - private function getExtendedStatus(): array - { - return $this->repository->getExtendedStatus($this->job); - } - - /** - * Get the installation token, either from the users preferences or from Bunq. - * - * @return InstallationToken - * - * @throws FireflyException - */ - private function getInstallationToken(): InstallationToken - { - Log::debug('Now in getInstallationToken().'); - $token = Preferences::getForUser($this->job->user, 'bunq_installation_token', null); - if (null !== $token) { - Log::debug('Have installation token, return it.'); - - return new InstallationToken($token->data); - } - Log::debug('Have no installation token, request one.'); - - // verify bunq api code: - $publicKey = $this->getPublicKey(); - $request = new InstallationTokenRequest; - $request->setPublicKey($publicKey); - $request->call(); - Log::debug('Sent request for installation token.'); - - $installationToken = $request->getInstallationToken(); - $installationId = $request->getInstallationId(); - $serverPublicKey = $request->getServerPublicKey(); - - Log::debug('Have all values from InstallationTokenRequest'); - - - Preferences::setForUser($this->job->user, 'bunq_installation_token', $installationToken->toArray()); - Preferences::setForUser($this->job->user, 'bunq_installation_id', $installationId->toArray()); - Preferences::setForUser($this->job->user, 'bunq_server_public_key', $serverPublicKey->toArray()); - - Log::debug('Stored token, ID and pub key.'); - - return $installationToken; - } - - /** - * Get the private key from the users preferences. - * - * @return string - */ - private function getPrivateKey(): string - { - Log::debug('In getPrivateKey()'); - $preference = Preferences::getForUser($this->job->user, 'bunq_private_key', null); - if (null === $preference) { - Log::debug('private key is null'); - // create key pair - $this->createKeyPair(); - } - $preference = Preferences::getForUser($this->job->user, 'bunq_private_key', null); - Log::debug('Return private key for user'); - - return (string)$preference->data; - } - - /** - * Get a public key from the users preferences. - * - * @return string - */ - private function getPublicKey(): string - { - Log::debug('Now in getPublicKey()'); - $preference = Preferences::getForUser($this->job->user, 'bunq_public_key', null); - if (null === $preference) { - Log::debug('public key is NULL.'); - // create key pair - $this->createKeyPair(); - } - $preference = Preferences::getForUser($this->job->user, 'bunq_public_key', null); - Log::debug('Return public key for user'); - - return (string)$preference->data; - } - - /** - * Request users server remote IP. Let's assume this value will not change any time soon. - * - * @return string - * - */ - private function getRemoteIp(): ?string - { - - $preference = Preferences::getForUser($this->job->user, 'external_ip', null); - if (null === $preference) { - - /** @var IPRetrievalInterface $service */ - $service = app(IPRetrievalInterface::class); - $serverIp = $service->getIP(); - if (null !== $serverIp) { - Preferences::setForUser($this->job->user, 'external_ip', $serverIp); - } - - return $serverIp; - } - - return $preference->data; - } - - /** - * Get the public key of the server, necessary to verify server signature. - * - * @return ServerPublicKey - * - * @throws FireflyException - */ - private function getServerPublicKey(): ServerPublicKey - { - $pref = Preferences::getForUser($this->job->user, 'bunq_server_public_key', null)->data; - if (null === $pref) { - throw new FireflyException('Cannot determine bunq server public key, but should have it at this point.'); - } - - return new ServerPublicKey($pref); - } - - /** - * Shorthand method. - * - * @return string - */ - private function getStatus(): string - { - return $this->repository->getStatus($this->job); - } - - /** - * Import the transactions that were found. - * - * @param array $payments - * - * @throws FireflyException - */ - private function importPayments(array $payments): void - { - Log::debug('Going to run importPayments()'); - $journals = new Collection; - $config = $this->getConfig(); - foreach ($payments as $accountId => $data) { - Log::debug(sprintf('Now running for bunq account #%d with %d payment(s).', $accountId, \count($data['payments']))); - /** @var Payment $payment */ - foreach ($data['payments'] as $index => $payment) { - Log::debug(sprintf('Now at payment #%d with ID #%d', $index, $payment->getId())); - // store or find counter party: - $counterParty = $payment->getCounterParty(); - $amount = $payment->getAmount(); - $paymentId = $payment->getId(); - if ($this->alreadyImported($paymentId)) { - Log::error(sprintf('Already imported bunq payment with id #%d', $paymentId)); - - // add three steps to keep up - $this->addSteps(3); - continue; - } - Log::debug(sprintf('Amount is %s %s', $amount->getCurrency(), $amount->getValue())); - $expected = AccountType::EXPENSE; - if (bccomp($amount->getValue(), '0') === 1) { - // amount + means that its a deposit. - $expected = AccountType::REVENUE; - Log::debug('Will make opposing account revenue.'); - } - $opposing = $this->convertToAccount($counterParty, $expected); - $account = $this->accountRepository->findNull($config['accounts-mapped'][$accountId]); - $type = TransactionType::WITHDRAWAL; - - $this->addStep(); - - Log::debug(sprintf('Will store withdrawal between "%s" (%d) and "%s" (%d)', $account->name, $account->id, $opposing->name, $opposing->id)); - - // start storing stuff: - $source = $account; - $destination = $opposing; - if (bccomp($amount->getValue(), '0') === 1) { - // its a deposit: - $source = $opposing; - $destination = $account; - $type = TransactionType::DEPOSIT; - Log::debug('Will make it a deposit.'); - } - if ($account->accountType->type === AccountType::ASSET && $opposing->accountType->type === AccountType::ASSET) { - $type = TransactionType::TRANSFER; - Log::debug('Both are assets, will make transfer.'); - } - - $storeData = [ - 'user' => $this->job->user_id, - 'type' => $type, - 'date' => $payment->getCreated(), - 'description' => $payment->getDescription(), - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'bill_id' => null, - 'bill_name' => null, - 'tags' => [$payment->getType(), $payment->getSubType()], - 'internal_reference' => $payment->getId(), - 'notes' => null, - 'bunq_payment_id' => $payment->getId(), - 'transactions' => [ - // single transaction: - [ - 'description' => null, - 'amount' => $amount->getValue(), - 'currency_id' => null, - 'currency_code' => $amount->getCurrency(), - 'foreign_amount' => null, - 'foreign_currency_id' => null, - 'foreign_currency_code' => null, - 'budget_id' => null, - 'budget_name' => null, - 'category_id' => null, - 'category_name' => null, - 'source_id' => $source->id, - 'source_name' => null, - 'destination_id' => $destination->id, - 'destination_name' => null, - 'reconciled' => false, - 'identifier' => 0, - ], - ], - ]; - $journal = $this->journalFactory->create($storeData); - Log::debug(sprintf('Stored journal with ID #%d', $journal->id)); - $this->addStep(); - $journals->push($journal); - - } - } - if ($journals->count() > 0) { - // link to tag - /** @var TagRepositoryInterface $repository */ - $repository = app(TagRepositoryInterface::class); - $repository->setUser($this->job->user); - $data = [ - 'tag' => trans('import.import_with_key', ['key' => $this->job->key]), - 'date' => new Carbon, - 'description' => null, - 'latitude' => null, - 'longitude' => null, - 'zoomLevel' => null, - 'tagMode' => 'nothing', - ]; - $tag = $repository->store($data); - $extended = $this->getExtendedStatus(); - $extended['tag'] = $tag->id; - $this->setExtendedStatus($extended); - - Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); - Log::debug('Looping journals...'); - $tagId = $tag->id; - - foreach ($journals as $journal) { - Log::debug(sprintf('Linking journal #%d to tag #%d...', $journal->id, $tagId)); - DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journal->id, 'tag_id' => $tagId]); - $this->addStep(); - } - Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $journals->count(), $tag->id, $tag->tag)); - } - - // set status to "finished"? - // update job: - $this->setStatus('finished'); - } - - /** - * To install Firefly III as a new device: - * - Send an installation token request. - * - Use this token to send a device server request - * - Store the installation token - * - Use the installation token each time we need a session. - * - * @throws FireflyException - */ - private function registerDevice(): DeviceServerId - { - Log::debug('Now in registerDevice()'); - $deviceServerId = Preferences::getForUser($this->job->user, 'bunq_device_server_id', null); - $serverIp = $this->getRemoteIp(); - if (null !== $deviceServerId) { - Log::debug('Already have device server ID.'); - - return new DeviceServerId($deviceServerId->data); - } - - Log::debug('Device server ID is null, we have to find an existing one or register a new one.'); - $installationToken = $this->getInstallationToken(); - $serverPublicKey = $this->getServerPublicKey(); - $apiKey = Preferences::getForUser($this->job->user, 'bunq_api_key', ''); - $this->addStep(); - - // try get the current from a list: - $deviceServerId = $this->getExistingDevice(); - $this->addStep(); - if (null !== $deviceServerId) { - Log::debug('Found device server ID in existing devices list.'); - - return $deviceServerId; - } - - Log::debug('Going to create new DeviceServerRequest() because nothing found in existing list.'); - $request = new DeviceServerRequest; - $request->setPrivateKey($this->getPrivateKey()); - $request->setDescription('Firefly III v' . config('firefly.version') . ' for ' . $this->job->user->email); - $request->setSecret($apiKey->data); - $request->setPermittedIps([$serverIp]); - $request->setInstallationToken($installationToken); - $request->setServerPublicKey($serverPublicKey); - $deviceServerId = null; - // try to register device: - try { - $request->call(); - $deviceServerId = $request->getDeviceServerId(); - } catch (FireflyException $e) { - Log::error($e->getMessage()); - // we really have to quit at this point :( - throw new FireflyException($e->getMessage()); - } - if (null === $deviceServerId) { - throw new FireflyException('Was not able to register server with bunq. Please see the log files.'); - } - - Preferences::setForUser($this->job->user, 'bunq_device_server_id', $deviceServerId->toArray()); - Log::debug(sprintf('Server ID: %s', json_encode($deviceServerId))); - - return $deviceServerId; - } - - /** - * Will download the transactions for each account that is selected to be imported from. - * Will of course also update the number of steps and what-not. - * - * @throws FireflyException - */ - private function runStageHaveAccountMapping(): void - { - $config = $this->getConfig(); - $user = new UserPerson($config['user_person']); - $mapping = $config['accounts-mapped']; - $token = new SessionToken($config['session_token']); - $count = 0; - $all = []; - if (0 === $user->getId()) { - $user = new UserCompany($config['user_company']); - Log::debug(sprintf('Will try to get transactions for company #%d', $user->getId())); - } - - $this->addTotalSteps(\count($config['accounts']) * 2); - - foreach ($config['accounts'] as $accountData) { - $this->addStep(); - $account = new MonetaryAccountBank($accountData); - $importId = $account->getId(); - if (isset($mapping[$importId])) { - Log::debug(sprintf('Will grab payments for account %s', $account->getDescription())); - $request = new ListPaymentRequest(); - $request->setPrivateKey($this->getPrivateKey()); - $request->setServerPublicKey($this->getServerPublicKey()); - $request->setSessionToken($token); - $request->setUserId($user->getId()); - $request->setAccount($account); - $request->call(); - $payments = $request->getPayments(); - - // store in array - $all[$account->getId()] = [ - 'account' => $account, - 'import_id' => $importId, - 'payments' => $payments, - ]; - $count += \count($payments); - } - Log::debug(sprintf('Total number of payments: %d', $count)); - $this->addStep(); - // add steps for import: - $this->addTotalSteps($count * 3); - $this->importPayments($all); - } - - // update job to be complete, I think? - } - - /** - * @throws FireflyException - */ - private function runStageLoggedIn(): void - { - $this->addStep(); - // grab new session token: - $config = $this->getConfig(); - $token = new SessionToken($config['session_token']); - $user = new UserPerson($config['user_person']); - if (0 === $user->getId()) { - $user = new UserCompany($config['user_company']); - } - - // list accounts request - $request = new ListMonetaryAccountRequest(); - $request->setServerPublicKey($this->getServerPublicKey()); - $request->setPrivateKey($this->getPrivateKey()); - $request->setUserId($user->getId()); - $request->setSessionToken($token); - $request->call(); - $accounts = $request->getMonetaryAccounts(); - $arr = []; - Log::debug(sprintf('Get monetary accounts, found %d accounts.', $accounts->count())); - $this->addStep(); - - /** @var MonetaryAccountBank $account */ - foreach ($accounts as $account) { - $arr[] = $account->toArray(); - } - - $config = $this->getConfig(); - $config['accounts'] = $arr; - $config['stage'] = 'have-accounts'; - $this->setConfig($config); - - // once the accounts are stored, go to configuring stage: - // update job, set status to "configuring". - $this->setStatus('configuring'); - $this->addStep(); - } - - /** - * Shorthand. - * - * @param array $config - */ - private function setConfig(array $config): void - { - $this->repository->setConfiguration($this->job, $config); - } - - /** - * Shorthand method. - * - * @param array $extended - */ - private function setExtendedStatus(array $extended): void - { - $this->repository->setExtendedStatus($this->job, $extended); - } - - /** - * Shorthand. - * - * @param string $status - */ - private function setStatus(string $status): void - { - $this->repository->setStatus($this->job, $status); + // TODO: Implement setJob() method. + throw new NotImplementedException; } } diff --git a/app/Import/Routine/FileRoutine.php b/app/Import/Routine/FileRoutine.php index b4c58bc856..a685eeac1d 100644 --- a/app/Import/Routine/FileRoutine.php +++ b/app/Import/Routine/FileRoutine.php @@ -24,6 +24,7 @@ namespace FireflyIII\Import\Routine; use Carbon\Carbon; use DB; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Import\FileProcessor\FileProcessorInterface; use FireflyIII\Import\Storage\ImportStorage; use FireflyIII\Models\ImportJob; @@ -38,271 +39,295 @@ use Log; */ class FileRoutine implements RoutineInterface { - /** @var Collection */ - public $errors; - /** @var Collection */ - public $journals; - /** @var int */ - public $lines = 0; - /** @var ImportJob */ - private $job; - - /** @var ImportJobRepositoryInterface */ - private $repository; - - /** - * ImportRoutine constructor. - */ - public function __construct() - { - $this->journals = new Collection; - $this->errors = new Collection; - } - - /** - * @return Collection - */ - public function getErrors(): Collection - { - return $this->errors; - } - - /** - * @return Collection - */ - public function getJournals(): Collection - { - return $this->journals; - } - - /** - * @return int - */ - public function getLines(): int - { - return $this->lines; - } - +// /** @var Collection */ +// public $errors; +// /** @var Collection */ +// public $journals; +// /** @var int */ +// public $lines = 0; +// /** @var ImportJob */ +// private $job; +// +// /** @var ImportJobRepositoryInterface */ +// private $repository; +// +// /** +// * ImportRoutine constructor. +// */ +// public function __construct() +// { +// $this->journals = new Collection; +// $this->errors = new Collection; +// } +// +// /** +// * @return Collection +// */ +// public function getErrors(): Collection +// { +// return $this->errors; +// } +// +// /** +// * @return Collection +// */ +// public function getJournals(): Collection +// { +// return $this->journals; +// } +// +// /** +// * @return int +// */ +// public function getLines(): int +// { +// return $this->lines; +// } +// +// /** +// * +// */ +// public function run(): bool +// { +// if ('configured' !== $this->getStatus()) { +// Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->getStatus())); +// +// return false; +// } +// set_time_limit(0); +// Log::info(sprintf('Start with import job %s', $this->job->key)); +// +// // total steps: 6 +// $this->setTotalSteps(6); +// +// $importObjects = $this->getImportObjects(); +// $this->lines = $importObjects->count(); +// $this->addStep(); +// +// // total steps can now be extended. File has been scanned. 7 steps per line: +// $this->addTotalSteps(7 * $this->lines); +// +// // once done, use storage thing to actually store them: +// Log::info(sprintf('Returned %d valid objects from file processor', $this->lines)); +// +// $storage = $this->storeObjects($importObjects); +// $this->addStep(); +// Log::debug('Back in run()'); +// +// Log::debug('Updated job...'); +// Log::debug(sprintf('%d journals in $storage->journals', $storage->journals->count())); +// $this->journals = $storage->journals; +// $this->errors = $storage->errors; +// +// Log::debug('Going to call createImportTag()'); +// +// // create tag, link tag to all journals: +// $this->createImportTag(); +// $this->addStep(); +// +// // update job: +// $this->setStatus('finished'); +// +// Log::info(sprintf('Done with import job %s', $this->job->key)); +// +// return true; +// } +// +// /** +// * @param ImportJob $job +// */ +// public function setJob(ImportJob $job) +// { +// $this->job = $job; +// $this->repository = app(ImportJobRepositoryInterface::class); +// $this->repository->setUser($job->user); +// } +// +// /** +// * @return Collection +// */ +// protected function getImportObjects(): Collection +// { +// $objects = new Collection; +// $fileType = $this->getConfig()['file-type'] ?? 'csv'; +// // will only respond to "file" +// $class = config(sprintf('import.options.file.processors.%s', $fileType)); +// /** @var FileProcessorInterface $processor */ +// $processor = app($class); +// $processor->setJob($this->job); +// +// if ('configured' === $this->getStatus()) { +// // set job as "running"... +// $this->setStatus('running'); +// +// Log::debug('Job is configured, start with run()'); +// $processor->run(); +// $objects = $processor->getObjects(); +// } +// +// return $objects; +// } +// +// /** +// * Shorthand method. +// */ +// private function addStep() +// { +// $this->repository->addStepsDone($this->job, 1); +// } +// +// /** +// * Shorthand +// * +// * @param int $steps +// */ +// private function addTotalSteps(int $steps) +// { +// $this->repository->addTotalSteps($this->job, $steps); +// } +// +// /** +// * +// */ +// private function createImportTag(): Tag +// { +// Log::debug('Now in createImportTag()'); +// +// if ($this->journals->count() < 1) { +// Log::info(sprintf('Will not create tag, %d journals imported.', $this->journals->count())); +// +// return new Tag; +// } +// $this->addTotalSteps($this->journals->count() + 2); +// +// /** @var TagRepositoryInterface $repository */ +// $repository = app(TagRepositoryInterface::class); +// $repository->setUser($this->job->user); +// $data = [ +// 'tag' => trans('import.import_with_key', ['key' => $this->job->key]), +// 'date' => new Carbon, +// 'description' => null, +// 'latitude' => null, +// 'longitude' => null, +// 'zoomLevel' => null, +// 'tagMode' => 'nothing', +// ]; +// $tag = $repository->store($data); +// $this->addStep(); +// $extended = $this->getExtendedStatus(); +// $extended['tag'] = $tag->id; +// $this->setExtendedStatus($extended); +// +// Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); +// Log::debug('Looping journals...'); +// $journalIds = $this->journals->pluck('id')->toArray(); +// $tagId = $tag->id; +// foreach ($journalIds as $journalId) { +// Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); +// DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); +// $this->addStep(); +// } +// Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $this->journals->count(), $tag->id, $tag->tag)); +// $this->addStep(); +// +// return $tag; +// } +// +// /** +// * Shorthand method +// * +// * @return array +// */ +// private function getConfig(): array +// { +// return $this->repository->getConfiguration($this->job); +// } +// +// /** +// * @return array +// */ +// private function getExtendedStatus(): array +// { +// return $this->repository->getExtendedStatus($this->job); +// } +// +// /** +// * Shorthand method. +// * +// * @return string +// */ +// private function getStatus(): string +// { +// return $this->repository->getStatus($this->job); +// } +// +// /** +// * @param array $extended +// */ +// private function setExtendedStatus(array $extended): void +// { +// $this->repository->setExtendedStatus($this->job, $extended); +// } +// +// /** +// * Shorthand +// * +// * @param string $status +// */ +// private function setStatus(string $status): void +// { +// $this->repository->setStatus($this->job, $status); +// } +// +// /** +// * Shorthand +// * +// * @param int $steps +// */ +// private function setTotalSteps(int $steps) +// { +// $this->repository->setTotalSteps($this->job, $steps); +// } +// +// /** +// * @param Collection $objects +// * +// * @return ImportStorage +// */ +// private function storeObjects(Collection $objects): ImportStorage +// { +// $config = $this->getConfig(); +// $storage = new ImportStorage; +// $storage->setJob($this->job); +// $storage->setDateFormat($config['date-format']); +// $storage->setObjects($objects); +// $storage->store(); +// Log::info('Back in storeObjects()'); +// +// return $storage; +// } /** + * At the end of each run(), the import routine must set the job to the expected status. * + * The final status of the routine must be "provider_finished". + * + * @return bool + * @throws FireflyException */ - public function run(): bool + public function run(): void { - if ('configured' !== $this->getStatus()) { - Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->getStatus())); - - return false; - } - set_time_limit(0); - Log::info(sprintf('Start with import job %s', $this->job->key)); - - // total steps: 6 - $this->setTotalSteps(6); - - $importObjects = $this->getImportObjects(); - $this->lines = $importObjects->count(); - $this->addStep(); - - // total steps can now be extended. File has been scanned. 7 steps per line: - $this->addTotalSteps(7 * $this->lines); - - // once done, use storage thing to actually store them: - Log::info(sprintf('Returned %d valid objects from file processor', $this->lines)); - - $storage = $this->storeObjects($importObjects); - $this->addStep(); - Log::debug('Back in run()'); - - Log::debug('Updated job...'); - Log::debug(sprintf('%d journals in $storage->journals', $storage->journals->count())); - $this->journals = $storage->journals; - $this->errors = $storage->errors; - - Log::debug('Going to call createImportTag()'); - - // create tag, link tag to all journals: - $this->createImportTag(); - $this->addStep(); - - // update job: - $this->setStatus('finished'); - - Log::info(sprintf('Done with import job %s', $this->job->key)); - - return true; + // TODO: Implement run() method. + throw new NotImplementedException; } /** * @param ImportJob $job + * + * @return mixed */ public function setJob(ImportJob $job) { - $this->job = $job; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($job->user); - } - - /** - * @return Collection - */ - protected function getImportObjects(): Collection - { - $objects = new Collection; - $fileType = $this->getConfig()['file-type'] ?? 'csv'; - // will only respond to "file" - $class = config(sprintf('import.options.file.processors.%s', $fileType)); - /** @var FileProcessorInterface $processor */ - $processor = app($class); - $processor->setJob($this->job); - - if ('configured' === $this->getStatus()) { - // set job as "running"... - $this->setStatus('running'); - - Log::debug('Job is configured, start with run()'); - $processor->run(); - $objects = $processor->getObjects(); - } - - return $objects; - } - - /** - * Shorthand method. - */ - private function addStep() - { - $this->repository->addStepsDone($this->job, 1); - } - - /** - * Shorthand - * - * @param int $steps - */ - private function addTotalSteps(int $steps) - { - $this->repository->addTotalSteps($this->job, $steps); - } - - /** - * - */ - private function createImportTag(): Tag - { - Log::debug('Now in createImportTag()'); - - if ($this->journals->count() < 1) { - Log::info(sprintf('Will not create tag, %d journals imported.', $this->journals->count())); - - return new Tag; - } - $this->addTotalSteps($this->journals->count() + 2); - - /** @var TagRepositoryInterface $repository */ - $repository = app(TagRepositoryInterface::class); - $repository->setUser($this->job->user); - $data = [ - 'tag' => trans('import.import_with_key', ['key' => $this->job->key]), - 'date' => new Carbon, - 'description' => null, - 'latitude' => null, - 'longitude' => null, - 'zoomLevel' => null, - 'tagMode' => 'nothing', - ]; - $tag = $repository->store($data); - $this->addStep(); - $extended = $this->getExtendedStatus(); - $extended['tag'] = $tag->id; - $this->setExtendedStatus($extended); - - Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); - Log::debug('Looping journals...'); - $journalIds = $this->journals->pluck('id')->toArray(); - $tagId = $tag->id; - foreach ($journalIds as $journalId) { - Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); - DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); - $this->addStep(); - } - Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $this->journals->count(), $tag->id, $tag->tag)); - $this->addStep(); - - return $tag; - } - - /** - * Shorthand method - * - * @return array - */ - private function getConfig(): array - { - return $this->repository->getConfiguration($this->job); - } - - /** - * @return array - */ - private function getExtendedStatus(): array - { - return $this->repository->getExtendedStatus($this->job); - } - - /** - * Shorthand method. - * - * @return string - */ - private function getStatus(): string - { - return $this->repository->getStatus($this->job); - } - - /** - * @param array $extended - */ - private function setExtendedStatus(array $extended): void - { - $this->repository->setExtendedStatus($this->job, $extended); - } - - /** - * Shorthand - * - * @param string $status - */ - private function setStatus(string $status): void - { - $this->repository->setStatus($this->job, $status); - } - - /** - * Shorthand - * - * @param int $steps - */ - private function setTotalSteps(int $steps) - { - $this->repository->setTotalSteps($this->job, $steps); - } - - /** - * @param Collection $objects - * - * @return ImportStorage - */ - private function storeObjects(Collection $objects): ImportStorage - { - $config = $this->getConfig(); - $storage = new ImportStorage; - $storage->setJob($this->job); - $storage->setDateFormat($config['date-format']); - $storage->setObjects($objects); - $storage->store(); - Log::info('Back in storeObjects()'); - - return $storage; + // TODO: Implement setJob() method. + throw new NotImplementedException; } } diff --git a/app/Import/Routine/SpectreRoutine.php b/app/Import/Routine/SpectreRoutine.php index 3147f7788f..abde781fad 100644 --- a/app/Import/Routine/SpectreRoutine.php +++ b/app/Import/Routine/SpectreRoutine.php @@ -52,542 +52,566 @@ use Preferences; */ class SpectreRoutine implements RoutineInterface { - /** @var Collection */ - public $errors; - /** @var Collection */ - public $journals; - /** @var int */ - public $lines = 0; - /** @var ImportJob */ - private $job; - - /** @var ImportJobRepositoryInterface */ - private $repository; - +// /** @var Collection */ +// public $errors; +// /** @var Collection */ +// public $journals; +// /** @var int */ +// public $lines = 0; +// /** @var ImportJob */ +// private $job; +// +// /** @var ImportJobRepositoryInterface */ +// private $repository; +// +// /** +// * ImportRoutine constructor. +// */ +// public function __construct() +// { +// $this->journals = new Collection; +// $this->errors = new Collection; +// } +// +// /** +// * @return Collection +// */ +// public function getErrors(): Collection +// { +// return $this->errors; +// } +// +// /** +// * @return Collection +// */ +// public function getJournals(): Collection +// { +// return $this->journals; +// } +// +// /** +// * @return int +// */ +// public function getLines(): int +// { +// return $this->lines; +// } +// +// /** +// * A Spectre job that ends up here is either "configured" or "running", and will be set to "running" +// * when it is "configured". +// * +// * Job has several stages, stored in extended status key 'stage' +// * +// * initial: just begun, nothing happened. action: get a customer and a token. Next status: has-token +// * has-token: redirect user to sandstorm, make user login. set job to: user-logged-in +// * user-logged-in: customer has an attempt. action: analyse/get attempt and go for next status. +// * if attempt failed: job status is error, save a warning somewhere? +// * if success, try to get accounts. Save in config key 'accounts'. set status: have-accounts and "configuring" +// * +// * have-accounts: make user link accounts and select accounts to import from. +// * +// * If job is "configuring" and stage "have-accounts" then present the accounts and make user link them to +// * own asset accounts. Store this mapping, set config to "have-account-mapping" and job status configured". +// * +// * have-account-mapping: start downloading transactions? +// * +// * +// * @return bool +// * +// * @throws FireflyException +// * @throws SpectreException +// * @throws \Illuminate\Container\EntryNotFoundException +// */ +// public function run(): bool +// { +// if ('configured' === $this->getStatus()) { +// $this->repository->updateStatus($this->job, 'running'); +// } +// Log::info(sprintf('Start with import job %s using Spectre.', $this->job->key)); +// set_time_limit(0); +// +// // check if job has token first! +// $stage = $this->getConfig()['stage'] ?? 'unknown'; +// +// switch ($stage) { +// case 'initial': +// // get customer and token: +// $this->runStageInitial(); +// break; +// case 'has-token': +// // import routine does nothing at this point: +// break; +// case 'user-logged-in': +// $this->runStageLoggedIn(); +// break; +// case 'have-account-mapping': +// $this->runStageHaveMapping(); +// break; +// default: +// throw new FireflyException(sprintf('Cannot handle stage %s', $stage)); +// } +// +// return true; +// } +// +// /** +// * @param ImportJob $job +// */ +// public function setJob(ImportJob $job) +// { +// $this->job = $job; +// $this->repository = app(ImportJobRepositoryInterface::class); +// $this->repository->setUser($job->user); +// } +// +// /** +// * @return Customer +// * +// * @throws \FireflyIII\Exceptions\FireflyException +// * @throws \FireflyIII\Services\Spectre\Exception\SpectreException +// * @throws \Illuminate\Container\EntryNotFoundException +// */ +// protected function createCustomer(): Customer +// { +// $newCustomerRequest = new NewCustomerRequest($this->job->user); +// $customer = null; +// try { +// $newCustomerRequest->call(); +// $customer = $newCustomerRequest->getCustomer(); +// } catch (Exception $e) { +// // already exists, must fetch customer instead. +// Log::warning(sprintf('Customer exists already for user, fetch it: %s', $e->getMessage())); +// } +// if (null === $customer) { +// $getCustomerRequest = new ListCustomersRequest($this->job->user); +// $getCustomerRequest->call(); +// $customers = $getCustomerRequest->getCustomers(); +// /** @var Customer $current */ +// foreach ($customers as $current) { +// if ('default_ff3_customer' === $current->getIdentifier()) { +// $customer = $current; +// break; +// } +// } +// } +// +// Preferences::setForUser($this->job->user, 'spectre_customer', $customer->toArray()); +// +// return $customer; +// } +// +// /** +// * @return Customer +// * +// * @throws FireflyException +// * @throws SpectreException +// * @throws \Illuminate\Container\EntryNotFoundException +// */ +// protected function getCustomer(): Customer +// { +// $config = $this->getConfig(); +// if (null !== $config['customer']) { +// $customer = new Customer($config['customer']); +// +// return $customer; +// } +// +// $customer = $this->createCustomer(); +// $config['customer'] = [ +// 'id' => $customer->getId(), +// 'identifier' => $customer->getIdentifier(), +// 'secret' => $customer->getSecret(), +// ]; +// $this->setConfig($config); +// +// return $customer; +// } +// +// /** +// * @param Customer $customer +// * @param string $returnUri +// * +// * @return Token +// * +// * @throws \FireflyIII\Exceptions\FireflyException +// * @throws \FireflyIII\Services\Spectre\Exception\SpectreException +// * @throws \Illuminate\Container\EntryNotFoundException +// */ +// protected function getToken(Customer $customer, string $returnUri): Token +// { +// $request = new CreateTokenRequest($this->job->user); +// $request->setUri($returnUri); +// $request->setCustomer($customer); +// $request->call(); +// Log::debug('Call to get token is finished'); +// +// return $request->getToken(); +// } +// +// /** +// * @throws FireflyException +// * @throws SpectreException +// * @throws \Illuminate\Container\EntryNotFoundException +// */ +// protected function runStageInitial(): void +// { +// Log::debug('In runStageInitial()'); +// +// // create customer if user does not have one: +// $customer = $this->getCustomer(); +// Log::debug(sprintf('Customer ID is %s', $customer->getId())); +// +// // use customer to request a token: +// $uri = route('import.status', [$this->job->key]); +// $token = $this->getToken($customer, $uri); +// Log::debug(sprintf('Token is %s', $token->getToken())); +// +// // update job, give it the token: +// $config = $this->getConfig(); +// $config['has-token'] = true; +// $config['token'] = $token->getToken(); +// $config['token-expires'] = $token->getExpiresAt()->format('U'); +// $config['token-url'] = $token->getConnectUrl(); +// $config['stage'] = 'has-token'; +// $this->setConfig($config); +// +// Log::debug('Job config is now', $config); +// +// // update job, set status to "configuring". +// $this->setStatus('configuring'); +// Log::debug(sprintf('Job status is now %s', $this->job->status)); +// $this->addStep(); +// } +// +// /** +// * @throws FireflyException +// * @throws SpectreException +// * @throws \Illuminate\Container\EntryNotFoundException +// */ +// protected function runStageLoggedIn(): void +// { +// Log::debug('In runStageLoggedIn'); +// // list all logins: +// $customer = $this->getCustomer(); +// $request = new ListLoginsRequest($this->job->user); +// $request->setCustomer($customer); +// $request->call(); +// +// $logins = $request->getLogins(); +// /** @var Login $final */ +// $final = null; +// // loop logins, find the latest with no error in it: +// $time = 0; +// /** @var Login $login */ +// foreach ($logins as $login) { +// $attempt = $login->getLastAttempt(); +// $attemptTime = (int)$attempt->getCreatedAt()->format('U'); +// if ($attemptTime > $time && null === $attempt->getFailErrorClass()) { +// $time = $attemptTime; +// $final = $login; +// } +// } +// if (null === $final) { +// Log::error('Could not find a valid login for this user.'); +// $this->repository->addError($this->job, 0, 'Spectre connection failed. Did you use invalid credentials, press Cancel or failed the 2FA challenge?'); +// $this->repository->setStatus($this->job, 'error'); +// +// return; +// } +// $this->addStep(); +// +// // list the users accounts using this login. +// $accountRequest = new ListAccountsRequest($this->job->user); +// $accountRequest->setLogin($login); +// $accountRequest->call(); +// $accounts = $accountRequest->getAccounts(); +// +// // store accounts in job: +// $all = []; +// /** @var Account $account */ +// foreach ($accounts as $account) { +// $all[] = $account->toArray(); +// } +// +// // update job: +// $config = $this->getConfig(); +// $config['accounts'] = $all; +// $config['login'] = $login->toArray(); +// $config['stage'] = 'have-accounts'; +// +// $this->setConfig($config); +// $this->setStatus('configuring'); +// $this->addStep(); +// } +// +// /** +// * Shorthand method. +// */ +// private function addStep() +// { +// $this->repository->addStepsDone($this->job, 1); +// } +// +// /** +// * Shorthand +// * +// * @param int $steps +// */ +// private function addTotalSteps(int $steps) +// { +// $this->repository->addTotalSteps($this->job, $steps); +// } +// +// /** +// * @return array +// */ +// private function getConfig(): array +// { +// return $this->repository->getConfiguration($this->job); +// } +// +// /** +// * Shorthand method. +// * +// * @return array +// */ +// private function getExtendedStatus(): array +// { +// return $this->repository->getExtendedStatus($this->job); +// } +// +// /** +// * Shorthand method. +// * +// * @return string +// */ +// private function getStatus(): string +// { +// return $this->repository->getStatus($this->job); +// } +// +// /** +// * @param array $all +// * +// * @throws FireflyException +// */ +// private function importTransactions(array $all) +// { +// Log::debug('Going to import transactions'); +// $collection = new Collection; +// // create import objects? +// foreach ($all as $accountId => $data) { +// Log::debug(sprintf('Now at account #%d', $accountId)); +// /** @var Transaction $transaction */ +// foreach ($data['transactions'] as $transaction) { +// Log::debug(sprintf('Now at transaction #%d', $transaction->getId())); +// /** @var Account $account */ +// $account = $data['account']; +// $importJournal = new ImportJournal; +// $importJournal->setUser($this->job->user); +// $importJournal->asset->setDefaultAccountId($data['import_id']); +// // call set value a bunch of times for various data entries: +// $tags = []; +// $tags[] = $transaction->getMode(); +// $tags[] = $transaction->getStatus(); +// if ($transaction->isDuplicated()) { +// $tags[] = 'possibly-duplicated'; +// } +// $extra = $transaction->getExtra()->toArray(); +// $notes = ''; +// // double space for newline in Markdown. +// $notes .= (string)trans('import.imported_from_account', ['account' => $account->getName()]) . ' ' . "\n"; +// +// foreach ($extra as $key => $value) { +// switch ($key) { +// case 'account_number': +// $importJournal->setValue(['role' => 'account-number', 'value' => $value]); +// break; +// case 'original_category': +// case 'original_subcategory': +// case 'customer_category_code': +// case 'customer_category_name': +// $tags[] = $value; +// break; +// case 'payee': +// $importJournal->setValue(['role' => 'opposing-name', 'value' => $value]); +// break; +// case 'original_amount': +// $importJournal->setValue(['role' => 'amount_foreign', 'value' => $value]); +// break; +// case 'original_currency_code': +// $importJournal->setValue(['role' => 'foreign-currency-code', 'value' => $value]); +// break; +// default: +// $notes .= $key . ': ' . $value . ' '; // for newline in Markdown. +// } +// } +// // hash +// $importJournal->setHash($transaction->getHash()); +// +// // account ID (Firefly III account): +// $importJournal->setValue(['role' => 'account-id', 'value' => $data['import_id'], 'mapped' => $data['import_id']]); +// +// // description: +// $importJournal->setValue(['role' => 'description', 'value' => $transaction->getDescription()]); +// +// // date: +// $importJournal->setValue(['role' => 'date-transaction', 'value' => $transaction->getMadeOn()->toIso8601String()]); +// +// // amount +// $importJournal->setValue(['role' => 'amount', 'value' => $transaction->getAmount()]); +// $importJournal->setValue(['role' => 'currency-code', 'value' => $transaction->getCurrencyCode()]); +// +// // various meta fields: +// $importJournal->setValue(['role' => 'category-name', 'value' => $transaction->getCategory()]); +// $importJournal->setValue(['role' => 'note', 'value' => $notes]); +// $importJournal->setValue(['role' => 'tags-comma', 'value' => implode(',', $tags)]); +// $collection->push($importJournal); +// } +// } +// $this->addStep(); +// Log::debug(sprintf('Going to try and store all %d them.', $collection->count())); +// +// $this->addTotalSteps(7 * $collection->count()); +// // try to store them (seven steps per transaction) +// $storage = new ImportStorage; +// +// $storage->setJob($this->job); +// $storage->setDateFormat('Y-m-d\TH:i:sO'); +// $storage->setObjects($collection); +// $storage->store(); +// Log::info('Back in importTransactions()'); +// +// // link to tag +// /** @var TagRepositoryInterface $repository */ +// $repository = app(TagRepositoryInterface::class); +// $repository->setUser($this->job->user); +// $data = [ +// 'tag' => trans('import.import_with_key', ['key' => $this->job->key]), +// 'date' => new Carbon, +// 'description' => null, +// 'latitude' => null, +// 'longitude' => null, +// 'zoomLevel' => null, +// 'tagMode' => 'nothing', +// ]; +// $tag = $repository->store($data); +// $extended = $this->getExtendedStatus(); +// $extended['tag'] = $tag->id; +// $this->setExtendedStatus($extended); +// +// Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); +// Log::debug('Looping journals...'); +// $journalIds = $storage->journals->pluck('id')->toArray(); +// $tagId = $tag->id; +// $this->addTotalSteps(\count($journalIds)); +// +// foreach ($journalIds as $journalId) { +// Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); +// DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); +// $this->addStep(); +// } +// Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $storage->journals->count(), $tag->id, $tag->tag)); +// +// // set status to "finished"? +// // update job: +// $this->setStatus('finished'); +// $this->addStep(); +// +// } +// +// /** +// * @throws FireflyException +// * @throws SpectreException +// * @throws \Illuminate\Container\EntryNotFoundException +// */ +// private function runStageHaveMapping() +// { +// $config = $this->getConfig(); +// $accounts = $config['accounts'] ?? []; +// $all = []; +// $count = 0; +// /** @var array $accountArray */ +// foreach ($accounts as $accountArray) { +// $account = new Account($accountArray); +// $importId = (int)($config['accounts-mapped'][$account->getId()] ?? 0.0); +// $doImport = 0 !== $importId; +// if (!$doImport) { +// Log::debug(sprintf('Will NOT import from Spectre account #%d ("%s")', $account->getId(), $account->getName())); +// continue; +// } +// // grab all transactions +// $listTransactionsRequest = new ListTransactionsRequest($this->job->user); +// $listTransactionsRequest->setAccount($account); +// $listTransactionsRequest->call(); +// $transactions = $listTransactionsRequest->getTransactions(); +// $all[$account->getId()] = [ +// 'account' => $account, +// 'import_id' => $importId, +// 'transactions' => $transactions, +// ]; +// $count += \count($transactions); +// } +// Log::debug(sprintf('Total number of transactions: %d', $count)); +// $this->addStep(); +// +// $this->importTransactions($all); +// } +// +// /** +// * Shorthand. +// * +// * @param array $config +// */ +// private function setConfig(array $config): void +// { +// $this->repository->setConfiguration($this->job, $config); +// +// } +// +// /** +// * Shorthand method. +// * +// * @param array $extended +// */ +// private function setExtendedStatus(array $extended): void +// { +// $this->repository->setExtendedStatus($this->job, $extended); +// +// } +// +// /** +// * Shorthand. +// * +// * @param string $status +// */ +// private function setStatus(string $status): void +// { +// $this->repository->setStatus($this->job, $status); +// } /** - * ImportRoutine constructor. - */ - public function __construct() - { - $this->journals = new Collection; - $this->errors = new Collection; - } - - /** - * @return Collection - */ - public function getErrors(): Collection - { - return $this->errors; - } - - /** - * @return Collection - */ - public function getJournals(): Collection - { - return $this->journals; - } - - /** - * @return int - */ - public function getLines(): int - { - return $this->lines; - } - - /** - * A Spectre job that ends up here is either "configured" or "running", and will be set to "running" - * when it is "configured". - * - * Job has several stages, stored in extended status key 'stage' - * - * initial: just begun, nothing happened. action: get a customer and a token. Next status: has-token - * has-token: redirect user to sandstorm, make user login. set job to: user-logged-in - * user-logged-in: customer has an attempt. action: analyse/get attempt and go for next status. - * if attempt failed: job status is error, save a warning somewhere? - * if success, try to get accounts. Save in config key 'accounts'. set status: have-accounts and "configuring" - * - * have-accounts: make user link accounts and select accounts to import from. - * - * If job is "configuring" and stage "have-accounts" then present the accounts and make user link them to - * own asset accounts. Store this mapping, set config to "have-account-mapping" and job status configured". - * - * have-account-mapping: start downloading transactions? + * At the end of each run(), the import routine must set the job to the expected status. * + * The final status of the routine must be "provider_finished". * * @return bool - * * @throws FireflyException - * @throws SpectreException - * @throws \Illuminate\Container\EntryNotFoundException */ - public function run(): bool + public function run(): void { - if ('configured' === $this->getStatus()) { - $this->repository->updateStatus($this->job, 'running'); - } - Log::info(sprintf('Start with import job %s using Spectre.', $this->job->key)); - set_time_limit(0); - - // check if job has token first! - $stage = $this->getConfig()['stage'] ?? 'unknown'; - - switch ($stage) { - case 'initial': - // get customer and token: - $this->runStageInitial(); - break; - case 'has-token': - // import routine does nothing at this point: - break; - case 'user-logged-in': - $this->runStageLoggedIn(); - break; - case 'have-account-mapping': - $this->runStageHaveMapping(); - break; - default: - throw new FireflyException(sprintf('Cannot handle stage %s', $stage)); - } - - return true; + // TODO: Implement run() method. + throw new NotImplementedException; } /** * @param ImportJob $job + * + * @return mixed */ public function setJob(ImportJob $job) { - $this->job = $job; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($job->user); - } - - /** - * @return Customer - * - * @throws \FireflyIII\Exceptions\FireflyException - * @throws \FireflyIII\Services\Spectre\Exception\SpectreException - * @throws \Illuminate\Container\EntryNotFoundException - */ - protected function createCustomer(): Customer - { - $newCustomerRequest = new NewCustomerRequest($this->job->user); - $customer = null; - try { - $newCustomerRequest->call(); - $customer = $newCustomerRequest->getCustomer(); - } catch (Exception $e) { - // already exists, must fetch customer instead. - Log::warning(sprintf('Customer exists already for user, fetch it: %s', $e->getMessage())); - } - if (null === $customer) { - $getCustomerRequest = new ListCustomersRequest($this->job->user); - $getCustomerRequest->call(); - $customers = $getCustomerRequest->getCustomers(); - /** @var Customer $current */ - foreach ($customers as $current) { - if ('default_ff3_customer' === $current->getIdentifier()) { - $customer = $current; - break; - } - } - } - - Preferences::setForUser($this->job->user, 'spectre_customer', $customer->toArray()); - - return $customer; - } - - /** - * @return Customer - * - * @throws FireflyException - * @throws SpectreException - * @throws \Illuminate\Container\EntryNotFoundException - */ - protected function getCustomer(): Customer - { - $config = $this->getConfig(); - if (null !== $config['customer']) { - $customer = new Customer($config['customer']); - - return $customer; - } - - $customer = $this->createCustomer(); - $config['customer'] = [ - 'id' => $customer->getId(), - 'identifier' => $customer->getIdentifier(), - 'secret' => $customer->getSecret(), - ]; - $this->setConfig($config); - - return $customer; - } - - /** - * @param Customer $customer - * @param string $returnUri - * - * @return Token - * - * @throws \FireflyIII\Exceptions\FireflyException - * @throws \FireflyIII\Services\Spectre\Exception\SpectreException - * @throws \Illuminate\Container\EntryNotFoundException - */ - protected function getToken(Customer $customer, string $returnUri): Token - { - $request = new CreateTokenRequest($this->job->user); - $request->setUri($returnUri); - $request->setCustomer($customer); - $request->call(); - Log::debug('Call to get token is finished'); - - return $request->getToken(); - } - - /** - * @throws FireflyException - * @throws SpectreException - * @throws \Illuminate\Container\EntryNotFoundException - */ - protected function runStageInitial(): void - { - Log::debug('In runStageInitial()'); - - // create customer if user does not have one: - $customer = $this->getCustomer(); - Log::debug(sprintf('Customer ID is %s', $customer->getId())); - - // use customer to request a token: - $uri = route('import.status', [$this->job->key]); - $token = $this->getToken($customer, $uri); - Log::debug(sprintf('Token is %s', $token->getToken())); - - // update job, give it the token: - $config = $this->getConfig(); - $config['has-token'] = true; - $config['token'] = $token->getToken(); - $config['token-expires'] = $token->getExpiresAt()->format('U'); - $config['token-url'] = $token->getConnectUrl(); - $config['stage'] = 'has-token'; - $this->setConfig($config); - - Log::debug('Job config is now', $config); - - // update job, set status to "configuring". - $this->setStatus('configuring'); - Log::debug(sprintf('Job status is now %s', $this->job->status)); - $this->addStep(); - } - - /** - * @throws FireflyException - * @throws SpectreException - * @throws \Illuminate\Container\EntryNotFoundException - */ - protected function runStageLoggedIn(): void - { - Log::debug('In runStageLoggedIn'); - // list all logins: - $customer = $this->getCustomer(); - $request = new ListLoginsRequest($this->job->user); - $request->setCustomer($customer); - $request->call(); - - $logins = $request->getLogins(); - /** @var Login $final */ - $final = null; - // loop logins, find the latest with no error in it: - $time = 0; - /** @var Login $login */ - foreach ($logins as $login) { - $attempt = $login->getLastAttempt(); - $attemptTime = (int)$attempt->getCreatedAt()->format('U'); - if ($attemptTime > $time && null === $attempt->getFailErrorClass()) { - $time = $attemptTime; - $final = $login; - } - } - if (null === $final) { - Log::error('Could not find a valid login for this user.'); - $this->repository->addError($this->job, 0, 'Spectre connection failed. Did you use invalid credentials, press Cancel or failed the 2FA challenge?'); - $this->repository->setStatus($this->job, 'error'); - - return; - } - $this->addStep(); - - // list the users accounts using this login. - $accountRequest = new ListAccountsRequest($this->job->user); - $accountRequest->setLogin($login); - $accountRequest->call(); - $accounts = $accountRequest->getAccounts(); - - // store accounts in job: - $all = []; - /** @var Account $account */ - foreach ($accounts as $account) { - $all[] = $account->toArray(); - } - - // update job: - $config = $this->getConfig(); - $config['accounts'] = $all; - $config['login'] = $login->toArray(); - $config['stage'] = 'have-accounts'; - - $this->setConfig($config); - $this->setStatus('configuring'); - $this->addStep(); - } - - /** - * Shorthand method. - */ - private function addStep() - { - $this->repository->addStepsDone($this->job, 1); - } - - /** - * Shorthand - * - * @param int $steps - */ - private function addTotalSteps(int $steps) - { - $this->repository->addTotalSteps($this->job, $steps); - } - - /** - * @return array - */ - private function getConfig(): array - { - return $this->repository->getConfiguration($this->job); - } - - /** - * Shorthand method. - * - * @return array - */ - private function getExtendedStatus(): array - { - return $this->repository->getExtendedStatus($this->job); - } - - /** - * Shorthand method. - * - * @return string - */ - private function getStatus(): string - { - return $this->repository->getStatus($this->job); - } - - /** - * @param array $all - * - * @throws FireflyException - */ - private function importTransactions(array $all) - { - Log::debug('Going to import transactions'); - $collection = new Collection; - // create import objects? - foreach ($all as $accountId => $data) { - Log::debug(sprintf('Now at account #%d', $accountId)); - /** @var Transaction $transaction */ - foreach ($data['transactions'] as $transaction) { - Log::debug(sprintf('Now at transaction #%d', $transaction->getId())); - /** @var Account $account */ - $account = $data['account']; - $importJournal = new ImportJournal; - $importJournal->setUser($this->job->user); - $importJournal->asset->setDefaultAccountId($data['import_id']); - // call set value a bunch of times for various data entries: - $tags = []; - $tags[] = $transaction->getMode(); - $tags[] = $transaction->getStatus(); - if ($transaction->isDuplicated()) { - $tags[] = 'possibly-duplicated'; - } - $extra = $transaction->getExtra()->toArray(); - $notes = ''; - // double space for newline in Markdown. - $notes .= (string)trans('import.imported_from_account', ['account' => $account->getName()]) . ' ' . "\n"; - - foreach ($extra as $key => $value) { - switch ($key) { - case 'account_number': - $importJournal->setValue(['role' => 'account-number', 'value' => $value]); - break; - case 'original_category': - case 'original_subcategory': - case 'customer_category_code': - case 'customer_category_name': - $tags[] = $value; - break; - case 'payee': - $importJournal->setValue(['role' => 'opposing-name', 'value' => $value]); - break; - case 'original_amount': - $importJournal->setValue(['role' => 'amount_foreign', 'value' => $value]); - break; - case 'original_currency_code': - $importJournal->setValue(['role' => 'foreign-currency-code', 'value' => $value]); - break; - default: - $notes .= $key . ': ' . $value . ' '; // for newline in Markdown. - } - } - // hash - $importJournal->setHash($transaction->getHash()); - - // account ID (Firefly III account): - $importJournal->setValue(['role' => 'account-id', 'value' => $data['import_id'], 'mapped' => $data['import_id']]); - - // description: - $importJournal->setValue(['role' => 'description', 'value' => $transaction->getDescription()]); - - // date: - $importJournal->setValue(['role' => 'date-transaction', 'value' => $transaction->getMadeOn()->toIso8601String()]); - - // amount - $importJournal->setValue(['role' => 'amount', 'value' => $transaction->getAmount()]); - $importJournal->setValue(['role' => 'currency-code', 'value' => $transaction->getCurrencyCode()]); - - // various meta fields: - $importJournal->setValue(['role' => 'category-name', 'value' => $transaction->getCategory()]); - $importJournal->setValue(['role' => 'note', 'value' => $notes]); - $importJournal->setValue(['role' => 'tags-comma', 'value' => implode(',', $tags)]); - $collection->push($importJournal); - } - } - $this->addStep(); - Log::debug(sprintf('Going to try and store all %d them.', $collection->count())); - - $this->addTotalSteps(7 * $collection->count()); - // try to store them (seven steps per transaction) - $storage = new ImportStorage; - - $storage->setJob($this->job); - $storage->setDateFormat('Y-m-d\TH:i:sO'); - $storage->setObjects($collection); - $storage->store(); - Log::info('Back in importTransactions()'); - - // link to tag - /** @var TagRepositoryInterface $repository */ - $repository = app(TagRepositoryInterface::class); - $repository->setUser($this->job->user); - $data = [ - 'tag' => trans('import.import_with_key', ['key' => $this->job->key]), - 'date' => new Carbon, - 'description' => null, - 'latitude' => null, - 'longitude' => null, - 'zoomLevel' => null, - 'tagMode' => 'nothing', - ]; - $tag = $repository->store($data); - $extended = $this->getExtendedStatus(); - $extended['tag'] = $tag->id; - $this->setExtendedStatus($extended); - - Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); - Log::debug('Looping journals...'); - $journalIds = $storage->journals->pluck('id')->toArray(); - $tagId = $tag->id; - $this->addTotalSteps(\count($journalIds)); - - foreach ($journalIds as $journalId) { - Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); - DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); - $this->addStep(); - } - Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $storage->journals->count(), $tag->id, $tag->tag)); - - // set status to "finished"? - // update job: - $this->setStatus('finished'); - $this->addStep(); - - } - - /** - * @throws FireflyException - * @throws SpectreException - * @throws \Illuminate\Container\EntryNotFoundException - */ - private function runStageHaveMapping() - { - $config = $this->getConfig(); - $accounts = $config['accounts'] ?? []; - $all = []; - $count = 0; - /** @var array $accountArray */ - foreach ($accounts as $accountArray) { - $account = new Account($accountArray); - $importId = (int)($config['accounts-mapped'][$account->getId()] ?? 0.0); - $doImport = 0 !== $importId; - if (!$doImport) { - Log::debug(sprintf('Will NOT import from Spectre account #%d ("%s")', $account->getId(), $account->getName())); - continue; - } - // grab all transactions - $listTransactionsRequest = new ListTransactionsRequest($this->job->user); - $listTransactionsRequest->setAccount($account); - $listTransactionsRequest->call(); - $transactions = $listTransactionsRequest->getTransactions(); - $all[$account->getId()] = [ - 'account' => $account, - 'import_id' => $importId, - 'transactions' => $transactions, - ]; - $count += \count($transactions); - } - Log::debug(sprintf('Total number of transactions: %d', $count)); - $this->addStep(); - - $this->importTransactions($all); - } - - /** - * Shorthand. - * - * @param array $config - */ - private function setConfig(array $config): void - { - $this->repository->setConfiguration($this->job, $config); - - } - - /** - * Shorthand method. - * - * @param array $extended - */ - private function setExtendedStatus(array $extended): void - { - $this->repository->setExtendedStatus($this->job, $extended); - - } - - /** - * Shorthand. - * - * @param string $status - */ - private function setStatus(string $status): void - { - $this->repository->setStatus($this->job, $status); + // TODO: Implement setJob() method. + throw new NotImplementedException; } } diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 2d08c9631a..99ee41171d 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -3,21 +3,21 @@ namespace FireflyIII\Import\Storage; use Carbon\Carbon; +use DB; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TransactionJournalFactory; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Models\ImportJob; -use FireflyIII\Models\Tag; +use FireflyIII\Models\Rule; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournalMeta; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use FireflyIII\TransactionRules\Processor; use Illuminate\Support\Collection; -use Illuminate\Support\MessageBag; use Log; -use DB; /** * Creates new transactions based upon arrays. Will first check the array for duplicates. @@ -40,11 +40,9 @@ class ImportArrayStorage private $transfers; /** - * ImportArrayStorage constructor. - * * @param ImportJob $importJob */ - public function __construct(ImportJob $importJob) + public function setJob(ImportJob $importJob): void { $this->importJob = $importJob; $this->countTransfers(); @@ -56,104 +54,61 @@ class ImportArrayStorage } /** - * Actually does the storing. + * Actually does the storing. Does three things. + * - Store journals + * - Link to tag + * - Run rules (if set to) * * @return Collection * @throws FireflyException */ public function store(): Collection { - $count = count($this->importJob->transactions); - Log::debug(sprintf('Now in store(). Count of items is %d', $count)); - $toStore = []; - foreach ($this->importJob->transactions as $index => $transaction) { - Log::debug(sprintf('Now at item %d out of %d', ($index + 1), $count)); - $existingId = $this->hashExists($transaction); - if (null !== $existingId) { - $this->logDuplicateObject($transaction, $existingId); - $this->repository->addErrorMessage( - $this->importJob, sprintf( - 'Entry #%d ("%s") could not be imported. It already exists.', - $index, $transaction['description'] - ) - ); - continue; - } - if ($this->checkForTransfers) { - if ($this->transferExists($transaction)) { - $this->logDuplicateTransfer($transaction); - $this->repository->addErrorMessage( - $this->importJob, sprintf( - 'Entry #%d ("%s") could not be imported. Such a transfer already exists.', - $index, - $transaction['description'] - ) - ); - continue; - } - } - $toStore[] = $transaction; + // store transactions + $this->setStatus('storing_data'); + $collection = $this->storeArray(); + $this->setStatus('stored_data'); + + // link tag: + $this->setStatus('linking_to_tag'); + $this->linkToTag($collection); + $this->setStatus('linked_to_tag'); + + // run rules, if configured to. + $config = $this->importJob->configuration; + if (isset($config['apply-rules']) && $config['apply-rules'] === true) { + $this->setStatus('applying_rules'); + $this->applyRules($collection); + $this->setStatus('rules_applied'); } - if (count($toStore) === 0) { - Log::info('No transactions to store left!'); - - return new Collection; - } - Log::debug('Going to store...'); - // now actually store them: - $collection = new Collection; - /** @var TransactionJournalFactory $factory */ - $factory = app(TransactionJournalFactory::class); - $factory->setUser($this->importJob->user); - foreach ($toStore as $store) { - // convert the date to an object: - $store['date'] = Carbon::createFromFormat('Y-m-d', $store['date']); - - // store the journal. - $collection->push($factory->create($store)); - } - Log::debug('DONE storing!'); - - - // create tag and append journals: - $this->createTag($collection); - return $collection; } /** * @param Collection $collection + * + * @throws FireflyException */ - private function createTag(Collection $collection): void + private function applyRules(Collection $collection): void { + $rules = $this->getRules(); + if ($rules->count() > 0) { + foreach ($collection as $journal) { + $rules->each( + function (Rule $rule) use ($journal) { + Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id)); + $processor = Processor::make($rule); + $processor->handleTransactionJournal($journal); + if ($rule->stop_processing) { + return false; + } - /** @var TagRepositoryInterface $repository */ - $repository = app(TagRepositoryInterface::class); - $repository->setUser($this->importJob->user); - $data = [ - 'tag' => trans('import.import_with_key', ['key' => $this->importJob->key]), - 'date' => new Carbon, - 'description' => null, - 'latitude' => null, - 'longitude' => null, - 'zoomLevel' => null, - 'tagMode' => 'nothing', - ]; - $tag = $repository->store($data); - - Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); - Log::debug('Looping journals...'); - $journalIds = $collection->pluck('id')->toArray(); - $tagId = $tag->id; - foreach ($journalIds as $journalId) { - Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); - DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); + return true; + } + ); + } } - Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $collection->count(), $tag->id, $tag->tag)); - - $this->repository->setTag($this->importJob, $tag); - } /** @@ -161,8 +116,10 @@ class ImportArrayStorage */ private function countTransfers(): void { + /** @var array $array */ + $array = $this->importJob->transactions; $count = 0; - foreach ($this->importJob->transactions as $transaction) { + foreach ($array as $transaction) { if (strtolower(TransactionType::TRANSFER) === $transaction['type']) { $count++; } @@ -177,6 +134,29 @@ class ImportArrayStorage } + /** + * @return Collection + */ + private function getRules(): Collection + { + /** @var Collection $set */ + $set = Rule::distinct() + ->where('rules.user_id', $this->importJob->user_id) + ->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id') + ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') + ->where('rule_groups.active', 1) + ->where('rule_triggers.trigger_type', 'user_action') + ->where('rule_triggers.trigger_value', 'store-journal') + ->where('rules.active', 1) + ->orderBy('rule_groups.order', 'ASC') + ->orderBy('rules.order', 'ASC') + ->get(['rules.*', 'rule_groups.order']); + + Log::debug(sprintf('Found %d user rules.', $set->count())); + + return $set; + } + /** * Get the users transfers, so they can be compared to whatever the user is trying to import. */ @@ -192,7 +172,6 @@ class ImportArrayStorage } - /** * @param array $transaction * @@ -222,6 +201,39 @@ class ImportArrayStorage return (int)$entry->transaction_journal_id; } + /** + * @param Collection $collection + */ + private function linkToTag(Collection $collection): void + { + /** @var TagRepositoryInterface $repository */ + $repository = app(TagRepositoryInterface::class); + $repository->setUser($this->importJob->user); + $data = [ + 'tag' => trans('import.import_with_key', ['key' => $this->importJob->key]), + 'date' => new Carbon, + 'description' => null, + 'latitude' => null, + 'longitude' => null, + 'zoomLevel' => null, + 'tagMode' => 'nothing', + ]; + $tag = $repository->store($data); + + Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); + Log::debug('Looping journals...'); + $journalIds = $collection->pluck('id')->toArray(); + $tagId = $tag->id; + foreach ($journalIds as $journalId) { + Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); + DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); + } + Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $collection->count(), $tag->id, $tag->tag)); + + $this->repository->setTag($this->importJob, $tag); + + } + /** * @param array $transaction * @param int $existingId @@ -255,6 +267,84 @@ class ImportArrayStorage ); } + /** + * Shorthand method to quickly set job status + * + * @param string $status + */ + private function setStatus(string $status): void + { + $this->repository->setStatus($this->importJob, $status); + } + + /** + * Store array as journals. + * + * @return Collection + * @throws FireflyException + */ + private function storeArray(): Collection + { + /** @var array $array */ + $array = $this->importJob->transactions; + $count = \count($array); + $toStore = []; + + Log::debug(sprintf('Now in store(). Count of items is %d', $count)); + + foreach ($array as $index => $transaction) { + Log::debug(sprintf('Now at item %d out of %d', $index + 1, $count)); + $existingId = $this->hashExists($transaction); + if (null !== $existingId) { + $this->logDuplicateObject($transaction, $existingId); + $this->repository->addErrorMessage( + $this->importJob, sprintf( + 'Entry #%d ("%s") could not be imported. It already exists.', + $index, $transaction['description'] + ) + ); + continue; + } + if ($this->checkForTransfers) { + if ($this->transferExists($transaction)) { + $this->logDuplicateTransfer($transaction); + $this->repository->addErrorMessage( + $this->importJob, sprintf( + 'Entry #%d ("%s") could not be imported. Such a transfer already exists.', + $index, + $transaction['description'] + ) + ); + continue; + } + } + $toStore[] = $transaction; + } + + if (\count($toStore) === 0) { + Log::info('No transactions to store left!'); + + return new Collection; + } + + Log::debug('Going to store...'); + // now actually store them: + $collection = new Collection; + /** @var TransactionJournalFactory $factory */ + $factory = app(TransactionJournalFactory::class); + $factory->setUser($this->importJob->user); + foreach ($toStore as $store) { + // convert the date to an object: + $store['date'] = Carbon::createFromFormat('Y-m-d', $store['date']); + + // store the journal. + $collection->push($factory->create($store)); + } + Log::debug('DONE storing!'); + + return $collection; + } + /** * Check if a transfer exists. * diff --git a/app/Support/Import/Routine/Fake/StageAhoyHandler.php b/app/Support/Import/Routine/Fake/StageAhoyHandler.php index 851b61ce06..32bbad15fd 100644 --- a/app/Support/Import/Routine/Fake/StageAhoyHandler.php +++ b/app/Support/Import/Routine/Fake/StageAhoyHandler.php @@ -36,7 +36,7 @@ class StageAhoyHandler */ public function run(): void { - for ($i = 0; $i < 15; $i++) { + for ($i = 0; $i < 5; $i++) { Log::debug(sprintf('Am now in stage AHOY hander, sleeping... (%d)', $i)); sleep(1); } diff --git a/app/Support/Import/Routine/Fake/StageNewHandler.php b/app/Support/Import/Routine/Fake/StageNewHandler.php index d12ef19f4f..3d31a008f5 100644 --- a/app/Support/Import/Routine/Fake/StageNewHandler.php +++ b/app/Support/Import/Routine/Fake/StageNewHandler.php @@ -36,7 +36,7 @@ class StageNewHandler */ public function run(): void { - for ($i = 0; $i < 15; $i++) { + for ($i = 0; $i < 5; $i++) { Log::debug(sprintf('Am now in stage new hander, sleeping... (%d)', $i)); sleep(1); } diff --git a/config/import.php b/config/import.php index 7ad5464dd5..3cab291af3 100644 --- a/config/import.php +++ b/config/import.php @@ -39,7 +39,7 @@ use FireflyIII\Import\Routine\SpectreRoutine; return [ 'enabled' => [ - 'fake' => true, + 'fake' => false, 'file' => true, 'bunq' => true, 'spectre' => true, diff --git a/database/migrations/2018_04_29_174524_changes_for_v474.php b/database/migrations/2018_04_29_174524_changes_for_v474.php index 4b527e5ea7..d47561bcc9 100644 --- a/database/migrations/2018_04_29_174524_changes_for_v474.php +++ b/database/migrations/2018_04_29_174524_changes_for_v474.php @@ -30,8 +30,8 @@ class ChangesForV474 extends Migration function (Blueprint $table) { $table->string('provider', 50)->after('file_type')->default(''); $table->string('stage', 50)->after('status')->default(''); - $table->longText('transactions')->after('extended_status'); - $table->longText('errors')->after('transactions'); + $table->longText('transactions')->after('extended_status')->nullable(); + $table->longText('errors')->after('transactions')->nullable(); $table->integer('tag_id', false, true)->nullable()->after('user_id'); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('set null'); diff --git a/public/js/ff/import/status_v2.js b/public/js/ff/import/status_v2.js index 512aaf5ccb..569944abf5 100644 --- a/public/js/ff/import/status_v2.js +++ b/public/js/ff/import/status_v2.js @@ -21,8 +21,8 @@ /** global: jobStatusUri */ var timeOutId; -var hasStartedJob = false; -var jobStorageStarted = false; +var jobRunRoutineStarted = false; +var jobStorageRoutineStarted = false; var checkInitialInterval = 1000; var checkNextInterval = 500; var maxLoops = 60; @@ -53,18 +53,12 @@ function reportJobJSONDone(data) { switch (data.status) { case "ready_to_run": if (startCount > 0) { - hasStartedJob = false; + jobRunRoutineStarted = false; } startCount++; sendJobPOSTStart(); recheckJobJSONStatus(); break; - case "running": - case "storing_data": - showProgressBox(data.status); - recheckJobJSONStatus(); - break; - case "need_job_config": // redirect user to configuration for this job. window.location.replace(jobConfigurationUri); @@ -74,11 +68,14 @@ function reportJobJSONDone(data) { sendJobPOSTStore(); recheckJobJSONStatus(); break; + case "storage_finished": case "finished": showJobResults(data); break; default: - console.error('Cannot handle status ' + data.status); + console.warn('No specific action for status ' + data.status); + showProgressBox(data.status); + recheckJobJSONStatus(); } } @@ -129,12 +126,12 @@ function recheckJobJSONStatus() { */ function sendJobPOSTStart() { console.log('In sendJobPOSTStart()'); - if (hasStartedJob) { + if (jobRunRoutineStarted) { console.log('Import job already started!'); return; } console.log('Job was started'); - hasStartedJob = true; + jobRunRoutineStarted = true; $.post(jobStartUri, {_token: token}).fail(reportJobPOSTFailure).done(reportJobPOSTDone) } @@ -143,12 +140,12 @@ function sendJobPOSTStart() { */ function sendJobPOSTStore() { console.log('In sendJobPOSTStore()'); - if (jobStorageStarted) { + if (jobStorageRoutineStarted) { console.log('Store job already started!'); return; } console.log('Storage job has started!'); - jobStorageStarted = true; + jobStorageRoutineStarted = true; $.post(jobStorageStartUri, {_token: token}).fail(reportJobPOSTFailure).done(reportJobPOSTDone) } @@ -185,14 +182,26 @@ function showProgressBox(status) { // hide initial status box: $('.status_initial').hide(); - if (status === 'running' || status === 'ready_to_run') { - $('#import-status-txt').text(langImportRunning); - } else { - $('#import-status-txt').text(langImportStoring); - } // show running box: $('.status_running').show(); + + if (status === 'running' || status === 'ready_to_run') { + $('#import-status-txt').text(langImportRunning); + return; + } + if (status === 'storing_data' || status === 'storage_finished' || status === 'stored_data') { + $('#import-status-txt').text(langImportStoring); + return; + } + if (status === 'applying_rules' || status === 'linking_to_tag' || status === 'linked_to_tag' || status === 'rules_applied') { + $('#import-status-txt').text(langImportRules); + return; + } + + $('#import-status-txt').text('Job status: ' + status); + + } /** diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 6eb7ad6bdb..66b29a4541 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -79,7 +79,7 @@ return [ 'job_config_fake_song_title' => 'Enter song name', 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', 'job_config_fake_album_title' => 'Enter album name', - 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. In this case, enter "station to station" to continue.', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', // import status page: 'import_with_key' => 'Import with key \':key\'', @@ -88,6 +88,7 @@ return [ 'status_running_title' => 'The import is running', 'status_job_running' => 'Please wait, running the import...', 'status_job_storing' => 'Please wait, storing data...', + 'status_job_rules' => 'Please wait, running rules...', 'status_fatal_title' => 'Fatal error', 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', diff --git a/resources/views/import/status.twig b/resources/views/import/status.twig index 6272a60ecd..4cc0744180 100644 --- a/resources/views/import/status.twig +++ b/resources/views/import/status.twig @@ -178,6 +178,7 @@ // import is running: var langImportRunning = '{{ trans('import.status_job_running') }}'; var langImportStoring = '{{ trans('import.status_job_storing') }}'; + var langImportRules = '{{ trans('import.status_job_rules') }}'; // some useful translations. {#var langImportTimeOutError = '(time out thing)';#} diff --git a/tests/Feature/Controllers/ExportControllerTest.php b/tests/Feature/Controllers/ExportControllerTest.php index 8d44b7f70c..43fd833e5b 100644 --- a/tests/Feature/Controllers/ExportControllerTest.php +++ b/tests/Feature/Controllers/ExportControllerTest.php @@ -116,7 +116,7 @@ class ExportControllerTest extends TestCase $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); $repository->shouldReceive('create')->andReturn($job); $repository->shouldReceive('cleanup'); - $accountRepos->shouldReceive('getAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->andReturn(new Collection); + $accountRepos->shouldReceive('getAccountsByType')->withArgs([[AccountType::ASSET, AccountType::DEFAULT]])->andReturn(new Collection); $this->be($this->user()); $response = $this->get(route('export.index')); diff --git a/tests/Feature/Controllers/Import/ConfigurationControllerTest.php b/tests/Feature/Controllers/Import/ConfigurationControllerTest.php deleted file mode 100644 index a8f7a9b78a..0000000000 --- a/tests/Feature/Controllers/Import/ConfigurationControllerTest.php +++ /dev/null @@ -1,131 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace Tests\Feature\Controllers\Import; - -use FireflyIII\Import\Configuration\FileConfigurator; -use FireflyIII\Models\ImportJob; -use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use Log; -use Tests\TestCase; - -/** - * Class AccountControllerTest - * - * @SuppressWarnings(PHPMD.TooManyPublicMethods) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class ConfigurationControllerTest extends TestCase -{ - /** - * - */ - public function setUp() - { - parent::setUp(); - Log::debug(sprintf('Now in %s.', \get_class($this))); - } - - /** - * @covers \FireflyIII\Http\Controllers\Import\ConfigurationController::__construct - * @covers \FireflyIII\Http\Controllers\Import\ConfigurationController::index - * @covers \FireflyIII\Http\Controllers\Import\ConfigurationController::makeConfigurator - */ - public function testIndex() - { - /** @var ImportJob $job */ - $job = $this->user()->importJobs()->where('key', 'configuring')->first(); - $configurator = $this->mock(FileConfigurator::class); - $repository = $this->mock(ImportJobRepositoryInterface::class); - $configurator->shouldReceive('setJob')->once(); - $configurator->shouldReceive('isJobConfigured')->once()->andReturn(false); - $configurator->shouldReceive('getNextView')->once()->andReturn('error'); // does not matter which view is returned. - $configurator->shouldReceive('getNextData')->once()->andReturn([]); - $repository->shouldReceive('updateStatus')->once(); - - $this->be($this->user()); - $response = $this->get(route('import.configure', [$job->key])); - $response->assertStatus(200); - } - - /** - * @covers \FireflyIII\Http\Controllers\Import\ConfigurationController::__construct - * @covers \FireflyIII\Http\Controllers\Import\ConfigurationController::index - * @covers \FireflyIII\Http\Controllers\Import\ConfigurationController::makeConfigurator - */ - public function testIndexConfigured() - { - /** @var ImportJob $job */ - $job = $this->user()->importJobs()->where('key', 'configured')->first(); - $configurator = $this->mock(FileConfigurator::class); - $repository = $this->mock(ImportJobRepositoryInterface::class); - $configurator->shouldReceive('setJob')->once(); - $configurator->shouldReceive('isJobConfigured')->once()->andReturn(true); - $repository->shouldReceive('updateStatus')->once(); - - $this->be($this->user()); - $response = $this->get(route('import.configure', [$job->key])); - $response->assertStatus(302); - $response->assertRedirect(route('import.status', [$job->key])); - } - - /** - * @covers \FireflyIII\Http\Controllers\Import\ConfigurationController::post - */ - public function testPost() - { - /** @var ImportJob $job */ - $job = $this->user()->importJobs()->where('key', 'configuring')->first(); - $data = ['some' => 'config']; - $configurator = $this->mock(FileConfigurator::class); - $repository = $this->mock(ImportJobRepositoryInterface::class); - $configurator->shouldReceive('setJob')->once(); - $configurator->shouldReceive('isJobConfigured')->once()->andReturn(false); - $configurator->shouldReceive('configureJob')->once()->withArgs([$data]); - $configurator->shouldReceive('getWarningMessage')->once()->andReturn('Some warning'); - - $this->be($this->user()); - $response = $this->post(route('import.configure.post', [$job->key]), $data); - $response->assertStatus(302); - $response->assertRedirect(route('import.configure', [$job->key])); - } - - /** - * @covers \FireflyIII\Http\Controllers\Import\ConfigurationController::post - */ - public function testPostConfigured() - { - /** @var ImportJob $job */ - $job = $this->user()->importJobs()->where('key', 'configuring')->first(); - $data = ['some' => 'config']; - $configurator = $this->mock(FileConfigurator::class); - $repository = $this->mock(ImportJobRepositoryInterface::class); - $configurator->shouldReceive('setJob')->once(); - $configurator->shouldReceive('isJobConfigured')->once()->andReturn(true); - - $this->be($this->user()); - $response = $this->post(route('import.configure.post', [$job->key]), $data); - $response->assertStatus(302); - $response->assertRedirect(route('import.status', [$job->key])); - } -} diff --git a/tests/Feature/Controllers/Import/IndexControllerTest.php b/tests/Feature/Controllers/Import/IndexControllerTest.php index 9b24a5d9b3..0632960131 100644 --- a/tests/Feature/Controllers/Import/IndexControllerTest.php +++ b/tests/Feature/Controllers/Import/IndexControllerTest.php @@ -22,13 +22,18 @@ declare(strict_types=1); namespace Tests\Feature\Controllers\Import; -use FireflyIII\Import\Routine\FileRoutine; +use FireflyIII\Import\Prerequisites\BunqPrerequisites; +use FireflyIII\Import\Prerequisites\FakePrerequisites; +use FireflyIII\Import\Prerequisites\FilePrerequisites; +use FireflyIII\Import\Prerequisites\SpectrePrerequisites; +use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use Log; +use Mockery; use Tests\TestCase; /** - * Class AccountControllerTest + * Class IndexControllerTest * * @SuppressWarnings(PHPMD.TooManyPublicMethods) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -46,73 +51,85 @@ class IndexControllerTest extends TestCase } /** - * @covers \FireflyIII\Http\Controllers\Import\IndexController::create + * @covers \FireflyIII\Http\Controllers\Import\IndexController */ - public function testCreate() + public function testCreateFake() { - $job = $this->user()->importJobs()->where('key', 'new')->first(); - $repository = $this->mock(ImportJobRepositoryInterface::class); - $repository->shouldReceive('create')->withArgs(['file'])->andReturn($job); + // mock stuff: + $repository = $this->mock(ImportJobRepositoryInterface::class); + $fakePrerequisites = $this->mock(FakePrerequisites::class); + + // fake job: + $importJob = new ImportJob; + $importJob->provider = 'fake'; + $importJob->key = 'fake_job_1'; + + // mock call: + $repository->shouldReceive('create')->withArgs(['fake'])->andReturn($importJob); + $fakePrerequisites->shouldReceive('isComplete')->once()->andReturn(false); + $fakePrerequisites->shouldReceive('setUser')->once(); + + $this->be($this->user()); - $response = $this->get(route('import.create-job', ['file'])); + $response = $this->get(route('import.create', ['fake'])); $response->assertStatus(302); - $response->assertRedirect(route('import.configure', ['new'])); - + // expect a redirect to prerequisites + $response->assertRedirect(route('import.prerequisites.index', ['fake', 'fake_job_1'])); } + /** - * @covers \FireflyIII\Http\Controllers\Import\IndexController::download + * @covers \FireflyIII\Http\Controllers\Import\IndexController */ - public function testDownload() + public function testCreateFakeNoPrereq() { - $repository = $this->mock(ImportJobRepositoryInterface::class); - //$job = $this->user()->importJobs()->where('key', 'testImport')->first(); + // mock stuff: + $repository = $this->mock(ImportJobRepositoryInterface::class); + $fakePrerequisites = $this->mock(FakePrerequisites::class); + + // fake job: + $importJob = new ImportJob; + $importJob->provider = 'fake'; + $importJob->key = 'fake_job_2'; + + // mock call: + $repository->shouldReceive('create')->withArgs(['fake'])->andReturn($importJob); + $fakePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $fakePrerequisites->shouldReceive('setUser')->once(); + $repository->shouldReceive('setStatus')->withArgs([Mockery::any(), 'has_prereq'])->andReturn($importJob)->once(); + + $this->be($this->user()); - $response = $this->get(route('import.download', ['testImport'])); - $response->assertStatus(200); + $response = $this->get(route('import.create', ['fake'])); + $response->assertStatus(302); + // expect a redirect to prerequisites + $response->assertRedirect(route('import.job.configuration.index', ['fake_job_2'])); } - /** - * @covers \FireflyIII\Http\Controllers\Import\IndexController::__construct - * @covers \FireflyIII\Http\Controllers\Import\IndexController::index - */ public function testIndex() { - $repository = $this->mock(ImportJobRepositoryInterface::class); $this->be($this->user()); + + // fake prerequisites providers: + $fake = $this->mock(FakePrerequisites::class); + $file = $this->mock(FilePrerequisites::class); + $bunq = $this->mock(BunqPrerequisites::class); + $spectre = $this->mock(SpectrePrerequisites::class); + + // call methods: + $fake->shouldReceive('setUser')->once(); + $file->shouldReceive('setUser')->once(); + $bunq->shouldReceive('setUser')->once(); + $spectre->shouldReceive('setUser')->once(); + + $fake->shouldReceive('isComplete')->once()->andReturn(true); + $file->shouldReceive('isComplete')->once()->andReturn(true); + $bunq->shouldReceive('isComplete')->once()->andReturn(true); + $spectre->shouldReceive('isComplete')->once()->andReturn(true); + + $response = $this->get(route('import.index')); $response->assertStatus(200); - - } - - /** - * @covers \FireflyIII\Http\Controllers\Import\IndexController::start - */ - public function testStart() - { - $repository = $this->mock(ImportJobRepositoryInterface::class); - $routine = $this->mock(FileRoutine::class); - $routine->shouldReceive('setJob')->once(); - $routine->shouldReceive('run')->once()->andReturn(true); - - $this->be($this->user()); - $response = $this->post(route('import.start', ['configured'])); - $response->assertStatus(200); - } - - /** - * @covers \FireflyIII\Http\Controllers\Import\IndexController::start - * @expectedExceptionMessage Job did not complete successfully. - */ - public function testStartFailed() - { - $repository = $this->mock(ImportJobRepositoryInterface::class); - $routine = $this->mock(FileRoutine::class); - $routine->shouldReceive('setJob')->once(); - $routine->shouldReceive('run')->once()->andReturn(false); - - $this->be($this->user()); - $response = $this->post(route('import.start', ['configured'])); - $response->assertStatus(500); + $response->assertSee('
    -
    + diff --git a/resources/views/import/file/roles.twig b/resources/views/import/file/roles.twig index 8730ccaf0f..a983dc1b35 100644 --- a/resources/views/import/file/roles.twig +++ b/resources/views/import/file/roles.twig @@ -1,7 +1,7 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.render(Route.getCurrentRoute.getName, job) }} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, importJob) }} {% endblock %} {% block content %} @@ -10,36 +10,34 @@
    -

    {{ trans('import.csv_roles_title') }}

    +

    {{ trans('import.job_config_roles_title') }}

    - {{ trans('import.csv_roles_text') }} + {{ trans('import.job_config_roles_text') }}

    - + -
    -

    {{ trans('import.csv_roles_table') }}

    +

    {{ trans('import.job_config_input') }}

    - - - - - + + + + {% for i in 0..(data.total -1) %} @@ -47,14 +45,14 @@ @@ -80,7 +78,6 @@
    {{ trans('import.csv_roles_column_name') }}{{ trans('import.csv_roles_column_example') }}{{ trans('import.csv_roles_column_role') }}{{ trans('import.csv_roles_do_map_value') }}{{ trans('import.job_config_roles_column_name') }}{{ trans('import.job_config_roles_column_example') }}{{ trans('import.job_config_roles_column_role') }}{{ trans('import.job_config_roles_do_map_value') }}
    {% if data.headers[i] == '' %} - {{ trans('import.csv_roles_column') }} #{{ loop.index }} + {{ trans('import.job_config_roles_colum_count') }} #{{ loop.index }} {% else %} {{ data.headers[i] }} {% endif %} {% if data.examples[i]|length == 0 %} - {{ trans('import.csv_roles_no_example_data') }} + {{ trans('import.job_config_roles_no_example') }} {% else %} {% for example in data.examples[i] %} {{ example }}
    @@ -64,12 +62,12 @@
    {{ Form.select(('role['~loop.index0~']'), data.roles, - job.configuration['column-roles'][loop.index0], + importJob.configuration['column-roles'][loop.index0], {class: 'form-control'}) }} {{ Form.checkbox(('map['~loop.index0~']'),1, - job.configuration['column-do-mapping'][loop.index0] + importJob.configuration['column-do-mapping'][loop.index0] ) }}
    -
    @@ -91,7 +88,7 @@
    From a4524b3c2cdf8369bbb38cf970d2c7500aebdcf9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 6 May 2018 20:42:07 +0200 Subject: [PATCH 048/182] Throw exception with empty amount. --- app/Factory/TransactionFactory.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index 468ff3f17e..ae8ee25b76 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Factory; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; @@ -45,10 +46,14 @@ class TransactionFactory * @param array $data * * @return Transaction + * @throws FireflyException */ - public function create(array $data): Transaction + public function create(array $data): ?Transaction { $currencyId = isset($data['currency']) ? $data['currency']->id : $data['currency_id']; + if ('' === $data['amount']) { + throw new FireflyException('Amount is an empty string, which Firefly III cannot handle. Apologies.'); + } return Transaction::create( [ @@ -72,6 +77,7 @@ class TransactionFactory * @param array $data * * @return Collection + * @throws FireflyException */ public function createPair(TransactionJournal $journal, array $data): Collection { From 1209f3b39afe5d45f822f2f8a011756eab4432ee Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 6 May 2018 20:42:30 +0200 Subject: [PATCH 049/182] First start for CSV file import. --- .../Import/JobStatusController.php | 8 +- app/Import/Mapper/Bills.php | 2 +- app/Import/Routine/FakeRoutine.php | 42 +- app/Import/Routine/FileRoutine.php | 611 ++++++++++-------- app/Import/Routine/RoutineInterface.php | 4 +- .../File/ConfigureMappingHandler.php | 179 +++-- .../Configuration/File/NewFileJobHandler.php | 70 +- .../Import/Routine/File/CSVProcessor.php | 62 ++ .../Routine/File/FileProcessorInterface.php | 49 ++ config/import.php | 8 +- public/js/ff/import/status_v2.js | 21 + resources/lang/en_US/import.php | 8 + resources/views/import/file/map.twig | 19 +- 13 files changed, 663 insertions(+), 420 deletions(-) create mode 100644 app/Support/Import/Routine/File/CSVProcessor.php create mode 100644 app/Support/Import/Routine/File/FileProcessorInterface.php diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index fd5e8127b8..f45a405b6a 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -116,13 +116,17 @@ class JobStatusController extends Controller public function start(ImportJob $importJob): JsonResponse { // catch impossible status: - $allowed = ['ready_to_run', 'need_job_config']; + $allowed = ['ready_to_run', 'need_job_config','error','running']; + // todo remove error and running. + if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { Log::error('Job is not ready.'); + // kill the job: + $this->repository->setStatus($importJob, 'error'); + return response()->json(['status' => 'NOK', 'message' => 'JobStatusController::start expects status "ready_to_run".']); } - $importProvider = $importJob->provider; $key = sprintf('import.routine.%s', $importProvider); $className = config($key); diff --git a/app/Import/Mapper/Bills.php b/app/Import/Mapper/Bills.php index 6457affe4f..8caa0f76a3 100644 --- a/app/Import/Mapper/Bills.php +++ b/app/Import/Mapper/Bills.php @@ -43,7 +43,7 @@ class Bills implements MapperInterface /** @var Bill $bill */ foreach ($result as $bill) { $billId = (int)$bill->id; - $list[$billId] = $bill->name . ' [' . $bill->match . ']'; + $list[$billId] = $bill->name; } asort($list); $list = [0 => trans('import.map_do_not_map')] + $list; diff --git a/app/Import/Routine/FakeRoutine.php b/app/Import/Routine/FakeRoutine.php index 5d392dcd8a..8a42c1e8e1 100644 --- a/app/Import/Routine/FakeRoutine.php +++ b/app/Import/Routine/FakeRoutine.php @@ -37,19 +37,10 @@ use Log; class FakeRoutine implements RoutineInterface { /** @var ImportJob */ - private $job; + private $importJob; /** @var ImportJobRepositoryInterface */ private $repository; - /** - * FakeRoutine constructor. - */ - public function __construct() - { - $this->repository = app(ImportJobRepositoryInterface::class); - } - - /** * Fake import routine has three stages: * @@ -63,49 +54,50 @@ class FakeRoutine implements RoutineInterface */ public function run(): void { - Log::debug(sprintf('Now in run() for fake routine with status: %s', $this->job->status)); - if ($this->job->status !== 'running') { + Log::debug(sprintf('Now in run() for fake routine with status: %s', $this->importJob->status)); + if ($this->importJob->status !== 'running') { throw new FireflyException('This fake job should not be started.'); // @codeCoverageIgnore } - switch ($this->job->stage) { + switch ($this->importJob->stage) { default: - throw new FireflyException(sprintf('Fake routine cannot handle stage "%s".', $this->job->stage)); // @codeCoverageIgnore + throw new FireflyException(sprintf('Fake routine cannot handle stage "%s".', $this->importJob->stage)); // @codeCoverageIgnore case 'new': /** @var StageNewHandler $handler */ $handler = app(StageNewHandler::class); $handler->run(); - $this->repository->setStage($this->job, 'ahoy'); + $this->repository->setStage($this->importJob, 'ahoy'); // set job finished this step: - $this->repository->setStatus($this->job, 'ready_to_run'); + $this->repository->setStatus($this->importJob, 'ready_to_run'); return; case 'ahoy': /** @var StageAhoyHandler $handler */ $handler = app(StageAhoyHandler::class); $handler->run(); - $this->repository->setStatus($this->job, 'need_job_config'); - $this->repository->setStage($this->job, 'final'); + $this->repository->setStatus($this->importJob, 'need_job_config'); + $this->repository->setStage($this->importJob, 'final'); break; case 'final': /** @var StageFinalHandler $handler */ $handler = app(StageFinalHandler::class); - $handler->setJob($this->job); + $handler->setJob($this->importJob); $transactions = $handler->getTransactions(); - $this->repository->setStatus($this->job, 'provider_finished'); - $this->repository->setStage($this->job, 'final'); - $this->repository->setTransactions($this->job, $transactions); + $this->repository->setStatus($this->importJob, 'provider_finished'); + $this->repository->setStage($this->importJob, 'final'); + $this->repository->setTransactions($this->importJob, $transactions); } } /** * @param ImportJob $job * - * @return mixed + * @return */ - public function setJob(ImportJob $job) + public function setJob(ImportJob $job): void { - $this->job = $job; + $this->importJob = $job; + $this->repository = app(ImportJobRepositoryInterface::class); $this->repository->setUser($job->user); } } diff --git a/app/Import/Routine/FileRoutine.php b/app/Import/Routine/FileRoutine.php index a6ce949dea..8e10511c3a 100644 --- a/app/Import/Routine/FileRoutine.php +++ b/app/Import/Routine/FileRoutine.php @@ -22,292 +22,23 @@ declare(strict_types=1); namespace FireflyIII\Import\Routine; -use Carbon\Carbon; -use DB; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Import\FileProcessor\FileProcessorInterface; -use FireflyIII\Import\Storage\ImportStorage; use FireflyIII\Models\ImportJob; -use FireflyIII\Models\Tag; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Repositories\Tag\TagRepositoryInterface; -use Illuminate\Support\Collection; +use FireflyIII\Support\Import\Routine\File\FileProcessorInterface; +use FireflyIII\Support\Import\Routine\File\FileRoutineInterface; use Log; /** - * @deprecated - * @codeCoverageIgnore * Class FileRoutine */ class FileRoutine implements RoutineInterface { -// /** @var Collection */ -// public $errors; -// /** @var Collection */ -// public $journals; -// /** @var int */ -// public $lines = 0; -// /** @var ImportJob */ -// private $job; -// -// /** @var ImportJobRepositoryInterface */ -// private $repository; -// -// /** -// * ImportRoutine constructor. -// */ -// public function __construct() -// { -// $this->journals = new Collection; -// $this->errors = new Collection; -// } -// -// /** -// * @return Collection -// */ -// public function getErrors(): Collection -// { -// return $this->errors; -// } -// -// /** -// * @return Collection -// */ -// public function getJournals(): Collection -// { -// return $this->journals; -// } -// -// /** -// * @return int -// */ -// public function getLines(): int -// { -// return $this->lines; -// } -// -// /** -// * -// */ -// public function run(): bool -// { -// if ('configured' !== $this->getStatus()) { -// Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->getStatus())); -// -// return false; -// } -// set_time_limit(0); -// Log::info(sprintf('Start with import job %s', $this->job->key)); -// -// // total steps: 6 -// $this->setTotalSteps(6); -// -// $importObjects = $this->getImportObjects(); -// $this->lines = $importObjects->count(); -// $this->addStep(); -// -// // total steps can now be extended. File has been scanned. 7 steps per line: -// $this->addTotalSteps(7 * $this->lines); -// -// // once done, use storage thing to actually store them: -// Log::info(sprintf('Returned %d valid objects from file processor', $this->lines)); -// -// $storage = $this->storeObjects($importObjects); -// $this->addStep(); -// Log::debug('Back in run()'); -// -// Log::debug('Updated job...'); -// Log::debug(sprintf('%d journals in $storage->journals', $storage->journals->count())); -// $this->journals = $storage->journals; -// $this->errors = $storage->errors; -// -// Log::debug('Going to call createImportTag()'); -// -// // create tag, link tag to all journals: -// $this->createImportTag(); -// $this->addStep(); -// -// // update job: -// $this->setStatus('finished'); -// -// Log::info(sprintf('Done with import job %s', $this->job->key)); -// -// return true; -// } -// -// /** -// * @param ImportJob $job -// */ -// public function setJob(ImportJob $job) -// { -// $this->job = $job; -// $this->repository = app(ImportJobRepositoryInterface::class); -// $this->repository->setUser($job->user); -// } -// -// /** -// * @return Collection -// */ -// protected function getImportObjects(): Collection -// { -// $objects = new Collection; -// $fileType = $this->getConfig()['file-type'] ?? 'csv'; -// // will only respond to "file" -// $class = config(sprintf('import.options.file.processors.%s', $fileType)); -// /** @var FileProcessorInterface $processor */ -// $processor = app($class); -// $processor->setJob($this->job); -// -// if ('configured' === $this->getStatus()) { -// // set job as "running"... -// $this->setStatus('running'); -// -// Log::debug('Job is configured, start with run()'); -// $processor->run(); -// $objects = $processor->getObjects(); -// } -// -// return $objects; -// } -// -// /** -// * Shorthand method. -// */ -// private function addStep() -// { -// $this->repository->addStepsDone($this->job, 1); -// } -// -// /** -// * Shorthand -// * -// * @param int $steps -// */ -// private function addTotalSteps(int $steps) -// { -// $this->repository->addTotalSteps($this->job, $steps); -// } -// -// /** -// * -// */ -// private function createImportTag(): Tag -// { -// Log::debug('Now in createImportTag()'); -// -// if ($this->journals->count() < 1) { -// Log::info(sprintf('Will not create tag, %d journals imported.', $this->journals->count())); -// -// return new Tag; -// } -// $this->addTotalSteps($this->journals->count() + 2); -// -// /** @var TagRepositoryInterface $repository */ -// $repository = app(TagRepositoryInterface::class); -// $repository->setUser($this->job->user); -// $data = [ -// 'tag' => trans('import.import_with_key', ['key' => $this->job->key]), -// 'date' => new Carbon, -// 'description' => null, -// 'latitude' => null, -// 'longitude' => null, -// 'zoomLevel' => null, -// 'tagMode' => 'nothing', -// ]; -// $tag = $repository->store($data); -// $this->addStep(); -// $extended = $this->getExtendedStatus(); -// $extended['tag'] = $tag->id; -// $this->setExtendedStatus($extended); -// -// Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); -// Log::debug('Looping journals...'); -// $journalIds = $this->journals->pluck('id')->toArray(); -// $tagId = $tag->id; -// foreach ($journalIds as $journalId) { -// Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); -// DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); -// $this->addStep(); -// } -// Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $this->journals->count(), $tag->id, $tag->tag)); -// $this->addStep(); -// -// return $tag; -// } -// -// /** -// * Shorthand method -// * -// * @return array -// */ -// private function getConfig(): array -// { -// return $this->repository->getConfiguration($this->job); -// } -// -// /** -// * @return array -// */ -// private function getExtendedStatus(): array -// { -// return $this->repository->getExtendedStatus($this->job); -// } -// -// /** -// * Shorthand method. -// * -// * @return string -// */ -// private function getStatus(): string -// { -// return $this->repository->getStatus($this->job); -// } -// -// /** -// * @param array $extended -// */ -// private function setExtendedStatus(array $extended): void -// { -// $this->repository->setExtendedStatus($this->job, $extended); -// } -// -// /** -// * Shorthand -// * -// * @param string $status -// */ -// private function setStatus(string $status): void -// { -// $this->repository->setStatus($this->job, $status); -// } -// -// /** -// * Shorthand -// * -// * @param int $steps -// */ -// private function setTotalSteps(int $steps) -// { -// $this->repository->setTotalSteps($this->job, $steps); -// } -// -// /** -// * @param Collection $objects -// * -// * @return ImportStorage -// */ -// private function storeObjects(Collection $objects): ImportStorage -// { -// $config = $this->getConfig(); -// $storage = new ImportStorage; -// $storage->setJob($this->job); -// $storage->setDateFormat($config['date-format']); -// $storage->setObjects($objects); -// $storage->store(); -// Log::info('Back in storeObjects()'); -// -// return $storage; -// } + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + /** * At the end of each run(), the import routine must set the job to the expected status. * @@ -318,18 +49,334 @@ class FileRoutine implements RoutineInterface */ public function run(): void { - // TODO: Implement run() method. - throw new NotImplementedException; + Log::debug(sprintf('Now in run() for file routine with status: %s', $this->importJob->status)); + if ($this->importJob->status !== 'running') { + throw new FireflyException('This file import job should not be started.'); // @codeCoverageIgnore + } + + switch ($this->importJob->stage) { + case 'ready_to_run': + // get processor, depending on file type + // is just CSV for now. + + $processor = $this->getProcessor(); + $transactions = $processor->run(); + + + // make processor run. + // then done! + // move to status 'processor_finished'. + // $this->repository->setStatus($this->importJob, 'provider_finished'); + // $this->repository->setStage($this->importJob, 'final'); + break; + default: + throw new FireflyException(sprintf('Import routine cannot handle stage "%s"', $this->importJob->stage)); + } + + exit; } /** * @param ImportJob $job * - * @return mixed + * @return void */ - public function setJob(ImportJob $job) + public function setJob(ImportJob $job): void { - // TODO: Implement setJob() method. - throw new NotImplementedException; + $this->importJob = $job; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($job->user); } + + /** + * Return the appropriate file routine handler for + * the file type of the job. + * + * @return FileProcessorInterface + */ + private function getProcessor(): FileProcessorInterface + { + $config = $this->repository->getConfiguration($this->importJob); + $type = $config['file-type'] ?? 'csv'; + $class = config(sprintf('import.options.file.processors.%s', $type)); + /** @var FileProcessorInterface $object */ + $object = app($class); + + return $object; + } + + + + + + + + + // /** @var Collection */ + // public $errors; + // /** @var Collection */ + // public $journals; + // /** @var int */ + // public $lines = 0; + // /** @var ImportJob */ + // private $job; + // + // /** @var ImportJobRepositoryInterface */ + // private $repository; + // + // /** + // * ImportRoutine constructor. + // */ + // public function __construct() + // { + // $this->journals = new Collection; + // $this->errors = new Collection; + // } + // + // /** + // * @return Collection + // */ + // public function getErrors(): Collection + // { + // return $this->errors; + // } + // + // /** + // * @return Collection + // */ + // public function getJournals(): Collection + // { + // return $this->journals; + // } + // + // /** + // * @return int + // */ + // public function getLines(): int + // { + // return $this->lines; + // } + // + // /** + // * + // */ + // public function run(): bool + // { + // if ('configured' !== $this->getStatus()) { + // Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->getStatus())); + // + // return false; + // } + // set_time_limit(0); + // Log::info(sprintf('Start with import job %s', $this->job->key)); + // + // // total steps: 6 + // $this->setTotalSteps(6); + // + // $importObjects = $this->getImportObjects(); + // $this->lines = $importObjects->count(); + // $this->addStep(); + // + // // total steps can now be extended. File has been scanned. 7 steps per line: + // $this->addTotalSteps(7 * $this->lines); + // + // // once done, use storage thing to actually store them: + // Log::info(sprintf('Returned %d valid objects from file processor', $this->lines)); + // + // $storage = $this->storeObjects($importObjects); + // $this->addStep(); + // Log::debug('Back in run()'); + // + // Log::debug('Updated job...'); + // Log::debug(sprintf('%d journals in $storage->journals', $storage->journals->count())); + // $this->journals = $storage->journals; + // $this->errors = $storage->errors; + // + // Log::debug('Going to call createImportTag()'); + // + // // create tag, link tag to all journals: + // $this->createImportTag(); + // $this->addStep(); + // + // // update job: + // $this->setStatus('finished'); + // + // Log::info(sprintf('Done with import job %s', $this->job->key)); + // + // return true; + // } + // + // /** + // * @param ImportJob $job + // */ + // public function setJob(ImportJob $job) + // { + // $this->job = $job; + // $this->repository = app(ImportJobRepositoryInterface::class); + // $this->repository->setUser($job->user); + // } + // + // /** + // * @return Collection + // */ + // protected function getImportObjects(): Collection + // { + // $objects = new Collection; + // $fileType = $this->getConfig()['file-type'] ?? 'csv'; + // // will only respond to "file" + // $class = config(sprintf('import.options.file.processors.%s', $fileType)); + // /** @var FileProcessorInterface $processor */ + // $processor = app($class); + // $processor->setJob($this->job); + // + // if ('configured' === $this->getStatus()) { + // // set job as "running"... + // $this->setStatus('running'); + // + // Log::debug('Job is configured, start with run()'); + // $processor->run(); + // $objects = $processor->getObjects(); + // } + // + // return $objects; + // } + // + // /** + // * Shorthand method. + // */ + // private function addStep() + // { + // $this->repository->addStepsDone($this->job, 1); + // } + // + // /** + // * Shorthand + // * + // * @param int $steps + // */ + // private function addTotalSteps(int $steps) + // { + // $this->repository->addTotalSteps($this->job, $steps); + // } + // + // /** + // * + // */ + // private function createImportTag(): Tag + // { + // Log::debug('Now in createImportTag()'); + // + // if ($this->journals->count() < 1) { + // Log::info(sprintf('Will not create tag, %d journals imported.', $this->journals->count())); + // + // return new Tag; + // } + // $this->addTotalSteps($this->journals->count() + 2); + // + // /** @var TagRepositoryInterface $repository */ + // $repository = app(TagRepositoryInterface::class); + // $repository->setUser($this->job->user); + // $data = [ + // 'tag' => trans('import.import_with_key', ['key' => $this->job->key]), + // 'date' => new Carbon, + // 'description' => null, + // 'latitude' => null, + // 'longitude' => null, + // 'zoomLevel' => null, + // 'tagMode' => 'nothing', + // ]; + // $tag = $repository->store($data); + // $this->addStep(); + // $extended = $this->getExtendedStatus(); + // $extended['tag'] = $tag->id; + // $this->setExtendedStatus($extended); + // + // Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); + // Log::debug('Looping journals...'); + // $journalIds = $this->journals->pluck('id')->toArray(); + // $tagId = $tag->id; + // foreach ($journalIds as $journalId) { + // Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); + // DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); + // $this->addStep(); + // } + // Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $this->journals->count(), $tag->id, $tag->tag)); + // $this->addStep(); + // + // return $tag; + // } + // + // /** + // * Shorthand method + // * + // * @return array + // */ + // private function getConfig(): array + // { + // return $this->repository->getConfiguration($this->job); + // } + // + // /** + // * @return array + // */ + // private function getExtendedStatus(): array + // { + // return $this->repository->getExtendedStatus($this->job); + // } + // + // /** + // * Shorthand method. + // * + // * @return string + // */ + // private function getStatus(): string + // { + // return $this->repository->getStatus($this->job); + // } + // + // /** + // * @param array $extended + // */ + // private function setExtendedStatus(array $extended): void + // { + // $this->repository->setExtendedStatus($this->job, $extended); + // } + // + // /** + // * Shorthand + // * + // * @param string $status + // */ + // private function setStatus(string $status): void + // { + // $this->repository->setStatus($this->job, $status); + // } + // + // /** + // * Shorthand + // * + // * @param int $steps + // */ + // private function setTotalSteps(int $steps) + // { + // $this->repository->setTotalSteps($this->job, $steps); + // } + // + // /** + // * @param Collection $objects + // * + // * @return ImportStorage + // */ + // private function storeObjects(Collection $objects): ImportStorage + // { + // $config = $this->getConfig(); + // $storage = new ImportStorage; + // $storage->setJob($this->job); + // $storage->setDateFormat($config['date-format']); + // $storage->setObjects($objects); + // $storage->store(); + // Log::info('Back in storeObjects()'); + // + // return $storage; + // } } diff --git a/app/Import/Routine/RoutineInterface.php b/app/Import/Routine/RoutineInterface.php index 484b0f5c37..51c0b69737 100644 --- a/app/Import/Routine/RoutineInterface.php +++ b/app/Import/Routine/RoutineInterface.php @@ -43,7 +43,7 @@ interface RoutineInterface /** * @param ImportJob $job * - * @return mixed + * @return void */ - public function setJob(ImportJob $job); + public function setJob(ImportJob $job): void; } diff --git a/app/Support/Import/Configuration/File/ConfigureMappingHandler.php b/app/Support/Import/Configuration/File/ConfigureMappingHandler.php index b5f2b52599..caad8cdc1f 100644 --- a/app/Support/Import/Configuration/File/ConfigureMappingHandler.php +++ b/app/Support/Import/Configuration/File/ConfigureMappingHandler.php @@ -26,6 +26,8 @@ namespace FireflyIII\Support\Import\Configuration\File; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Import\Mapper\MapperInterface; +use FireflyIII\Import\MapperPreProcess\PreProcessorInterface; +use FireflyIII\Import\Specifics\SpecificInterface; use FireflyIII\Models\Attachment; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; @@ -33,6 +35,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; use League\Csv\Exception; use League\Csv\Reader; +use League\Csv\Statement; use Log; /** @@ -58,6 +61,22 @@ class ConfigureMappingHandler implements ConfigurationInterface */ public function configureJob(array $data): MessageBag { + $config = $this->importJob->configuration; + + if (isset($data['mapping']) && \is_array($data['mapping'])) { + foreach ($data['mapping'] as $index => $array) { + $config['column-mapping-config'][$index] = []; + foreach ($array as $value => $mapId) { + $mapId = (int)$mapId; + if (0 !== $mapId) { + $config['column-mapping-config'][$index][$value] = $mapId; + } + } + } + } + $this->repository->setConfiguration($this->importJob, $config); + $this->repository->setStage($this->importJob, 'ready_to_run'); + return new MessageBag; } @@ -79,65 +98,11 @@ class ConfigureMappingHandler implements ConfigurationInterface Log::error($e->getMessage()); throw new FireflyException('Cannot get reader: ' . $e->getMessage()); } - // - // if ($config['has-headers']) { - // $offset = 1; - // } - // $stmt = (new Statement)->offset($offset); - // $results = $stmt->process($reader); - // $this->validSpecifics = array_keys(config('csv.import_specifics')); - // $indexes = array_keys($this->data); - // $rowIndex = 0; - // foreach ($results as $rowIndex => $row) { - // $row = $this->runSpecifics($row); - // - // //do something here - // foreach ($indexes as $index) { // this is simply 1, 2, 3, etc. - // if (!isset($row[$index])) { - // // don't really know how to handle this. Just skip, for now. - // continue; - // } - // $value = trim($row[$index]); - // if (\strlen($value) > 0) { - // // we can do some preprocessing here, - // // which is exclusively to fix the tags: - // if (null !== $this->data[$index]['preProcessMap'] && \strlen($this->data[$index]['preProcessMap']) > 0) { - // /** @var PreProcessorInterface $preProcessor */ - // $preProcessor = app($this->data[$index]['preProcessMap']); - // $result = $preProcessor->run($value); - // $this->data[$index]['values'] = array_merge($this->data[$index]['values'], $result); - // - // Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]); - // Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]); - // Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $this->data[$index]['values']]); - // - // continue; - // } - // - // $this->data[$index]['values'][] = $value; - // } - // } - // } - // $setIndexes = array_keys($this->data); - // foreach ($setIndexes as $index) { - // $this->data[$index]['values'] = array_unique($this->data[$index]['values']); - // asort($this->data[$index]['values']); - // // if the count of this array is zero, there is nothing to map. - // if (\count($this->data[$index]['values']) === 0) { - // unset($this->data[$index]); - // } - // } - // unset($setIndexes); - // - // // save number of rows, thus number of steps, in job: - // $steps = $rowIndex * 5; - // $extended = $this->job->extended_status; - // $extended['steps'] = $steps; - // $this->job->extended_status = $extended; - // $this->job->save(); - // - // return $this->data; - // */ + + // get ALL values for the mappable columns from the CSV file: + $columnConfig = $this->getValuesForMapping($reader, $config, $columnConfig); + + return $columnConfig; } /** @@ -152,6 +117,36 @@ class ConfigureMappingHandler implements ConfigurationInterface $this->columnConfig = []; } + /** + * Apply the users selected specifics on the current row. + * + * @param array $config + * @param array $validSpecifics + * @param array $row + * + * @return array + */ + private function applySpecifics(array $config, array $validSpecifics, array $row): array + { + // run specifics here: + // and this is the point where the specifix go to work. + $specifics = $config['specifics'] ?? []; + $names = array_keys($specifics); + foreach ($names as $name) { + if (!\in_array($name, $validSpecifics)) { + continue; + } + $class = config(sprintf('csv.import_specifics.%s', $name)); + /** @var SpecificInterface $specific */ + $specific = app($class); + + // it returns the row, possibly modified: + $row = $specific->run($row); + } + + return $row; + } + /** * Create the "mapper" class that will eventually return the correct data for the user * to map against. For example: a list of asset accounts. A list of budgets. A list of tags. @@ -275,6 +270,72 @@ class ConfigureMappingHandler implements ConfigurationInterface return $reader; } + /** + * Read the CSV file. For each row, check for each column: + * + * - If it can be mapped. And if so, + * - Run the pre-processor + * - Add the value to the list of "values" that the user must map. + * + * @param Reader $reader + * @param array $columnConfig + * + * @return array + * @throws FireflyException + */ + private function getValuesForMapping(Reader $reader, array $config, array $columnConfig): array + { + $offset = isset($config['has-headers']) && $config['has-headers'] === true ? 1 : 0; + try { + $stmt = (new Statement)->offset($offset); + } catch (Exception $e) { + throw new FireflyException(sprintf('Could not create reader: %s', $e->getMessage())); + } + $results = $stmt->process($reader); + $validSpecifics = array_keys(config('csv.import_specifics')); + $validIndexes = array_keys($columnConfig); // the actually columns that can be mapped. + foreach ($results as $rowIndex => $row) { + $row = $this->applySpecifics($config, $validSpecifics, $row); + + //do something here + /** @var int $currentIndex */ + foreach ($validIndexes as $currentIndex) { // this is simply 1, 2, 3, etc. + if (!isset($row[$currentIndex])) { + // don't need to handle this. Continue. + continue; + } + $value = trim($row[$currentIndex]); + if (\strlen($value) === 0) { + continue; + } + // we can do some preprocessing here, + // which is exclusively to fix the tags: + if (null !== $columnConfig[$currentIndex]['preProcessMap'] && \strlen($columnConfig[$currentIndex]['preProcessMap']) > 0) { + /** @var PreProcessorInterface $preProcessor */ + $preProcessor = app($columnConfig[$currentIndex]['preProcessMap']); + $result = $preProcessor->run($value); + // can merge array, this is usually the case: + $columnConfig[$currentIndex]['values'] = array_merge($columnConfig[$currentIndex]['values'], $result); + continue; + } + $columnConfig[$currentIndex]['values'][] = $value; + } + } + + // loop array again. This time, do uniqueness. + // and remove arrays that have 0 values. + foreach ($validIndexes as $currentIndex) { + $columnConfig[$currentIndex]['values'] = array_unique($columnConfig[$currentIndex]['values']); + asort($columnConfig[$currentIndex]['values']); + // if the count of this array is zero, there is nothing to map. + if (\count($columnConfig[$currentIndex]['values']) === 0) { + unset($columnConfig[$currentIndex]); + } + } + + return $columnConfig; + } + /** * For each given column name, will return either the name (when it's a valid one) * or return the _ignore column. diff --git a/app/Support/Import/Configuration/File/NewFileJobHandler.php b/app/Support/Import/Configuration/File/NewFileJobHandler.php index c4fa0cd340..de4cb070cf 100644 --- a/app/Support/Import/Configuration/File/NewFileJobHandler.php +++ b/app/Support/Import/Configuration/File/NewFileJobHandler.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\Configuration\File; use Crypt; -use FireflyIII\Console\Commands\Import; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Attachment; use FireflyIII\Models\ImportJob; @@ -35,7 +35,6 @@ use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Support\MessageBag; use Log; use Storage; -use Exception; /** * Class NewFileJobHandler @@ -50,37 +49,6 @@ class NewFileJobHandler implements ConfigurationInterface /** @var ImportJobRepositoryInterface */ private $repository; - /** - * Get the data necessary to show the configuration screen. - * - * @return array - */ - public function getNextData(): array - { - $importFileTypes = []; - $defaultImportType = config('import.options.file.default_import_format'); - - foreach (config('import.options.file.import_formats') as $type) { - $importFileTypes[$type] = trans('import.import_file_type_' . $type); - } - - return [ - 'default_type' => $defaultImportType, - 'file_types' => $importFileTypes, - ]; - } - - /** - * @param ImportJob $job - */ - public function setJob(ImportJob $job): void - { - $this->importJob = $job; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($job->user); - - } - /** * Store data associated with current stage. * @@ -118,13 +86,47 @@ class NewFileJobHandler implements ConfigurationInterface $this->storeConfig($attachment); } } - + // set file type in config: + $config = $this->repository->getConfiguration($this->importJob); + $config['file-type'] = $data['import_file_type']; + $this->repository->setConfiguration($this->importJob, $config); $this->repository->setStage($this->importJob, 'configure-upload'); return new MessageBag(); } + /** + * Get the data necessary to show the configuration screen. + * + * @return array + */ + public function getNextData(): array + { + $importFileTypes = []; + $defaultImportType = config('import.options.file.default_import_format'); + + foreach (config('import.options.file.import_formats') as $type) { + $importFileTypes[$type] = trans('import.import_file_type_' . $type); + } + + return [ + 'default_type' => $defaultImportType, + 'file_types' => $importFileTypes, + ]; + } + + /** + * @param ImportJob $job + */ + public function setJob(ImportJob $job): void + { + $this->importJob = $job; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($job->user); + + } + /** * @param Attachment $attachment * diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php new file mode 100644 index 0000000000..650eddfbde --- /dev/null +++ b/app/Support/Import/Routine/File/CSVProcessor.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Routine\File; + +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; + + +/** + * Class CSVProcessor + * + * @package FireflyIII\Support\Import\Routine\File + */ +class CSVProcessor implements FileProcessorInterface +{ + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * Fires the file processor. + * + * @return array + */ + public function run(): array + { + die('here we are'); + } + + /** + * @param ImportJob $job + */ + public function setJob(ImportJob $job): void + { + $this->importJob = $job; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($job->user); + + } +} \ No newline at end of file diff --git a/app/Support/Import/Routine/File/FileProcessorInterface.php b/app/Support/Import/Routine/File/FileProcessorInterface.php new file mode 100644 index 0000000000..6dd51883db --- /dev/null +++ b/app/Support/Import/Routine/File/FileProcessorInterface.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Routine\File; +use FireflyIII\Models\ImportJob; + + +/** + * Interface FileProcessorInterface + * + * @package FireflyIII\Support\Import\Routine\File + */ +interface FileProcessorInterface +{ + + /** + * Fires the file processor. + * + * @return array + */ + public function run(): array; + + /** + * Set values. + * + * @param ImportJob $job + */ + public function setJob(ImportJob $job): void; +} \ No newline at end of file diff --git a/config/import.php b/config/import.php index 3cab291af3..46a0b06687 100644 --- a/config/import.php +++ b/config/import.php @@ -2,19 +2,17 @@ declare(strict_types=1); use FireflyIII\Import\Configuration\BunqConfigurator; -use FireflyIII\Import\Configuration\FileConfigurator; use FireflyIII\Import\Configuration\SpectreConfigurator; -use FireflyIII\Import\FileProcessor\CsvProcessor; use FireflyIII\Import\JobConfiguration\FakeJobConfiguration; use FireflyIII\Import\JobConfiguration\FileJobConfiguration; use FireflyIII\Import\Prerequisites\BunqPrerequisites; use FireflyIII\Import\Prerequisites\FakePrerequisites; -use FireflyIII\Import\Prerequisites\FilePrerequisites; use FireflyIII\Import\Prerequisites\SpectrePrerequisites; use FireflyIII\Import\Routine\BunqRoutine; use FireflyIII\Import\Routine\FakeRoutine; use FireflyIII\Import\Routine\FileRoutine; use FireflyIII\Import\Routine\SpectreRoutine; +use FireflyIII\Support\Import\Routine\File\CSVProcessor; /** * import.php @@ -58,7 +56,7 @@ return [ ], 'prerequisites' => [ 'fake' => FakePrerequisites::class, - 'file' => FilePrerequisites::class, + 'file' => false, 'bunq' => BunqPrerequisites::class, 'spectre' => SpectrePrerequisites::class, 'plaid' => false, @@ -98,7 +96,7 @@ return [ 'import_formats' => ['csv'], // mt940 'default_import_format' => 'csv', 'processors' => [ - 'csv' => CsvProcessor::class, + 'csv' => CSVProcessor::class, ], ], 'bunq' => [ diff --git a/public/js/ff/import/status_v2.js b/public/js/ff/import/status_v2.js index 569944abf5..de9a12702c 100644 --- a/public/js/ff/import/status_v2.js +++ b/public/js/ff/import/status_v2.js @@ -63,6 +63,9 @@ function reportJobJSONDone(data) { // redirect user to configuration for this job. window.location.replace(jobConfigurationUri); break; + case 'error': + reportJobError(); + break; case 'provider_finished': // call routine to store stuff: sendJobPOSTStore(); @@ -226,6 +229,24 @@ function reportJobPOSTFailure(xhr, status, error) { // show error box. } +/** + * Show error to user. + */ +function reportJobError() { + console.log('In reportJobError()'); + // cancel checking again for job status: + clearTimeout(timeOutId); + + // hide status boxes: + $('.statusbox').hide(); + + // show fatal error box: + $('.fatal_error').show(); + + $('.fatal_error_txt').text('Job reports error. Please start again. Apologies.'); + // show error box. +} + function reportJobPOSTDone(data) { console.log('In function reportJobPOSTDone() with status "' + data.status + '"'); if (data.status === 'NOK') { diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index f60d78ef93..afba73d3c9 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -124,6 +124,14 @@ return [ 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', 'job_config_roles_colum_count' => 'Column', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'job_config_field_value' => 'Field value', + 'job_config_field_mapped' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'job_config_map_submit' => 'Start the import', // import status page: diff --git a/resources/views/import/file/map.twig b/resources/views/import/file/map.twig index d765d54d47..3f2035d263 100644 --- a/resources/views/import/file/map.twig +++ b/resources/views/import/file/map.twig @@ -11,15 +11,15 @@
    -

    {{ trans('import.file_map_title') }}

    +

    {{ trans('import.job_config_map_title') }}

    - {{ trans('import.file_map_text') }} + {{ trans('import.job_config_map_text') }}

    {% if data|length == 0 %}

    - {{ trans('import.file_nothing_to_map') }} + {{ trans('import.job_config_map_nothing') }}

    {% endif %}
    @@ -31,8 +31,7 @@ - - {% for field in data %} + {% for index, field in data %}
    @@ -43,8 +42,8 @@ - - + + @@ -54,9 +53,9 @@ {{ option }} {% endfor %} @@ -74,7 +73,7 @@
    From 690c9203c85e68e8b7e92d8651a5b7b3ea5d8c28 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 6 May 2018 21:06:23 +0200 Subject: [PATCH 050/182] Start processing files. --- app/Import/Routine/FileRoutine.php | 3 +- .../Import/Configuration/File/Initial.php | 144 ------- app/Support/Import/Configuration/File/Map.php | 319 --------------- .../Import/Configuration/File/Roles.php | 367 ------------------ .../Configuration/File/UploadConfig.php | 190 --------- .../Import/Routine/File/CSVProcessor.php | 158 +++++++- 6 files changed, 157 insertions(+), 1024 deletions(-) delete mode 100644 app/Support/Import/Configuration/File/Initial.php delete mode 100644 app/Support/Import/Configuration/File/Map.php delete mode 100644 app/Support/Import/Configuration/File/Roles.php delete mode 100644 app/Support/Import/Configuration/File/UploadConfig.php diff --git a/app/Import/Routine/FileRoutine.php b/app/Import/Routine/FileRoutine.php index 8e10511c3a..65e77616ea 100644 --- a/app/Import/Routine/FileRoutine.php +++ b/app/Import/Routine/FileRoutine.php @@ -58,11 +58,10 @@ class FileRoutine implements RoutineInterface case 'ready_to_run': // get processor, depending on file type // is just CSV for now. - $processor = $this->getProcessor(); + $processor->setJob($this->importJob); $transactions = $processor->run(); - // make processor run. // then done! // move to status 'processor_finished'. diff --git a/app/Support/Import/Configuration/File/Initial.php b/app/Support/Import/Configuration/File/Initial.php deleted file mode 100644 index 4b4e95254b..0000000000 --- a/app/Support/Import/Configuration/File/Initial.php +++ /dev/null @@ -1,144 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Import\Configuration\File; - -use FireflyIII\Models\ImportJob; -use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use Log; - -/** - * @deprecated - * @codeCoverageIgnore - * Class Initial. - */ -class Initial -{ - /** @var ImportJob */ - private $job; - - /** @var ImportJobRepositoryInterface */ - private $repository; - - /** @var string */ - private $warning = ''; - - public function __construct() - { - Log::debug('Constructed Initial.'); - } - - /** - * Get the data necessary to show the configuration screen. - * - * @return array - */ - public function getData(): array - { - $importFileTypes = []; - $defaultImportType = config('import.options.file.default_import_format'); - - foreach (config('import.options.file.import_formats') as $type) { - $importFileTypes[$type] = trans('import.import_file_type_' . $type); - } - - return [ - 'default_type' => $defaultImportType, - 'file_types' => $importFileTypes, - ]; - } - - /** - * Return possible warning to user. - * - * @return string - */ - public function getWarningMessage(): string - { - return $this->warning; - } - - /** - * @param ImportJob $job - * - * @return ConfigurationInterface - */ - public function setJob(ImportJob $job): ConfigurationInterface - { - $this->job = $job; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($job->user); - - return $this; - } - - /** - * Store the result. - * - * @param array $data - * - * @return bool - */ - public function storeConfiguration(array $data): bool - { - Log::debug('Now in storeConfiguration for file Upload.'); - $config = $this->getConfig(); - $type = $data['import_file_type'] ?? 'csv'; // assume it's a CSV file. - $config['file-type'] = \in_array($type, config('import.options.file.import_formats'), true) ? $type : 'csv'; - - // update config: - $this->repository->setConfiguration($this->job, $config); - - // make repository process file: - $uploaded = $this->repository->processFile($this->job, $data['import_file'] ?? null); - Log::debug(sprintf('Result of upload is %s', var_export($uploaded, true))); - - // process config, if present: - if (isset($data['configuration_file'])) { - Log::debug('Will also upload configuration.'); - $this->repository->processConfiguration($this->job, $data['configuration_file']); - } - - if (false === $uploaded) { - $this->warning = (string)trans('firefly.upload_error'); - - return true; - } - - // if file was upload safely, assume we can go to the next stage: - $config = $this->getConfig(); - $config['stage'] = 'upload-config'; - $this->repository->setConfiguration($this->job, $config); - - return true; - } - - /** - * Short hand method. - * - * @return array - */ - private function getConfig(): array - { - return $this->repository->getConfiguration($this->job); - } -} diff --git a/app/Support/Import/Configuration/File/Map.php b/app/Support/Import/Configuration/File/Map.php deleted file mode 100644 index 81b07e3971..0000000000 --- a/app/Support/Import/Configuration/File/Map.php +++ /dev/null @@ -1,319 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Import\Configuration\File; - -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Import\Mapper\MapperInterface; -use FireflyIII\Import\MapperPreProcess\PreProcessorInterface; -use FireflyIII\Import\Specifics\SpecificInterface; -use FireflyIII\Models\ImportJob; -use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use League\Csv\Reader; -use League\Csv\Statement; -use Log; - -/** - * @deprecated - * @codeCoverageIgnore - * Class Mapping. - */ -class Map -{ - /** @var array that holds each column to be mapped by the user */ - private $data = []; - /** @var ImportJob */ - private $job; - /** @var ImportJobRepositoryInterface */ - private $repository; - /** @var array */ - private $validSpecifics = []; - - /** - * @return array - * - * @throws FireflyException - * @throws \League\Csv\Exception - */ - public function getData(): array - { - $this->getMappableColumns(); - - // in order to actually map we also need all possible values from the CSV file. - $config = $this->getConfig(); - $content = $this->repository->uploadFileContents($this->job); - $offset = 0; - /** @var Reader $reader */ - $reader = Reader::createFromString($content); - $reader->setDelimiter($config['delimiter']); - if ($config['has-headers']) { - $offset = 1; - } - $stmt = (new Statement)->offset($offset); - $results = $stmt->process($reader); - $this->validSpecifics = array_keys(config('csv.import_specifics')); - $indexes = array_keys($this->data); - $rowIndex = 0; - foreach ($results as $rowIndex => $row) { - $row = $this->runSpecifics($row); - - //do something here - foreach ($indexes as $index) { // this is simply 1, 2, 3, etc. - if (!isset($row[$index])) { - // don't really know how to handle this. Just skip, for now. - continue; - } - $value = trim($row[$index]); - if (\strlen($value) > 0) { - // we can do some preprocessing here, - // which is exclusively to fix the tags: - if (null !== $this->data[$index]['preProcessMap'] && \strlen($this->data[$index]['preProcessMap']) > 0) { - /** @var PreProcessorInterface $preProcessor */ - $preProcessor = app($this->data[$index]['preProcessMap']); - $result = $preProcessor->run($value); - $this->data[$index]['values'] = array_merge($this->data[$index]['values'], $result); - - Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]); - Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]); - Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $this->data[$index]['values']]); - - continue; - } - - $this->data[$index]['values'][] = $value; - } - } - } - $setIndexes = array_keys($this->data); - foreach ($setIndexes as $index) { - $this->data[$index]['values'] = array_unique($this->data[$index]['values']); - asort($this->data[$index]['values']); - // if the count of this array is zero, there is nothing to map. - if (\count($this->data[$index]['values']) === 0) { - unset($this->data[$index]); - } - } - unset($setIndexes); - - // save number of rows, thus number of steps, in job: - $steps = $rowIndex * 5; - $extended = $this->job->extended_status; - $extended['steps'] = $steps; - $this->job->extended_status = $extended; - $this->job->save(); - - return $this->data; - } - - /** - * Return possible warning to user. - * - * @return string - */ - public function getWarningMessage(): string - { - return ''; - } - - /** - * @param ImportJob $job - * - * @return ConfigurationInterface - */ - public function setJob(ImportJob $job): ConfigurationInterface - { - $this->job = $job; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($job->user); - - return $this; - } - - /** - * Store the result. - * - * @param array $data - * - * @return bool - */ - public function storeConfiguration(array $data): bool - { - $config = $this->getConfig(); - - if (isset($data['mapping'])) { - foreach ($data['mapping'] as $index => $array) { - $config['column-mapping-config'][$index] = []; - foreach ($array as $value => $mapId) { - $mapId = (int)$mapId; - if (0 !== $mapId) { - $config['column-mapping-config'][$index][$value] = $mapId; - } - } - } - } - - // set thing to be completed. - $config['stage'] = 'ready'; - $this->saveConfig($config); - - return true; - } - - /** - * @param string $column - * - * @return MapperInterface - */ - private function createMapper(string $column): MapperInterface - { - $mapperClass = config('csv.import_roles.' . $column . '.mapper'); - $mapperName = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass); - - return app($mapperName); - } - - /** - * Short hand method. - * - * @return array - */ - private function getConfig(): array - { - return $this->repository->getConfiguration($this->job); - } - - /** - * @return bool - * - * @throws FireflyException - */ - private function getMappableColumns(): bool - { - $config = $this->getConfig(); - /** - * @var int - * @var bool $mustBeMapped - */ - foreach ($config['column-do-mapping'] as $index => $mustBeMapped) { - $column = $this->validateColumnName($config['column-roles'][$index] ?? '_ignore'); - $shouldMap = $this->shouldMapColumn($column, $mustBeMapped); - if ($shouldMap) { - // create configuration entry for this specific column and add column to $this->data array for later processing. - $this->data[$index] = [ - 'name' => $column, - 'index' => $index, - 'options' => $this->createMapper($column)->getMap(), - 'preProcessMap' => $this->getPreProcessorName($column), - 'values' => [], - ]; - } - } - - return true; - } - - /** - * @param string $column - * - * @return string - */ - private function getPreProcessorName(string $column): string - { - $name = ''; - $hasPreProcess = config('csv.import_roles.' . $column . '.pre-process-map'); - $preProcessClass = config('csv.import_roles.' . $column . '.pre-process-mapper'); - - if (null !== $hasPreProcess && true === $hasPreProcess && null !== $preProcessClass) { - $name = sprintf('\\FireflyIII\\Import\\MapperPreProcess\\%s', $preProcessClass); - } - - return $name; - } - - /** - * @param array $row - * - * @return array - * - * @throws FireflyException - */ - private function runSpecifics(array $row): array - { - // run specifics here: - // and this is the point where the specifix go to work. - $config = $this->getConfig(); - $specifics = $config['specifics'] ?? []; - $names = array_keys($specifics); - foreach ($names as $name) { - if (!\in_array($name, $this->validSpecifics)) { - throw new FireflyException(sprintf('"%s" is not a valid class name', $name)); - } - $class = config('csv.import_specifics.' . $name); - /** @var SpecificInterface $specific */ - $specific = app($class); - - // it returns the row, possibly modified: - $row = $specific->run($row); - } - - return $row; - } - - /** - * @param array $array - */ - private function saveConfig(array $array) - { - $this->repository->setConfiguration($this->job, $array); - } - - /** - * @param string $column - * @param bool $mustBeMapped - * - * @return bool - */ - private function shouldMapColumn(string $column, bool $mustBeMapped): bool - { - $canBeMapped = config('csv.import_roles.' . $column . '.mappable'); - - return $canBeMapped && $mustBeMapped; - } - - /** - * @param string $column - * - * @return string - * - * @throws FireflyException - */ - private function validateColumnName(string $column): string - { - // is valid column? - $validColumns = array_keys(config('csv.import_roles')); - if (!\in_array($column, $validColumns)) { - throw new FireflyException(sprintf('"%s" is not a valid column.', $column)); - } - - return $column; - } -} diff --git a/app/Support/Import/Configuration/File/Roles.php b/app/Support/Import/Configuration/File/Roles.php deleted file mode 100644 index 73bac35795..0000000000 --- a/app/Support/Import/Configuration/File/Roles.php +++ /dev/null @@ -1,367 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Import\Configuration\File; - -use FireflyIII\Import\Specifics\SpecificInterface; -use FireflyIII\Models\ImportJob; -use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use League\Csv\Reader; -use League\Csv\Statement; -use Log; - -/** - * @deprecated - * @codeCoverageIgnore - * Class Roles. - */ -class Roles -{ - /** - * @var array - */ - private $data = []; - /** @var ImportJob */ - private $job; - /** @var ImportJobRepositoryInterface */ - private $repository; - /** @var string */ - private $warning = ''; - - /** - * Get the data necessary to show the configuration screen. - * - * @return array - * - * @throws \League\Csv\Exception - */ - public function getData(): array - { - $content = $this->repository->uploadFileContents($this->job); - $config = $this->getConfig(); - $headers = []; - $offset = 0; - - // create CSV reader. - $reader = Reader::createFromString($content); - $reader->setDelimiter($config['delimiter']); - - // CSV headers. Ignore reader. Simply get the first row. - if ($config['has-headers']) { - $offset = 1; - $stmt = (new Statement)->limit(1)->offset(0); - $records = $stmt->process($reader); - $headers = $records->fetchOne(0); - Log::debug('Detected file headers:', $headers); - } - - // example rows: - $stmt = (new Statement)->limit((int)config('csv.example_rows', 5))->offset($offset); - // set data: - $roles = $this->getRoles(); - asort($roles); - $this->data = [ - 'examples' => [], - 'roles' => $roles, - 'total' => 0, - 'headers' => $headers, - ]; - - $records = $stmt->process($reader); - foreach ($records as $row) { - $row = array_values($row); - $row = $this->processSpecifics($row); - $count = \count($row); - $this->data['total'] = $count > $this->data['total'] ? $count : $this->data['total']; - $this->processRow($row); - } - - $this->updateColumCount(); - $this->makeExamplesUnique(); - - return $this->data; - } - - /** - * Return possible warning to user. - * - * @return string - */ - public function getWarningMessage(): string - { - return $this->warning; - } - - /** - * @param ImportJob $job - * - * @return ConfigurationInterface - */ - public function setJob(ImportJob $job): ConfigurationInterface - { - $this->job = $job; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($job->user); - - return $this; - } - - /** - * Store the result. - * - * @param array $data - * - * @return bool - */ - public function storeConfiguration(array $data): bool - { - Log::debug('Now in storeConfiguration of Roles.'); - $config = $this->getConfig(); - $count = $config['column-count']; - for ($i = 0; $i < $count; ++$i) { - $role = $data['role'][$i] ?? '_ignore'; - $mapping = isset($data['map'][$i]) && $data['map'][$i] === '1' ? true : false; - $config['column-roles'][$i] = $role; - $config['column-do-mapping'][$i] = $mapping; - Log::debug(sprintf('Column %d has been given role %s (mapping: %s)', $i, $role, var_export($mapping, true))); - } - - $this->saveConfig($config); - $this->ignoreUnmappableColumns(); - $res = $this->isRolesComplete(); - if ($res === true) { - $config = $this->getConfig(); - $config['stage'] = 'map'; - $this->saveConfig($config); - $this->isMappingNecessary(); - } - - return true; - } - - /** - * Short hand method. - * - * @return array - */ - private function getConfig(): array - { - return $this->repository->getConfiguration($this->job); - } - - /** - * @return array - */ - private function getRoles(): array - { - $roles = []; - foreach (array_keys(config('csv.import_roles')) as $role) { - $roles[$role] = trans('import.column_' . $role); - } - - return $roles; - } - - /** - * @return bool - */ - private function ignoreUnmappableColumns(): bool - { - Log::debug('Now in ignoreUnmappableColumns()'); - $config = $this->getConfig(); - $count = $config['column-count']; - for ($i = 0; $i < $count; ++$i) { - $role = $config['column-roles'][$i] ?? '_ignore'; - $mapping = $config['column-do-mapping'][$i] ?? false; - Log::debug(sprintf('Role for column %d is %s, and mapping is %s', $i, $role, var_export($mapping, true))); - - if ('_ignore' === $role && true === $mapping) { - $mapping = false; - Log::debug(sprintf('Column %d has type %s so it cannot be mapped.', $i, $role)); - } - $config['column-do-mapping'][$i] = $mapping; - } - $this->saveConfig($config); - - return true; - } - - /** - * @return bool - */ - private function isMappingNecessary() - { - $config = $this->getConfig(); - $count = $config['column-count']; - $toBeMapped = 0; - for ($i = 0; $i < $count; ++$i) { - $mapping = $config['column-do-mapping'][$i] ?? false; - if (true === $mapping) { - ++$toBeMapped; - } - } - Log::debug(sprintf('Found %d columns that need mapping.', $toBeMapped)); - if (0 === $toBeMapped) { - $config['stage'] = 'ready'; - } - - $this->saveConfig($config); - - return true; - } - - /** - * @return bool - */ - private function isRolesComplete(): bool - { - $config = $this->getConfig(); - $count = $config['column-count']; - $assigned = 0; - - // check if data actually contains amount column (foreign amount does not count) - $hasAmount = false; - $hasForeignAmount = false; - $hasForeignCode = false; - for ($i = 0; $i < $count; ++$i) { - $role = $config['column-roles'][$i] ?? '_ignore'; - if ('_ignore' !== $role) { - ++$assigned; - } - if (\in_array($role, ['amount', 'amount_credit', 'amount_debit'])) { - $hasAmount = true; - } - if ($role === 'foreign-currency-code') { - $hasForeignCode = true; - } - if ($role === 'amount_foreign') { - $hasForeignAmount = true; - } - } - Log::debug( - sprintf( - 'Assigned is %d, hasAmount %s, hasForeignCode %s, hasForeignAmount %s', - $assigned, - var_export($hasAmount, true), - var_export($hasForeignCode, true), - var_export($hasForeignAmount, true) - ) - ); - // all assigned and correct foreign info - if ($assigned > 0 && $hasAmount && ($hasForeignCode === $hasForeignAmount)) { - $this->warning = ''; - $this->saveConfig($config); - Log::debug('isRolesComplete() returns true.'); - - return true; - } - // warn if has foreign amount but no currency code: - if ($hasForeignAmount && !$hasForeignCode) { - $this->warning = (string)trans('import.foreign_amount_warning'); - Log::debug('isRolesComplete() returns FALSE because foreign amount present without foreign code.'); - - return false; - } - - if (0 === $assigned || !$hasAmount) { - $this->warning = (string)trans('import.roles_warning'); - Log::debug('isRolesComplete() returns FALSE because no amount present.'); - - return false; - } - Log::debug('isRolesComplete() returns FALSE because no reason.'); - - return false; - } - - /** - * make unique example data. - */ - private function makeExamplesUnique(): bool - { - foreach ($this->data['examples'] as $index => $values) { - $this->data['examples'][$index] = array_unique($values); - } - - return true; - } - - /** - * @param array $row - * - * @return bool - */ - private function processRow(array $row): bool - { - foreach ($row as $index => $value) { - $value = trim($value); - if (\strlen($value) > 0) { - $this->data['examples'][$index][] = $value; - } - } - - return true; - } - - /** - * run specifics here: - * and this is the point where the specifix go to work. - * - * @param array $row - * - * @return array - */ - private function processSpecifics(array $row): array - { - $config = $this->getConfig(); - $specifics = $config['specifics'] ?? []; - $names = array_keys($specifics); - foreach ($names as $name) { - /** @var SpecificInterface $specific */ - $specific = app('FireflyIII\Import\Specifics\\' . $name); - $row = $specific->run($row); - } - - return $row; - } - - /** - * @param array $array - */ - private function saveConfig(array $array) - { - $this->repository->setConfiguration($this->job, $array); - } - - /** - * @return bool - */ - private function updateColumCount(): bool - { - $config = $this->getConfig(); - $count = $this->data['total']; - $config['column-count'] = $count; - $this->saveConfig($config); - - return true; - } -} diff --git a/app/Support/Import/Configuration/File/UploadConfig.php b/app/Support/Import/Configuration/File/UploadConfig.php deleted file mode 100644 index 5c3d1f175b..0000000000 --- a/app/Support/Import/Configuration/File/UploadConfig.php +++ /dev/null @@ -1,190 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Import\Configuration\File; - -use FireflyIII\Models\AccountType; -use FireflyIII\Models\ImportJob; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use Log; - -/** - * @deprecated - * @codeCoverageIgnore - * Class UploadConfig. - */ -class UploadConfig -{ - /** @var AccountRepositoryInterface */ - private $accountRepository; - /** - * @var ImportJob - */ - private $job; - /** @var ImportJobRepositoryInterface */ - private $repository; - - /** - * @return array - */ - public function getData(): array - { - $accounts = $this->accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - $delimiters = [ - ',' => trans('form.csv_comma'), - ';' => trans('form.csv_semicolon'), - 'tab' => trans('form.csv_tab'), - ]; - $config = $this->getConfig(); - $config['date-format'] = $config['date-format'] ?? 'Ymd'; - $specifics = []; - $this->saveConfig($config); - - // collect specifics. - foreach (config('csv.import_specifics') as $name => $className) { - $specifics[$name] = [ - 'name' => $className::getName(), - 'description' => $className::getDescription(), - ]; - } - - $data = [ - 'accounts' => app('expandedform')->makeSelectList($accounts), - 'specifix' => [], - 'delimiters' => $delimiters, - 'specifics' => $specifics, - ]; - - return $data; - } - - /** - * Return possible warning to user. - * - * @return string - */ - public function getWarningMessage(): string - { - return ''; - } - - /** - * @param ImportJob $job - * - * @return ConfigurationInterface - */ - public function setJob(ImportJob $job): ConfigurationInterface - { - $this->job = $job; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->accountRepository = app(AccountRepositoryInterface::class); - $this->repository->setUser($job->user); - $this->accountRepository->setUser($job->user); - - return $this; - } - - /** - * Store the result. - * - * @param array $data - * - * @return bool - */ - public function storeConfiguration(array $data): bool - { - Log::debug('Now in Initial::storeConfiguration()'); - $config = $this->getConfig(); - $importId = (int)($data['csv_import_account'] ?? 0.0); - $account = $this->accountRepository->find($importId); - $delimiter = (string)$data['csv_delimiter']; - - // set "headers": - $config['has-headers'] = (int)($data['has_headers'] ?? 0.0) === 1; - $config['date-format'] = (string)$data['date_format']; - $config['delimiter'] = 'tab' === $delimiter ? "\t" : $delimiter; - $config['apply-rules'] = (int)($data['apply_rules'] ?? 0.0) === 1; - - Log::debug('Entered import account.', ['id' => $importId]); - - - if (null !== $account->id) { - Log::debug('Found account.', ['id' => $account->id, 'name' => $account->name]); - $config['import-account'] = $account->id; - } - - if (null === $account->id) { - Log::error('Could not find anything for csv_import_account.', ['id' => $importId]); - } - $config = $this->storeSpecifics($data, $config); - Log::debug('Final config is ', $config); - - // onto the next stage! - - $config['stage'] = 'roles'; - $this->saveConfig($config); - - return true; - } - - /** - * Short hand method. - * - * @return array - */ - private function getConfig(): array - { - return $this->repository->getConfiguration($this->job); - } - - /** - * @param array $array - */ - private function saveConfig(array $array) - { - $this->repository->setConfiguration($this->job, $array); - } - - /** - * @param array $data - * @param array $config - * - * @return array - */ - private function storeSpecifics(array $data, array $config): array - { - // loop specifics. - if (isset($data['specifics']) && \is_array($data['specifics'])) { - $names = array_keys($data['specifics']); - foreach ($names as $name) { - // verify their content. - $className = sprintf('FireflyIII\Import\Specifics\%s', $name); - if (class_exists($className)) { - $config['specifics'][$name] = 1; - } - } - } - - return $config; - } -} diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php index 650eddfbde..ff8476659b 100644 --- a/app/Support/Import/Routine/File/CSVProcessor.php +++ b/app/Support/Import/Routine/File/CSVProcessor.php @@ -23,8 +23,16 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\Routine\File; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; +use FireflyIII\Models\Attachment; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use Illuminate\Support\Collection; +use League\Csv\Exception; +use League\Csv\Reader; +use League\Csv\Statement; +use Log; /** @@ -34,6 +42,8 @@ use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; */ class CSVProcessor implements FileProcessorInterface { + /** @var AttachmentHelperInterface */ + private $attachments; /** @var ImportJob */ private $importJob; /** @var ImportJobRepositoryInterface */ @@ -43,9 +53,33 @@ class CSVProcessor implements FileProcessorInterface * Fires the file processor. * * @return array + * @throws FireflyException */ public function run(): array { + $config = $this->importJob->configuration; + + // in order to actually map we also need to read the FULL file. + try { + $reader = $this->getReader(); + } catch (Exception $e) { + Log::error($e->getMessage()); + throw new FireflyException('Cannot get reader: ' . $e->getMessage()); + } + // get mapping from config + $roles = $config['column-roles']; + + // get all lines from file: + $lines = $this->getLines($reader, $config); + + // make import objects, according to their role: + $importables = $this->processLines($lines, $roles); + + echo '
    ';
    +        print_r($importables);
    +        print_r($lines);
    +
    +        exit;
             die('here we are');
         }
     
    @@ -54,9 +88,129 @@ class CSVProcessor implements FileProcessorInterface
          */
         public function setJob(ImportJob $job): void
         {
    -        $this->importJob  = $job;
    -        $this->repository = app(ImportJobRepositoryInterface::class);
    +        $this->importJob   = $job;
    +        $this->repository  = app(ImportJobRepositoryInterface::class);
    +        $this->attachments = app(AttachmentHelperInterface::class);
             $this->repository->setUser($job->user);
     
         }
    +
    +    /**
    +     * Returns all lines from the CSV file.
    +     *
    +     * @param Reader $reader
    +     * @param array  $config
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    private function getLines(Reader $reader, array $config): array
    +    {
    +        $offset = isset($config['has-headers']) && $config['has-headers'] === true ? 1 : 0;
    +        try {
    +            $stmt = (new Statement)->offset($offset);
    +        } catch (Exception $e) {
    +            throw new FireflyException(sprintf('Could not execute statement: %s', $e->getMessage()));
    +        }
    +        $results = $stmt->process($reader);
    +        $lines   = [];
    +        foreach ($results as $line) {
    +            $lines[] = array_values($line);
    +        }
    +
    +        return $lines;
    +    }
    +
    +    /**
    +     * Return an instance of a CSV file reader so content of the file can be read.
    +     *
    +     * @throws \League\Csv\Exception
    +     */
    +    private function getReader(): Reader
    +    {
    +        $content = '';
    +        /** @var Collection $collection */
    +        $collection = $this->importJob->attachments;
    +        /** @var Attachment $attachment */
    +        foreach ($collection as $attachment) {
    +            if ($attachment->filename === 'import_file') {
    +                $content = $this->attachments->getAttachmentContent($attachment);
    +                break;
    +            }
    +        }
    +        $config = $this->repository->getConfiguration($this->importJob);
    +        $reader = Reader::createFromString($content);
    +        $reader->setDelimiter($config['delimiter']);
    +
    +        return $reader;
    +    }
    +
    +    /**
    +     * Process a single column. Return is an array with:
    +     * [0 => key, 1 => value]
    +     * where the first item is the key under which the value
    +     * must be stored, and the second is the value.
    +     *
    +     * @param int    $column
    +     * @param string $value
    +     * @param string $role
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    private function processColumn(int $column, string $value, string $role): array
    +    {
    +        switch ($role) {
    +            default:
    +                throw new FireflyException(sprintf('Cannot handle role "%s" with value "%s"', $role, $value));
    +
    +
    +                // feed each line into a new class which will process
    +            // the line. 
    +        }
    +    }
    +
    +    /**
    +     * Process all lines in the CSV file. Each line is processed separately.
    +     *
    +     * @param array $lines
    +     * @param array $roles
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    private function processLines(array $lines, array $roles): array
    +    {
    +        $processed = [];
    +        foreach ($lines as $line) {
    +            $processed[] = $this->processSingleLine($line, $roles);
    +
    +        }
    +
    +        return $processed;
    +    }
    +
    +    /**
    +     * Process a single line in the CSV file.
    +     * Each column is processed separately.
    +     *
    +     * @param array $line
    +     * @param array $roles
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    private function processSingleLine(array $line, array $roles): array
    +    {
    +        // todo run all specifics on row.
    +        $transaction = [];
    +        foreach ($line as $column => $value) {
    +            $value = trim($value);
    +            $role  = $roles[$column] ?? '_ignore';
    +            [$key, $result] = $this->processColumn($column, $value, $role);
    +            // if relevant, find mapped value:
    +        }
    +
    +        return $transaction;
    +    }
     }
    \ No newline at end of file
    
    From 626f7357bbbba64af7690842e99f325490d6052f Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Mon, 7 May 2018 19:21:12 +0200
    Subject: [PATCH 051/182] Improve CSV import routine.
    
    ---
     app/Import/Routine/FileRoutine.php            |   1 -
     .../Import/Placeholder/ColumnValue.php        | 113 +++++++++
     .../Import/Placeholder/ImportTransaction.php  | 220 ++++++++++++++++++
     .../Import/Routine/File/CSVProcessor.php      | 137 ++++++++---
     4 files changed, 435 insertions(+), 36 deletions(-)
     create mode 100644 app/Support/Import/Placeholder/ColumnValue.php
     create mode 100644 app/Support/Import/Placeholder/ImportTransaction.php
    
    diff --git a/app/Import/Routine/FileRoutine.php b/app/Import/Routine/FileRoutine.php
    index 65e77616ea..f39153c91b 100644
    --- a/app/Import/Routine/FileRoutine.php
    +++ b/app/Import/Routine/FileRoutine.php
    @@ -26,7 +26,6 @@ use FireflyIII\Exceptions\FireflyException;
     use FireflyIII\Models\ImportJob;
     use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
     use FireflyIII\Support\Import\Routine\File\FileProcessorInterface;
    -use FireflyIII\Support\Import\Routine\File\FileRoutineInterface;
     use Log;
     
     /**
    diff --git a/app/Support/Import/Placeholder/ColumnValue.php b/app/Support/Import/Placeholder/ColumnValue.php
    new file mode 100644
    index 0000000000..238a9687bc
    --- /dev/null
    +++ b/app/Support/Import/Placeholder/ColumnValue.php
    @@ -0,0 +1,113 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace FireflyIII\Support\Import\Placeholder;
    +
    +/**
    + * Class Column
    + */
    +class ColumnValue
    +{
    +    /** @var int */
    +    private $mappedValue;
    +    /** @var string */
    +    private $originalRole;
    +    /** @var string */
    +    private $role;
    +    /** @var string */
    +    private $value;
    +
    +    /**
    +     * ColumnValue constructor.
    +     */
    +    public function __construct()
    +    {
    +        $this->mappedValue = 0;
    +    }
    +
    +    /**
    +     * @return int
    +     */
    +    public function getMappedValue(): int
    +    {
    +        return $this->mappedValue;
    +    }
    +
    +    /**
    +     * @param int $mappedValue
    +     */
    +    public function setMappedValue(int $mappedValue): void
    +    {
    +        $this->mappedValue = $mappedValue;
    +    }
    +
    +    /**
    +     * @return string
    +     */
    +    public function getOriginalRole(): string
    +    {
    +        return $this->originalRole;
    +    }
    +
    +    /**
    +     * @param string $originalRole
    +     */
    +    public function setOriginalRole(string $originalRole): void
    +    {
    +        $this->originalRole = $originalRole;
    +    }
    +
    +    /**
    +     * @return string
    +     */
    +    public function getRole(): string
    +    {
    +        return $this->role;
    +    }
    +
    +    /**
    +     * @param string $role
    +     */
    +    public function setRole(string $role): void
    +    {
    +        $this->role = $role;
    +    }
    +
    +    /**
    +     * @return string
    +     */
    +    public function getValue(): string
    +    {
    +        return $this->value;
    +    }
    +
    +    /**
    +     * @param string $value
    +     */
    +    public function setValue(string $value): void
    +    {
    +        $this->value = $value;
    +    }
    +
    +
    +}
    \ No newline at end of file
    diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php
    new file mode 100644
    index 0000000000..57ba4522b6
    --- /dev/null
    +++ b/app/Support/Import/Placeholder/ImportTransaction.php
    @@ -0,0 +1,220 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace FireflyIII\Support\Import\Placeholder;
    +
    +use FireflyIII\Exceptions\FireflyException;
    +
    +/**
    + * Class ImportTransaction
    + */
    +class ImportTransaction
    +{
    +    /** @var string */
    +    private $accountBic;
    +    /** @var string */
    +    private $accountIban;
    +    /** @var int */
    +    private $accountId;
    +    /** @var string */
    +    private $accountName;
    +    /** @var string */
    +    private $amount;
    +    /** @var string */
    +    private $amountCredit;
    +    /** @var string */
    +    private $amountDebit;
    +    /** @var int */
    +    private $billId;
    +    /** @var int */
    +    private $budgetId;
    +    /** @var int */
    +    private $categoryId;
    +    /** @var int */
    +    private $currencyId;
    +    /** @var string */
    +    private $date;
    +    /** @var string */
    +    private $description;
    +    /** @var string */
    +    private $externalId;
    +    /** @var string */
    +    private $foreignAmount;
    +    /** @var int */
    +    private $foreignCurrencyId;
    +    /** @var array */
    +    private $meta;
    +    /** @var array */
    +    private $modifiers;
    +    /** @var string */
    +    private $note;
    +    /** @var string */
    +    private $opposingBic;
    +    /** @var string */
    +    private $opposingIban;
    +    /** @var int */
    +    private $opposingId;
    +    /** @var string */
    +    private $opposingName;
    +    /** @var array */
    +    private $tags;
    +
    +    /**
    +     * ImportTransaction constructor.
    +     */
    +    public function __construct()
    +    {
    +        $this->tags        = [];
    +        $this->modifiers   = [];
    +        $this->meta        = [];
    +        $this->description = '';
    +        $this->note        = '';
    +    }
    +
    +    /**
    +     * @param ColumnValue $columnValue
    +     *
    +     * @throws FireflyException
    +     */
    +    public function addColumnValue(ColumnValue $columnValue): void
    +    {
    +        switch ($columnValue->getRole()) {
    +            default:
    +                throw new FireflyException(
    +                    sprintf('ImportTransaction cannot handle role "%s" with value "%s"', $columnValue->getRole(), $columnValue->getValue())
    +                );
    +            case 'account-id':
    +                // could be the result of a mapping?
    +                $this->accountId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'account-name':
    +                $this->accountName = $columnValue->getValue();
    +                break;
    +            case 'sepa-ct-id';
    +            case 'sepa-ct-op';
    +            case 'sepa-db';
    +            case 'sepa-cc':
    +            case 'sepa-country';
    +            case 'sepa-ep';
    +            case 'sepa-ci';
    +            case 'internal-reference':
    +            case 'date-interest':
    +            case 'date-invoice':
    +            case 'date-book':
    +            case 'date-payment':
    +            case 'date-process':
    +            case 'date-due':
    +                $this->meta[$columnValue->getRole()] = $columnValue->getValue();
    +                break;
    +            case'amount_debit':
    +                $this->amountDebit = $columnValue->getValue();
    +                break;
    +            case'amount_credit':
    +                $this->amountCredit = $columnValue->getValue();
    +                break;
    +            case 'amount':
    +                $this->amount = $columnValue->getValue();
    +                break;
    +            case 'amount_foreign':
    +                $this->foreignAmount = $columnValue->getValue();
    +                break;
    +            case 'foreign-currency-id':
    +                $this->foreignCurrencyId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'bill-id':
    +                $this->billId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'budget-id':
    +                $this->budgetId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'category-id':
    +                $this->categoryId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'currency-id':
    +                $this->currencyId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'date-transaction':
    +                $this->date = $columnValue->getValue();
    +                break;
    +            case 'description':
    +                $this->description .= $columnValue->getValue();
    +                break;
    +            case 'note':
    +                $this->note .= $columnValue->getValue();
    +                break;
    +            case 'external-id':
    +                $this->externalId = $columnValue->getValue();
    +                break;
    +            case 'rabo-debit-credit':
    +            case 'ing-debit-credit':
    +                $this->modifiers[$columnValue->getRole()] = $columnValue->getValue();
    +                break;
    +            case 'opposing-id':
    +                $this->opposingId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'opposing-name':
    +                $this->opposingName = $columnValue->getValue();
    +                break;
    +            case 'opposing-bic':
    +                $this->opposingBic = $columnValue->getValue();
    +                break;
    +            case 'tags-comma':
    +                // todo split using pre-processor.
    +                $this->tags = $columnValue->getValue();
    +                break;
    +            case 'tags-space':
    +                // todo split using pre-processor.
    +                $this->tags = $columnValue->getValue();
    +                break;
    +            case 'account-iban':
    +                $this->accountIban = $columnValue->getValue();
    +                break;
    +            case 'opposing-iban':
    +                $this->opposingIban = $columnValue->getValue();
    +                break;
    +            case '_ignore':
    +            case 'bill-name':
    +            case 'currency-name':
    +            case 'currency-code':
    +            case 'foreign-currency-code':
    +            case 'currency-symbol':
    +            case 'budget-name':
    +            case 'category-name':
    +            case 'account-number':
    +            case 'opposing-number':
    +        }
    +    }
    +
    +    /**
    +     * Returns the mapped value if it exists in the ColumnValue object.
    +     *
    +     * @param ColumnValue $columnValue
    +     *
    +     * @return int
    +     */
    +    private function getMappedValue(ColumnValue $columnValue): int
    +    {
    +        return $columnValue->getMappedValue() > 0 ? $columnValue->getMappedValue() : (int)$columnValue->getValue();
    +    }
    +
    +}
    \ No newline at end of file
    diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php
    index ff8476659b..e9a0d873d9 100644
    --- a/app/Support/Import/Routine/File/CSVProcessor.php
    +++ b/app/Support/Import/Routine/File/CSVProcessor.php
    @@ -28,6 +28,8 @@ use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
     use FireflyIII\Models\Attachment;
     use FireflyIII\Models\ImportJob;
     use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
    +use FireflyIII\Support\Import\Placeholder\ColumnValue;
    +use FireflyIII\Support\Import\Placeholder\ImportTransaction;
     use Illuminate\Support\Collection;
     use League\Csv\Exception;
     use League\Csv\Reader;
    @@ -44,8 +46,12 @@ class CSVProcessor implements FileProcessorInterface
     {
         /** @var AttachmentHelperInterface */
         private $attachments;
    +    /** @var array */
    +    private $config;
         /** @var ImportJob */
         private $importJob;
    +    /** @var array */
    +    private $mappedValues;
         /** @var ImportJobRepositoryInterface */
         private $repository;
     
    @@ -57,7 +63,7 @@ class CSVProcessor implements FileProcessorInterface
          */
         public function run(): array
         {
    -        $config = $this->importJob->configuration;
    +
     
             // in order to actually map we also need to read the FULL file.
             try {
    @@ -66,14 +72,11 @@ class CSVProcessor implements FileProcessorInterface
                 Log::error($e->getMessage());
                 throw new FireflyException('Cannot get reader: ' . $e->getMessage());
             }
    -        // get mapping from config
    -        $roles = $config['column-roles'];
    -
             // get all lines from file:
    -        $lines = $this->getLines($reader, $config);
    +        $lines = $this->getLines($reader);
     
             // make import objects, according to their role:
    -        $importables = $this->processLines($lines, $roles);
    +        $importables = $this->processLines($lines);
     
             echo '
    ';
             print_r($importables);
    @@ -89,6 +92,7 @@ class CSVProcessor implements FileProcessorInterface
         public function setJob(ImportJob $job): void
         {
             $this->importJob   = $job;
    +        $this->config      = $job->configuration;
             $this->repository  = app(ImportJobRepositoryInterface::class);
             $this->attachments = app(AttachmentHelperInterface::class);
             $this->repository->setUser($job->user);
    @@ -99,14 +103,13 @@ class CSVProcessor implements FileProcessorInterface
          * Returns all lines from the CSV file.
          *
          * @param Reader $reader
    -     * @param array  $config
          *
          * @return array
          * @throws FireflyException
          */
    -    private function getLines(Reader $reader, array $config): array
    +    private function getLines(Reader $reader): array
         {
    -        $offset = isset($config['has-headers']) && $config['has-headers'] === true ? 1 : 0;
    +        $offset = isset($this->config['has-headers']) && $this->config['has-headers'] === true ? 1 : 0;
             try {
                 $stmt = (new Statement)->offset($offset);
             } catch (Exception $e) {
    @@ -146,45 +149,93 @@ class CSVProcessor implements FileProcessorInterface
         }
     
         /**
    -     * Process a single column. Return is an array with:
    -     * [0 => key, 1 => value]
    -     * where the first item is the key under which the value
    -     * must be stored, and the second is the value.
    +     * If the value in the column is mapped to a certain ID,
    +     * the column where this ID must be placed will change.
          *
    -     * @param int    $column
    -     * @param string $value
    -     * @param string $role
    +     * For example, if you map role "budget-name" with value "groceries" to 1,
    +     * then that should become the budget-id. Not the name.
          *
    -     * @return array
    +     * @param int $column
    +     * @param int $mapped
    +     *
    +     * @return string
          * @throws FireflyException
          */
    -    private function processColumn(int $column, string $value, string $role): array
    +    private function getRoleForColumn(int $column, int $mapped): string
         {
    +        $roles = $this->config['column-roles'];
    +        $role  = $roles[$column] ?? '_ignore';
    +        if ($mapped === 0) {
    +            Log::debug(sprintf('Column #%d with role "%s" is not mapped.', $column, $role));
    +
    +            return $role;
    +        }
    +        if (!(isset($this->config['column-do-mapping'][$column]) && $this->config['column-do-mapping'][$column] === true)) {
    +
    +            return $role;
    +        }
             switch ($role) {
                 default:
    -                throw new FireflyException(sprintf('Cannot handle role "%s" with value "%s"', $role, $value));
    -
    -
    -                // feed each line into a new class which will process
    -            // the line. 
    +                throw new FireflyException(sprintf('Cannot indicate new role for mapped role "%s"', $role));
    +            case 'account-id':
    +            case 'account-name':
    +            case 'account-iban':
    +            case 'account-number':
    +                $newRole = 'account-id';
    +                break;
    +            case 'foreign-currency-id':
    +            case 'foreign-currency-code':
    +                $newRole = 'foreign-currency-id';
    +                break;
    +            case 'bill-id':
    +            case 'bill-name':
    +                $newRole = 'bill-id';
    +                break;
    +            case 'currency-id':
    +            case 'currency-name':
    +            case 'currency-code':
    +            case 'currency-symbol':
    +                $newRole = 'currency-id';
    +                break;
    +            case 'budget-id':
    +            case 'budget-name':
    +                $newRole = 'budget-id';
    +                break;
    +            case 'category-id':
    +            case 'category-name':
    +                $newRole = 'category-id';
    +                break;
    +            case 'opposing-id':
    +            case 'opposing-name':
    +            case 'opposing-iban':
    +            case 'opposing-number':
    +                $newRole = 'opposing-id';
    +                break;
             }
    +        Log::debug(sprintf('Role was "%s", but because of mapping, role becomes "%s"', $role, $newRole));
    +
    +        // also store the $mapped values in a "mappedValues" array.
    +        $this->mappedValues[$newRole] = $mapped;
    +
    +        return $newRole;
         }
     
    +
         /**
          * Process all lines in the CSV file. Each line is processed separately.
          *
          * @param array $lines
    -     * @param array $roles
          *
          * @return array
          * @throws FireflyException
          */
    -    private function processLines(array $lines, array $roles): array
    +    private function processLines(array $lines): array
         {
             $processed = [];
    -        foreach ($lines as $line) {
    -            $processed[] = $this->processSingleLine($line, $roles);
    -
    +        $count     = \count($lines);
    +        foreach ($lines as $index => $line) {
    +            Log::debug(sprintf('Now at line #%d of #%d', $index, $count));
    +            $processed[] = $this->processSingleLine($line);
             }
     
             return $processed;
    @@ -197,18 +248,34 @@ class CSVProcessor implements FileProcessorInterface
          * @param array $line
          * @param array $roles
          *
    -     * @return array
    +     * @return ImportTransaction
          * @throws FireflyException
          */
    -    private function processSingleLine(array $line, array $roles): array
    +    private function processSingleLine(array $line): ImportTransaction
         {
    +        $transaction = new ImportTransaction;
             // todo run all specifics on row.
    -        $transaction = [];
             foreach ($line as $column => $value) {
    -            $value = trim($value);
    -            $role  = $roles[$column] ?? '_ignore';
    -            [$key, $result] = $this->processColumn($column, $value, $role);
    -            // if relevant, find mapped value:
    +            $value        = trim($value);
    +            $originalRole = $this->config['column-roles'][$column] ?? '_ignore';
    +            if (\strlen($value) > 0 && $originalRole !== '_ignore') {
    +
    +                // is a mapped value present?
    +                $mapped = $this->config['column-mapping-config'][$column][$value] ?? 0;
    +                // the role might change.
    +                $role = $this->getRoleForColumn($column, $mapped);
    +
    +                $columnValue = new ColumnValue;
    +                $columnValue->setValue($value);
    +                $columnValue->setRole($role);
    +                $columnValue->setMappedValue($mapped);
    +                $columnValue->setOriginalRole($originalRole);
    +                $transaction->addColumnValue($columnValue);
    +
    +                // create object that parses this column value.
    +
    +                Log::debug(sprintf('Now at column #%d (%s), value "%s"', $column, $role, $value));
    +            }
             }
     
             return $transaction;
    
    From 7f4feb0cfc5e4e02c3828453c5cd5ee3f48fdd14 Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Mon, 7 May 2018 20:35:14 +0200
    Subject: [PATCH 052/182] More code for import routine.
    
    ---
     app/Repositories/Bill/BillRepository.php      |  16 +-
     .../Bill/BillRepositoryInterface.php          |   9 +
     app/Repositories/Budget/BudgetRepository.php  |  12 +
     .../Budget/BudgetRepositoryInterface.php      |  10 +
     .../Category/CategoryRepository.php           |  12 +
     .../Category/CategoryRepositoryInterface.php  |  10 +
     .../Import/Placeholder/ImportTransaction.php  | 206 ++++++++++++++----
     .../Import/Routine/File/CSVProcessor.php      | 155 ++++++++++++-
     8 files changed, 380 insertions(+), 50 deletions(-)
    
    diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php
    index 670f984f53..e7aabdee2a 100644
    --- a/app/Repositories/Bill/BillRepository.php
    +++ b/app/Repositories/Bill/BillRepository.php
    @@ -243,6 +243,18 @@ class BillRepository implements BillRepositoryInterface
             return $sum;
         }
     
    +    /**
    +     * Get all bills with these ID's.
    +     *
    +     * @param array $billIds
    +     *
    +     * @return Collection
    +     */
    +    public function getByIds(array $billIds): Collection
    +    {
    +        return $this->user->bills()->whereIn('id', $billIds)->get();
    +    }
    +
         /**
          * Get text or return empty string.
          *
    @@ -393,11 +405,11 @@ class BillRepository implements BillRepositoryInterface
             $rules = $this->user->rules()
                                 ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id')
                                 ->where('rule_actions.action_type', 'link_to_bill')
    -                            ->get(['rules.id', 'rules.title', 'rule_actions.action_value','rules.active']);
    +                            ->get(['rules.id', 'rules.title', 'rule_actions.action_value', 'rules.active']);
             $array = [];
             foreach ($rules as $rule) {
                 $array[$rule->action_value]   = $array[$rule->action_value] ?? [];
    -            $array[$rule->action_value][] = ['id' => $rule->id, 'title' => $rule->title,'active' => $rule->active];
    +            $array[$rule->action_value][] = ['id' => $rule->id, 'title' => $rule->title, 'active' => $rule->active];
             }
             $return = [];
             foreach ($collection as $bill) {
    diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php
    index 37f7e6abd7..3c9e1fa8f9 100644
    --- a/app/Repositories/Bill/BillRepositoryInterface.php
    +++ b/app/Repositories/Bill/BillRepositoryInterface.php
    @@ -98,6 +98,15 @@ interface BillRepositoryInterface
          */
         public function getBillsUnpaidInRange(Carbon $start, Carbon $end): string;
     
    +    /**
    +     * Get all bills with these ID's.
    +     *
    +     * @param array $billIds
    +     *
    +     * @return Collection
    +     */
    +    public function getByIds(array $billIds): Collection;
    +
         /**
          * Get text or return empty string.
          *
    diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php
    index 903c2b0f27..c0ee968441 100644
    --- a/app/Repositories/Budget/BudgetRepository.php
    +++ b/app/Repositories/Budget/BudgetRepository.php
    @@ -458,6 +458,18 @@ class BudgetRepository implements BudgetRepositoryInterface
             return $set;
         }
     
    +    /**
    +     * Get all budgets with these ID's.
    +     *
    +     * @param array $budgetIds
    +     *
    +     * @return Collection
    +     */
    +    public function getByIds(array $budgetIds): Collection
    +    {
    +        return $this->user->budgets()->whereIn('id', $budgetIds)->get();
    +    }
    +
         /**
          * @return Collection
          */
    diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php
    index 36879ca327..a929e92484 100644
    --- a/app/Repositories/Budget/BudgetRepositoryInterface.php
    +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php
    @@ -34,6 +34,16 @@ use Illuminate\Support\Collection;
      */
     interface BudgetRepositoryInterface
     {
    +
    +    /**
    +     * Get all budgets with these ID's.
    +     *
    +     * @param array $budgetIds
    +     *
    +     * @return Collection
    +     */
    +    public function getByIds(array $budgetIds): Collection;
    +
         /**
          * A method that returns the amount of money budgeted per day for this budget,
          * on average.
    diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php
    index 82964375f8..e9e5a97909 100644
    --- a/app/Repositories/Category/CategoryRepository.php
    +++ b/app/Repositories/Category/CategoryRepository.php
    @@ -153,6 +153,18 @@ class CategoryRepository implements CategoryRepositoryInterface
             return $firstJournalDate;
         }
     
    +    /**
    +     * Get all categories with ID's.
    +     *
    +     * @param array $categoryIds
    +     *
    +     * @return Collection
    +     */
    +    public function getByIds(array $categoryIds): Collection
    +    {
    +        return $this->user->categories()->whereIn('id', $categoryIds)->get();
    +    }
    +
         /**
          * Returns a list of all the categories belonging to a user.
          *
    diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php
    index e224ef84e7..8c16732bcb 100644
    --- a/app/Repositories/Category/CategoryRepositoryInterface.php
    +++ b/app/Repositories/Category/CategoryRepositoryInterface.php
    @@ -32,6 +32,7 @@ use Illuminate\Support\Collection;
      */
     interface CategoryRepositoryInterface
     {
    +
         /**
          * @param Category $category
          *
    @@ -84,6 +85,15 @@ interface CategoryRepositoryInterface
          */
         public function firstUseDate(Category $category): ?Carbon;
     
    +    /**
    +     * Get all categories with ID's.
    +     *
    +     * @param array $categoryIds
    +     *
    +     * @return Collection
    +     */
    +    public function getByIds(array $categoryIds): Collection;
    +
         /**
          * Returns a list of all the categories belonging to a user.
          *
    diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php
    index 57ba4522b6..e928b7be2b 100644
    --- a/app/Support/Import/Placeholder/ImportTransaction.php
    +++ b/app/Support/Import/Placeholder/ImportTransaction.php
    @@ -39,6 +39,8 @@ class ImportTransaction
         /** @var string */
         private $accountName;
         /** @var string */
    +    private $accountNumber;
    +    /** @var string */
         private $amount;
         /** @var string */
         private $amountCredit;
    @@ -46,13 +48,25 @@ class ImportTransaction
         private $amountDebit;
         /** @var int */
         private $billId;
    +    /** @var string */
    +    private $billName;
         /** @var int */
         private $budgetId;
    +    /** @var string */
    +    private $budgetName;
         /** @var int */
         private $categoryId;
    +    /** @var string */
    +    private $categoryName;
    +    /** @var string */
    +    private $currencyCode;
         /** @var int */
         private $currencyId;
         /** @var string */
    +    private $currencyName;
    +    /** @var string */
    +    private $currencySymbol;
    +    /** @var string */
         private $date;
         /** @var string */
         private $description;
    @@ -60,6 +74,8 @@ class ImportTransaction
         private $externalId;
         /** @var string */
         private $foreignAmount;
    +    /** @var string */
    +    private $foreignCurrencyCode;
         /** @var int */
         private $foreignCurrencyId;
         /** @var array */
    @@ -76,9 +92,29 @@ class ImportTransaction
         private $opposingId;
         /** @var string */
         private $opposingName;
    +    /** @var string */
    +    private $opposingNumber;
         /** @var array */
         private $tags;
     
    +    /**
    +     * @return array
    +     */
    +    public function getMeta(): array
    +    {
    +        return $this->meta;
    +    }
    +
    +    /**
    +     * @return string
    +     */
    +    public function getNote(): string
    +    {
    +        return $this->note;
    +    }
    +
    +
    +
         /**
          * ImportTransaction constructor.
          */
    @@ -89,8 +125,44 @@ class ImportTransaction
             $this->meta        = [];
             $this->description = '';
             $this->note        = '';
    +
    +        // mappable items, set to 0:
    +        $this->accountId         = 0;
    +        $this->budgetId          = 0;
    +        $this->billId            = 0;
    +        $this->currencyId        = 0;
    +        $this->categoryId        = 0;
    +        $this->foreignCurrencyId = 0;
    +        $this->opposingId        = 0;
    +
         }
     
    +    /**
    +     * @return string
    +     */
    +    public function getDescription(): string
    +    {
    +        return $this->description;
    +    }
    +
    +    /**
    +     * @return int
    +     */
    +    public function getBillId(): int
    +    {
    +        return $this->billId;
    +    }
    +
    +    /**
    +     * @return null|string
    +     */
    +    public function getBillName(): ?string
    +    {
    +        return $this->billName;
    +    }
    +
    +
    +
         /**
          * @param ColumnValue $columnValue
          *
    @@ -107,9 +179,63 @@ class ImportTransaction
                     // could be the result of a mapping?
                     $this->accountId = $this->getMappedValue($columnValue);
                     break;
    +            case 'account-iban':
    +                $this->accountIban = $columnValue->getValue();
    +                break;
                 case 'account-name':
                     $this->accountName = $columnValue->getValue();
                     break;
    +            case 'account-bic':
    +                $this->accountBic = $columnValue->getValue();
    +                break;
    +            case 'account-number':
    +                $this->accountNumber = $columnValue->getValue();
    +                break;
    +            case'amount_debit':
    +                $this->amountDebit = $columnValue->getValue();
    +                break;
    +            case'amount_credit':
    +                $this->amountCredit = $columnValue->getValue();
    +                break;
    +            case 'amount':
    +                $this->amount = $columnValue->getValue();
    +                break;
    +            case 'amount_foreign':
    +                $this->foreignAmount = $columnValue->getValue();
    +                break;
    +            case 'bill-id':
    +                $this->billId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'bill-name':
    +                $this->billName = $columnValue->getValue();
    +                break;
    +            case 'budget-id':
    +                $this->budgetId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'budget-name':
    +                $this->budgetName = $columnValue->getValue();
    +                break;
    +            case 'category-id':
    +                $this->categoryId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'category-name':
    +                $this->categoryName = $columnValue->getValue();
    +                break;
    +            case 'currency-id':
    +                $this->currencyId = $this->getMappedValue($columnValue);
    +                break;
    +            case 'currency-name':
    +                $this->currencyName = $columnValue->getValue();
    +                break;
    +            case 'currency-code':
    +                $this->currencyCode = $columnValue->getValue();
    +                break;
    +            case 'currency-symbol':
    +                $this->currencySymbol = $columnValue->getValue();
    +                break;
    +            case 'external-id':
    +                $this->externalId = $columnValue->getValue();
    +                break;
                 case 'sepa-ct-id';
                 case 'sepa-ct-op';
                 case 'sepa-db';
    @@ -126,33 +252,14 @@ class ImportTransaction
                 case 'date-due':
                     $this->meta[$columnValue->getRole()] = $columnValue->getValue();
                     break;
    -            case'amount_debit':
    -                $this->amountDebit = $columnValue->getValue();
    -                break;
    -            case'amount_credit':
    -                $this->amountCredit = $columnValue->getValue();
    -                break;
    -            case 'amount':
    -                $this->amount = $columnValue->getValue();
    -                break;
    -            case 'amount_foreign':
    -                $this->foreignAmount = $columnValue->getValue();
    -                break;
    +
                 case 'foreign-currency-id':
                     $this->foreignCurrencyId = $this->getMappedValue($columnValue);
                     break;
    -            case 'bill-id':
    -                $this->billId = $this->getMappedValue($columnValue);
    -                break;
    -            case 'budget-id':
    -                $this->budgetId = $this->getMappedValue($columnValue);
    -                break;
    -            case 'category-id':
    -                $this->categoryId = $this->getMappedValue($columnValue);
    -                break;
    -            case 'currency-id':
    -                $this->currencyId = $this->getMappedValue($columnValue);
    +            case 'foreign-currency-code':
    +                $this->foreignCurrencyCode = $columnValue->getValue();
                     break;
    +
                 case 'date-transaction':
                     $this->date = $columnValue->getValue();
                     break;
    @@ -162,22 +269,28 @@ class ImportTransaction
                 case 'note':
                     $this->note .= $columnValue->getValue();
                     break;
    -            case 'external-id':
    -                $this->externalId = $columnValue->getValue();
    -                break;
    -            case 'rabo-debit-credit':
    -            case 'ing-debit-credit':
    -                $this->modifiers[$columnValue->getRole()] = $columnValue->getValue();
    -                break;
    +
                 case 'opposing-id':
                     $this->opposingId = $this->getMappedValue($columnValue);
                     break;
    +            case 'opposing-iban':
    +                $this->opposingIban = $columnValue->getValue();
    +                break;
                 case 'opposing-name':
                     $this->opposingName = $columnValue->getValue();
                     break;
                 case 'opposing-bic':
                     $this->opposingBic = $columnValue->getValue();
                     break;
    +            case 'opposing-number':
    +                $this->opposingNumber = $columnValue->getValue();
    +                break;
    +
    +            case 'rabo-debit-credit':
    +            case 'ing-debit-credit':
    +                $this->modifiers[$columnValue->getRole()] = $columnValue->getValue();
    +                break;
    +
                 case 'tags-comma':
                     // todo split using pre-processor.
                     $this->tags = $columnValue->getValue();
    @@ -186,25 +299,28 @@ class ImportTransaction
                     // todo split using pre-processor.
                     $this->tags = $columnValue->getValue();
                     break;
    -            case 'account-iban':
    -                $this->accountIban = $columnValue->getValue();
    -                break;
    -            case 'opposing-iban':
    -                $this->opposingIban = $columnValue->getValue();
    -                break;
                 case '_ignore':
    -            case 'bill-name':
    -            case 'currency-name':
    -            case 'currency-code':
    -            case 'foreign-currency-code':
    -            case 'currency-symbol':
    -            case 'budget-name':
    -            case 'category-name':
    -            case 'account-number':
    -            case 'opposing-number':
    +                break;
    +
             }
         }
     
    +    /**
    +     * @return string
    +     */
    +    public function getDate(): string
    +    {
    +        return $this->date;
    +    }
    +
    +    /**
    +     * @return array
    +     */
    +    public function getTags(): array
    +    {
    +        return $this->tags;
    +    }
    +
         /**
          * Returns the mapped value if it exists in the ColumnValue object.
          *
    diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php
    index e9a0d873d9..0698da05d6 100644
    --- a/app/Support/Import/Routine/File/CSVProcessor.php
    +++ b/app/Support/Import/Routine/File/CSVProcessor.php
    @@ -23,10 +23,16 @@ declare(strict_types=1);
     
     namespace FireflyIII\Support\Import\Routine\File;
     
    +use Carbon\Carbon;
     use FireflyIII\Exceptions\FireflyException;
     use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
     use FireflyIII\Models\Attachment;
     use FireflyIII\Models\ImportJob;
    +use FireflyIII\Repositories\Account\AccountRepositoryInterface;
    +use FireflyIII\Repositories\Bill\BillRepositoryInterface;
    +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
    +use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
    +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
     use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
     use FireflyIII\Support\Import\Placeholder\ColumnValue;
     use FireflyIII\Support\Import\Placeholder\ImportTransaction;
    @@ -78,6 +84,12 @@ class CSVProcessor implements FileProcessorInterface
             // make import objects, according to their role:
             $importables = $this->processLines($lines);
     
    +        // now validate all mapped values:
    +        $this->validateMappedValues();
    +
    +
    +        $array = $this->parseImportables($importables);
    +
             echo '
    ';
             print_r($importables);
             print_r($lines);
    @@ -215,11 +227,87 @@ class CSVProcessor implements FileProcessorInterface
             Log::debug(sprintf('Role was "%s", but because of mapping, role becomes "%s"', $role, $newRole));
     
             // also store the $mapped values in a "mappedValues" array.
    -        $this->mappedValues[$newRole] = $mapped;
    +        $this->mappedValues[$newRole][] = $mapped;
     
             return $newRole;
         }
     
    +    /**
    +     * Each entry is an ImportTransaction that must be converted to an array compatible with the
    +     * journal factory. To do so some stuff must still be resolved. See below.
    +     *
    +     * @param array $importables
    +     *
    +     * @return array
    +     */
    +    private function parseImportables(array $importables): array
    +    {
    +        $array = [];
    +        /** @var ImportTransaction $importable */
    +        foreach ($importables as $importable) {
    +
    +            // todo: verify bill mapping
    +            // todo: verify currency mapping.
    +
    +
    +            $entry = [
    +                'type'               => 'unknown', // todo
    +                'date'               => Carbon::createFromFormat($this->config['date-format'] ?? 'Ymd', $importable->getDate()),
    +                'tags'               => $importable->getTags(), // todo make sure its filled.
    +                'user'               => $this->importJob->user_id,
    +                'notes'              => $importable->getNote(),
    +
    +                // all custom fields:
    +                'internal_reference' => $importable->getMeta()['internal-reference'] ?? null,
    +                'sepa-cc'            => $importable->getMeta()['sepa-cc'] ?? null,
    +                'sepa-ct-op'         => $importable->getMeta()['sepa-ct-op'] ?? null,
    +                'sepa-ct-id'         => $importable->getMeta()['sepa-ct-id'] ?? null,
    +                'sepa-db'            => $importable->getMeta()['sepa-db'] ?? null,
    +                'sepa-country'       => $importable->getMeta()['sepa-countru'] ?? null,
    +                'sepa-ep'            => $importable->getMeta()['sepa-ep'] ?? null,
    +                'sepa-ci'            => $importable->getMeta()['sepa-ci'] ?? null,
    +                'interest_date'      => $importable->getMeta()['date-interest'] ?? null,
    +                'book_date'          => $importable->getMeta()['date-book'] ?? null,
    +                'process_date'       => $importable->getMeta()['date-process'] ?? null,
    +                'due_date'           => $importable->getMeta()['date-due'] ?? null,
    +                'payment_date'       => $importable->getMeta()['date-payment'] ?? null,
    +                'invoice_date'       => $importable->getMeta()['date-invoice'] ?? null,
    +                // todo external ID
    +
    +                // journal data:
    +                'description'        => $importable->getDescription(),
    +                'piggy_bank_id'      => null,
    +                'piggy_bank_name'    => null,
    +                'bill_id'            => $importable->getBillId() === 0 ? null : $importable->getBillId(), //
    +                'bill_name'          => $importable->getBillId() !== 0 ? null : $importable->getBillName(),
    +
    +                // transaction data:
    +                'transactions'       => [
    +                    [
    +                        'currency_id'           => null, // todo find ma
    +                        'currency_code'         => 'EUR',
    +                        'description'           => null,
    +                        'amount'                => random_int(500, 5000) / 100,
    +                        'budget_id'             => null,
    +                        'budget_name'           => null,
    +                        'category_id'           => null,
    +                        'category_name'         => null,
    +                        'source_id'             => null,
    +                        'source_name'           => 'Checking Account',
    +                        'destination_id'        => null,
    +                        'destination_name'      => 'Random expense account #' . random_int(1, 10000),
    +                        'foreign_currency_id'   => null,
    +                        'foreign_currency_code' => null,
    +                        'foreign_amount'        => null,
    +                        'reconciled'            => false,
    +                        'identifier'            => 0,
    +                    ],
    +                ],
    +            ];
    +        }
    +
    +        return $array;
    +    }
     
         /**
          * Process all lines in the CSV file. Each line is processed separately.
    @@ -272,12 +360,73 @@ class CSVProcessor implements FileProcessorInterface
                     $columnValue->setOriginalRole($originalRole);
                     $transaction->addColumnValue($columnValue);
     
    -                // create object that parses this column value.
    -
                     Log::debug(sprintf('Now at column #%d (%s), value "%s"', $column, $role, $value));
                 }
             }
     
             return $transaction;
         }
    +
    +    /**
    +     * For each value that has been mapped, this method will check if the mapped value(s) are actually existing
    +     *
    +     * User may indicate that he wants his categories mapped to category #3, #4, #5 but if #5 is owned by another
    +     * user it will be removed.
    +     *
    +     * @throws FireflyException
    +     */
    +    private function validateMappedValues()
    +    {
    +        foreach ($this->mappedValues as $role => $values) {
    +            $values = array_unique($values);
    +            if (count($values) > 0) {
    +                switch ($role) {
    +                    default:
    +                        throw new FireflyException(sprintf('Cannot validate mapped values for role "%s"', $role));
    +                    case 'opposing-id':
    +                    case 'account-id':
    +                        /** @var AccountRepositoryInterface $repository */
    +                        $repository = app(AccountRepositoryInterface::class);
    +                        $repository->setUser($this->importJob->user);
    +                        $set                       = $repository->getAccountsById($values);
    +                        $valid                     = $set->pluck('id')->toArray();
    +                        $this->mappedValues[$role] = $valid;
    +                        break;
    +                    case 'currency-id':
    +                    case 'foreign-currency-id':
    +                        /** @var CurrencyRepositoryInterface $repository */
    +                        $repository = app(CurrencyRepositoryInterface::class);
    +                        $repository->setUser($this->importJob->user);
    +                        $set                       = $repository->getByIds($values);
    +                        $valid                     = $set->pluck('id')->toArray();
    +                        $this->mappedValues[$role] = $valid;
    +                        break;
    +                    case 'bill-id':
    +                        /** @var BillRepositoryInterface $repository */
    +                        $repository = app(BillRepositoryInterface::class);
    +                        $repository->setUser($this->importJob->user);
    +                        $set                       = $repository->getByIds($values);
    +                        $valid                     = $set->pluck('id')->toArray();
    +                        $this->mappedValues[$role] = $valid;
    +                        break;
    +                    case 'budget-id':
    +                        /** @var BudgetRepositoryInterface $repository */
    +                        $repository = app(BudgetRepositoryInterface::class);
    +                        $repository->setUser($this->importJob->user);
    +                        $set                       = $repository->getByIds($values);
    +                        $valid                     = $set->pluck('id')->toArray();
    +                        $this->mappedValues[$role] = $valid;
    +                        break;
    +                    case 'category-id':
    +                        /** @var CategoryRepositoryInterface $repository */
    +                        $repository = app(CategoryRepositoryInterface::class);
    +                        $repository->setUser($this->importJob->user);
    +                        $set                       = $repository->getByIds($values);
    +                        $valid                     = $set->pluck('id')->toArray();
    +                        $this->mappedValues[$role] = $valid;
    +                        break;
    +                }
    +            }
    +        }
    +    }
     }
    \ No newline at end of file
    
    From 6ef0eb73d070583030e17b69ee08ebda748f7187 Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Wed, 9 May 2018 20:53:39 +0200
    Subject: [PATCH 053/182] Improve importer.
    
    ---
     .../Account/AccountRepositoryInterface.php    |   5 +-
     .../Account/FindAccountsTrait.php             |   9 +-
     .../Import/Placeholder/ImportTransaction.php  | 260 ++++++++++---
     .../Import/Routine/File/CSVProcessor.php      | 346 ++++++++++++++----
     4 files changed, 488 insertions(+), 132 deletions(-)
    
    diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php
    index 8c8bdaf7ea..7820888a99 100644
    --- a/app/Repositories/Account/AccountRepositoryInterface.php
    +++ b/app/Repositories/Account/AccountRepositoryInterface.php
    @@ -66,10 +66,9 @@ interface AccountRepositoryInterface
          * @param string $number
          * @param array  $types
          *
    -     * @deprecated
    -     * @return Account
    +     * @return Account|null
          */
    -    public function findByAccountNumber(string $number, array $types): Account;
    +    public function findByAccountNumber(string $number, array $types): ?Account;
     
         /**
          * @param string $iban
    diff --git a/app/Repositories/Account/FindAccountsTrait.php b/app/Repositories/Account/FindAccountsTrait.php
    index d520af06fe..79451ff6e4 100644
    --- a/app/Repositories/Account/FindAccountsTrait.php
    +++ b/app/Repositories/Account/FindAccountsTrait.php
    @@ -58,12 +58,9 @@ trait FindAccountsTrait
         /**
          * @param string $number
          * @param array  $types
    -     *
    -     *
    -     * @deprecated
    -     * @return Account
    +     * @return Account|null
          */
    -    public function findByAccountNumber(string $number, array $types): Account
    +    public function findByAccountNumber(string $number, array $types): ?Account
         {
             $query = $this->user->accounts()
                                 ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
    @@ -81,7 +78,7 @@ trait FindAccountsTrait
                 return $accounts->first();
             }
     
    -        return new Account;
    +        return null;
         }
     
         /**
    diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php
    index e928b7be2b..86b19eb2e9 100644
    --- a/app/Support/Import/Placeholder/ImportTransaction.php
    +++ b/app/Support/Import/Placeholder/ImportTransaction.php
    @@ -24,6 +24,11 @@ declare(strict_types=1);
     namespace FireflyIII\Support\Import\Placeholder;
     
     use FireflyIII\Exceptions\FireflyException;
    +use FireflyIII\Import\Converter\Amount;
    +use FireflyIII\Import\Converter\AmountCredit;
    +use FireflyIII\Import\Converter\AmountDebit;
    +use FireflyIII\Import\Converter\ConverterInterface;
    +use Log;
     
     /**
      * Class ImportTransaction
    @@ -97,24 +102,6 @@ class ImportTransaction
         /** @var array */
         private $tags;
     
    -    /**
    -     * @return array
    -     */
    -    public function getMeta(): array
    -    {
    -        return $this->meta;
    -    }
    -
    -    /**
    -     * @return string
    -     */
    -    public function getNote(): string
    -    {
    -        return $this->note;
    -    }
    -
    -
    -
         /**
          * ImportTransaction constructor.
          */
    @@ -137,32 +124,6 @@ class ImportTransaction
     
         }
     
    -    /**
    -     * @return string
    -     */
    -    public function getDescription(): string
    -    {
    -        return $this->description;
    -    }
    -
    -    /**
    -     * @return int
    -     */
    -    public function getBillId(): int
    -    {
    -        return $this->billId;
    -    }
    -
    -    /**
    -     * @return null|string
    -     */
    -    public function getBillName(): ?string
    -    {
    -        return $this->billName;
    -    }
    -
    -
    -
         /**
          * @param ColumnValue $columnValue
          *
    @@ -305,6 +266,135 @@ class ImportTransaction
             }
         }
     
    +    /**
    +     * Calculate the amount of this transaction.
    +     *
    +     * @return string
    +     * @throws FireflyException
    +     */
    +    public function calculateAmount(): string
    +    {
    +        Log::debug('Now in importTransaction->calculateAmount()');
    +        $info = $this->selectAmountInput();
    +
    +        if (0 === \count($info)) {
    +            throw new FireflyException('No amount information for this row.');
    +        }
    +        $class = $info['class'] ?? '';
    +        if (0 === \strlen($class)) {
    +            throw new FireflyException('No amount information (conversion class) for this row.');
    +        }
    +
    +        Log::debug(sprintf('Converter class is %s', $info['class']));
    +        /** @var ConverterInterface $amountConverter */
    +        $amountConverter = app($info['class']);
    +        $result          = $amountConverter->convert($info['amount']);
    +        Log::debug(sprintf('First attempt to convert gives "%s"', $result));
    +        // modify
    +        /**
    +         * @var string $role
    +         * @var string $modifier
    +         */
    +        foreach ($this->modifiers as $role => $modifier) {
    +            $class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $role)));
    +            /** @var ConverterInterface $converter */
    +            $converter = app($class);
    +            Log::debug(sprintf('Now launching converter %s', $class));
    +            $conversion = $converter->convert($modifier);
    +            if ($conversion === -1) {
    +                $result = app('steam')->negative($result);
    +            }
    +            if ($conversion === 1) {
    +                $result = app('steam')->positive($result);
    +            }
    +            Log::debug(sprintf('convertedAmount after conversion is  %s', $result));
    +        }
    +
    +        Log::debug(sprintf('After modifiers the result is: "%s"', $result));
    +
    +
    +        return $result;
    +    }
    +
    +    /**
    +     * This array is being used to map the account the user is using.
    +     *
    +     * @return array
    +     */
    +    public function getAccountData(): array
    +    {
    +        return [
    +            'iban'   => $this->accountIban,
    +            'name'   => $this->accountName,
    +            'number' => $this->accountNumber,
    +            'bic'    => $this->accountBic,
    +        ];
    +    }
    +
    +    /**
    +     * @return int
    +     */
    +    public function getAccountId(): int
    +    {
    +        return $this->accountId;
    +    }
    +
    +    /**
    +     * @return int
    +     */
    +    public function getBillId(): int
    +    {
    +        return $this->billId;
    +    }
    +
    +    /**
    +     * @return null|string
    +     */
    +    public function getBillName(): ?string
    +    {
    +        return $this->billName;
    +    }
    +
    +    /**
    +     * @return int
    +     */
    +    public function getBudgetId(): int
    +    {
    +        return $this->budgetId;
    +    }
    +
    +    /**
    +     * @return string|null
    +     */
    +    public function getBudgetName(): ?string
    +    {
    +        return $this->budgetName;
    +    }
    +
    +    /**
    +     * @return int
    +     */
    +    public function getCategoryId(): int
    +    {
    +        return $this->categoryId;
    +    }
    +
    +    /**
    +     * @return string|null
    +     */
    +    public function getCategoryName(): ?string
    +    {
    +        return $this->categoryName;
    +    }
    +
    +    /**
    +     * @return int
    +     */
    +    public function getCurrencyId(): int
    +    {
    +        return $this->currencyId;
    +    }
    +
         /**
          * @return string
          */
    @@ -313,11 +403,64 @@ class ImportTransaction
             return $this->date;
         }
     
    +    /**
    +     * @return string
    +     */
    +    public function getDescription(): string
    +    {
    +        return $this->description;
    +    }
    +
    +    /**
    +     * @return int
    +     */
    +    public function getForeignCurrencyId(): int
    +    {
    +        return $this->foreignCurrencyId;
    +    }
    +
    +    /**
    +     * @return array
    +     */
    +    public function getMeta(): array
    +    {
    +        return $this->meta;
    +    }
    +
    +    /**
    +     * @return string
    +     */
    +    public function getNote(): string
    +    {
    +        return $this->note;
    +    }
    +
    +    public function getOpposingAccountData(): array
    +    {
    +        return [
    +            'iban'   => $this->opposingIban,
    +            'name'   => $this->opposingName,
    +            'number' => $this->opposingNumber,
    +            'bic'    => $this->opposingBic,
    +        ];
    +    }
    +
    +    /**
    +     * @return int
    +     */
    +    public function getOpposingId(): int
    +    {
    +        return $this->opposingId;
    +    }
    +
         /**
          * @return array
          */
         public function getTags(): array
         {
    +        return [];
    +
    +        // todo make sure this is an array
             return $this->tags;
         }
     
    @@ -333,4 +476,33 @@ class ImportTransaction
             return $columnValue->getMappedValue() > 0 ? $columnValue->getMappedValue() : (int)$columnValue->getValue();
         }
     
    +    /**
    +     * This methods decides which input value to use for the amount calculation.
    +     *
    +     * @return array
    +     */
    +    private function selectAmountInput()
    +    {
    +        $info           = [];
    +        $converterClass = '';
    +        if (null !== $this->amount) {
    +            Log::debug('Amount value is not NULL, assume this is the correct value.');
    +            $converterClass = Amount::class;
    +            $info['amount'] = $this->amount;
    +        }
    +        if (null !== $this->amountDebit) {
    +            Log::debug('Amount DEBIT value is not NULL, assume this is the correct value (overrules Amount).');
    +            $converterClass = AmountDebit::class;
    +            $info['amount'] = $this->amountDebit;
    +        }
    +        if (null !== $this->amountCredit) {
    +            Log::debug('Amount CREDIT value is not NULL, assume this is the correct value (overrules Amount and AmountDebit).');
    +            $converterClass = AmountCredit::class;
    +            $info['amount'] = $this->amountCredit;
    +        }
    +        $info['class'] = $converterClass;
    +
    +        return $info;
    +    }
    +
     }
    \ No newline at end of file
    diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php
    index 0698da05d6..1edfb5ddd8 100644
    --- a/app/Support/Import/Routine/File/CSVProcessor.php
    +++ b/app/Support/Import/Routine/File/CSVProcessor.php
    @@ -26,6 +26,8 @@ namespace FireflyIII\Support\Import\Routine\File;
     use Carbon\Carbon;
     use FireflyIII\Exceptions\FireflyException;
     use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
    +use FireflyIII\Models\Account;
    +use FireflyIII\Models\AccountType;
     use FireflyIII\Models\Attachment;
     use FireflyIII\Models\ImportJob;
     use FireflyIII\Repositories\Account\AccountRepositoryInterface;
    @@ -50,6 +52,8 @@ use Log;
      */
     class CSVProcessor implements FileProcessorInterface
     {
    +    /** @var AccountRepositoryInterface */
    +    private $accountRepos;
         /** @var AttachmentHelperInterface */
         private $attachments;
         /** @var array */
    @@ -69,7 +73,7 @@ class CSVProcessor implements FileProcessorInterface
          */
         public function run(): array
         {
    -
    +        Log::debug('Now in CSVProcessor() run');
     
             // in order to actually map we also need to read the FULL file.
             try {
    @@ -91,6 +95,7 @@ class CSVProcessor implements FileProcessorInterface
             $array = $this->parseImportables($importables);
     
             echo '
    ';
    +        print_r($array);
             print_r($importables);
             print_r($lines);
     
    @@ -103,11 +108,14 @@ class CSVProcessor implements FileProcessorInterface
          */
         public function setJob(ImportJob $job): void
         {
    -        $this->importJob   = $job;
    -        $this->config      = $job->configuration;
    -        $this->repository  = app(ImportJobRepositoryInterface::class);
    -        $this->attachments = app(AttachmentHelperInterface::class);
    +        Log::debug('Now in setJob()');
    +        $this->importJob    = $job;
    +        $this->config       = $job->configuration;
    +        $this->repository   = app(ImportJobRepositoryInterface::class);
    +        $this->attachments  = app(AttachmentHelperInterface::class);
    +        $this->accountRepos = app(AccountRepositoryInterface::class);
             $this->repository->setUser($job->user);
    +        $this->accountRepos->setUser($job->user);
     
         }
     
    @@ -121,6 +129,7 @@ class CSVProcessor implements FileProcessorInterface
          */
         private function getLines(Reader $reader): array
         {
    +        Log::debug('now in getLines()');
             $offset = isset($this->config['has-headers']) && $this->config['has-headers'] === true ? 1 : 0;
             try {
                 $stmt = (new Statement)->offset($offset);
    @@ -143,6 +152,7 @@ class CSVProcessor implements FileProcessorInterface
          */
         private function getReader(): Reader
         {
    +        Log::debug('Now in getReader()');
             $content = '';
             /** @var Collection $collection */
             $collection = $this->importJob->attachments;
    @@ -195,28 +205,28 @@ class CSVProcessor implements FileProcessorInterface
                 case 'account-number':
                     $newRole = 'account-id';
                     break;
    -            case 'foreign-currency-id':
    -            case 'foreign-currency-code':
    -                $newRole = 'foreign-currency-id';
    -                break;
                 case 'bill-id':
                 case 'bill-name':
                     $newRole = 'bill-id';
                     break;
    +            case 'budget-id':
    +            case 'budget-name':
    +                $newRole = 'budget-id';
    +                break;
                 case 'currency-id':
                 case 'currency-name':
                 case 'currency-code':
                 case 'currency-symbol':
                     $newRole = 'currency-id';
                     break;
    -            case 'budget-id':
    -            case 'budget-name':
    -                $newRole = 'budget-id';
    -                break;
                 case 'category-id':
                 case 'category-name':
                     $newRole = 'category-id';
                     break;
    +            case 'foreign-currency-id':
    +            case 'foreign-currency-code':
    +                $newRole = 'foreign-currency-id';
    +                break;
                 case 'opposing-id':
                 case 'opposing-name':
                 case 'opposing-iban':
    @@ -232,6 +242,120 @@ class CSVProcessor implements FileProcessorInterface
             return $newRole;
         }
     
    +    /**
    +     * Based upon data in the importable, try to find or create the asset account account.
    +     *
    +     * @param $importable
    +     *
    +     * @return Account
    +     */
    +    private function mapAssetAccount(?int $accountId, array $accountData): Account
    +    {
    +        Log::debug('Now in mapAssetAccount()');
    +        if ((int)$accountId > 0) {
    +            // find asset account with this ID:
    +            $result = $this->accountRepos->findNull($accountId);
    +            if (null !== $result) {
    +                Log::debug(sprintf('Found account "%s" based on given ID %d. Return it!', $result->name, $accountId));
    +
    +                return $result;
    +            }
    +        }
    +        // find by (respectively):
    +        // IBAN, accountNumber, name,
    +        $fields = ['iban' => 'findByIbanNull', 'number' => 'findByAccountNumber', 'name' => 'findByName'];
    +        foreach ($fields as $field => $function) {
    +            $value = $accountData[$field];
    +            if (null === $value) {
    +                continue;
    +            }
    +            $result = $this->accountRepos->$function($value, [AccountType::ASSET]);
    +            Log::debug(sprintf('Going to run %s() with argument "%s" (asset account)', $function, $value));
    +            if (null !== $result) {
    +                Log::debug(sprintf('Found asset account "%s". Return it!', $result->name, $accountId));
    +
    +                return $result;
    +            }
    +        }
    +        Log::debug('Found nothing. Will return default account.');
    +        // still NULL? Return default account.
    +        $default = null;
    +        if (isset($this->config['import-account'])) {
    +            $default = $this->accountRepos->findNull((int)$this->config['import-account']);
    +        }
    +        if (null === $default) {
    +            Log::debug('Default account is NULL! Simply result first account in system.');
    +            $default = $this->accountRepos->getAccountsByType([AccountType::ASSET])->first();
    +        }
    +
    +        Log::debug(sprintf('Return default account "%s" (#%d). Return it!', $default->name, $default->id));
    +
    +        return $default;
    +    }
    +
    +    /**
    +     * @param int|null $accountId
    +     * @param string   $amount
    +     * @param array    $accountData
    +     *
    +     * @return Account
    +     */
    +    private function mapOpposingAccount(?int $accountId, string $amount, array $accountData): Account
    +    {
    +        Log::debug('Now in mapOpposingAccount()');
    +        if ((int)$accountId > 0) {
    +            // find any account with this ID:
    +            $result = $this->accountRepos->findNull($accountId);
    +            if (null !== $result) {
    +                Log::debug(sprintf('Found account "%s" (%s) based on given ID %d. Return it!', $result->name, $result->accountType->type, $accountId));
    +
    +                return $result;
    +            }
    +        }
    +        // default assumption is we're looking for an expense account.
    +        $expectedType = AccountType::EXPENSE;
    +        Log::debug(sprintf('Going to search for accounts of type %s', $expectedType));
    +        if (bccomp($amount, '0') === 1) {
    +            // more than zero.
    +            $expectedType = AccountType::REVENUE;
    +            Log::debug(sprintf('Because amount is %s, will instead search for accounts of type %s', $amount, $expectedType));
    +        }
    +
    +        // first search for $expectedType, then find asset:
    +        $searchTypes = [$expectedType, AccountType::ASSET];
    +        foreach ($searchTypes as $type) {
    +            // find by (respectively):
    +            // IBAN, accountNumber, name,
    +            $fields = ['iban' => 'findByIbanNull', 'number' => 'findByAccountNumber', 'name' => 'findByName'];
    +            foreach ($fields as $field => $function) {
    +                $value = $accountData[$field];
    +                if (null === $value) {
    +                    continue;
    +                }
    +                Log::debug(sprintf('Will search for account of type "%s" using %s() and argument %s.', $type, $function, $value));
    +                $result = $this->accountRepos->$function($value, [$type]);
    +                if (null !== $result) {
    +                    Log::debug(sprintf('Found result: Account #%d, named "%s"', $result->id, $result->name));
    +
    +                    return $result;
    +                }
    +            }
    +        }
    +        // not found? Create it!
    +        $creation = [
    +            'name'            => $accountData['name'],
    +            'iban'            => $accountData['iban'],
    +            'accountNumber'   => $accountData['number'],
    +            'account_type_id' => null,
    +            'accountType'     => $expectedType,
    +            'active'          => true,
    +            'BIC'             => $accountData['bic'],
    +        ];
    +        Log::debug('Will try to store a new account: ', $creation);
    +
    +        return $this->accountRepos->store($creation);
    +    }
    +
         /**
          * Each entry is an ImportTransaction that must be converted to an array compatible with the
          * journal factory. To do so some stuff must still be resolved. See below.
    @@ -239,76 +363,119 @@ class CSVProcessor implements FileProcessorInterface
          * @param array $importables
          *
          * @return array
    +     * @throws FireflyException
          */
         private function parseImportables(array $importables): array
         {
    +        Log::debug('Now in parseImportables()');
             $array = [];
    +        $total = count($importables);
             /** @var ImportTransaction $importable */
    -        foreach ($importables as $importable) {
    -
    -            // todo: verify bill mapping
    -            // todo: verify currency mapping.
    -
    -
    -            $entry = [
    -                'type'               => 'unknown', // todo
    -                'date'               => Carbon::createFromFormat($this->config['date-format'] ?? 'Ymd', $importable->getDate()),
    -                'tags'               => $importable->getTags(), // todo make sure its filled.
    -                'user'               => $this->importJob->user_id,
    -                'notes'              => $importable->getNote(),
    -
    -                // all custom fields:
    -                'internal_reference' => $importable->getMeta()['internal-reference'] ?? null,
    -                'sepa-cc'            => $importable->getMeta()['sepa-cc'] ?? null,
    -                'sepa-ct-op'         => $importable->getMeta()['sepa-ct-op'] ?? null,
    -                'sepa-ct-id'         => $importable->getMeta()['sepa-ct-id'] ?? null,
    -                'sepa-db'            => $importable->getMeta()['sepa-db'] ?? null,
    -                'sepa-country'       => $importable->getMeta()['sepa-countru'] ?? null,
    -                'sepa-ep'            => $importable->getMeta()['sepa-ep'] ?? null,
    -                'sepa-ci'            => $importable->getMeta()['sepa-ci'] ?? null,
    -                'interest_date'      => $importable->getMeta()['date-interest'] ?? null,
    -                'book_date'          => $importable->getMeta()['date-book'] ?? null,
    -                'process_date'       => $importable->getMeta()['date-process'] ?? null,
    -                'due_date'           => $importable->getMeta()['date-due'] ?? null,
    -                'payment_date'       => $importable->getMeta()['date-payment'] ?? null,
    -                'invoice_date'       => $importable->getMeta()['date-invoice'] ?? null,
    -                // todo external ID
    -
    -                // journal data:
    -                'description'        => $importable->getDescription(),
    -                'piggy_bank_id'      => null,
    -                'piggy_bank_name'    => null,
    -                'bill_id'            => $importable->getBillId() === 0 ? null : $importable->getBillId(), //
    -                'bill_name'          => $importable->getBillId() !== 0 ? null : $importable->getBillName(),
    -
    -                // transaction data:
    -                'transactions'       => [
    -                    [
    -                        'currency_id'           => null, // todo find ma
    -                        'currency_code'         => 'EUR',
    -                        'description'           => null,
    -                        'amount'                => random_int(500, 5000) / 100,
    -                        'budget_id'             => null,
    -                        'budget_name'           => null,
    -                        'category_id'           => null,
    -                        'category_name'         => null,
    -                        'source_id'             => null,
    -                        'source_name'           => 'Checking Account',
    -                        'destination_id'        => null,
    -                        'destination_name'      => 'Random expense account #' . random_int(1, 10000),
    -                        'foreign_currency_id'   => null,
    -                        'foreign_currency_code' => null,
    -                        'foreign_amount'        => null,
    -                        'reconciled'            => false,
    -                        'identifier'            => 0,
    -                    ],
    -                ],
    -            ];
    +        foreach ($importables as $index => $importable) {
    +            Log::debug(sprintf('Now going to parse importable %d of %d', $index + 1, $total));
    +            $array[] = $this->parseSingleImportable($importable);
             }
     
             return $array;
         }
     
    +    /**
    +     * @param ImportTransaction $importable
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    private function parseSingleImportable(ImportTransaction $importable): array
    +    {
    +
    +        $amount = $importable->calculateAmount();
    +
    +        /**
    +         * first finalise the amount. cehck debit en credit.
    +         * then get the accounts.
    +         * ->account always assumes were looking for an asset account.
    +         *   cannot create anything, will return the default account when nothing comes up.
    +         *
    +         * neg + account = assume asset account?
    +         * neg = assume withdrawal
    +         * pos = assume
    +         */
    +
    +        $accountId         = $this->verifyObjectId('account-id', $importable->getAccountId());
    +        $billId            = $this->verifyObjectId('bill-id', $importable->getForeignCurrencyId());
    +        $budgetId          = $this->verifyObjectId('budget-id', $importable->getBudgetId());
    +        $currencyId        = $this->verifyObjectId('currency-id', $importable->getForeignCurrencyId());
    +        $categoryId        = $this->verifyObjectId('category-id', $importable->getCategoryId());
    +        $foreignCurrencyId = $this->verifyObjectId('foreign-currency-id', $importable->getForeignCurrencyId());
    +        $opposingId        = $this->verifyObjectId('opposing-id', $importable->getOpposingId());
    +        // also needs amount to be final.
    +        //$account           = $this->mapAccount($accountId, $importable->getAccountData());
    +        $asset    = $this->mapAssetAccount($accountId, $importable->getAccountData());
    +        $sourceId = $asset->id;
    +        $opposing = $this->mapOpposingAccount($opposingId, $amount, $importable->getOpposingAccountData());
    +        $destId   = $opposing->id;
    +
    +        if (bccomp($amount, '0') === 1) {
    +            // amount is positive? Then switch:
    +            [$destId, $sourceId] = [$sourceId, $destId];
    +        }
    +
    +        return [
    +            'type'               => 'unknown', // todo
    +            'date'               => Carbon::createFromFormat($this->config['date-format'] ?? 'Ymd', $importable->getDate()),
    +            'tags'               => $importable->getTags(), // todo make sure its filled.
    +            'user'               => $this->importJob->user_id,
    +            'notes'              => $importable->getNote(),
    +
    +            // all custom fields:
    +            'internal_reference' => $importable->getMeta()['internal-reference'] ?? null,
    +            'sepa-cc'            => $importable->getMeta()['sepa-cc'] ?? null,
    +            'sepa-ct-op'         => $importable->getMeta()['sepa-ct-op'] ?? null,
    +            'sepa-ct-id'         => $importable->getMeta()['sepa-ct-id'] ?? null,
    +            'sepa-db'            => $importable->getMeta()['sepa-db'] ?? null,
    +            'sepa-country'       => $importable->getMeta()['sepa-countru'] ?? null,
    +            'sepa-ep'            => $importable->getMeta()['sepa-ep'] ?? null,
    +            'sepa-ci'            => $importable->getMeta()['sepa-ci'] ?? null,
    +            'interest_date'      => $importable->getMeta()['date-interest'] ?? null,
    +            'book_date'          => $importable->getMeta()['date-book'] ?? null,
    +            'process_date'       => $importable->getMeta()['date-process'] ?? null,
    +            'due_date'           => $importable->getMeta()['date-due'] ?? null,
    +            'payment_date'       => $importable->getMeta()['date-payment'] ?? null,
    +            'invoice_date'       => $importable->getMeta()['date-invoice'] ?? null,
    +            // todo external ID
    +
    +            // journal data:
    +            'description'        => $importable->getDescription(),
    +            'piggy_bank_id'      => null,
    +            'piggy_bank_name'    => null,
    +            'bill_id'            => $billId,
    +            'bill_name'          => null === $budgetId ? $importable->getBillName() : null,
    +
    +            // transaction data:
    +            'transactions'       => [
    +                [
    +                    'currency_id'           => $currencyId, // todo what if null?
    +                    'currency_code'         => null,
    +                    'description'           => $importable->getDescription(),
    +                    'amount'                => $amount,
    +                    'budget_id'             => $budgetId,
    +                    'budget_name'           => null === $budgetId ? $importable->getBudgetName() : null,
    +                    'category_id'           => $categoryId,
    +                    'category_name'         => null === $categoryId ? $importable->getCategoryName() : null,
    +                    'source_id'             => $sourceId,
    +                    'source_name'           => null,
    +                    'destination_id'        => $destId,
    +                    'destination_name'      => null,
    +                    'foreign_currency_id'   => $foreignCurrencyId,
    +                    'foreign_currency_code' => null,
    +                    'foreign_amount'        => null, // todo get me.
    +                    'reconciled'            => false,
    +                    'identifier'            => 0,
    +                ],
    +            ],
    +        ];
    +    }
    +
         /**
          * Process all lines in the CSV file. Each line is processed separately.
          *
    @@ -319,6 +486,7 @@ class CSVProcessor implements FileProcessorInterface
          */
         private function processLines(array $lines): array
         {
    +        Log::debug('Now in processLines()');
             $processed = [];
             $count     = \count($lines);
             foreach ($lines as $index => $line) {
    @@ -341,11 +509,14 @@ class CSVProcessor implements FileProcessorInterface
          */
         private function processSingleLine(array $line): ImportTransaction
         {
    +        Log::debug('Now in processSingleLine()');
             $transaction = new ImportTransaction;
             // todo run all specifics on row.
             foreach ($line as $column => $value) {
    +
                 $value        = trim($value);
                 $originalRole = $this->config['column-roles'][$column] ?? '_ignore';
    +            Log::debug(sprintf('Now at column #%d (%s), value "%s"', $column, $originalRole, $value));
                 if (\strlen($value) > 0 && $originalRole !== '_ignore') {
     
                     // is a mapped value present?
    @@ -359,8 +530,9 @@ class CSVProcessor implements FileProcessorInterface
                     $columnValue->setMappedValue($mapped);
                     $columnValue->setOriginalRole($originalRole);
                     $transaction->addColumnValue($columnValue);
    -
    -                Log::debug(sprintf('Now at column #%d (%s), value "%s"', $column, $role, $value));
    +            }
    +            if ('' === $value) {
    +                Log::debug('Column skipped because value is empty.');
                 }
             }
     
    @@ -377,6 +549,7 @@ class CSVProcessor implements FileProcessorInterface
          */
         private function validateMappedValues()
         {
    +        Log::debug('Now in validateMappedValues()');
             foreach ($this->mappedValues as $role => $values) {
                 $values = array_unique($values);
                 if (count($values) > 0) {
    @@ -385,10 +558,7 @@ class CSVProcessor implements FileProcessorInterface
                             throw new FireflyException(sprintf('Cannot validate mapped values for role "%s"', $role));
                         case 'opposing-id':
                         case 'account-id':
    -                        /** @var AccountRepositoryInterface $repository */
    -                        $repository = app(AccountRepositoryInterface::class);
    -                        $repository->setUser($this->importJob->user);
    -                        $set                       = $repository->getAccountsById($values);
    +                        $set                       = $this->accountRepos->getAccountsById($values);
                             $valid                     = $set->pluck('id')->toArray();
                             $this->mappedValues[$role] = $valid;
                             break;
    @@ -429,4 +599,22 @@ class CSVProcessor implements FileProcessorInterface
                 }
             }
         }
    +
    +    /**
    +     * A small function that verifies if this particular key (ID) is present in the list
    +     * of valid keys.
    +     *
    +     * @param string $key
    +     * @param int    $objectId
    +     *
    +     * @return int|null
    +     */
    +    private function verifyObjectId(string $key, int $objectId): ?int
    +    {
    +        if (isset($this->mappedValues[$key]) && in_array($objectId, $this->mappedValues[$key])) {
    +            return $objectId;
    +        }
    +
    +        return null;
    +    }
     }
    \ No newline at end of file
    
    From 116f7ed6131ac04ee3f7bfba3b57a769c104c47a Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Thu, 10 May 2018 06:26:57 +0200
    Subject: [PATCH 054/182] First attempt to run file import.
    
    ---
     app/Import/Routine/FileRoutine.php            | 10 +---
     .../Import/Routine/File/CSVProcessor.php      | 58 +++++++++++++------
     2 files changed, 42 insertions(+), 26 deletions(-)
    
    diff --git a/app/Import/Routine/FileRoutine.php b/app/Import/Routine/FileRoutine.php
    index f39153c91b..d6237568a5 100644
    --- a/app/Import/Routine/FileRoutine.php
    +++ b/app/Import/Routine/FileRoutine.php
    @@ -61,17 +61,13 @@ class FileRoutine implements RoutineInterface
                     $processor->setJob($this->importJob);
                     $transactions = $processor->run();
     
    -                // make processor run.
    -                // then done!
    -                // move to status 'processor_finished'.
    -                // $this->repository->setStatus($this->importJob, 'provider_finished');
    -                // $this->repository->setStage($this->importJob, 'final');
    +                $this->repository->setStatus($this->importJob, 'provider_finished');
    +                $this->repository->setStage($this->importJob, 'final');
    +                $this->repository->setTransactions($this->importJob, $transactions);
                     break;
                 default:
                     throw new FireflyException(sprintf('Import routine cannot handle stage "%s"', $this->importJob->stage));
             }
    -
    -        exit;
         }
     
         /**
    diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php
    index 1edfb5ddd8..b7dafd4ab9 100644
    --- a/app/Support/Import/Routine/File/CSVProcessor.php
    +++ b/app/Support/Import/Routine/File/CSVProcessor.php
    @@ -24,6 +24,7 @@ declare(strict_types=1);
     namespace FireflyIII\Support\Import\Routine\File;
     
     use Carbon\Carbon;
    +use Carbon\Exceptions\InvalidDateException;
     use FireflyIII\Exceptions\FireflyException;
     use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
     use FireflyIII\Models\Account;
    @@ -91,16 +92,7 @@ class CSVProcessor implements FileProcessorInterface
             // now validate all mapped values:
             $this->validateMappedValues();
     
    -
    -        $array = $this->parseImportables($importables);
    -
    -        echo '
    ';
    -        print_r($array);
    -        print_r($importables);
    -        print_r($lines);
    -
    -        exit;
    -        die('here we are');
    +        return $this->parseImportables($importables);
         }
     
         /**
    @@ -401,6 +393,7 @@ class CSVProcessor implements FileProcessorInterface
              * pos = assume
              */
     
    +        $transactionType   = 'unknown';
             $accountId         = $this->verifyObjectId('account-id', $importable->getAccountId());
             $billId            = $this->verifyObjectId('bill-id', $importable->getForeignCurrencyId());
             $budgetId          = $this->verifyObjectId('budget-id', $importable->getBudgetId());
    @@ -410,19 +403,45 @@ class CSVProcessor implements FileProcessorInterface
             $opposingId        = $this->verifyObjectId('opposing-id', $importable->getOpposingId());
             // also needs amount to be final.
             //$account           = $this->mapAccount($accountId, $importable->getAccountData());
    -        $asset    = $this->mapAssetAccount($accountId, $importable->getAccountData());
    -        $sourceId = $asset->id;
    -        $opposing = $this->mapOpposingAccount($opposingId, $amount, $importable->getOpposingAccountData());
    -        $destId   = $opposing->id;
    +        $source      = $this->mapAssetAccount($accountId, $importable->getAccountData());
    +        $destination = $this->mapOpposingAccount($opposingId, $amount, $importable->getOpposingAccountData());
     
             if (bccomp($amount, '0') === 1) {
                 // amount is positive? Then switch:
    -            [$destId, $sourceId] = [$sourceId, $destId];
    +            [$destination, $source] = [$source, $destination];
             }
     
    +        if ($source->accountType->type === AccountType::ASSET && $destination->accountType->type === AccountType::ASSET) {
    +            $transactionType = 'transfer';
    +        }
    +        if ($source->accountType->type === AccountType::REVENUE) {
    +            $transactionType = 'deposit';
    +        }
    +        if ($destination->accountType->type === AccountType::EXPENSE) {
    +            $transactionType = 'withdrawal';
    +        }
    +        if ($transactionType === 'unknown') {
    +            Log::error(
    +                sprintf(
    +                    'Cannot determine transaction type. Source account is a %s, destination is a %s',
    +                    $source->accountType->type, $destination->accountType->type
    +                ), ['source' => $source->toArray(), 'dest' => $destination->toArray()]
    +            );
    +        }
    +
    +        try {
    +            $date = Carbon::createFromFormat($this->config['date-format'] ?? 'Ymd', $importable->getDate());
    +        } catch (InvalidDateException $e) {
    +            Log::error($e->getMessage());
    +            Log::error($e->getTraceAsString());
    +            $date = new Carbon;
    +        }
    +
    +        $dateStr = $date->format('Y-m-d');
    +
             return [
    -            'type'               => 'unknown', // todo
    -            'date'               => Carbon::createFromFormat($this->config['date-format'] ?? 'Ymd', $importable->getDate()),
    +            'type'               => $transactionType,
    +            'date'               => $dateStr,
                 'tags'               => $importable->getTags(), // todo make sure its filled.
                 'user'               => $this->importJob->user_id,
                 'notes'              => $importable->getNote(),
    @@ -462,9 +481,9 @@ class CSVProcessor implements FileProcessorInterface
                         'budget_name'           => null === $budgetId ? $importable->getBudgetName() : null,
                         'category_id'           => $categoryId,
                         'category_name'         => null === $categoryId ? $importable->getCategoryName() : null,
    -                    'source_id'             => $sourceId,
    +                    'source_id'             => $source->id,
                         'source_name'           => null,
    -                    'destination_id'        => $destId,
    +                    'destination_id'        => $destination->id,
                         'destination_name'      => null,
                         'foreign_currency_id'   => $foreignCurrencyId,
                         'foreign_currency_code' => null,
    @@ -474,6 +493,7 @@ class CSVProcessor implements FileProcessorInterface
                     ],
                 ],
             ];
    +
         }
     
         /**
    
    From cabcb9c6d0456a83c0d8d60c1f58db1614a2ad2b Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Thu, 10 May 2018 09:10:16 +0200
    Subject: [PATCH 055/182] Basic storage routine works, basic file handling.
    
    ---
     app/Factory/TransactionCurrencyFactory.php    |   6 +-
     app/Factory/TransactionFactory.php            |  11 +-
     app/Factory/TransactionJournalFactory.php     |   4 +-
     .../Import/JobStatusController.php            |   4 +-
     app/Import/Converter/RabobankDebitCredit.php  |   6 +
     .../Prerequisites/FilePrerequisites.php       |  81 +--------
     app/Import/Storage/ImportArrayStorage.php     |   7 +-
     .../Currency/CurrencyRepository.php           |   4 +-
     .../Currency/CurrencyRepositoryInterface.php  |   4 +-
     .../Support/TransactionServiceTrait.php       |   1 +
     .../Import/Placeholder/ImportTransaction.php  |  68 +++++++-
     .../Import/Routine/File/CSVProcessor.php      | 159 ++++++++++++++----
     public/js/ff/import/status_v2.js              |  23 ++-
     .../Import/IndexControllerTest.php            |   5 +-
     .../Import/JobStatusControllerTest.php        |  44 ++---
     tests/Unit/Import/Mapper/BillsTest.php        |   8 +-
     16 files changed, 278 insertions(+), 157 deletions(-)
    
    diff --git a/app/Factory/TransactionCurrencyFactory.php b/app/Factory/TransactionCurrencyFactory.php
    index 1c95a60e7f..2196cf4594 100644
    --- a/app/Factory/TransactionCurrencyFactory.php
    +++ b/app/Factory/TransactionCurrencyFactory.php
    @@ -68,7 +68,8 @@ class TransactionCurrencyFactory
             $currencyCode = (string)$currencyCode;
             $currencyId   = (int)$currencyId;
     
    -        if (\strlen($currencyCode) === 0 && (int)$currencyId === 0) {
    +        if ('' === $currencyCode && $currencyId === 0) {
    +            Log::warning('Cannot find anything on empty currency code and empty currency ID!');
                 return null;
             }
     
    @@ -78,6 +79,7 @@ class TransactionCurrencyFactory
                 if (null !== $currency) {
                     return $currency;
                 }
    +            Log::warning(sprintf('Currency ID is %d but found nothing!', $currencyId));
             }
             // then by code:
             if (\strlen($currencyCode) > 0) {
    @@ -85,7 +87,9 @@ class TransactionCurrencyFactory
                 if (null !== $currency) {
                     return $currency;
                 }
    +            Log::warning(sprintf('Currency code is %d but found nothing!', $currencyCode));
             }
    +        Log::warning('Found nothing for currency.');
     
             return null;
         }
    diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php
    index ae8ee25b76..a395793694 100644
    --- a/app/Factory/TransactionFactory.php
    +++ b/app/Factory/TransactionFactory.php
    @@ -31,6 +31,7 @@ use FireflyIII\Models\TransactionType;
     use FireflyIII\Services\Internal\Support\TransactionServiceTrait;
     use FireflyIII\User;
     use Illuminate\Support\Collection;
    +use Log;
     
     /**
      * Class TransactionFactory
    @@ -50,10 +51,17 @@ class TransactionFactory
          */
         public function create(array $data): ?Transaction
         {
    -        $currencyId = isset($data['currency']) ? $data['currency']->id : $data['currency_id'];
    +        Log::debug('Start of TransactionFactory::create()');
    +        $currencyId = $data['currency_id'] ?? null;
    +        $currencyId = isset($data['currency']) ? $data['currency']->id : $currencyId;
    +        Log::debug('We dont make it here');
             if ('' === $data['amount']) {
                 throw new FireflyException('Amount is an empty string, which Firefly III cannot handle. Apologies.');
             }
    +        if (null === $currencyId) {
    +            throw new FireflyException('Cannot store transaction without currency information.');
    +        }
    +        $data['foreign_amount'] = '' === (string)$data['foreign_amount'] ? null : $data['foreign_amount'];
     
             return Transaction::create(
                 [
    @@ -81,6 +89,7 @@ class TransactionFactory
          */
         public function createPair(TransactionJournal $journal, array $data): Collection
         {
    +        Log::debug('Start of TransactionFactory::createPair()');
             // all this data is the same for both transactions:
             $currency    = $this->findCurrency($data['currency_id'], $data['currency_code']);
             $description = $journal->description === $data['description'] ? null : $data['description'];
    diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php
    index 4afdb35465..816fe9ea58 100644
    --- a/app/Factory/TransactionJournalFactory.php
    +++ b/app/Factory/TransactionJournalFactory.php
    @@ -70,8 +70,10 @@ class TransactionJournalFactory
             $factory = app(TransactionFactory::class);
             $factory->setUser($this->user);
     
    +        Log::debug(sprintf('Found %d transactions in array.', \count($data['transactions'])));
             /** @var array $trData */
    -        foreach ($data['transactions'] as $trData) {
    +        foreach ($data['transactions'] as $index => $trData) {
    +            Log::debug(sprintf('Now storing transaction %d of %d', $index + 1, \count($data['transactions'])));
                 $factory->createPair($journal, $trData);
             }
             $journal->completed = true;
    diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php
    index f45a405b6a..d86faa35c9 100644
    --- a/app/Http/Controllers/Import/JobStatusController.php
    +++ b/app/Http/Controllers/Import/JobStatusController.php
    @@ -174,11 +174,11 @@ class JobStatusController extends Controller
         public function store(ImportJob $importJob): JsonResponse
         {
             // catch impossible status:
    -        $allowed = ['provider_finished', 'storing_data'];
    +        $allowed = ['provider_finished', 'storing_data','error'];
             if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) {
                 Log::error('Job is not ready.');
     
    -            return response()->json(['status' => 'NOK', 'message' => 'JobStatusController::start expects status "provider_finished".']);
    +            return response()->json(['status' => 'NOK', 'message' => sprintf('JobStatusController::start expects status "provider_finished" instead of "%s".', $importJob->status)]);
             }
     
             // set job to be storing data:
    diff --git a/app/Import/Converter/RabobankDebitCredit.php b/app/Import/Converter/RabobankDebitCredit.php
    index 242f4ca38f..d426e52b5b 100644
    --- a/app/Import/Converter/RabobankDebitCredit.php
    +++ b/app/Import/Converter/RabobankDebitCredit.php
    @@ -43,6 +43,12 @@ class RabobankDebitCredit implements ConverterInterface
     
                 return -1;
             }
    +        // old format:
    +        if('A' === $value) {
    +            Log::debug('Return -1');
    +
    +            return -1;
    +        }
     
             Log::debug('Return 1');
     
    diff --git a/app/Import/Prerequisites/FilePrerequisites.php b/app/Import/Prerequisites/FilePrerequisites.php
    index 07e236af14..c9200489e3 100644
    --- a/app/Import/Prerequisites/FilePrerequisites.php
    +++ b/app/Import/Prerequisites/FilePrerequisites.php
    @@ -22,95 +22,16 @@ declare(strict_types=1);
     
     namespace FireflyIII\Import\Prerequisites;
     
    -use FireflyIII\Exceptions\FireflyException;
     use FireflyIII\User;
    -use Illuminate\Http\Request;
     use Illuminate\Support\MessageBag;
     
     /**
    - * @deprecated
      * @codeCoverageIgnore
    + *
      * This class contains all the routines necessary to import from a file. Hint: there are none.
      */
     class FilePrerequisites implements PrerequisitesInterface
     {
    -    //    /** @var User */
    -    //    private $user;
    -    //
    -    //    /**
    -    //     * Returns view name that allows user to fill in prerequisites. Currently asks for the API key.
    -    //     *
    -    //     * @return string
    -    //     */
    -    //    public function getView(): string
    -    //    {
    -    //        return '';
    -    //    }
    -    //
    -    //    /**
    -    //     * Returns any values required for the prerequisites-view.
    -    //     *
    -    //     * @return array
    -    //     */
    -    //    public function getViewParameters(): array
    -    //    {
    -    //        return [];
    -    //    }
    -    //
    -    //    /**
    -    //     * Returns if this import method has any special prerequisites such as config
    -    //     * variables or other things. The only thing we verify is the presence of the API key. Everything else
    -    //     * tumbles into place: no installation token? Will be requested. No device server? Will be created. Etc.
    -    //     *
    -    //     * True if prerequisites. False if not.
    -    //     *
    -    //     * @return bool
    -    //     *
    -    //     * @throws FireflyException
    -    //     */
    -    //    public function hasPrerequisites(): bool
    -    //    {
    -    //        if ($this->user->hasRole('demo')) {
    -    //            throw new FireflyException('Apologies, the demo user cannot import files.');
    -    //        }
    -    //
    -    //        return false;
    -    //    }
    -    //
    -    //    /**
    -    //     * Indicate if all prerequisites have been met.
    -    //     *
    -    //     * @return bool
    -    //     */
    -    //    public function isComplete(): bool
    -    //    {
    -    //        // has no prerequisites, so always return true.
    -    //        return true;
    -    //    }
    -    //
    -    //    /**
    -    //     * Set the user for this Prerequisites-routine. Class is expected to implement and save this.
    -    //     *
    -    //     * @param User $user
    -    //     */
    -    //    public function setUser(User $user): void
    -    //    {
    -    //        $this->user = $user;
    -    //
    -    //    }
    -    //
    -    //    /**
    -    //     * This method responds to the user's submission of an API key. It tries to register this instance as a new Firefly III device.
    -    //     * If this fails, the error is returned in a message bag and the user is notified (this is fairly friendly).
    -    //     *
    -    //     * @param Request $request
    -    //     *
    -    //     * @return MessageBag
    -    //     */
    -    //    public function storePrerequisites(Request $request): MessageBag
    -    //    {
    -    //        return new MessageBag;
    -    //    }
         /**
          * Returns view name that allows user to fill in prerequisites.
          *
    diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php
    index 35b49cfe80..294dc9c5e5 100644
    --- a/app/Import/Storage/ImportArrayStorage.php
    +++ b/app/Import/Storage/ImportArrayStorage.php
    @@ -333,8 +333,8 @@ class ImportArrayStorage
                 }
                 $toStore[] = $transaction;
             }
    -
    -        if (\count($toStore) === 0) {
    +        $count = \count($toStore);
    +        if ($count === 0) {
                 Log::info('No transactions to store left!');
     
                 return new Collection;
    @@ -343,7 +343,8 @@ class ImportArrayStorage
             Log::debug('Going to store...');
             // now actually store them:
             $collection = new Collection;
    -        foreach ($toStore as $store) {
    +        foreach ($toStore as $index => $store) {
    +            Log::debug(sprintf('Going to store entry %d of %d', $index + 1, $count));
                 // convert the date to an object:
                 $store['date'] = Carbon::createFromFormat('Y-m-d', $store['date']);
     
    diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php
    index c79aedf8ab..4258432124 100644
    --- a/app/Repositories/Currency/CurrencyRepository.php
    +++ b/app/Repositories/Currency/CurrencyRepository.php
    @@ -305,9 +305,9 @@ class CurrencyRepository implements CurrencyRepositoryInterface
         /**
          * @param array $data
          *
    -     * @return TransactionCurrency
    +     * @return TransactionCurrency|null
          */
    -    public function store(array $data): TransactionCurrency
    +    public function store(array $data): ?TransactionCurrency
         {
             /** @var TransactionCurrencyFactory $factory */
             $factory = app(TransactionCurrencyFactory::class);
    diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php
    index 2462ae8af5..6f1be847d7 100644
    --- a/app/Repositories/Currency/CurrencyRepositoryInterface.php
    +++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php
    @@ -170,9 +170,9 @@ interface CurrencyRepositoryInterface
         /**
          * @param array $data
          *
    -     * @return TransactionCurrency
    +     * @return TransactionCurrency|null
          */
    -    public function store(array $data): TransactionCurrency;
    +    public function store(array $data): ?TransactionCurrency;
     
         /**
          * @param TransactionCurrency $currency
    diff --git a/app/Services/Internal/Support/TransactionServiceTrait.php b/app/Services/Internal/Support/TransactionServiceTrait.php
    index 59cb9e6a49..2a0769df52 100644
    --- a/app/Services/Internal/Support/TransactionServiceTrait.php
    +++ b/app/Services/Internal/Support/TransactionServiceTrait.php
    @@ -247,6 +247,7 @@ trait TransactionServiceTrait
          */
         protected function setForeignAmount(Transaction $transaction, ?string $amount): void
         {
    +        $amount                      = '' === (string)$amount ? null : $amount;
             $transaction->foreign_amount = $amount;
             $transaction->save();
         }
    diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php
    index 86b19eb2e9..7b059695bc 100644
    --- a/app/Support/Import/Placeholder/ImportTransaction.php
    +++ b/app/Support/Import/Placeholder/ImportTransaction.php
    @@ -278,11 +278,15 @@ class ImportTransaction
             $info = $this->selectAmountInput();
     
             if (0 === \count($info)) {
    -            throw new FireflyException('No amount information for this row.');
    +            Log::error('No amount information for this row.');
    +
    +            return '';
             }
             $class = $info['class'] ?? '';
    -        if (0 === \strlen($class)) {
    -            throw new FireflyException('No amount information (conversion class) for this row.');
    +        if ('' === $class) {
    +            Log::error('No amount information (conversion class) for this row.');
    +
    +            return '';
             }
     
             Log::debug(sprintf('Converter class is %s', $info['class']));
    @@ -316,6 +320,47 @@ class ImportTransaction
             return $result;
         }
     
    +    /**
    +     * The method that calculates the foreign amount isn't nearly as complex,\
    +     * because Firefly III only supports one foreign amount field. So the foreign amount is there
    +     * or isn't. That's about it. However, if it's there, modifiers will be applied too.
    +     *
    +     * @return string
    +     */
    +    public function calculateForeignAmount(): string
    +    {
    +        if (null === $this->foreignAmount) {
    +            Log::debug('ImportTransaction holds no foreign amount info.');
    +            return '';
    +        }
    +        /** @var ConverterInterface $amountConverter */
    +        $amountConverter = app(Amount::class);
    +        $result          = $amountConverter->convert($this->foreignAmount);
    +        Log::debug(sprintf('First attempt to convert foreign amount gives "%s"', $result));
    +        /**
    +         * @var string $role
    +         * @var string $modifier
    +         */
    +        foreach ($this->modifiers as $role => $modifier) {
    +            $class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $role)));
    +            /** @var ConverterInterface $converter */
    +            $converter = app($class);
    +            Log::debug(sprintf('Now launching converter %s', $class));
    +            $conversion = $converter->convert($modifier);
    +            if ($conversion === -1) {
    +                $result = app('steam')->negative($result);
    +            }
    +            if ($conversion === 1) {
    +                $result = app('steam')->positive($result);
    +            }
    +            Log::debug(sprintf('Foreign amount after conversion is  %s', $result));
    +        }
    +
    +        Log::debug(sprintf('After modifiers the foreign amount is: "%s"', $result));
    +
    +        return $result;
    +    }
    +
         /**
          * This array is being used to map the account the user is using.
          *
    @@ -387,6 +432,18 @@ class ImportTransaction
             return $this->categoryName;
         }
     
    +    /**
    +     * @return array
    +     */
    +    public function getCurrencyData(): array
    +    {
    +        return [
    +            'name'   => $this->currencyName,
    +            'code'   => $this->currencyCode,
    +            'symbol' => $this->currencySymbol,
    +        ];
    +    }
    +
         /**
          * @return int
          */
    @@ -435,6 +492,9 @@ class ImportTransaction
             return $this->note;
         }
     
    +    /**
    +     * @return array
    +     */
         public function getOpposingAccountData(): array
         {
             return [
    @@ -481,7 +541,7 @@ class ImportTransaction
          *
          * @return array
          */
    -    private function selectAmountInput()
    +    private function selectAmountInput(): array
         {
             $info           = [];
             $converterClass = '';
    diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php
    index b7dafd4ab9..f0edce71e2 100644
    --- a/app/Support/Import/Routine/File/CSVProcessor.php
    +++ b/app/Support/Import/Routine/File/CSVProcessor.php
    @@ -31,6 +31,7 @@ use FireflyIII\Models\Account;
     use FireflyIII\Models\AccountType;
     use FireflyIII\Models\Attachment;
     use FireflyIII\Models\ImportJob;
    +use FireflyIII\Models\TransactionCurrency;
     use FireflyIII\Repositories\Account\AccountRepositoryInterface;
     use FireflyIII\Repositories\Bill\BillRepositoryInterface;
     use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
    @@ -59,6 +60,10 @@ class CSVProcessor implements FileProcessorInterface
         private $attachments;
         /** @var array */
         private $config;
    +    /** @var CurrencyRepositoryInterface */
    +    private $currencyRepos;
    +    /** @var TransactionCurrency */
    +    private $defaultCurrency;
         /** @var ImportJob */
         private $importJob;
         /** @var array */
    @@ -101,13 +106,16 @@ class CSVProcessor implements FileProcessorInterface
         public function setJob(ImportJob $job): void
         {
             Log::debug('Now in setJob()');
    -        $this->importJob    = $job;
    -        $this->config       = $job->configuration;
    -        $this->repository   = app(ImportJobRepositoryInterface::class);
    -        $this->attachments  = app(AttachmentHelperInterface::class);
    -        $this->accountRepos = app(AccountRepositoryInterface::class);
    +        $this->importJob     = $job;
    +        $this->config        = $job->configuration;
    +        $this->repository    = app(ImportJobRepositoryInterface::class);
    +        $this->attachments   = app(AttachmentHelperInterface::class);
    +        $this->accountRepos  = app(AccountRepositoryInterface::class);
    +        $this->currencyRepos = app(CurrencyRepositoryInterface::class);
             $this->repository->setUser($job->user);
             $this->accountRepos->setUser($job->user);
    +        $this->currencyRepos->setUser($job->user);
    +        $this->defaultCurrency = app('amount')->getDefaultCurrencyByUser($job->user);
     
         }
     
    @@ -237,7 +245,8 @@ class CSVProcessor implements FileProcessorInterface
         /**
          * Based upon data in the importable, try to find or create the asset account account.
          *
    -     * @param $importable
    +     * @param int|null $accountId
    +     * @param array    $accountData
          *
          * @return Account
          */
    @@ -247,11 +256,16 @@ class CSVProcessor implements FileProcessorInterface
             if ((int)$accountId > 0) {
                 // find asset account with this ID:
                 $result = $this->accountRepos->findNull($accountId);
    -            if (null !== $result) {
    -                Log::debug(sprintf('Found account "%s" based on given ID %d. Return it!', $result->name, $accountId));
    +            if (null !== $result && $result->accountType->type === AccountType::ASSET) {
    +                Log::debug(sprintf('Found asset account "%s" based on given ID %d', $result->name, $accountId));
     
                     return $result;
                 }
    +            if (null !== $result && $result->accountType->type !== AccountType::ASSET) {
    +                Log::warning(
    +                    sprintf('Found account "%s" based on given ID %d but its a %s, return nothing.', $result->name, $accountId, $result->accountType->type)
    +                );
    +            }
             }
             // find by (respectively):
             // IBAN, accountNumber, name,
    @@ -264,7 +278,7 @@ class CSVProcessor implements FileProcessorInterface
                 $result = $this->accountRepos->$function($value, [AccountType::ASSET]);
                 Log::debug(sprintf('Going to run %s() with argument "%s" (asset account)', $function, $value));
                 if (null !== $result) {
    -                Log::debug(sprintf('Found asset account "%s". Return it!', $result->name, $accountId));
    +                Log::debug(sprintf('Found asset account "%s". Return it!', $result->name));
     
                     return $result;
                 }
    @@ -285,6 +299,49 @@ class CSVProcessor implements FileProcessorInterface
             return $default;
         }
     
    +    /**
    +     * @param int|null $currencyId
    +     * @param array    $currencyData
    +     *
    +     * @return TransactionCurrency|null
    +     */
    +    private function mapCurrency(?int $currencyId, array $currencyData): ?TransactionCurrency
    +    {
    +        if ((int)$currencyId > 0) {
    +            $result = $this->currencyRepos->findNull($currencyId);
    +            if (null !== $result) {
    +                Log::debug(sprintf('Found currency %s based on ID, return it.', $result->code));
    +
    +                return $result;
    +            }
    +        }
    +        // try to find it by all other fields.
    +        $fields = ['code' => 'findByCodeNull', 'symbol' => 'findBySymbolNull', 'name' => 'findByNameNull'];
    +        foreach ($fields as $field => $function) {
    +            $value = $currencyData[$field];
    +            if ('' === (string)$value) {
    +                continue;
    +            }
    +            Log::debug(sprintf('Will search for currency using %s() and argument "%s".', $function, $value));
    +            $result = $this->currencyRepos->$function($value);
    +            if (null !== $result) {
    +                Log::debug(sprintf('Found result: Currency #%d, code "%s"', $result->id, $result->code));
    +
    +                return $result;
    +            }
    +        }
    +        // if still nothing, and fields not null, try to create it
    +        $creation = [
    +            'code'           => $currencyData['code'],
    +            'name'           => $currencyData['name'],
    +            'symbol'         => $currencyData['symbol'],
    +            'decimal_places' => 2,
    +        ];
    +
    +        // could be NULL
    +        return $this->currencyRepos->store($creation);
    +    }
    +
         /**
          * @param int|null $accountId
          * @param string   $amount
    @@ -294,18 +351,9 @@ class CSVProcessor implements FileProcessorInterface
          */
         private function mapOpposingAccount(?int $accountId, string $amount, array $accountData): Account
         {
    -        Log::debug('Now in mapOpposingAccount()');
    -        if ((int)$accountId > 0) {
    -            // find any account with this ID:
    -            $result = $this->accountRepos->findNull($accountId);
    -            if (null !== $result) {
    -                Log::debug(sprintf('Found account "%s" (%s) based on given ID %d. Return it!', $result->name, $result->accountType->type, $accountId));
    -
    -                return $result;
    -            }
    -        }
             // default assumption is we're looking for an expense account.
             $expectedType = AccountType::EXPENSE;
    +        $result       = null;
             Log::debug(sprintf('Going to search for accounts of type %s', $expectedType));
             if (bccomp($amount, '0') === 1) {
                 // more than zero.
    @@ -313,6 +361,37 @@ class CSVProcessor implements FileProcessorInterface
                 Log::debug(sprintf('Because amount is %s, will instead search for accounts of type %s', $amount, $expectedType));
             }
     
    +        Log::debug('Now in mapOpposingAccount()');
    +        if ((int)$accountId > 0) {
    +            // find any account with this ID:
    +            $result = $this->accountRepos->findNull($accountId);
    +            if (null !== $result && $result->accountType->type === $expectedType) {
    +                Log::debug(sprintf('Found account "%s" (%s) based on given ID %d. Return it!', $result->name, $result->accountType->type, $accountId));
    +
    +                return $result;
    +            }
    +            if (null !== $result && $result->accountType->type !== $expectedType) {
    +                Log::warning(
    +                    sprintf(
    +                        'Found account "%s" (%s) based on given ID %d, but need a %s. Return nothing.', $result->name, $result->accountType->type, $accountId,
    +                        $expectedType
    +                    )
    +                );
    +            }
    +        }
    +        // if result is not null, system has found an account
    +        // but it's of the wrong type. If we dont have a name, use
    +        // the result's name, iban in the search below.
    +        if (null !== $result && '' === (string)$accountData['name']) {
    +            Log::debug(sprintf('Will search for account with name "%s" instead of NULL.', $result->name));
    +            $accountData['name'] = $result->name;
    +        }
    +        if (null !== $result && '' === $accountData['iban'] && '' !== (string)$result->iban) {
    +            Log::debug(sprintf('Will search for account with IBAN "%s" instead of NULL.', $result->iban));
    +            $accountData['iban'] = $result->iban;
    +        }
    +
    +
             // first search for $expectedType, then find asset:
             $searchTypes = [$expectedType, AccountType::ASSET];
             foreach ($searchTypes as $type) {
    @@ -321,10 +400,10 @@ class CSVProcessor implements FileProcessorInterface
                 $fields = ['iban' => 'findByIbanNull', 'number' => 'findByAccountNumber', 'name' => 'findByName'];
                 foreach ($fields as $field => $function) {
                     $value = $accountData[$field];
    -                if (null === $value) {
    +                if ('' === (string)$value) {
                         continue;
                     }
    -                Log::debug(sprintf('Will search for account of type "%s" using %s() and argument %s.', $type, $function, $value));
    +                Log::debug(sprintf('Will search for account of type "%s" using %s() and argument "%s".', $type, $function, $value));
                     $result = $this->accountRepos->$function($value, [$type]);
                     if (null !== $result) {
                         Log::debug(sprintf('Found result: Account #%d, named "%s"', $result->id, $result->name));
    @@ -335,7 +414,7 @@ class CSVProcessor implements FileProcessorInterface
             }
             // not found? Create it!
             $creation = [
    -            'name'            => $accountData['name'],
    +            'name'            => $accountData['name'] ?? '(no name)',
                 'iban'            => $accountData['iban'],
                 'accountNumber'   => $accountData['number'],
                 'account_type_id' => null,
    @@ -361,26 +440,40 @@ class CSVProcessor implements FileProcessorInterface
         {
             Log::debug('Now in parseImportables()');
             $array = [];
    -        $total = count($importables);
    +        $total = \count($importables);
             /** @var ImportTransaction $importable */
             foreach ($importables as $index => $importable) {
                 Log::debug(sprintf('Now going to parse importable %d of %d', $index + 1, $total));
    -            $array[] = $this->parseSingleImportable($importable);
    +            $result = $this->parseSingleImportable($index, $importable);
    +            if (null !== $result) {
    +                $array[] = $result;
    +            }
             }
     
             return $array;
         }
     
         /**
    +     * @param int               $index
          * @param ImportTransaction $importable
          *
          * @return array
          * @throws FireflyException
          */
    -    private function parseSingleImportable(ImportTransaction $importable): array
    +    private function parseSingleImportable(int $index, ImportTransaction $importable): ?array
         {
     
    -        $amount = $importable->calculateAmount();
    +        $amount        = $importable->calculateAmount();
    +        $foreignAmount = $importable->calculateForeignAmount();
    +        if ('' === $amount) {
    +            $amount = $foreignAmount;
    +        }
    +        if ('' === $amount) {
    +            $this->repository->addErrorMessage($this->importJob, sprintf('No transaction amount information in row %d', $index + 1));
    +
    +            return null;
    +        }
    +
     
             /**
              * first finalise the amount. cehck debit en credit.
    @@ -397,7 +490,7 @@ class CSVProcessor implements FileProcessorInterface
             $accountId         = $this->verifyObjectId('account-id', $importable->getAccountId());
             $billId            = $this->verifyObjectId('bill-id', $importable->getForeignCurrencyId());
             $budgetId          = $this->verifyObjectId('budget-id', $importable->getBudgetId());
    -        $currencyId        = $this->verifyObjectId('currency-id', $importable->getForeignCurrencyId());
    +        $currencyId        = $this->verifyObjectId('currency-id', $importable->getCurrencyId());
             $categoryId        = $this->verifyObjectId('category-id', $importable->getCategoryId());
             $foreignCurrencyId = $this->verifyObjectId('foreign-currency-id', $importable->getForeignCurrencyId());
             $opposingId        = $this->verifyObjectId('opposing-id', $importable->getOpposingId());
    @@ -405,6 +498,12 @@ class CSVProcessor implements FileProcessorInterface
             //$account           = $this->mapAccount($accountId, $importable->getAccountData());
             $source      = $this->mapAssetAccount($accountId, $importable->getAccountData());
             $destination = $this->mapOpposingAccount($opposingId, $amount, $importable->getOpposingAccountData());
    +        $currency    = $this->mapCurrency($currencyId, $importable->getCurrencyData());
    +        $foreignCurrency = $this->mapCurrency($foreignCurrencyId, $importable->getForeignCurrencyData());
    +        if (null === $currency) {
    +            Log::debug(sprintf('Could not map currency, use default (%s)', $this->defaultCurrency->code));
    +            $currency = $this->defaultCurrency;
    +        }
     
             if (bccomp($amount, '0') === 1) {
                 // amount is positive? Then switch:
    @@ -473,7 +572,7 @@ class CSVProcessor implements FileProcessorInterface
                 // transaction data:
                 'transactions'       => [
                     [
    -                    'currency_id'           => $currencyId, // todo what if null?
    +                    'currency_id'           => $currency->id,
                         'currency_code'         => null,
                         'description'           => $importable->getDescription(),
                         'amount'                => $amount,
    @@ -487,7 +586,7 @@ class CSVProcessor implements FileProcessorInterface
                         'destination_name'      => null,
                         'foreign_currency_id'   => $foreignCurrencyId,
                         'foreign_currency_code' => null,
    -                    'foreign_amount'        => null, // todo get me.
    +                    'foreign_amount'        => $foreignAmount, // todo get me.
                         'reconciled'            => false,
                         'identifier'            => 0,
                     ],
    @@ -537,7 +636,7 @@ class CSVProcessor implements FileProcessorInterface
                 $value        = trim($value);
                 $originalRole = $this->config['column-roles'][$column] ?? '_ignore';
                 Log::debug(sprintf('Now at column #%d (%s), value "%s"', $column, $originalRole, $value));
    -            if (\strlen($value) > 0 && $originalRole !== '_ignore') {
    +            if ($originalRole !== '_ignore' && \strlen($value) > 0) {
     
                     // is a mapped value present?
                     $mapped = $this->config['column-mapping-config'][$column][$value] ?? 0;
    diff --git a/public/js/ff/import/status_v2.js b/public/js/ff/import/status_v2.js
    index de9a12702c..e99f7061a4 100644
    --- a/public/js/ff/import/status_v2.js
    +++ b/public/js/ff/import/status_v2.js
    @@ -28,6 +28,7 @@ var checkNextInterval = 500;
     var maxLoops = 60;
     var totalLoops = 0;
     var startCount = 0;
    +var jobFailed = false;
     
     $(function () {
         "use strict";
    @@ -39,7 +40,12 @@ $(function () {
      */
     function checkJobJSONStatus() {
         console.log('In checkJobJSONStatus()');
    -    $.getJSON(jobStatusUri).done(reportJobJSONDone).fail(reportJobJSONFailure);
    +    if(jobFailed === false) {
    +        $.getJSON(jobStatusUri).done(reportJobJSONDone).fail(reportJobJSONFailure);
    +    }
    +    if(jobFailed === true) {
    +        console.error('Job has failed, will not check.');
    +    }
     }
     
     /**
    @@ -115,12 +121,15 @@ function showJobResults(data) {
      */
     function recheckJobJSONStatus() {
         console.log('In recheckJobJSONStatus()');
    -    if (maxLoops !== 0 && totalLoops < maxLoops) {
    +    if (maxLoops !== 0 && totalLoops < maxLoops && jobFailed === false) {
             timeOutId = setTimeout(checkJobJSONStatus, checkNextInterval);
         }
         if (maxLoops !== 0) {
             console.log('max: ' + maxLoops + ' current: ' + totalLoops);
         }
    +    if(jobFailed === true) {
    +        console.error('Job has failed, will not do recheck.');
    +    }
         totalLoops++;
     }
     
    @@ -133,6 +142,10 @@ function sendJobPOSTStart() {
             console.log('Import job already started!');
             return;
         }
    +    if(jobFailed === true) {
    +        console.log('Job has failed, will not start again.');
    +        return;
    +    }
         console.log('Job was started');
         jobRunRoutineStarted = true;
         $.post(jobStartUri, {_token: token}).fail(reportJobPOSTFailure).done(reportJobPOSTDone)
    @@ -147,6 +160,10 @@ function sendJobPOSTStore() {
             console.log('Store job already started!');
             return;
         }
    +    if(jobFailed === true) {
    +        console.log('Job has failed, will not start again.');
    +        return;
    +    }
         console.log('Storage job has started!');
         jobStorageRoutineStarted = true;
         $.post(jobStorageStartUri, {_token: token}).fail(reportJobPOSTFailure).done(reportJobPOSTDone)
    @@ -162,9 +179,11 @@ function sendJobPOSTStore() {
      */
     function reportJobJSONFailure(xhr, status, error) {
         console.log('In reportJobJSONFailure()');
    +    jobFailed = true;
         // cancel checking again for job status:
         clearTimeout(timeOutId);
     
    +
         // hide status boxes:
         $('.statusbox').hide();
     
    diff --git a/tests/Feature/Controllers/Import/IndexControllerTest.php b/tests/Feature/Controllers/Import/IndexControllerTest.php
    index 0632960131..a9e5c2d659 100644
    --- a/tests/Feature/Controllers/Import/IndexControllerTest.php
    +++ b/tests/Feature/Controllers/Import/IndexControllerTest.php
    @@ -81,7 +81,7 @@ class IndexControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\Import\IndexController
          */
    -    public function testCreateFakeNoPrereq()
    +    public function testCreateFakeNoPrereq(): void
         {
             // mock stuff:
             $repository        = $this->mock(ImportJobRepositoryInterface::class);
    @@ -112,18 +112,15 @@ class IndexControllerTest extends TestCase
     
             // fake prerequisites providers:
             $fake    = $this->mock(FakePrerequisites::class);
    -        $file    = $this->mock(FilePrerequisites::class);
             $bunq    = $this->mock(BunqPrerequisites::class);
             $spectre = $this->mock(SpectrePrerequisites::class);
     
             // call methods:
             $fake->shouldReceive('setUser')->once();
    -        $file->shouldReceive('setUser')->once();
             $bunq->shouldReceive('setUser')->once();
             $spectre->shouldReceive('setUser')->once();
     
             $fake->shouldReceive('isComplete')->once()->andReturn(true);
    -        $file->shouldReceive('isComplete')->once()->andReturn(true);
             $bunq->shouldReceive('isComplete')->once()->andReturn(true);
             $spectre->shouldReceive('isComplete')->once()->andReturn(true);
     
    diff --git a/tests/Feature/Controllers/Import/JobStatusControllerTest.php b/tests/Feature/Controllers/Import/JobStatusControllerTest.php
    index 362f878b0c..486100077c 100644
    --- a/tests/Feature/Controllers/Import/JobStatusControllerTest.php
    +++ b/tests/Feature/Controllers/Import/JobStatusControllerTest.php
    @@ -288,7 +288,7 @@ class JobStatusControllerTest extends TestCase
             $this->be($this->user());
             $response = $this->post(route('import.job.start', [$job->key]));
             $response->assertStatus(200);
    -        $response->assertExactJson(['status' => 'NOK', 'message' => 'JobStatusController::start expects state "ready_to_run".']);
    +        $response->assertExactJson(['status' => 'NOK', 'message' => 'JobStatusController::start expects status "ready_to_run".']);
         }
     
         /**
    @@ -322,26 +322,6 @@ class JobStatusControllerTest extends TestCase
             $response->assertExactJson(['status' => 'OK', 'message' => 'storage_finished']);
         }
     
    -    /**
    -     * @covers \FireflyIII\Http\Controllers\Import\JobStatusController
    -     */
    -    public function testStoreInvalidState(): void
    -    {
    -        $job               = new ImportJob;
    -        $job->user_id      = $this->user()->id;
    -        $job->key          = 'Kfake_job_' . random_int(1, 1000);
    -        $job->status       = 'some_bad_state';
    -        $job->provider     = 'fake';
    -        $job->transactions = [];
    -        $job->file_type    = '';
    -        $job->save();
    -
    -        $this->be($this->user());
    -        $response = $this->post(route('import.job.store', [$job->key]));
    -        $response->assertStatus(200);
    -        $response->assertExactJson(['status' => 'NOK', 'message' => 'JobStatusController::start expects state "provider_finished".']);
    -    }
    -
         /**
          * @covers \FireflyIII\Http\Controllers\Import\JobStatusController
          */
    @@ -372,4 +352,26 @@ class JobStatusControllerTest extends TestCase
             $response->assertStatus(200);
             $response->assertExactJson(['status' => 'NOK', 'message' => 'The import storage routine crashed: Some storage exception.']);
         }
    +
    +    /**
    +     * @covers \FireflyIII\Http\Controllers\Import\JobStatusController
    +     */
    +    public function testStoreInvalidState(): void
    +    {
    +        $job               = new ImportJob;
    +        $job->user_id      = $this->user()->id;
    +        $job->key          = 'Kfake_job_' . random_int(1, 1000);
    +        $job->status       = 'some_bad_state';
    +        $job->provider     = 'fake';
    +        $job->transactions = [];
    +        $job->file_type    = '';
    +        $job->save();
    +
    +        $this->be($this->user());
    +        $response = $this->post(route('import.job.store', [$job->key]));
    +        $response->assertStatus(200);
    +        $response->assertExactJson(
    +            ['status' => 'NOK', 'message' => 'JobStatusController::start expects status "provider_finished" instead of "' . $job->status . '".']
    +        );
    +    }
     }
    diff --git a/tests/Unit/Import/Mapper/BillsTest.php b/tests/Unit/Import/Mapper/BillsTest.php
    index 355bc43a98..2d25dae93e 100644
    --- a/tests/Unit/Import/Mapper/BillsTest.php
    +++ b/tests/Unit/Import/Mapper/BillsTest.php
    @@ -40,11 +40,11 @@ class BillsTest extends TestCase
          */
         public function testGetMapBasic()
         {
    -        $one        = new Bill();
    +        $one        = new Bill;
             $one->id    = 5;
             $one->name  = 'Something';
             $one->match = 'hi,bye';
    -        $two        = new Account;
    +        $two        = new Bill;
             $two->id    = 9;
             $two->name  = 'Else';
             $two->match = 'match';
    @@ -59,8 +59,8 @@ class BillsTest extends TestCase
             // assert this is what the result looks like:
             $result = [
                 0 => (string)trans('import.map_do_not_map'),
    -            9 => 'Else [match]',
    -            5 => 'Something [hi,bye]',
    +            9 => 'Else',
    +            5 => 'Something',
             ];
             $this->assertEquals($result, $mapping);
         }
    
    From 73f29ebf6996a88662fa045c81af98cec8feec2b Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Thu, 10 May 2018 09:18:40 +0200
    Subject: [PATCH 056/182] make sure class is compatible with interface.
    
    ---
     app/Import/Routine/BunqRoutine.php    | 4 +---
     app/Import/Routine/SpectreRoutine.php | 4 +---
     2 files changed, 2 insertions(+), 6 deletions(-)
    
    diff --git a/app/Import/Routine/BunqRoutine.php b/app/Import/Routine/BunqRoutine.php
    index 1d705eba93..6117391639 100644
    --- a/app/Import/Routine/BunqRoutine.php
    +++ b/app/Import/Routine/BunqRoutine.php
    @@ -929,10 +929,8 @@ class BunqRoutine implements RoutineInterface
     
         /**
          * @param ImportJob $job
    -     *
    -     * @return mixed
          */
    -    public function setJob(ImportJob $job)
    +    public function setJob(ImportJob $job): void
         {
             // TODO: Implement setJob() method.
             throw new NotImplementedException;
    diff --git a/app/Import/Routine/SpectreRoutine.php b/app/Import/Routine/SpectreRoutine.php
    index 16eeba2c65..85a3c8f54c 100644
    --- a/app/Import/Routine/SpectreRoutine.php
    +++ b/app/Import/Routine/SpectreRoutine.php
    @@ -608,10 +608,8 @@ class SpectreRoutine implements RoutineInterface
     
         /**
          * @param ImportJob $job
    -     *
    -     * @return mixed
          */
    -    public function setJob(ImportJob $job)
    +    public function setJob(ImportJob $job): void
         {
             // TODO: Implement setJob() method.
             throw new NotImplementedException;
    
    From 6bd23d897f26d8dbf315b4d56914edee6d790e04 Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Thu, 10 May 2018 09:48:13 +0200
    Subject: [PATCH 057/182] Try to get details about OAuth exceptions.
    
    ---
     app/Exceptions/Handler.php | 16 ++++++++++++++--
     1 file changed, 14 insertions(+), 2 deletions(-)
    
    diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
    index 9bcf356980..f3fc3e628c 100644
    --- a/app/Exceptions/Handler.php
    +++ b/app/Exceptions/Handler.php
    @@ -28,6 +28,7 @@ use FireflyIII\Jobs\MailError;
     use Illuminate\Auth\AuthenticationException;
     use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
     use Illuminate\Validation\ValidationException;
    +use League\OAuth2\Server\Exception\OAuthServerException;
     use Request;
     use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
     
    @@ -79,6 +80,11 @@ class Handler extends ExceptionHandler
                 return response()->json(['message' => 'Unauthenticated', 'exception' => 'AuthenticationException'], 401);
             }
     
    +        if ($exception instanceof OAuthServerException && $request->expectsJson()) {
    +            // somehow Laravel handler does not catch this:
    +            return response()->json(['message' => $exception->getMessage(), 'exception' => 'OAuthServerException'], 401);
    +        }
    +
             if ($request->expectsJson()) {
                 $isDebug = config('app.debug', false);
                 if ($isDebug) {
    @@ -96,7 +102,7 @@ class Handler extends ExceptionHandler
                 return response()->json(['message' => 'Internal Firefly III Exception. See log files.', 'exception' => \get_class($exception)], 500);
             }
     
    -        if ($exception instanceof FireflyException || $exception instanceof ErrorException) {
    +        if ($exception instanceof FireflyException || $exception instanceof ErrorException || $exception instanceof OAuthServerException) {
                 $isDebug = env('APP_DEBUG', false);
     
                 return response()->view('errors.FireflyException', ['exception' => $exception, 'debug' => $isDebug], 500);
    @@ -121,7 +127,13 @@ class Handler extends ExceptionHandler
         public function report(Exception $exception)
         {
             $doMailError = env('SEND_ERROR_MESSAGE', true);
    -        if (($exception instanceof FireflyException || $exception instanceof ErrorException) && $doMailError) {
    +        if (
    +            (
    +                $exception instanceof FireflyException
    +                || $exception instanceof ErrorException
    +                || $exception instanceof OAuthServerException
    +            )
    +            && $doMailError) {
                 $userData = [
                     'id'    => 0,
                     'email' => 'unknown@example.com',
    
    From 274162afcdff93c7164d02f9ed716e0b124bd3a5 Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Thu, 10 May 2018 20:05:02 +0200
    Subject: [PATCH 058/182] Fix test that could come up with journals with 0
     transactions, and improve test coverage for file routine.
    
    ---
     .../JobConfiguration/FileJobConfiguration.php |  21 +-
     app/Import/Routine/FileRoutine.php            | 304 +--------------
     app/Import/Routine/RoutineInterface.php       |   1 -
     .../Configuration/File/NewFileJobHandler.php  | 164 +++++---
     .../Import/JobConfigurationControllerTest.php |  50 ++-
     .../Converter/RabobankDebitCreditTest.php     |  44 ++-
     .../FileJobConfigurationTest.php              | 367 ++++++++++++++++++
     tests/Unit/Import/Routine/FakeRoutineTest.php |   6 +-
     tests/Unit/Import/Routine/FileRoutineTest.php |  78 ++++
     .../Triggers/HasAnyCategoryTest.php           |  20 +-
     10 files changed, 655 insertions(+), 400 deletions(-)
     create mode 100644 tests/Unit/Import/JobConfiguration/FileJobConfigurationTest.php
     create mode 100644 tests/Unit/Import/Routine/FileRoutineTest.php
    
    diff --git a/app/Import/JobConfiguration/FileJobConfiguration.php b/app/Import/JobConfiguration/FileJobConfiguration.php
    index 27eaa21e0a..75b97c8aaf 100644
    --- a/app/Import/JobConfiguration/FileJobConfiguration.php
    +++ b/app/Import/JobConfiguration/FileJobConfiguration.php
    @@ -48,6 +48,7 @@ class FileJobConfiguration implements JobConfigurationInterface
          */
         public function __construct()
         {
    +        $this->repository = app(ImportJobRepositoryInterface::class);
         }
     
         /**
    @@ -57,11 +58,7 @@ class FileJobConfiguration implements JobConfigurationInterface
          */
         public function configurationComplete(): bool
         {
    -        if ($this->importJob->stage === 'ready_to_run') {
    -            return true;
    -        }
    -
    -        return false;
    +        return $this->importJob->stage === 'ready_to_run';
         }
     
         /**
    @@ -129,8 +126,7 @@ class FileJobConfiguration implements JobConfigurationInterface
          */
         public function setJob(ImportJob $job): void
         {
    -        $this->importJob  = $job;
    -        $this->repository = app(ImportJobRepositoryInterface::class);
    +        $this->importJob = $job;
             $this->repository->setUser($job->user);
         }
     
    @@ -156,17 +152,6 @@ class FileJobConfiguration implements JobConfigurationInterface
                 case 'map':
                     $class = ConfigureMappingHandler::class;
                     break;
    -            //            case 'upload-config': // has file, needs file config.
    -            //                $class = UploadConfig::class;
    -            //                break;
    -            //            case 'roles': // has configured file, needs roles.
    -            //                $class = Roles::class;
    -            //                break;
    -            //            case 'map': // has roles, needs mapping.
    -            //                $class = Map::class;
    -            //                break;
    -            //            default:
    -            //                break;
             }
             if (!class_exists($class)) {
                 throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class)); // @codeCoverageIgnore
    diff --git a/app/Import/Routine/FileRoutine.php b/app/Import/Routine/FileRoutine.php
    index d6237568a5..d626d83754 100644
    --- a/app/Import/Routine/FileRoutine.php
    +++ b/app/Import/Routine/FileRoutine.php
    @@ -43,7 +43,6 @@ class FileRoutine implements RoutineInterface
          *
          * The final status of the routine must be "provider_finished".
          *
    -     * @return bool
          * @throws FireflyException
          */
         public function run(): void
    @@ -52,22 +51,20 @@ class FileRoutine implements RoutineInterface
             if ($this->importJob->status !== 'running') {
                 throw new FireflyException('This file import job should not be started.'); // @codeCoverageIgnore
             }
    +        if ($this->importJob->stage === 'ready_to_run') {
    +            // get processor, depending on file type
    +            // is just CSV for now.
    +            $processor = $this->getProcessor();
    +            $processor->setJob($this->importJob);
    +            $transactions = $processor->run();
     
    -        switch ($this->importJob->stage) {
    -            case 'ready_to_run':
    -                // get processor, depending on file type
    -                // is just CSV for now.
    -                $processor    = $this->getProcessor();
    -                $processor->setJob($this->importJob);
    -                $transactions = $processor->run();
    +            $this->repository->setStatus($this->importJob, 'provider_finished');
    +            $this->repository->setStage($this->importJob, 'final');
    +            $this->repository->setTransactions($this->importJob, $transactions);
     
    -                $this->repository->setStatus($this->importJob, 'provider_finished');
    -                $this->repository->setStage($this->importJob, 'final');
    -                $this->repository->setTransactions($this->importJob, $transactions);
    -                break;
    -            default:
    -                throw new FireflyException(sprintf('Import routine cannot handle stage "%s"', $this->importJob->stage));
    +            return;
             }
    +        throw new FireflyException(sprintf('Import routine cannot handle stage "%s"', $this->importJob->stage)); // @codeCoverageIgnore
         }
     
         /**
    @@ -93,284 +90,7 @@ class FileRoutine implements RoutineInterface
             $config = $this->repository->getConfiguration($this->importJob);
             $type   = $config['file-type'] ?? 'csv';
             $class  = config(sprintf('import.options.file.processors.%s', $type));
    -        /** @var FileProcessorInterface $object */
    -        $object = app($class);
     
    -        return $object;
    +        return app($class);
         }
    -
    -
    -
    -
    -
    -
    -
    -
    -    //    /** @var Collection */
    -    //    public $errors;
    -    //    /** @var Collection */
    -    //    public $journals;
    -    //    /** @var int */
    -    //    public $lines = 0;
    -    //    /** @var ImportJob */
    -    //    private $job;
    -    //
    -    //    /** @var ImportJobRepositoryInterface */
    -    //    private $repository;
    -    //
    -    //    /**
    -    //     * ImportRoutine constructor.
    -    //     */
    -    //    public function __construct()
    -    //    {
    -    //        $this->journals = new Collection;
    -    //        $this->errors   = new Collection;
    -    //    }
    -    //
    -    //    /**
    -    //     * @return Collection
    -    //     */
    -    //    public function getErrors(): Collection
    -    //    {
    -    //        return $this->errors;
    -    //    }
    -    //
    -    //    /**
    -    //     * @return Collection
    -    //     */
    -    //    public function getJournals(): Collection
    -    //    {
    -    //        return $this->journals;
    -    //    }
    -    //
    -    //    /**
    -    //     * @return int
    -    //     */
    -    //    public function getLines(): int
    -    //    {
    -    //        return $this->lines;
    -    //    }
    -    //
    -    //    /**
    -    //     *
    -    //     */
    -    //    public function run(): bool
    -    //    {
    -    //        if ('configured' !== $this->getStatus()) {
    -    //            Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->getStatus()));
    -    //
    -    //            return false;
    -    //        }
    -    //        set_time_limit(0);
    -    //        Log::info(sprintf('Start with import job %s', $this->job->key));
    -    //
    -    //        // total steps: 6
    -    //        $this->setTotalSteps(6);
    -    //
    -    //        $importObjects = $this->getImportObjects();
    -    //        $this->lines   = $importObjects->count();
    -    //        $this->addStep();
    -    //
    -    //        // total steps can now be extended. File has been scanned. 7 steps per line:
    -    //        $this->addTotalSteps(7 * $this->lines);
    -    //
    -    //        // once done, use storage thing to actually store them:
    -    //        Log::info(sprintf('Returned %d valid objects from file processor', $this->lines));
    -    //
    -    //        $storage = $this->storeObjects($importObjects);
    -    //        $this->addStep();
    -    //        Log::debug('Back in run()');
    -    //
    -    //        Log::debug('Updated job...');
    -    //        Log::debug(sprintf('%d journals in $storage->journals', $storage->journals->count()));
    -    //        $this->journals = $storage->journals;
    -    //        $this->errors   = $storage->errors;
    -    //
    -    //        Log::debug('Going to call createImportTag()');
    -    //
    -    //        // create tag, link tag to all journals:
    -    //        $this->createImportTag();
    -    //        $this->addStep();
    -    //
    -    //        // update job:
    -    //        $this->setStatus('finished');
    -    //
    -    //        Log::info(sprintf('Done with import job %s', $this->job->key));
    -    //
    -    //        return true;
    -    //    }
    -    //
    -    //    /**
    -    //     * @param ImportJob $job
    -    //     */
    -    //    public function setJob(ImportJob $job)
    -    //    {
    -    //        $this->job        = $job;
    -    //        $this->repository = app(ImportJobRepositoryInterface::class);
    -    //        $this->repository->setUser($job->user);
    -    //    }
    -    //
    -    //    /**
    -    //     * @return Collection
    -    //     */
    -    //    protected function getImportObjects(): Collection
    -    //    {
    -    //        $objects  = new Collection;
    -    //        $fileType = $this->getConfig()['file-type'] ?? 'csv';
    -    //        // will only respond to "file"
    -    //        $class = config(sprintf('import.options.file.processors.%s', $fileType));
    -    //        /** @var FileProcessorInterface $processor */
    -    //        $processor = app($class);
    -    //        $processor->setJob($this->job);
    -    //
    -    //        if ('configured' === $this->getStatus()) {
    -    //            // set job as "running"...
    -    //            $this->setStatus('running');
    -    //
    -    //            Log::debug('Job is configured, start with run()');
    -    //            $processor->run();
    -    //            $objects = $processor->getObjects();
    -    //        }
    -    //
    -    //        return $objects;
    -    //    }
    -    //
    -    //    /**
    -    //     * Shorthand method.
    -    //     */
    -    //    private function addStep()
    -    //    {
    -    //        $this->repository->addStepsDone($this->job, 1);
    -    //    }
    -    //
    -    //    /**
    -    //     * Shorthand
    -    //     *
    -    //     * @param int $steps
    -    //     */
    -    //    private function addTotalSteps(int $steps)
    -    //    {
    -    //        $this->repository->addTotalSteps($this->job, $steps);
    -    //    }
    -    //
    -    //    /**
    -    //     *
    -    //     */
    -    //    private function createImportTag(): Tag
    -    //    {
    -    //        Log::debug('Now in createImportTag()');
    -    //
    -    //        if ($this->journals->count() < 1) {
    -    //            Log::info(sprintf('Will not create tag, %d journals imported.', $this->journals->count()));
    -    //
    -    //            return new Tag;
    -    //        }
    -    //        $this->addTotalSteps($this->journals->count() + 2);
    -    //
    -    //        /** @var TagRepositoryInterface $repository */
    -    //        $repository = app(TagRepositoryInterface::class);
    -    //        $repository->setUser($this->job->user);
    -    //        $data = [
    -    //            'tag'         => trans('import.import_with_key', ['key' => $this->job->key]),
    -    //            'date'        => new Carbon,
    -    //            'description' => null,
    -    //            'latitude'    => null,
    -    //            'longitude'   => null,
    -    //            'zoomLevel'   => null,
    -    //            'tagMode'     => 'nothing',
    -    //        ];
    -    //        $tag  = $repository->store($data);
    -    //        $this->addStep();
    -    //        $extended        = $this->getExtendedStatus();
    -    //        $extended['tag'] = $tag->id;
    -    //        $this->setExtendedStatus($extended);
    -    //
    -    //        Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag));
    -    //        Log::debug('Looping journals...');
    -    //        $journalIds = $this->journals->pluck('id')->toArray();
    -    //        $tagId      = $tag->id;
    -    //        foreach ($journalIds as $journalId) {
    -    //            Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId));
    -    //            DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]);
    -    //            $this->addStep();
    -    //        }
    -    //        Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $this->journals->count(), $tag->id, $tag->tag));
    -    //        $this->addStep();
    -    //
    -    //        return $tag;
    -    //    }
    -    //
    -    //    /**
    -    //     * Shorthand method
    -    //     *
    -    //     * @return array
    -    //     */
    -    //    private function getConfig(): array
    -    //    {
    -    //        return $this->repository->getConfiguration($this->job);
    -    //    }
    -    //
    -    //    /**
    -    //     * @return array
    -    //     */
    -    //    private function getExtendedStatus(): array
    -    //    {
    -    //        return $this->repository->getExtendedStatus($this->job);
    -    //    }
    -    //
    -    //    /**
    -    //     * Shorthand method.
    -    //     *
    -    //     * @return string
    -    //     */
    -    //    private function getStatus(): string
    -    //    {
    -    //        return $this->repository->getStatus($this->job);
    -    //    }
    -    //
    -    //    /**
    -    //     * @param array $extended
    -    //     */
    -    //    private function setExtendedStatus(array $extended): void
    -    //    {
    -    //        $this->repository->setExtendedStatus($this->job, $extended);
    -    //    }
    -    //
    -    //    /**
    -    //     * Shorthand
    -    //     *
    -    //     * @param string $status
    -    //     */
    -    //    private function setStatus(string $status): void
    -    //    {
    -    //        $this->repository->setStatus($this->job, $status);
    -    //    }
    -    //
    -    //    /**
    -    //     * Shorthand
    -    //     *
    -    //     * @param int $steps
    -    //     */
    -    //    private function setTotalSteps(int $steps)
    -    //    {
    -    //        $this->repository->setTotalSteps($this->job, $steps);
    -    //    }
    -    //
    -    //    /**
    -    //     * @param Collection $objects
    -    //     *
    -    //     * @return ImportStorage
    -    //     */
    -    //    private function storeObjects(Collection $objects): ImportStorage
    -    //    {
    -    //        $config  = $this->getConfig();
    -    //        $storage = new ImportStorage;
    -    //        $storage->setJob($this->job);
    -    //        $storage->setDateFormat($config['date-format']);
    -    //        $storage->setObjects($objects);
    -    //        $storage->store();
    -    //        Log::info('Back in storeObjects()');
    -    //
    -    //        return $storage;
    -    //    }
     }
    diff --git a/app/Import/Routine/RoutineInterface.php b/app/Import/Routine/RoutineInterface.php
    index 51c0b69737..f1f967460d 100644
    --- a/app/Import/Routine/RoutineInterface.php
    +++ b/app/Import/Routine/RoutineInterface.php
    @@ -35,7 +35,6 @@ interface RoutineInterface
          *
          * The final status of the routine must be "provider_finished".
          *
    -     * @return bool
          * @throws FireflyException
          */
         public function run(): void;
    diff --git a/app/Support/Import/Configuration/File/NewFileJobHandler.php b/app/Support/Import/Configuration/File/NewFileJobHandler.php
    index de4cb070cf..a6f42e9d2f 100644
    --- a/app/Support/Import/Configuration/File/NewFileJobHandler.php
    +++ b/app/Support/Import/Configuration/File/NewFileJobHandler.php
    @@ -32,6 +32,7 @@ use FireflyIII\Models\ImportJob;
     use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
     use Illuminate\Contracts\Encryption\DecryptException;
     use Illuminate\Contracts\Filesystem\FileNotFoundException;
    +use Illuminate\Support\Collection;
     use Illuminate\Support\MessageBag;
     use Log;
     use Storage;
    @@ -61,7 +62,108 @@ class NewFileJobHandler implements ConfigurationInterface
         {
             // nothing to store, validate upload
             // and push to next stage.
    -        $messages    = new MessageBag;
    +        $messages = $this->validateAttachments();
    +
    +        if ($messages->count() > 0) {
    +            return $messages;
    +        }
    +
    +        // store config if it's in one of the attachments.
    +        $this->storeConfiguration();
    +
    +        // set file type in config:
    +        $config              = $this->repository->getConfiguration($this->importJob);
    +        $config['file-type'] = $data['import_file_type'];
    +        $this->repository->setConfiguration($this->importJob, $config);
    +        $this->repository->setStage($this->importJob, 'configure-upload');
    +
    +        return new MessageBag();
    +
    +    }
    +
    +    /**
    +     * Get the data necessary to show the configuration screen.
    +     *
    +     * @return array
    +     */
    +    public function getNextData(): array
    +    {
    +        /** @var array $allowedTypes */
    +        $allowedTypes      = config('import.options.file.import_formats');
    +        $importFileTypes   = [];
    +        $defaultImportType = config('import.options.file.default_import_format');
    +        foreach ($allowedTypes as $type) {
    +            $importFileTypes[$type] = trans('import.import_file_type_' . $type);
    +        }
    +
    +        return [
    +            'default_type' => $defaultImportType,
    +            'file_types'   => $importFileTypes,
    +        ];
    +    }
    +
    +    /**
    +     * @param ImportJob $job
    +     */
    +    public function setJob(ImportJob $job): void
    +    {
    +        $this->importJob  = $job;
    +        $this->repository = app(ImportJobRepositoryInterface::class);
    +        $this->repository->setUser($job->user);
    +
    +    }
    +
    +    /**
    +     * Take attachment, extract config, and put in job.\
    +     *
    +     * @param Attachment $attachment
    +     *
    +     * @throws FireflyException
    +     */
    +    public function storeConfig(Attachment $attachment): void
    +    {
    +        $disk = Storage::disk('upload');
    +        try {
    +            $content = $disk->get(sprintf('at-%d.data', $attachment->id));
    +            $content = Crypt::decrypt($content);
    +        } catch (FileNotFoundException $e) {
    +            Log::error($e->getMessage());
    +            throw new FireflyException($e->getMessage());
    +        }
    +        $json = json_decode($content, true);
    +        if (null !== $json) {
    +            $this->repository->setConfiguration($this->importJob, $json);
    +        }
    +    }
    +
    +    /**
    +     * Store config from job.
    +     *
    +     * @throws FireflyException
    +     */
    +    public function storeConfiguration(): void
    +    {
    +        /** @var Collection $attachments */
    +        $attachments = $this->importJob->attachments;
    +        /** @var Attachment $attachment */
    +        foreach ($attachments as $attachment) {
    +            // if file is configuration file, store it into the job.
    +            if ($attachment->filename === 'configuration_file') {
    +                $this->storeConfig($attachment);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Check if all attachments are UTF8.
    +     *
    +     * @return MessageBag
    +     * @throws FireflyException
    +     */
    +    public function validateAttachments(): MessageBag
    +    {
    +        $messages = new MessageBag;
    +        /** @var Collection $attachments */
             $attachments = $this->importJob->attachments;
             /** @var Attachment $attachment */
             foreach ($attachments as $attachment) {
    @@ -86,45 +188,8 @@ class NewFileJobHandler implements ConfigurationInterface
                     $this->storeConfig($attachment);
                 }
             }
    -        // set file type in config:
    -        $config = $this->repository->getConfiguration($this->importJob);
    -        $config['file-type'] = $data['import_file_type'];
    -        $this->repository->setConfiguration($this->importJob, $config);
    -        $this->repository->setStage($this->importJob, 'configure-upload');
    -
    -        return new MessageBag();
    -
    -    }
    -
    -    /**
    -     * Get the data necessary to show the configuration screen.
    -     *
    -     * @return array
    -     */
    -    public function getNextData(): array
    -    {
    -        $importFileTypes   = [];
    -        $defaultImportType = config('import.options.file.default_import_format');
    -
    -        foreach (config('import.options.file.import_formats') as $type) {
    -            $importFileTypes[$type] = trans('import.import_file_type_' . $type);
    -        }
    -
    -        return [
    -            'default_type' => $defaultImportType,
    -            'file_types'   => $importFileTypes,
    -        ];
    -    }
    -
    -    /**
    -     * @param ImportJob $job
    -     */
    -    public function setJob(ImportJob $job): void
    -    {
    -        $this->importJob  = $job;
    -        $this->repository = app(ImportJobRepositoryInterface::class);
    -        $this->repository->setUser($job->user);
     
    +        return $messages;
         }
     
         /**
    @@ -154,25 +219,4 @@ class NewFileJobHandler implements ConfigurationInterface
     
             return true;
         }
    -
    -    /**
    -     * @param Attachment $attachment
    -     *
    -     * @throws FireflyException
    -     */
    -    private function storeConfig(Attachment $attachment): void
    -    {
    -        $disk = Storage::disk('upload');
    -        try {
    -            $content = $disk->get(sprintf('at-%d.data', $attachment->id));
    -            $content = Crypt::decrypt($content);
    -        } catch (FileNotFoundException $e) {
    -            Log::error($e->getMessage());
    -            throw new FireflyException($e->getMessage());
    -        }
    -        $json = json_decode($content, true);
    -        if (null !== $json) {
    -            $this->repository->setConfiguration($this->importJob, $json);
    -        }
    -    }
     }
    \ No newline at end of file
    diff --git a/tests/Feature/Controllers/Import/JobConfigurationControllerTest.php b/tests/Feature/Controllers/Import/JobConfigurationControllerTest.php
    index dd04607562..f9fb5b9950 100644
    --- a/tests/Feature/Controllers/Import/JobConfigurationControllerTest.php
    +++ b/tests/Feature/Controllers/Import/JobConfigurationControllerTest.php
    @@ -26,6 +26,7 @@ namespace Tests\Feature\Controllers\Import;
     use FireflyIII\Import\JobConfiguration\FakeJobConfiguration;
     use FireflyIII\Models\ImportJob;
     use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
    +use Illuminate\Http\UploadedFile;
     use Illuminate\Support\MessageBag;
     use Log;
     use Mockery;
    @@ -165,6 +166,35 @@ class JobConfigurationControllerTest extends TestCase
             $response->assertSessionHas('warning', $messages->first());
         }
     
    +    /**
    +     * @covers \FireflyIII\Http\Controllers\Import\JobConfigurationController
    +     */
    +    public function testPostBadState(): void
    +    {
    +
    +        $job            = new ImportJob;
    +        $job->user_id   = $this->user()->id;
    +        $job->key       = 'Ffake_job_' . random_int(1, 1000);
    +        $job->status    = 'some_bad_state';
    +        $job->provider  = 'fake';
    +        $job->file_type = '';
    +        $job->save();
    +
    +        $messages = new MessageBag;
    +        $messages->add('some', 'srrange message');
    +
    +        // mock repositories and configuration handling classes:
    +        $repository   = $this->mock(ImportJobRepositoryInterface::class);
    +        $configurator = $this->mock(FakeJobConfiguration::class);
    +
    +        // call thing.
    +        $this->be($this->user());
    +        $response = $this->post(route('import.job.configuration.post', [$job->key]));
    +        $response->assertStatus(302);
    +        $response->assertRedirect(route('import.index'));
    +        $response->assertSessionHas('error', 'To access this page, your import job cannot have status "some_bad_state".');
    +    }
    +
         /**
          * @covers \FireflyIII\Http\Controllers\Import\JobConfigurationController
          */
    @@ -198,13 +228,13 @@ class JobConfigurationControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\Import\JobConfigurationController
          */
    -    public function testPostBadState(): void
    +    public function testPostWithUpload(): void
         {
    -
    +        $file           = UploadedFile::fake()->image('avatar.jpg');
             $job            = new ImportJob;
             $job->user_id   = $this->user()->id;
    -        $job->key       = 'Ffake_job_' . random_int(1, 1000);
    -        $job->status    = 'some_bad_state';
    +        $job->key       = 'Dfake_job_' . random_int(1, 1000);
    +        $job->status    = 'has_prereq';
             $job->provider  = 'fake';
             $job->file_type = '';
             $job->save();
    @@ -216,12 +246,18 @@ class JobConfigurationControllerTest extends TestCase
             $repository   = $this->mock(ImportJobRepositoryInterface::class);
             $configurator = $this->mock(FakeJobConfiguration::class);
     
    +        // mock calls:
    +        $configurator->shouldReceive('setJob')->once();
    +        $configurator->shouldReceive('configurationComplete')->once()->andReturn(false);
    +        $configurator->shouldReceive('configureJob')->once()->andReturn($messages);
    +        $repository->shouldReceive('storeFileUpload')->once()->andReturn(new MessageBag);
    +
             // call thing.
             $this->be($this->user());
    -        $response = $this->post(route('import.job.configuration.post', [$job->key]));
    +        $response = $this->post(route('import.job.configuration.post', [$job->key]), ['import_file' => $file]);
             $response->assertStatus(302);
    -        $response->assertRedirect(route('import.index'));
    -        $response->assertSessionHas('error', 'To access this page, your import job cannot have status "some_bad_state".');
    +        $response->assertRedirect(route('import.job.configuration.index', [$job->key]));
    +        $response->assertSessionHas('warning', $messages->first());
         }
     
     
    diff --git a/tests/Unit/Import/Converter/RabobankDebitCreditTest.php b/tests/Unit/Import/Converter/RabobankDebitCreditTest.php
    index fd330b1041..5961b46318 100644
    --- a/tests/Unit/Import/Converter/RabobankDebitCreditTest.php
    +++ b/tests/Unit/Import/Converter/RabobankDebitCreditTest.php
    @@ -34,17 +34,7 @@ class RabobankDebitCreditTest extends TestCase
         /**
          * @covers \FireflyIII\Import\Converter\RabobankDebitCredit::convert()
          */
    -    public function testConvertAf()
    -    {
    -        $converter = new RabobankDebitCredit;
    -        $result    = $converter->convert('D');
    -        $this->assertEquals(-1, $result);
    -    }
    -
    -    /**
    -     * @covers \FireflyIII\Import\Converter\RabobankDebitCredit::convert()
    -     */
    -    public function testConvertAnything()
    +    public function testConvertAnything(): void
         {
             $converter = new RabobankDebitCredit;
             $result    = $converter->convert('9083jkdkj');
    @@ -54,10 +44,40 @@ class RabobankDebitCreditTest extends TestCase
         /**
          * @covers \FireflyIII\Import\Converter\RabobankDebitCredit::convert()
          */
    -    public function testConvertBij()
    +    public function testConvertCredit(): void
         {
             $converter = new RabobankDebitCredit;
             $result    = $converter->convert('C');
             $this->assertEquals(1, $result);
         }
    +
    +    /**
    +     * @covers \FireflyIII\Import\Converter\RabobankDebitCredit::convert()
    +     */
    +    public function testConvertCreditOld(): void
    +    {
    +        $converter = new RabobankDebitCredit;
    +        $result    = $converter->convert('B');
    +        $this->assertEquals(1, $result);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Import\Converter\RabobankDebitCredit::convert()
    +     */
    +    public function testConvertDebit(): void
    +    {
    +        $converter = new RabobankDebitCredit;
    +        $result    = $converter->convert('D');
    +        $this->assertEquals(-1, $result);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Import\Converter\RabobankDebitCredit::convert()
    +     */
    +    public function testConvertDebitOld(): void
    +    {
    +        $converter = new RabobankDebitCredit;
    +        $result    = $converter->convert('A');
    +        $this->assertEquals(-1, $result);
    +    }
     }
    diff --git a/tests/Unit/Import/JobConfiguration/FileJobConfigurationTest.php b/tests/Unit/Import/JobConfiguration/FileJobConfigurationTest.php
    new file mode 100644
    index 0000000000..12e90c7f2a
    --- /dev/null
    +++ b/tests/Unit/Import/JobConfiguration/FileJobConfigurationTest.php
    @@ -0,0 +1,367 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace Tests\Unit\Import\JobConfiguration;
    +
    +
    +use FireflyIII\Exceptions\FireflyException;
    +use FireflyIII\Import\JobConfiguration\FileJobConfiguration;
    +use FireflyIII\Models\ImportJob;
    +use FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler;
    +use FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler;
    +use FireflyIII\Support\Import\Configuration\File\ConfigureUploadHandler;
    +use FireflyIII\Support\Import\Configuration\File\NewFileJobHandler;
    +use Illuminate\Support\MessageBag;
    +use Mockery;
    +use Tests\TestCase;
    +
    +/**
    + * Class FileJobConfigurationTest
    + */
    +class FileJobConfigurationTest extends TestCase
    +{
    +    /**
    +     * No config, job is new.
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testCCFalse(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'File_A_unit_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        // should be false:
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +        $this->assertFalse($configurator->configurationComplete());
    +    }
    +
    +    /**
    +     * Job is ready to run.
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testCCTrue(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'File_B_unit_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'ready_to_run';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        // should be false:
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +        $this->assertTrue($configurator->configurationComplete());
    +    }
    +
    +    /**
    +     * Configure the job when the stage is "map". Won't test other combo's because they're covered by other tests.
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testConfigureJob(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'I-file_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'map';
    +        $job->provider      = 'file';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $bag          = new MessageBag;
    +        $result       = null;
    +
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +
    +        $handler = $this->mock(ConfigureMappingHandler::class);
    +        $handler->shouldReceive('setJob')->once()->withArgs([Mockery::any()]);
    +        $handler->shouldReceive('configureJob')->withArgs([['c' => 'd']])->andReturn($bag)->once();
    +
    +        try {
    +            $result = $configurator->configureJob(['c' => 'd']);
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals($bag, $result);
    +    }
    +
    +    /**
    +     * Get next data when stage is "configure-upload". Expect a certain class to be called.
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testGetNextDataCU(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'G-file_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'configure-upload';
    +        $job->provider      = 'file';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $result       = 'x';
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +
    +        $handler = $this->mock(ConfigureUploadHandler::class);
    +        $handler->shouldReceive('setJob')->once()->withArgs([Mockery::any()]);
    +        $handler->shouldReceive('getNextData')->andReturn(['a' => 'b'])->withNoArgs()->once();
    +
    +        try {
    +            $result = $configurator->getNextData();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals(['a' => 'b'], $result);
    +    }
    +
    +    /**
    +     * Get next data when stage is "map". Expect a certain class to be called.
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testGetNextDataMap(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'H-file_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'map';
    +        $job->provider      = 'file';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $result       = 'x';
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +
    +        $handler = $this->mock(ConfigureMappingHandler::class);
    +        $handler->shouldReceive('setJob')->once()->withArgs([Mockery::any()]);
    +        $handler->shouldReceive('getNextData')->andReturn(['a' => 'b'])->withNoArgs()->once();
    +
    +        try {
    +            $result = $configurator->getNextData();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals(['a' => 'b'], $result);
    +    }
    +
    +    /**
    +     * Get next data when stage is "new". Expect a certain class to be called.
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testGetNextDataNew(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'F-file_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'file';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $result       = 'x';
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +
    +        $handler = $this->mock(NewFileJobHandler::class);
    +        $handler->shouldReceive('setJob')->once()->withArgs([Mockery::any()]);
    +        $handler->shouldReceive('getNextData')->andReturn(['a' => 'b'])->withNoArgs()->once();
    +
    +        try {
    +            $result = $configurator->getNextData();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals(['a' => 'b'], $result);
    +    }
    +
    +    /**
    +     * Get next data when stage is "roles". Expect a certain class to be called.
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testGetNextDataRoles(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'H-file_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'roles';
    +        $job->provider      = 'file';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $result       = 'x';
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +
    +        $handler = $this->mock(ConfigureRolesHandler::class);
    +        $handler->shouldReceive('setJob')->once()->withArgs([Mockery::any()]);
    +        $handler->shouldReceive('getNextData')->andReturn(['a' => 'b'])->withNoArgs()->once();
    +
    +        try {
    +            $result = $configurator->getNextData();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals(['a' => 'b'], $result);
    +    }
    +
    +    /**
    +     * Get view when stage is "configure-upload".
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testGetNextViewCU(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'Dfile_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'configure-upload';
    +        $job->provider      = 'file';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $result       = 'x';
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +        try {
    +            $result = $configurator->getNextView();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals('import.file.configure-upload', $result);
    +    }
    +
    +    /**
    +     * Get view when stage is "map".
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testGetNextViewMap(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'Ffile_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'map';
    +        $job->provider      = 'file';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $result       = 'x';
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +        try {
    +            $result = $configurator->getNextView();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals('import.file.map', $result);
    +    }
    +
    +    /**
    +     * Get view when stage is "new".
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testGetNextViewNew(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'Cfile_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'file';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $result       = 'x';
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +        try {
    +            $result = $configurator->getNextView();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals('import.file.new', $result);
    +    }
    +
    +    /**
    +     * Get view when stage is "roles".
    +     *
    +     * @covers \FireflyIII\Import\JobConfiguration\FileJobConfiguration
    +     */
    +    public function testGetNextViewRoles(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'Efile_' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'roles';
    +        $job->provider      = 'file';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $result       = 'x';
    +        $configurator = new FileJobConfiguration;
    +        $configurator->setJob($job);
    +        try {
    +            $result = $configurator->getNextView();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals('import.file.roles', $result);
    +    }
    +}
    \ No newline at end of file
    diff --git a/tests/Unit/Import/Routine/FakeRoutineTest.php b/tests/Unit/Import/Routine/FakeRoutineTest.php
    index 9f2abdc9f3..b8b2511b36 100644
    --- a/tests/Unit/Import/Routine/FakeRoutineTest.php
    +++ b/tests/Unit/Import/Routine/FakeRoutineTest.php
    @@ -41,7 +41,7 @@ class FakeRoutineTest extends TestCase
         /**
          * @covers \FireflyIII\Import\Routine\FakeRoutine
          */
    -    public function testRunAhoy()
    +    public function testRunAhoy(): void
         {
             $job                = new ImportJob;
             $job->user_id       = $this->user()->id;
    @@ -76,7 +76,7 @@ class FakeRoutineTest extends TestCase
         /**
          * @covers \FireflyIII\Import\Routine\FakeRoutine
          */
    -    public function testRunFinal()
    +    public function testRunFinal(): void
         {
             $job                = new ImportJob;
             $job->user_id       = $this->user()->id;
    @@ -112,7 +112,7 @@ class FakeRoutineTest extends TestCase
         /**
          * @covers \FireflyIII\Import\Routine\FakeRoutine
          */
    -    public function testRunNew()
    +    public function testRunNew(): void
         {
             $job                = new ImportJob;
             $job->user_id       = $this->user()->id;
    diff --git a/tests/Unit/Import/Routine/FileRoutineTest.php b/tests/Unit/Import/Routine/FileRoutineTest.php
    new file mode 100644
    index 0000000000..3d48bf98c2
    --- /dev/null
    +++ b/tests/Unit/Import/Routine/FileRoutineTest.php
    @@ -0,0 +1,78 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace Tests\Unit\Import\Routine;
    +
    +
    +use FireflyIII\Exceptions\FireflyException;
    +use FireflyIII\Import\Routine\FileRoutine;
    +use FireflyIII\Models\ImportJob;
    +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
    +use FireflyIII\Support\Import\Routine\File\CSVProcessor;
    +use Mockery;
    +use Tests\TestCase;
    +
    +/**
    + * Class FileRoutineTest
    + */
    +class FileRoutineTest extends TestCase
    +{
    +
    +    /**
    +     * @covers \FireflyIII\Import\Routine\FileRoutine
    +     */
    +    public function testRunDefault(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'a_fr_' . random_int(1, 1000);
    +        $job->status        = 'running';
    +        $job->stage         = 'ready_to_run';
    +        $job->provider      = 'file';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        // mock
    +        $processor  = $this->mock(CSVProcessor::class);
    +        $repository = $this->mock(ImportJobRepositoryInterface::class);
    +
    +        // calls
    +        $repository->shouldReceive('setUser')->once();
    +        $repository->shouldReceive('setStatus')->withArgs([Mockery::any(), 'provider_finished'])->once();
    +        $repository->shouldReceive('setStage')->withArgs([Mockery::any(), 'final'])->once();
    +        $repository->shouldReceive('setTransactions')->withArgs([Mockery::any(), ['a' => 'b']])->once();
    +        $repository->shouldReceive('getConfiguration')->withArgs([Mockery::any()])->once()->andReturn([]);
    +        $processor->shouldReceive('setJob')->once();
    +        $processor->shouldReceive('run')->once()->andReturn(['a' => 'b']);
    +
    +
    +        $routine = new FileRoutine;
    +        $routine->setJob($job);
    +        try {
    +            $routine->run();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +    }
    +}
    \ No newline at end of file
    diff --git a/tests/Unit/TransactionRules/Triggers/HasAnyCategoryTest.php b/tests/Unit/TransactionRules/Triggers/HasAnyCategoryTest.php
    index d1b0081bd2..0c6d828515 100644
    --- a/tests/Unit/TransactionRules/Triggers/HasAnyCategoryTest.php
    +++ b/tests/Unit/TransactionRules/Triggers/HasAnyCategoryTest.php
    @@ -35,7 +35,7 @@ class HasAnyCategoryTest extends TestCase
         /**
          * @covers \FireflyIII\TransactionRules\Triggers\HasAnyCategory::triggered
          */
    -    public function testTriggered()
    +    public function testTriggered(): void
         {
             $journal  = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first();
             $category = $journal->user->categories()->first();
    @@ -51,14 +51,14 @@ class HasAnyCategoryTest extends TestCase
         /**
          * @covers \FireflyIII\TransactionRules\Triggers\HasAnyCategory::triggered
          */
    -    public function testTriggeredNot()
    +    public function testTriggeredNot(): void
         {
             $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first();
             $journal->categories()->detach();
     
             // also detach transactions:
             /** @var Transaction $transaction */
    -        foreach($journal->transactions as $transaction) {
    +        foreach ($journal->transactions as $transaction) {
                 $transaction->categories()->detach();
             }
     
    @@ -71,10 +71,16 @@ class HasAnyCategoryTest extends TestCase
         /**
          * @covers \FireflyIII\TransactionRules\Triggers\HasAnyCategory::triggered
          */
    -    public function testTriggeredTransactions()
    +    public function testTriggeredTransactions(): void
         {
    -        /** @var TransactionJournal $journal */
    -        $journal  = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first();
    +        $count   = 0;
    +        $journal = null;
    +        while ($count === 0) {
    +            /** @var TransactionJournal $journal */
    +            $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first();
    +            $count   = $journal->transactions()->count();
    +        }
    +
             $category = $journal->user->categories()->first();
             $journal->categories()->detach();
             $this->assertEquals(0, $journal->categories()->count());
    @@ -94,7 +100,7 @@ class HasAnyCategoryTest extends TestCase
         /**
          * @covers \FireflyIII\TransactionRules\Triggers\HasAnyCategory::willMatchEverything
          */
    -    public function testWillMatchEverything()
    +    public function testWillMatchEverything(): void
         {
             $value  = '';
             $result = HasAnyCategory::willMatchEverything($value);
    
    From 6f984aa59150ebbf91cf3249b57498dc2204c09d Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Thu, 10 May 2018 23:01:21 +0200
    Subject: [PATCH 059/182] Improve test coverage.
    
    ---
     .../ImportJob/ImportJobRepository.php         | 180 ++++---
     .../ImportJobRepositoryInterface.php          |   9 +
     .../File/ConfigureMappingHandler.php          | 223 ++++----
     .../File/ConfigureMappingHandlerTest.php      | 488 ++++++++++++++++++
     .../Triggers/HasNoCategoryTest.php            |   8 +-
     5 files changed, 706 insertions(+), 202 deletions(-)
     create mode 100644 tests/Unit/Support/Import/Configuration/File/ConfigureMappingHandlerTest.php
    
    diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php
    index c21cb4c030..c375c36bf1 100644
    --- a/app/Repositories/ImportJob/ImportJobRepository.php
    +++ b/app/Repositories/ImportJob/ImportJobRepository.php
    @@ -30,6 +30,7 @@ use FireflyIII\Models\Tag;
     use FireflyIII\Models\TransactionJournalMeta;
     use FireflyIII\Repositories\User\UserRepositoryInterface;
     use FireflyIII\User;
    +use Illuminate\Support\Collection;
     use Illuminate\Support\MessageBag;
     use Illuminate\Support\Str;
     use Log;
    @@ -42,12 +43,12 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
      */
     class ImportJobRepository implements ImportJobRepositoryInterface
     {
    -    /** @var User */
    -    private $user;
    -    /** @var int */
    -    private $maxUploadSize;
         /** @var \Illuminate\Contracts\Filesystem\Filesystem */
         protected $uploadDisk;
    +    /** @var int */
    +    private $maxUploadSize;
    +    /** @var User */
    +    private $user;
     
         public function __construct()
         {
    @@ -70,6 +71,24 @@ class ImportJobRepository implements ImportJobRepositoryInterface
             return $this->setExtendedStatus($job, $extended);
         }
     
    +    /**
    +     * Add message to job.
    +     *
    +     * @param ImportJob $job
    +     * @param string    $error
    +     *
    +     * @return ImportJob
    +     */
    +    public function addErrorMessage(ImportJob $job, string $error): ImportJob
    +    {
    +        $errors      = $job->errors;
    +        $errors[]    = $error;
    +        $job->errors = $errors;
    +        $job->save();
    +
    +        return $job;
    +    }
    +
         /**
          * @param ImportJob $job
          * @param int       $steps
    @@ -176,6 +195,18 @@ class ImportJobRepository implements ImportJobRepositoryInterface
             return $result;
         }
     
    +    /**
    +     * Return all attachments for job.
    +     *
    +     * @param ImportJob $job
    +     *
    +     * @return Collection
    +     */
    +    public function getAttachments(ImportJob $job): Collection
    +    {
    +        return $job->attachments()->get();
    +    }
    +
         /**
          * Return configuration of job.
          *
    @@ -393,6 +424,20 @@ class ImportJobRepository implements ImportJobRepositoryInterface
             return $this->setExtendedStatus($job, $status);
         }
     
    +    /**
    +     * @param ImportJob $job
    +     * @param Tag       $tag
    +     *
    +     * @return ImportJob
    +     */
    +    public function setTag(ImportJob $job, Tag $tag): ImportJob
    +    {
    +        $job->tag()->associate($tag);
    +        $job->save();
    +
    +        return $job;
    +    }
    +
         /**
          * @param ImportJob $job
          * @param int       $count
    @@ -408,43 +453,6 @@ class ImportJobRepository implements ImportJobRepositoryInterface
             return $this->setExtendedStatus($job, $status);
         }
     
    -    /**
    -     * @param User $user
    -     */
    -    public function setUser(User $user)
    -    {
    -        $this->user = $user;
    -    }
    -
    -    /**
    -     * @param ImportJob $job
    -     * @param string    $status
    -     *
    -     * @return ImportJob
    -     */
    -    public function updateStatus(ImportJob $job, string $status): ImportJob
    -    {
    -        $job->status = $status;
    -        $job->save();
    -
    -        return $job;
    -    }
    -
    -    /**
    -     * Return import file content.
    -     *
    -     * @deprecated
    -     *
    -     * @param ImportJob $job
    -     *
    -     * @return string
    -     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
    -     */
    -    public function uploadFileContents(ImportJob $job): string
    -    {
    -        return $job->uploadFileContents();
    -    }
    -
         /**
          * @param ImportJob $job
          * @param array     $transactions
    @@ -460,52 +468,13 @@ class ImportJobRepository implements ImportJobRepositoryInterface
         }
     
         /**
    -     * Add message to job.
    -     *
    -     * @param ImportJob $job
    -     * @param string    $error
    -     *
    -     * @return ImportJob
    +     * @param User $user
          */
    -    public function addErrorMessage(ImportJob $job, string $error): ImportJob
    +    public function setUser(User $user)
         {
    -        $errors      = $job->errors;
    -        $errors[]    = $error;
    -        $job->errors = $errors;
    -        $job->save();
    -
    -        return $job;
    +        $this->user = $user;
         }
     
    -    /**
    -     * @param ImportJob $job
    -     * @param Tag       $tag
    -     *
    -     * @return ImportJob
    -     */
    -    public function setTag(ImportJob $job, Tag $tag): ImportJob
    -    {
    -        $job->tag()->associate($tag);
    -        $job->save();
    -
    -        return $job;
    -    }
    -
    -    /**
    -     * @codeCoverageIgnore
    -     *
    -     * @param UploadedFile $file
    -     *
    -     * @return bool
    -     */
    -    protected function validSize(UploadedFile $file): bool
    -    {
    -        $size = $file->getSize();
    -
    -        return $size > $this->maxUploadSize;
    -    }
    -
    -
         /**
          * Handle upload for job.
          *
    @@ -526,7 +495,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface
                 return $messages;
             }
             $count = $job->attachments()->get()->filter(
    -            function (Attachment $att) use($name) {
    +            function (Attachment $att) use ($name) {
                     return $att->filename === $name;
                 }
             )->count();
    @@ -560,4 +529,47 @@ class ImportJobRepository implements ImportJobRepositoryInterface
             // return it.
             return new MessageBag;
         }
    +
    +    /**
    +     * @param ImportJob $job
    +     * @param string    $status
    +     *
    +     * @return ImportJob
    +     */
    +    public function updateStatus(ImportJob $job, string $status): ImportJob
    +    {
    +        $job->status = $status;
    +        $job->save();
    +
    +        return $job;
    +    }
    +
    +    /**
    +     * Return import file content.
    +     *
    +     * @deprecated
    +     *
    +     * @param ImportJob $job
    +     *
    +     * @return string
    +     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
    +     */
    +    public function uploadFileContents(ImportJob $job): string
    +    {
    +        return $job->uploadFileContents();
    +    }
    +
    +    /**
    +     * @codeCoverageIgnore
    +     *
    +     * @param UploadedFile $file
    +     *
    +     * @return bool
    +     */
    +    protected function validSize(UploadedFile $file): bool
    +    {
    +        $size = $file->getSize();
    +
    +        return $size > $this->maxUploadSize;
    +    }
     }
    diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php
    index 0bfc61e75e..8e5fba8756 100644
    --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php
    +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php
    @@ -26,6 +26,7 @@ use FireflyIII\Exceptions\FireflyException;
     use FireflyIII\Models\ImportJob;
     use FireflyIII\Models\Tag;
     use FireflyIII\User;
    +use Illuminate\Support\Collection;
     use Illuminate\Support\MessageBag;
     use Symfony\Component\HttpFoundation\File\UploadedFile;
     
    @@ -34,6 +35,14 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
      */
     interface ImportJobRepositoryInterface
     {
    +    /**
    +     * Return all attachments for job.
    +     *
    +     * @param ImportJob $job
    +     *
    +     * @return Collection
    +     */
    +    public function getAttachments(ImportJob $job): Collection;
     
         /**
          * Handle upload for job.
    diff --git a/app/Support/Import/Configuration/File/ConfigureMappingHandler.php b/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    index caad8cdc1f..651db8cc0f 100644
    --- a/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    +++ b/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    @@ -26,7 +26,6 @@ namespace FireflyIII\Support\Import\Configuration\File;
     use FireflyIII\Exceptions\FireflyException;
     use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
     use FireflyIII\Import\Mapper\MapperInterface;
    -use FireflyIII\Import\MapperPreProcess\PreProcessorInterface;
     use FireflyIII\Import\Specifics\SpecificInterface;
     use FireflyIII\Models\Attachment;
     use FireflyIII\Models\ImportJob;
    @@ -52,86 +51,21 @@ class ConfigureMappingHandler implements ConfigurationInterface
         /** @var ImportJobRepositoryInterface */
         private $repository;
     
    -    /**
    -     * Store data associated with current stage.
    -     *
    -     * @param array $data
    -     *
    -     * @return MessageBag
    -     */
    -    public function configureJob(array $data): MessageBag
    -    {
    -        $config = $this->importJob->configuration;
    -
    -        if (isset($data['mapping']) && \is_array($data['mapping'])) {
    -            foreach ($data['mapping'] as $index => $array) {
    -                $config['column-mapping-config'][$index] = [];
    -                foreach ($array as $value => $mapId) {
    -                    $mapId = (int)$mapId;
    -                    if (0 !== $mapId) {
    -                        $config['column-mapping-config'][$index][$value] = $mapId;
    -                    }
    -                }
    -            }
    -        }
    -        $this->repository->setConfiguration($this->importJob, $config);
    -        $this->repository->setStage($this->importJob, 'ready_to_run');
    -
    -        return new MessageBag;
    -    }
    -
    -    /**
    -     * Get the data necessary to show the configuration screen.
    -     *
    -     * @return array
    -     * @throws FireflyException
    -     */
    -    public function getNextData(): array
    -    {
    -        $config       = $this->importJob->configuration;
    -        $columnConfig = $this->doColumnConfig($config);
    -
    -        // in order to actually map we also need to read the FULL file.
    -        try {
    -            $reader = $this->getReader();
    -        } catch (Exception $e) {
    -            Log::error($e->getMessage());
    -            throw new FireflyException('Cannot get reader: ' . $e->getMessage());
    -        }
    -
    -        // get ALL values for the mappable columns from the CSV file:
    -        $columnConfig = $this->getValuesForMapping($reader, $config, $columnConfig);
    -
    -        return $columnConfig;
    -    }
    -
    -    /**
    -     * @param ImportJob $job
    -     */
    -    public function setJob(ImportJob $job): void
    -    {
    -        $this->importJob  = $job;
    -        $this->repository = app(ImportJobRepositoryInterface::class);
    -        $this->repository->setUser($job->user);
    -        $this->attachments  = app(AttachmentHelperInterface::class);
    -        $this->columnConfig = [];
    -    }
    -
         /**
          * Apply the users selected specifics on the current row.
          *
          * @param array $config
    -     * @param array $validSpecifics
          * @param array $row
          *
          * @return array
          */
    -    private function applySpecifics(array $config, array $validSpecifics, array $row): array
    +    public function applySpecifics(array $config, array $row): array
         {
             // run specifics here:
             // and this is the point where the specifix go to work.
    -        $specifics = $config['specifics'] ?? [];
    -        $names     = array_keys($specifics);
    +        $validSpecifics = array_keys(config('csv.import_specifics'));
    +        $specifics      = $config['specifics'] ?? [];
    +        $names          = array_keys($specifics);
             foreach ($names as $name) {
                 if (!\in_array($name, $validSpecifics)) {
                     continue;
    @@ -148,23 +82,31 @@ class ConfigureMappingHandler implements ConfigurationInterface
         }
     
         /**
    -     * Create the "mapper" class that will eventually return the correct data for the user
    -     * to map against. For example: a list of asset accounts. A list of budgets. A list of tags.
    +     * Store data associated with current stage.
          *
    -     * @param string $column
    +     * @param array $data
          *
    -     * @return MapperInterface
    -     * @throws FireflyException
    +     * @return MessageBag
          */
    -    private function createMapper(string $column): MapperInterface
    +    public function configureJob(array $data): MessageBag
         {
    -        $mapperClass = config('csv.import_roles.' . $column . '.mapper');
    -        $mapperName  = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass);
    -        if (!class_exists($mapperName)) {
    -            throw new FireflyException(sprintf('Class "%s" does not exist. Cannot map "%s"', $mapperName, $column));
    -        }
    +        $config = $this->importJob->configuration;
     
    -        return app($mapperName);
    +        if (isset($data['mapping']) && \is_array($data['mapping'])) {
    +            foreach ($data['mapping'] as $index => $array) {
    +                $config['column-mapping-config'][$index] = [];
    +                foreach ($array as $value => $mapId) {
    +                    $mapId = (int)$mapId;
    +                    if (0 !== $mapId) {
    +                        $config['column-mapping-config'][$index][(string)$value] = $mapId;
    +                    }
    +                }
    +            }
    +        }
    +        $this->repository->setConfiguration($this->importJob, $config);
    +        $this->repository->setStage($this->importJob, 'ready_to_run');
    +
    +        return new MessageBag;
         }
     
         /**
    @@ -178,7 +120,7 @@ class ConfigureMappingHandler implements ConfigurationInterface
          * @return array the column configuration.
          * @throws FireflyException
          */
    -    private function doColumnConfig(array $config): array
    +    public function doColumnConfig(array $config): array
         {
             /** @var array $requestMapping */
             $requestMapping = $config['column-do-mapping'] ?? [];
    @@ -217,11 +159,36 @@ class ConfigureMappingHandler implements ConfigurationInterface
          *
          * @return bool
          */
    -    private function doMapOfColumn(string $name, bool $requested): bool
    +    public function doMapOfColumn(string $name, bool $requested): bool
         {
             $canBeMapped = config('csv.import_roles.' . $name . '.mappable');
     
    -        return $canBeMapped && $requested;
    +        return $canBeMapped === true && $requested === true;
    +    }
    +
    +    /**
    +     * Get the data necessary to show the configuration screen.
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    public function getNextData(): array
    +    {
    +        $config       = $this->importJob->configuration;
    +        $columnConfig = $this->doColumnConfig($config);
    +
    +        // in order to actually map we also need to read the FULL file.
    +        try {
    +            $reader = $this->getReader();
    +        } catch (Exception $e) {
    +            Log::error($e->getMessage());
    +            throw new FireflyException('Cannot get reader: ' . $e->getMessage());
    +        }
    +
    +        // get ALL values for the mappable columns from the CSV file:
    +        $columnConfig = $this->getValuesForMapping($reader, $config, $columnConfig);
    +
    +        return $columnConfig;
         }
     
         /**
    @@ -233,7 +200,7 @@ class ConfigureMappingHandler implements ConfigurationInterface
          *
          * @return string
          */
    -    private function getPreProcessorName(string $column): string
    +    public function getPreProcessorName(string $column): string
         {
             $name            = '';
             $hasPreProcess   = config(sprintf('csv.import_roles.%s.pre-process-map', $column));
    @@ -251,11 +218,11 @@ class ConfigureMappingHandler implements ConfigurationInterface
          *
          * @throws \League\Csv\Exception
          */
    -    private function getReader(): Reader
    +    public function getReader(): Reader
         {
             $content = '';
             /** @var Collection $collection */
    -        $collection = $this->importJob->attachments;
    +        $collection = $this->repository->getAttachments($this->importJob);
             /** @var Attachment $attachment */
             foreach ($collection as $attachment) {
                 if ($attachment->filename === 'import_file') {
    @@ -283,53 +250,45 @@ class ConfigureMappingHandler implements ConfigurationInterface
          * @return array
          * @throws FireflyException
          */
    -    private function getValuesForMapping(Reader $reader, array $config, array $columnConfig): array
    +    public function getValuesForMapping(Reader $reader, array $config, array $columnConfig): array
         {
             $offset = isset($config['has-headers']) && $config['has-headers'] === true ? 1 : 0;
             try {
                 $stmt = (new Statement)->offset($offset);
    +            // @codeCoverageIgnoreStart
             } catch (Exception $e) {
                 throw new FireflyException(sprintf('Could not create reader: %s', $e->getMessage()));
             }
    -        $results        = $stmt->process($reader);
    -        $validSpecifics = array_keys(config('csv.import_specifics'));
    -        $validIndexes   = array_keys($columnConfig); // the actually columns that can be mapped.
    -        foreach ($results as $rowIndex => $row) {
    -            $row = $this->applySpecifics($config, $validSpecifics, $row);
    +        // @codeCoverageIgnoreEnd
    +        $results         = $stmt->process($reader);
    +        $mappableColumns = array_keys($columnConfig); // the actually columns that can be mapped.
    +        foreach ($results as $lineIndex => $line) {
    +            Log::debug(sprintf('Trying to collect values for line #%d', $lineIndex));
    +            $line = $this->applySpecifics($config, $line);
     
    -            //do something here
    -            /** @var int $currentIndex */
    -            foreach ($validIndexes as $currentIndex) { // this is simply 1, 2, 3, etc.
    -                if (!isset($row[$currentIndex])) {
    +            /** @var int $columnIndex */
    +            foreach ($mappableColumns as $columnIndex) { // this is simply 1, 2, 3, etc.
    +                if (!isset($line[$columnIndex])) {
                         // don't need to handle this. Continue.
                         continue;
                     }
    -                $value = trim($row[$currentIndex]);
    +                $value = trim($line[$columnIndex]);
                     if (\strlen($value) === 0) {
    +                    // value is empty, ignore it.
                         continue;
                     }
    -                // we can do some preprocessing here,
    -                // which is exclusively to fix the tags:
    -                if (null !== $columnConfig[$currentIndex]['preProcessMap'] && \strlen($columnConfig[$currentIndex]['preProcessMap']) > 0) {
    -                    /** @var PreProcessorInterface $preProcessor */
    -                    $preProcessor = app($columnConfig[$currentIndex]['preProcessMap']);
    -                    $result       = $preProcessor->run($value);
    -                    // can merge array, this is usually the case:
    -                    $columnConfig[$currentIndex]['values'] = array_merge($columnConfig[$currentIndex]['values'], $result);
    -                    continue;
    -                }
    -                $columnConfig[$currentIndex]['values'][] = $value;
    +                $columnConfig[$columnIndex]['values'][] = $value;
                 }
             }
     
             // loop array again. This time, do uniqueness.
             // and remove arrays that have 0 values.
    -        foreach ($validIndexes as $currentIndex) {
    -            $columnConfig[$currentIndex]['values'] = array_unique($columnConfig[$currentIndex]['values']);
    -            asort($columnConfig[$currentIndex]['values']);
    +        foreach ($mappableColumns as $columnIndex) {
    +            $columnConfig[$columnIndex]['values'] = array_unique($columnConfig[$columnIndex]['values']);
    +            asort($columnConfig[$columnIndex]['values']);
                 // if the count of this array is zero, there is nothing to map.
    -            if (\count($columnConfig[$currentIndex]['values']) === 0) {
    -                unset($columnConfig[$currentIndex]);
    +            if (\count($columnConfig[$columnIndex]['values']) === 0) {
    +                unset($columnConfig[$columnIndex]);
                 }
             }
     
    @@ -344,7 +303,7 @@ class ConfigureMappingHandler implements ConfigurationInterface
          *
          * @return string
          */
    -    private function sanitizeColumnName(string $name): string
    +    public function sanitizeColumnName(string $name): string
         {
             /** @var array $validColumns */
             $validColumns = array_keys(config('csv.import_roles'));
    @@ -354,4 +313,36 @@ class ConfigureMappingHandler implements ConfigurationInterface
     
             return $name;
         }
    +
    +    /**
    +     * @param ImportJob $job
    +     */
    +    public function setJob(ImportJob $job): void
    +    {
    +        $this->importJob  = $job;
    +        $this->repository = app(ImportJobRepositoryInterface::class);
    +        $this->repository->setUser($job->user);
    +        $this->attachments  = app(AttachmentHelperInterface::class);
    +        $this->columnConfig = [];
    +    }
    +
    +    /**
    +     * Create the "mapper" class that will eventually return the correct data for the user
    +     * to map against. For example: a list of asset accounts. A list of budgets. A list of tags.
    +     *
    +     * @param string $column
    +     *
    +     * @return MapperInterface
    +     * @throws FireflyException
    +     */
    +    private function createMapper(string $column): MapperInterface
    +    {
    +        $mapperClass = config('csv.import_roles.' . $column . '.mapper');
    +        $mapperName  = sprintf('FireflyIII\\Import\Mapper\\%s', $mapperClass);
    +        if (!class_exists($mapperName)) {
    +            throw new FireflyException(sprintf('Class "%s" does not exist. Cannot map "%s"', $mapperName, $column)); // @codeCoverageIgnore
    +        }
    +
    +        return app($mapperName);
    +    }
     }
    \ No newline at end of file
    diff --git a/tests/Unit/Support/Import/Configuration/File/ConfigureMappingHandlerTest.php b/tests/Unit/Support/Import/Configuration/File/ConfigureMappingHandlerTest.php
    new file mode 100644
    index 0000000000..394fc5bc51
    --- /dev/null
    +++ b/tests/Unit/Support/Import/Configuration/File/ConfigureMappingHandlerTest.php
    @@ -0,0 +1,488 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace Tests\Unit\Support\Import\Configuration\File;
    +
    +use FireflyIII\Exceptions\FireflyException;
    +use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
    +use FireflyIII\Import\Mapper\Budgets;
    +use FireflyIII\Import\Specifics\IngDescription;
    +use FireflyIII\Models\Attachment;
    +use FireflyIII\Models\ImportJob;
    +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
    +use FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler;
    +use Illuminate\Support\Collection;
    +use League\Csv\Exception;
    +use League\Csv\Reader;
    +use Mockery;
    +use Tests\TestCase;
    +
    +/**
    + * Class ConfigureMappingHandlerTest
    + *
    + * @package Tests\Unit\Support\Import\Configuration\File
    + */
    +class ConfigureMappingHandlerTest extends TestCase
    +{
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler
    +     */
    +    public function testApplySpecifics(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'mapG' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $expected = ['a' => 'ING'];
    +
    +        // mock ING description (see below)
    +        $ingDescr = $this->mock(IngDescription::class);
    +        $ingDescr->shouldReceive('run')->once()->andReturn($expected);
    +
    +        $config = [
    +            'specifics' => [
    +                'IngDescription' => 1,
    +                'bad-specific'   => 1,
    +            ],
    +        ];
    +
    +        $handler = new ConfigureMappingHandler;
    +        $handler->setJob($job);
    +        $result = $handler->applySpecifics($config, []);
    +        $this->assertEquals($expected, $result);
    +
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler
    +     */
    +    public function testConfigureJob(): void
    +    {
    +        // create fake input for class method:
    +        $input          = [
    +            'mapping' => [
    +
    +                0 => [// column
    +                      'fake-iban'        => 1,
    +                      'other-fake-value' => '2', // string
    +                ],
    +                1 => [
    +                    3                  => 2, // fake number
    +                    'final-fake-value' => 3,
    +                    'mapped-to-zero'   => 0,
    +                ],
    +
    +            ],
    +        ];
    +        $expectedResult = [
    +            'column-mapping-config' =>
    +                [
    +                    0 => [
    +                        'fake-iban'        => 1,
    +                        'other-fake-value' => 2,
    +                    ],
    +                    1 => [
    +                        '3'                => 2,
    +                        'final-fake-value' => 3,
    +                    ],
    +                ],
    +
    +        ];
    +
    +
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'mapA' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +
    +        // mock repos
    +        $repository = $this->mock(ImportJobRepositoryInterface::class);
    +
    +        // run configure mapping handler.
    +        // expect specific results:
    +        $repository->shouldReceive('setUser')->once();
    +        $repository->shouldReceive('setStage')->once()->withArgs([Mockery::any(), 'ready_to_run']);
    +        $repository->shouldReceive('setConfiguration')->once()->withArgs([Mockery::any(), $expectedResult]);
    +
    +
    +        $handler = new ConfigureMappingHandler;
    +        $handler->setJob($job);
    +        $handler->configureJob($input);
    +
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler
    +     */
    +    public function testDoColumnConfig(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'mapE' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $fakeBudgets = [
    +            0 => 'dont map',
    +            1 => 'Fake budget A',
    +            4 => 'Other fake budget',
    +        ];
    +
    +        // fake budget mapper (see below)
    +        $budgetMapper = $this->mock(Budgets::class);
    +        $budgetMapper->shouldReceive('getMap')->once()->andReturn($fakeBudgets);
    +
    +        // input array:
    +        $input = [
    +            'column-roles'      => [
    +                0 => 'description', // cannot be mapped
    +                1 => 'sepa-ct-id', // cannot be mapped
    +                2 => 'tags-space', // cannot be mapped, has a pre-processor.
    +                3 => 'account-id', // can be mapped
    +                4 => 'budget-id' // can be mapped.
    +            ],
    +            'column-do-mapping' => [
    +                0 => false, // don't try to map description
    +                1 => true,  // try to map sepa (cannot)
    +                2 => true,  // try to map tags (cannot)
    +                3 => false, // dont map mappable
    +                4 => true, // want to map, AND can map.
    +            ],
    +        ];
    +
    +        $expected = [
    +            4 => [
    +                'name'          => 'budget-id',
    +                'options'       => $fakeBudgets,
    +                'preProcessMap' => '',
    +                'values'        => [],
    +            ],
    +        ];
    +
    +        $handler = new ConfigureMappingHandler;
    +        $handler->setJob($job);
    +        try {
    +            $result = $handler->doColumnConfig($input);
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +
    +        $this->assertEquals($expected, $result);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler
    +     */
    +    public function testDoMapOfColumn(): void
    +    {
    +
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'mapC' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $combinations = [
    +            ['role' => 'description', 'expected' => false, 'requested' => false], // description cannot be mapped. Will always return false.
    +            ['role' => 'description', 'expected' => false, 'requested' => true], // description cannot be mapped. Will always return false.
    +            ['role' => 'currency-id', 'expected' => false, 'requested' => false], // if not requested, return false.
    +            ['role' => 'currency-id', 'expected' => true, 'requested' => true], // if requested, return true.
    +        ];
    +
    +        $handler = new ConfigureMappingHandler;
    +        $handler->setJob($job);
    +        foreach ($combinations as $info) {
    +            $this->assertEquals($info['expected'], $handler->doMapOfColumn($info['role'], $info['requested']));
    +        }
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler
    +     */
    +    public function testGetNextData(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'mapH' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [
    +            'column-roles'      => [
    +                0 => 'description', // cannot be mapped
    +                1 => 'sepa-ct-id', // cannot be mapped
    +                2 => 'tags-space', // cannot be mapped, has a pre-processor.
    +                3 => 'account-id', // can be mapped
    +                4 => 'budget-id' // can be mapped.
    +            ],
    +            'column-do-mapping' => [
    +                0 => false, // don't try to map description
    +                1 => true,  // try to map sepa (cannot)
    +                2 => true,  // try to map tags (cannot)
    +                3 => false, // dont map mappable
    +                4 => true, // want to map, AND can map.
    +            ],
    +            'delimiter'         => ',',
    +        ];
    +        $job->save();
    +
    +        // make one attachment.
    +        $att                  = new Attachment;
    +        $att->filename        = 'import_file';
    +        $att->user_id         = $this->user()->id;
    +        $att->attachable_id   = $job->id;
    +        $att->attachable_type = Attachment::class;
    +        $att->md5             = md5('hello');
    +        $att->mime            = 'fake';
    +        $att->size            = 3;
    +        $att->save();
    +
    +        // fake some data.
    +        $fileContent = "column1,column2,column3,column4,column5\nvalue1,value2,value3,value4,value5";
    +        $fakeBudgets = [
    +            0 => 'dont map',
    +            1 => 'Fake budget A',
    +            4 => 'Other fake budget',
    +        ];
    +        // mock some helpers:
    +        $attachments = $this->mock(AttachmentHelperInterface::class);
    +        $repository  = $this->mock(ImportJobRepositoryInterface::class);
    +        $repository->shouldReceive('getConfiguration')->once()->withArgs([Mockery::any()])->andReturn($job->configuration);
    +        $repository->shouldReceive('setUser')->once();
    +        $repository->shouldReceive('getAttachments')->once()->withArgs([Mockery::any()])->andReturn(new Collection([$att]));
    +        $attachments->shouldReceive('getAttachmentContent')->withArgs([Mockery::any()])->andReturn($fileContent);
    +        $budgetMapper = $this->mock(Budgets::class);
    +        $budgetMapper->shouldReceive('getMap')->once()->andReturn($fakeBudgets);
    +
    +
    +        $handler = new ConfigureMappingHandler;
    +        $handler->setJob($job);
    +        try {
    +            $result = $handler->getNextData();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $expected = [
    +            4 => [ // is the one with the budget id, remember?
    +                   'name'          => 'budget-id',
    +                   'options'       => $fakeBudgets,
    +                   'preProcessMap' => '',
    +                   'values'        => ['column5', 'value5'], // see $fileContent
    +            ],
    +        ];
    +
    +        $this->assertEquals($expected, $result);
    +
    +
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler
    +     */
    +    public function testGetPreProcessorName(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'mapD' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $combinations = [
    +            ['role' => 'tags-space', 'expected' => '\\FireflyIII\\Import\\MapperPreProcess\\TagsSpace'], // tags- space has a pre-processor. Return it.
    +            ['role' => 'description', 'expected' => ''], // description has not.
    +            ['role' => 'no-such-role', 'expected' => ''], // not existing role has not.
    +        ];
    +
    +        $handler = new ConfigureMappingHandler;
    +        $handler->setJob($job);
    +        foreach ($combinations as $info) {
    +            $this->assertEquals($info['expected'], $handler->getPreProcessorName($info['role']));
    +        }
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler
    +     */
    +    public function testGetReader(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'mapF' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        // make one attachment.
    +        $att                  = new Attachment;
    +        $att->filename        = 'import_file';
    +        $att->user_id         = $this->user()->id;
    +        $att->attachable_id   = $job->id;
    +        $att->attachable_type = Attachment::class;
    +        $att->md5             = md5('hello');
    +        $att->mime            = 'fake';
    +        $att->size            = 3;
    +        $att->save();
    +        $config = [
    +            'delimiter' => ',',
    +        ];
    +
    +        $fileContent = "column1,column2,column3\nvalue1,value2,value3";
    +
    +        // mock some helpers:
    +        $attachments = $this->mock(AttachmentHelperInterface::class);
    +        $repository  = $this->mock(ImportJobRepositoryInterface::class);
    +        $repository->shouldReceive('getConfiguration')->once()->withArgs([Mockery::any()])->andReturn($config);
    +        $repository->shouldReceive('setUser')->once();
    +        $repository->shouldReceive('getAttachments')->once()->withArgs([Mockery::any()])->andReturn(new Collection([$att]));
    +        $attachments->shouldReceive('getAttachmentContent')->withArgs([Mockery::any()])->andReturn($fileContent);
    +
    +        $handler = new ConfigureMappingHandler;
    +        $handler->setJob($job);
    +        try {
    +            $reader = $handler->getReader();
    +        } catch (Exception $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler
    +     */
    +    public function testGetValuesForMapping(): void
    +    {
    +        // create a reader to use in method.
    +        // 5 columns, of which #4 (index 3) is budget-id
    +        // 5 columns, of which #5 (index 4) is tags-space
    +        $file   = "value1,value2,value3,1,some tags here\nvalue4,value5,value6,2,more tags there\nvalueX,valueY,valueZ\nA,B,C,,\nd,e,f,1,xxx";
    +        $reader = Reader::createFromString($file);
    +
    +        // make config for use in method.
    +        $config = [
    +            'has-headers' => false,
    +        ];
    +
    +        // make column config
    +        $columnConfig = [
    +            3 => [
    +                'name'          => 'budget-id',
    +                'options'       => [
    +                    0 => 'dont map',
    +                    1 => 'Fake budget A',
    +                    4 => 'Other fake budget',
    +                ],
    +                'preProcessMap' => '',
    +                'values'        => [],
    +            ],
    +        ];
    +
    +        // expected result
    +        $expected = [
    +            3 => [
    +                'name'          => 'budget-id',
    +                'options'       => [
    +                    0 => 'dont map',
    +                    1 => 'Fake budget A',
    +                    4 => 'Other fake budget',
    +                ],
    +                'preProcessMap' => '',
    +                'values'        => ['1', '2'] // all values from column 3 of "CSV" file, minus double values
    +            ],
    +        ];
    +
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'mapB' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $handler = new ConfigureMappingHandler;
    +        $handler->setJob($job);
    +        $result = [];
    +        try {
    +            $result = $handler->getValuesForMapping($reader, $config, $columnConfig);
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals($expected, $result);
    +
    +
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureMappingHandler
    +     */
    +    public function testSanitizeColumnName(): void
    +    {
    +
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'mapB' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $handler = new ConfigureMappingHandler;
    +        $handler->setJob($job);
    +        $keys = array_keys(config('csv.import_roles'));
    +        foreach ($keys as $key) {
    +            $this->assertEquals($key, $handler->sanitizeColumnName($key));
    +        }
    +        $this->assertEquals('_ignore', $handler->sanitizeColumnName('some-bad-name'));
    +    }
    +
    +}
    \ No newline at end of file
    diff --git a/tests/Unit/TransactionRules/Triggers/HasNoCategoryTest.php b/tests/Unit/TransactionRules/Triggers/HasNoCategoryTest.php
    index 18875e841c..8eae77fd89 100644
    --- a/tests/Unit/TransactionRules/Triggers/HasNoCategoryTest.php
    +++ b/tests/Unit/TransactionRules/Triggers/HasNoCategoryTest.php
    @@ -75,12 +75,16 @@ class HasNoCategoryTest extends TestCase
          */
         public function testTriggeredTransaction()
         {
    -        $journal     = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first();
    +        $count = 0;
    +        while ($count === 0) {
    +            $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first();
    +            $count   = $journal->transactions()->count();
    +        }
             $transaction = $journal->transactions()->first();
             $category    = $journal->user->categories()->first();
     
             $journal->categories()->detach();
    -        $transaction->categories()->save($category);
    +        $transaction->categories()->sync([$category->id]);
             $this->assertEquals(0, $journal->categories()->count());
             $this->assertEquals(1, $transaction->categories()->count());
     
    
    From 50874c9cf793945a475c32552b21a9736a904ea0 Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Thu, 10 May 2018 23:11:11 +0200
    Subject: [PATCH 060/182] Rename some variables.
    
    ---
     .../File/ConfigureMappingHandler.php          |  2 ++
     .../File/ConfigureRolesHandler.php            | 29 +++++++++----------
     2 files changed, 16 insertions(+), 15 deletions(-)
    
    diff --git a/app/Support/Import/Configuration/File/ConfigureMappingHandler.php b/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    index 651db8cc0f..ff7418dffa 100644
    --- a/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    +++ b/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    @@ -180,10 +180,12 @@ class ConfigureMappingHandler implements ConfigurationInterface
             // in order to actually map we also need to read the FULL file.
             try {
                 $reader = $this->getReader();
    +            // @codeCoverageIgnoreStart
             } catch (Exception $e) {
                 Log::error($e->getMessage());
                 throw new FireflyException('Cannot get reader: ' . $e->getMessage());
             }
    +        // @codeCoverageIgnoreEnd
     
             // get ALL values for the mappable columns from the CSV file:
             $columnConfig = $this->getValuesForMapping($reader, $config, $columnConfig);
    diff --git a/app/Support/Import/Configuration/File/ConfigureRolesHandler.php b/app/Support/Import/Configuration/File/ConfigureRolesHandler.php
    index b24266cbf6..caaf42681c 100644
    --- a/app/Support/Import/Configuration/File/ConfigureRolesHandler.php
    +++ b/app/Support/Import/Configuration/File/ConfigureRolesHandler.php
    @@ -180,21 +180,20 @@ class ConfigureRolesHandler implements ConfigurationInterface
             }
     
     
    -
             return new MessageBag;
         }
     
         /**
          * Extracts example data from a single row and store it in the class.
          *
    -     * @param array $row
    +     * @param array $line
          */
    -    private function getExampleFromRow(array $row): void
    +    private function getExampleFromLine(array $line): void
         {
    -        foreach ($row as $index => $value) {
    +        foreach ($line as $column => $value) {
                 $value = trim($value);
                 if (\strlen($value) > 0) {
    -                $this->examples[$index][] = $value;
    +                $this->examples[$column][] = $value;
                 }
             }
         }
    @@ -223,13 +222,13 @@ class ConfigureRolesHandler implements ConfigurationInterface
     
             // grab the records:
             $records = $stmt->process($reader);
    -        /** @var array $row */
    -        foreach ($records as $row) {
    -            $row                = array_values($row);
    -            $row                = $this->processSpecifics($row);
    -            $count              = \count($row);
    +        /** @var array $line */
    +        foreach ($records as $line) {
    +            $line               = array_values($line);
    +            $line               = $this->processSpecifics($line);
    +            $count              = \count($line);
                 $this->totalColumns = $count > $this->totalColumns ? $count : $this->totalColumns;
    -            $this->getExampleFromRow($row);
    +            $this->getExampleFromLine($line);
             }
             // save column count:
             $this->saveColumCount();
    @@ -361,11 +360,11 @@ class ConfigureRolesHandler implements ConfigurationInterface
         /**
          * if the user has configured specific fixes to be applied, they must be applied to the example data as well.
          *
    -     * @param array $row
    +     * @param array $line
          *
          * @return array
          */
    -    private function processSpecifics(array $row): array
    +    private function processSpecifics(array $line): array
         {
             $config    = $this->importJob->configuration;
             $specifics = $config['specifics'] ?? [];
    @@ -373,10 +372,10 @@ class ConfigureRolesHandler implements ConfigurationInterface
             foreach ($names as $name) {
                 /** @var SpecificInterface $specific */
                 $specific = app('FireflyIII\Import\Specifics\\' . $name);
    -            $row      = $specific->run($row);
    +            $line     = $specific->run($line);
             }
     
    -        return $row;
    +        return $line;
     
         }
     
    
    From 5a560b42ef631edd76b4d74453d5fd1de11ab34d Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Fri, 11 May 2018 09:51:47 +0200
    Subject: [PATCH 061/182] Improve test coverage.
    
    ---
     .../File/ConfigureMappingHandler.php          |  44 +-
     .../File/ConfigureRolesHandler.php            | 225 ++++----
     .../File/ConfigureUploadHandler.php           |   6 +-
     .../Configuration/File/NewFileJobHandler.php  |   2 +-
     .../Import/Placeholder/ColumnValue.php        |   2 +-
     .../Import/Placeholder/ImportTransaction.php  |   6 +-
     .../Import/Routine/File/CSVProcessor.php      |   2 +-
     .../Routine/File/FileProcessorInterface.php   |   2 +-
     .../views/import/file/configure-upload.twig   |   2 +-
     .../FileJobConfigurationTest.php              |   2 +-
     tests/Unit/Import/Routine/FileRoutineTest.php |   2 +-
     .../File/ConfigureMappingHandlerTest.php      |   2 +-
     .../File/ConfigureRolesHandlerTest.php        | 525 ++++++++++++++++++
     13 files changed, 684 insertions(+), 138 deletions(-)
     create mode 100644 tests/Unit/Support/Import/Configuration/File/ConfigureRolesHandlerTest.php
    
    diff --git a/app/Support/Import/Configuration/File/ConfigureMappingHandler.php b/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    index ff7418dffa..5e5025c4e2 100644
    --- a/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    +++ b/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    @@ -67,7 +67,7 @@ class ConfigureMappingHandler implements ConfigurationInterface
             $specifics      = $config['specifics'] ?? [];
             $names          = array_keys($specifics);
             foreach ($names as $name) {
    -            if (!\in_array($name, $validSpecifics)) {
    +            if (!\in_array($name, $validSpecifics, true)) {
                     continue;
                 }
                 $class = config(sprintf('csv.import_specifics.%s', $name));
    @@ -109,6 +109,26 @@ class ConfigureMappingHandler implements ConfigurationInterface
             return new MessageBag;
         }
     
    +    /**
    +     * Create the "mapper" class that will eventually return the correct data for the user
    +     * to map against. For example: a list of asset accounts. A list of budgets. A list of tags.
    +     *
    +     * @param string $column
    +     *
    +     * @return MapperInterface
    +     * @throws FireflyException
    +     */
    +    public function createMapper(string $column): MapperInterface
    +    {
    +        $mapperClass = config('csv.import_roles.' . $column . '.mapper');
    +        $mapperName  = sprintf('FireflyIII\\Import\Mapper\\%s', $mapperClass);
    +        if (!class_exists($mapperName)) {
    +            throw new FireflyException(sprintf('Class "%s" does not exist. Cannot map "%s"', $mapperName, $column)); // @codeCoverageIgnore
    +        }
    +
    +        return app($mapperName);
    +    }
    +
         /**
          * For each column in the configuration of the job, will:
          * - validate the role.
    @@ -327,24 +347,4 @@ class ConfigureMappingHandler implements ConfigurationInterface
             $this->attachments  = app(AttachmentHelperInterface::class);
             $this->columnConfig = [];
         }
    -
    -    /**
    -     * Create the "mapper" class that will eventually return the correct data for the user
    -     * to map against. For example: a list of asset accounts. A list of budgets. A list of tags.
    -     *
    -     * @param string $column
    -     *
    -     * @return MapperInterface
    -     * @throws FireflyException
    -     */
    -    private function createMapper(string $column): MapperInterface
    -    {
    -        $mapperClass = config('csv.import_roles.' . $column . '.mapper');
    -        $mapperName  = sprintf('FireflyIII\\Import\Mapper\\%s', $mapperClass);
    -        if (!class_exists($mapperName)) {
    -            throw new FireflyException(sprintf('Class "%s" does not exist. Cannot map "%s"', $mapperName, $column)); // @codeCoverageIgnore
    -        }
    -
    -        return app($mapperName);
    -    }
    -}
    \ No newline at end of file
    +}
    diff --git a/app/Support/Import/Configuration/File/ConfigureRolesHandler.php b/app/Support/Import/Configuration/File/ConfigureRolesHandler.php
    index caaf42681c..e857d32c16 100644
    --- a/app/Support/Import/Configuration/File/ConfigureRolesHandler.php
    +++ b/app/Support/Import/Configuration/File/ConfigureRolesHandler.php
    @@ -52,80 +52,6 @@ class ConfigureRolesHandler implements ConfigurationInterface
         /** @var int */
         private $totalColumns;
     
    -    /**
    -     * Store data associated with current stage.
    -     *
    -     * @param array $data
    -     *
    -     * @return MessageBag
    -     */
    -    public function configureJob(array $data): MessageBag
    -    {
    -        $config = $this->importJob->configuration;
    -        $count  = $config['column-count'];
    -        for ($i = 0; $i < $count; ++$i) {
    -            $role                            = $data['role'][$i] ?? '_ignore';
    -            $mapping                         = (isset($data['map'][$i]) && $data['map'][$i] === '1');
    -            $config['column-roles'][$i]      = $role;
    -            $config['column-do-mapping'][$i] = $mapping;
    -            Log::debug(sprintf('Column %d has been given role %s (mapping: %s)', $i, $role, var_export($mapping, true)));
    -        }
    -        $config   = $this->ignoreUnmappableColumns($config);
    -        $messages = $this->configurationComplete($config);
    -
    -        if ($messages->count() === 0) {
    -            $this->repository->setStage($this->importJob, 'ready_to_run');
    -            if ($this->isMappingNecessary($config)) {
    -                $this->repository->setStage($this->importJob, 'map');
    -            }
    -            $this->repository->setConfiguration($this->importJob, $config);
    -        }
    -
    -        return $messages;
    -    }
    -
    -    /**
    -     * Get the data necessary to show the configuration screen.
    -     *
    -     * @return array
    -     * @throws FireflyException
    -     */
    -    public function getNextData(): array
    -    {
    -        try {
    -            $reader = $this->getReader();
    -        } catch (Exception $e) {
    -            Log::error($e->getMessage());
    -            throw new FireflyException($e->getMessage());
    -        }
    -        $headers = $this->getHeaders($reader);
    -
    -        // get example rows:
    -        $this->getExamples($reader);
    -
    -        return [
    -            'examples' => $this->examples,
    -            'roles'    => $this->getRoles(),
    -            'total'    => $this->totalColumns,
    -            'headers'  => $headers,
    -        ];
    -    }
    -
    -    /**
    -     * Set job and some start values.
    -     *
    -     * @param ImportJob $job
    -     */
    -    public function setJob(ImportJob $job): void
    -    {
    -        $this->importJob  = $job;
    -        $this->repository = app(ImportJobRepositoryInterface::class);
    -        $this->repository->setUser($job->user);
    -        $this->attachments  = app(AttachmentHelperInterface::class);
    -        $this->totalColumns = 0;
    -        $this->examples     = [];
    -    }
    -
         /**
          * Verifies that the configuration of the job is actually complete, and valid.
          *
    @@ -133,8 +59,10 @@ class ConfigureRolesHandler implements ConfigurationInterface
          *
          * @return MessageBag
          */
    -    private function configurationComplete(array $config): MessageBag
    +    public function configurationComplete(array $config): MessageBag
         {
    +        /** @var array $roles */
    +        $roles    = $config['column-roles'];
             $count    = $config['column-count'];
             $assigned = 0;
     
    @@ -142,8 +70,7 @@ class ConfigureRolesHandler implements ConfigurationInterface
             $hasAmount        = false;
             $hasForeignAmount = false;
             $hasForeignCode   = false;
    -        for ($i = 0; $i < $count; ++$i) {
    -            $role = $config['column-roles'][$i] ?? '_ignore';
    +        foreach ($roles as $role) {
                 if ('_ignore' !== $role) {
                     ++$assigned;
                 }
    @@ -180,7 +107,39 @@ class ConfigureRolesHandler implements ConfigurationInterface
             }
     
     
    -        return new MessageBag;
    +        return new MessageBag; // @codeCoverageIgnore
    +    }
    +
    +    /**
    +     * Store data associated with current stage.
    +     *
    +     * @param array $data
    +     *
    +     * @return MessageBag
    +     */
    +    public function configureJob(array $data): MessageBag
    +    {
    +        $config = $this->importJob->configuration;
    +        $count  = $config['column-count'];
    +        for ($i = 0; $i < $count; ++$i) {
    +            $role                            = $data['role'][$i] ?? '_ignore';
    +            $mapping                         = (isset($data['map'][$i]) && $data['map'][$i] === '1');
    +            $config['column-roles'][$i]      = $role;
    +            $config['column-do-mapping'][$i] = $mapping;
    +            Log::debug(sprintf('Column %d has been given role %s (mapping: %s)', $i, $role, var_export($mapping, true)));
    +        }
    +        $config   = $this->ignoreUnmappableColumns($config);
    +        $messages = $this->configurationComplete($config);
    +
    +        if ($messages->count() === 0) {
    +            $this->repository->setStage($this->importJob, 'ready_to_run');
    +            if ($this->isMappingNecessary($config)) {
    +                $this->repository->setStage($this->importJob, 'map');
    +            }
    +            $this->repository->setConfiguration($this->importJob, $config);
    +        }
    +
    +        return $messages;
         }
     
         /**
    @@ -188,7 +147,7 @@ class ConfigureRolesHandler implements ConfigurationInterface
          *
          * @param array $line
          */
    -    private function getExampleFromLine(array $line): void
    +    public function getExampleFromLine(array $line): void
         {
             foreach ($line as $column => $value) {
                 $value = trim($value);
    @@ -198,34 +157,43 @@ class ConfigureRolesHandler implements ConfigurationInterface
             }
         }
     
    +    /**
    +     * @return array
    +     */
    +    public function getExamples(): array
    +    {
    +        return $this->examples;
    +    }
    +
         /**
          * Return a bunch of examples from the CSV file the user has uploaded.
          *
          * @param Reader $reader
    +     * @param array  $config
          *
          * @throws FireflyException
          */
    -    private function getExamples(Reader $reader): void
    +    public function getExamplesFromFile(Reader $reader, array $config): void
         {
    -        // configure example data:
    -        $config = $this->importJob->configuration;
             $limit  = (int)config('csv.example_rows', 5);
             $offset = isset($config['has-headers']) && $config['has-headers'] === true ? 1 : 0;
     
             // make statement.
             try {
                 $stmt = (new Statement)->limit($limit)->offset($offset);
    +            // @codeCoverageIgnoreStart
             } catch (Exception $e) {
                 Log::error($e->getMessage());
                 throw new FireflyException($e->getMessage());
             }
    +        // @codeCoverageIgnoreEnd
     
             // grab the records:
             $records = $stmt->process($reader);
             /** @var array $line */
             foreach ($records as $line) {
                 $line               = array_values($line);
    -            $line               = $this->processSpecifics($line);
    +            $line               = $this->processSpecifics($config, $line);
                 $count              = \count($line);
                 $this->totalColumns = $count > $this->totalColumns ? $count : $this->totalColumns;
                 $this->getExampleFromLine($line);
    @@ -239,39 +207,71 @@ class ConfigureRolesHandler implements ConfigurationInterface
          * Get the header row, if one is present.
          *
          * @param Reader $reader
    +     * @param array  $config
          *
          * @return array
          * @throws FireflyException
          */
    -    private function getHeaders(Reader $reader): array
    +    public function getHeaders(Reader $reader, array $config): array
         {
             $headers = [];
    -        $config  = $this->importJob->configuration;
    -        if ($config['has-headers']) {
    +        if (isset($config['has-headers']) && $config['has-headers'] === true) {
                 try {
                     $stmt    = (new Statement)->limit(1)->offset(0);
                     $records = $stmt->process($reader);
                     $headers = $records->fetchOne(0);
    +                // @codeCoverageIgnoreStart
                 } catch (Exception $e) {
                     Log::error($e->getMessage());
                     throw new FireflyException($e->getMessage());
                 }
    +            // @codeCoverageIgnoreEnd
                 Log::debug('Detected file headers:', $headers);
             }
     
             return $headers;
         }
     
    +    /**
    +     * Get the data necessary to show the configuration screen.
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    public function getNextData(): array
    +    {
    +        try {
    +            $reader = $this->getReader();
    +            // @codeCoverageIgnoreStart
    +        } catch (Exception $e) {
    +            Log::error($e->getMessage());
    +            throw new FireflyException($e->getMessage());
    +        }
    +        // @codeCoverageIgnoreEnd
    +        $configuration = $this->importJob->configuration;
    +        $headers       = $this->getHeaders($reader, $configuration);
    +
    +        // get example rows:
    +        $this->getExamplesFromFile($reader, $configuration);
    +
    +        return [
    +            'examples' => $this->examples,
    +            'roles'    => $this->getRoles(),
    +            'total'    => $this->totalColumns,
    +            'headers'  => $headers,
    +        ];
    +    }
    +
         /**
          * Return an instance of a CSV file reader so content of the file can be read.
          *
          * @throws \League\Csv\Exception
          */
    -    private function getReader(): Reader
    +    public function getReader(): Reader
         {
             $content = '';
             /** @var Collection $collection */
    -        $collection = $this->importJob->attachments;
    +        $collection = $this->repository->getAttachments($this->importJob);
             /** @var Attachment $attachment */
             foreach ($collection as $attachment) {
                 if ($attachment->filename === 'import_file') {
    @@ -289,9 +289,10 @@ class ConfigureRolesHandler implements ConfigurationInterface
         /**
          * Returns all possible roles and translate their name. Then sort them.
          *
    +     * @codeCoverageIgnore
          * @return array
          */
    -    private function getRoles(): array
    +    public function getRoles(): array
         {
             $roles = [];
             foreach (array_keys(config('csv.import_roles')) as $role) {
    @@ -310,7 +311,7 @@ class ConfigureRolesHandler implements ConfigurationInterface
          *
          * @return array
          */
    -    private function ignoreUnmappableColumns(array $config): array
    +    public function ignoreUnmappableColumns(array $config): array
         {
             $count = $config['column-count'];
             for ($i = 0; $i < $count; ++$i) {
    @@ -333,13 +334,13 @@ class ConfigureRolesHandler implements ConfigurationInterface
          *
          * @return bool
          */
    -    private function isMappingNecessary(array $config): bool
    +    public function isMappingNecessary(array $config): bool
         {
    -        $count      = $config['column-count'];
    +        /** @var array $doMapping */
    +        $doMapping  = $config['column-do-mapping'] ?? [];
             $toBeMapped = 0;
    -        for ($i = 0; $i < $count; ++$i) {
    -            $mapping = $config['column-do-mapping'][$i] ?? false;
    -            if (true === $mapping) {
    +        foreach ($doMapping as $doMap) {
    +            if (true === $doMap) {
                     ++$toBeMapped;
                 }
             }
    @@ -350,7 +351,7 @@ class ConfigureRolesHandler implements ConfigurationInterface
         /**
          * Make sure that the examples do not contain double data values.
          */
    -    private function makeExamplesUnique(): void
    +    public function makeExamplesUnique(): void
         {
             foreach ($this->examples as $index => $values) {
                 $this->examples[$index] = array_unique($values);
    @@ -360,16 +361,20 @@ class ConfigureRolesHandler implements ConfigurationInterface
         /**
          * if the user has configured specific fixes to be applied, they must be applied to the example data as well.
          *
    +     * @param array $config
          * @param array $line
          *
          * @return array
          */
    -    private function processSpecifics(array $line): array
    +    public function processSpecifics(array $config, array $line): array
         {
    -        $config    = $this->importJob->configuration;
    -        $specifics = $config['specifics'] ?? [];
    -        $names     = array_keys($specifics);
    +        $validSpecifics = array_keys(config('csv.import_specifics'));
    +        $specifics      = $config['specifics'] ?? [];
    +        $names          = array_keys($specifics);
             foreach ($names as $name) {
    +            if (!\in_array($name, $validSpecifics, true)) {
    +                continue;
    +            }
                 /** @var SpecificInterface $specific */
                 $specific = app('FireflyIII\Import\Specifics\\' . $name);
                 $line     = $specific->run($line);
    @@ -381,13 +386,29 @@ class ConfigureRolesHandler implements ConfigurationInterface
     
         /**
          * Save the column count in the job. It's used in a later stage.
    +     * TODO move config out of this method (make it a parameter).
          *
          * @return void
          */
    -    private function saveColumCount(): void
    +    public function saveColumCount(): void
         {
             $config                 = $this->importJob->configuration;
             $config['column-count'] = $this->totalColumns;
             $this->repository->setConfiguration($this->importJob, $config);
         }
    -}
    \ No newline at end of file
    +
    +    /**
    +     * Set job and some start values.
    +     *
    +     * @param ImportJob $job
    +     */
    +    public function setJob(ImportJob $job): void
    +    {
    +        $this->importJob  = $job;
    +        $this->repository = app(ImportJobRepositoryInterface::class);
    +        $this->repository->setUser($job->user);
    +        $this->attachments  = app(AttachmentHelperInterface::class);
    +        $this->totalColumns = 0;
    +        $this->examples     = [];
    +    }
    +}
    diff --git a/app/Support/Import/Configuration/File/ConfigureUploadHandler.php b/app/Support/Import/Configuration/File/ConfigureUploadHandler.php
    index 7fd36b8204..553423aa07 100644
    --- a/app/Support/Import/Configuration/File/ConfigureUploadHandler.php
    +++ b/app/Support/Import/Configuration/File/ConfigureUploadHandler.php
    @@ -145,7 +145,7 @@ class ConfigureUploadHandler implements ConfigurationInterface
          *
          * @return array
          */
    -    private function getSpecifics(array $data): array
    +    public function getSpecifics(array $data): array
         {
             $return = [];
             // check if specifics given are correct:
    @@ -153,7 +153,7 @@ class ConfigureUploadHandler implements ConfigurationInterface
     
                 foreach ($data['specifics'] as $name) {
                     // verify their content.
    -                $className = sprintf('FireflyIII\Import\Specifics\%s', $name);
    +                $className = sprintf('FireflyIII\\Import\\Specifics\\%s', $name);
                     if (class_exists($className)) {
                         $return[$name] = 1;
                     }
    @@ -162,4 +162,4 @@ class ConfigureUploadHandler implements ConfigurationInterface
     
             return $return;
         }
    -}
    \ No newline at end of file
    +}
    diff --git a/app/Support/Import/Configuration/File/NewFileJobHandler.php b/app/Support/Import/Configuration/File/NewFileJobHandler.php
    index a6f42e9d2f..f3ebcc61fe 100644
    --- a/app/Support/Import/Configuration/File/NewFileJobHandler.php
    +++ b/app/Support/Import/Configuration/File/NewFileJobHandler.php
    @@ -219,4 +219,4 @@ class NewFileJobHandler implements ConfigurationInterface
     
             return true;
         }
    -}
    \ No newline at end of file
    +}
    diff --git a/app/Support/Import/Placeholder/ColumnValue.php b/app/Support/Import/Placeholder/ColumnValue.php
    index 238a9687bc..151fcf0695 100644
    --- a/app/Support/Import/Placeholder/ColumnValue.php
    +++ b/app/Support/Import/Placeholder/ColumnValue.php
    @@ -110,4 +110,4 @@ class ColumnValue
         }
     
     
    -}
    \ No newline at end of file
    +}
    diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php
    index 7b059695bc..da40907a9c 100644
    --- a/app/Support/Import/Placeholder/ImportTransaction.php
    +++ b/app/Support/Import/Placeholder/ImportTransaction.php
    @@ -300,7 +300,7 @@ class ImportTransaction
              * @var string $modifier
              */
             foreach ($this->modifiers as $role => $modifier) {
    -            $class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $role)));
    +            $class = sprintf('FireflyIII\\Import\\Converter\\%s', config(sprintf('csv.import_roles.%s.converter', $role)));
                 /** @var ConverterInterface $converter */
                 $converter = app($class);
                 Log::debug(sprintf('Now launching converter %s', $class));
    @@ -342,7 +342,7 @@ class ImportTransaction
              * @var string $modifier
              */
             foreach ($this->modifiers as $role => $modifier) {
    -            $class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $role)));
    +            $class = sprintf('FireflyIII\\Import\\Converter\\%s', config(sprintf('csv.import_roles.%s.converter', $role)));
                 /** @var ConverterInterface $converter */
                 $converter = app($class);
                 Log::debug(sprintf('Now launching converter %s', $class));
    @@ -565,4 +565,4 @@ class ImportTransaction
             return $info;
         }
     
    -}
    \ No newline at end of file
    +}
    diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php
    index f0edce71e2..ec924af90d 100644
    --- a/app/Support/Import/Routine/File/CSVProcessor.php
    +++ b/app/Support/Import/Routine/File/CSVProcessor.php
    @@ -736,4 +736,4 @@ class CSVProcessor implements FileProcessorInterface
     
             return null;
         }
    -}
    \ No newline at end of file
    +}
    diff --git a/app/Support/Import/Routine/File/FileProcessorInterface.php b/app/Support/Import/Routine/File/FileProcessorInterface.php
    index 6dd51883db..7aa49a8f20 100644
    --- a/app/Support/Import/Routine/File/FileProcessorInterface.php
    +++ b/app/Support/Import/Routine/File/FileProcessorInterface.php
    @@ -46,4 +46,4 @@ interface FileProcessorInterface
          * @param ImportJob $job
          */
         public function setJob(ImportJob $job): void;
    -}
    \ No newline at end of file
    +}
    diff --git a/resources/views/import/file/configure-upload.twig b/resources/views/import/file/configure-upload.twig
    index f5d4d59e25..59de0c8a74 100644
    --- a/resources/views/import/file/configure-upload.twig
    +++ b/resources/views/import/file/configure-upload.twig
    @@ -112,4 +112,4 @@
         
         
         
    -{% endblock %}
    \ No newline at end of file
    +{% endblock %}
    diff --git a/tests/Unit/Import/JobConfiguration/FileJobConfigurationTest.php b/tests/Unit/Import/JobConfiguration/FileJobConfigurationTest.php
    index 12e90c7f2a..ca7af58510 100644
    --- a/tests/Unit/Import/JobConfiguration/FileJobConfigurationTest.php
    +++ b/tests/Unit/Import/JobConfiguration/FileJobConfigurationTest.php
    @@ -364,4 +364,4 @@ class FileJobConfigurationTest extends TestCase
             }
             $this->assertEquals('import.file.roles', $result);
         }
    -}
    \ No newline at end of file
    +}
    diff --git a/tests/Unit/Import/Routine/FileRoutineTest.php b/tests/Unit/Import/Routine/FileRoutineTest.php
    index 3d48bf98c2..90056bc034 100644
    --- a/tests/Unit/Import/Routine/FileRoutineTest.php
    +++ b/tests/Unit/Import/Routine/FileRoutineTest.php
    @@ -75,4 +75,4 @@ class FileRoutineTest extends TestCase
                 $this->assertTrue(false, $e->getMessage());
             }
         }
    -}
    \ No newline at end of file
    +}
    diff --git a/tests/Unit/Support/Import/Configuration/File/ConfigureMappingHandlerTest.php b/tests/Unit/Support/Import/Configuration/File/ConfigureMappingHandlerTest.php
    index 394fc5bc51..558c4421b2 100644
    --- a/tests/Unit/Support/Import/Configuration/File/ConfigureMappingHandlerTest.php
    +++ b/tests/Unit/Support/Import/Configuration/File/ConfigureMappingHandlerTest.php
    @@ -485,4 +485,4 @@ class ConfigureMappingHandlerTest extends TestCase
             $this->assertEquals('_ignore', $handler->sanitizeColumnName('some-bad-name'));
         }
     
    -}
    \ No newline at end of file
    +}
    diff --git a/tests/Unit/Support/Import/Configuration/File/ConfigureRolesHandlerTest.php b/tests/Unit/Support/Import/Configuration/File/ConfigureRolesHandlerTest.php
    new file mode 100644
    index 0000000000..1a190867f4
    --- /dev/null
    +++ b/tests/Unit/Support/Import/Configuration/File/ConfigureRolesHandlerTest.php
    @@ -0,0 +1,525 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace Tests\Unit\Support\Import\Configuration\File;
    +
    +
    +use Exception;
    +use FireflyIII\Exceptions\FireflyException;
    +use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
    +use FireflyIII\Import\Specifics\IngDescription;
    +use FireflyIII\Models\Attachment;
    +use FireflyIII\Models\ImportJob;
    +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
    +use FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler;
    +use Illuminate\Support\Collection;
    +use League\Csv\Reader;
    +use Mockery;
    +use Tests\TestCase;
    +
    +/**
    + * Class ConfigureRolesHandlerTest
    + */
    +class ConfigureRolesHandlerTest extends TestCase
    +{
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testConfigurationCompleteBasic(): void
    +    {
    +        $config  = [
    +            'column-count' => 5,
    +            'column-roles' => [
    +                0 => 'amount',
    +                1 => 'description',
    +                2 => 'note',
    +                3 => 'foreign-currency-code',
    +                4 => 'amount_foreign',
    +            ],
    +        ];
    +        $handler = new ConfigureRolesHandler();
    +        $result  = $handler->configurationComplete($config);
    +        $this->assertCount(0, $result);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testConfigurationCompleteForeign(): void
    +    {
    +        $config  = [
    +            'column-count' => 5,
    +            'column-roles' => [
    +                0 => 'amount',
    +                1 => 'description',
    +                2 => 'note',
    +                3 => 'amount_foreign',
    +                4 => 'sepa-cc',
    +            ],
    +        ];
    +        $handler = new ConfigureRolesHandler();
    +        $result  = $handler->configurationComplete($config);
    +        $this->assertCount(1, $result);
    +        $this->assertEquals(
    +            'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.',
    +            $result->get('error')[0]
    +        );
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testConfigurationCompleteNoAmount(): void
    +    {
    +        $config  = [
    +            'column-count' => 5,
    +            'column-roles' => [
    +                0 => 'sepa-cc',
    +                1 => 'description',
    +                2 => 'note',
    +                3 => 'foreign-currency-code',
    +                4 => 'amount_foreign',
    +            ],
    +        ];
    +        $handler = new ConfigureRolesHandler();
    +        $result  = $handler->configurationComplete($config);
    +        $this->assertCount(1, $result);
    +        $this->assertEquals(
    +            'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.',
    +            $result->get('error')[0]
    +        );
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testConfigureJob(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'role-B' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [
    +            'column-count' => 5,
    +        ];
    +        $job->save();
    +
    +        $data = [
    +            'role' => [
    +                0 => 'description',
    +                1 => 'budget-id',
    +                2 => 'sepa-cc',
    +                4 => 'amount', // no column 3.
    +            ],
    +            'map'  => [
    +                0 => '1', // map column 0 (which cannot be mapped anyway)
    +                1 => '1', // map column 1 (which CAN be mapped)
    +            ],
    +        ];
    +
    +        $expected = [
    +            'column-count'      => 5,
    +            'column-roles'      => [
    +                0 => 'description',
    +                1 => 'budget-id',
    +                2 => 'sepa-cc',
    +                3 => '_ignore', // added column 3
    +                4 => 'amount',
    +            ],
    +            'column-do-mapping' => [false, true, false, false, false],
    +        ];
    +
    +        $repository = $this->mock(ImportJobRepositoryInterface::class);
    +        $repository->shouldReceive('setUser')->once();
    +        $repository->shouldReceive('setStage')->once()->withArgs([Mockery::any(), 'ready_to_run']);
    +        $repository->shouldReceive('setStage')->once()->withArgs([Mockery::any(), 'map']);
    +        $repository->shouldReceive('setConfiguration')->once()->withArgs([Mockery::any(), $expected]);
    +
    +        $handler = new ConfigureRolesHandler();
    +        $handler->setJob($job);
    +        $handler->configureJob($data);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testGetExampleFromLine(): void
    +    {
    +
    +        $lines = [
    +            ['one', 'two', '', 'three'],
    +            ['four', 'five', '', 'six'],
    +        ];
    +
    +        $handler = new ConfigureRolesHandler;
    +        foreach ($lines as $line) {
    +            $handler->getExampleFromLine($line);
    +        }
    +        $expected = [
    +            0 => ['one', 'four'],
    +            1 => ['two', 'five'],
    +            3 => ['three', 'six'],
    +        ];
    +        $this->assertEquals($expected, $handler->getExamples());
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testGetExamplesFromFile(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'role-x' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [
    +            'specifics'   => [],
    +            'has-headers' => false,
    +        ];
    +        $job->save();
    +
    +        $file    = "one,two,,three\nfour,five,,six\none,three,X,three";
    +        $reader  = Reader::createFromString($file);
    +        $handler = new ConfigureRolesHandler;
    +        $handler->setJob($job);
    +        try {
    +            $handler->getExamplesFromFile($reader, $job->configuration);
    +        } catch (Exception $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +
    +        $expected = [
    +            0 => ['one', 'four'],
    +            1 => ['two', 'five', 'three'],
    +            2 => ['X'],
    +            3 => ['three', 'six'],
    +        ];
    +        $this->assertEquals($expected, $handler->getExamples());
    +
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testGetHeadersHas(): void
    +    {
    +        // create a reader to use in method.
    +        // 5 columns, of which #4 (index 3) is budget-id
    +        // 5 columns, of which #5 (index 4) is tags-space
    +        $file   = "header1,header2,header3,header4,header5\nvalue4,value5,value6,2,more tags there\nvalueX,valueY,valueZ\nA,B,C,,\nd,e,f,1,xxx";
    +        $reader = Reader::createFromString($file);
    +        $config = ['has-headers' => true];
    +
    +        $handler = new ConfigureRolesHandler;
    +        try {
    +            $headers = $handler->getHeaders($reader, $config);
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals(['header1', 'header2', 'header3', 'header4', 'header5'], $headers);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testGetHeadersNone(): void
    +    {
    +        // create a reader to use in method.
    +        // 5 columns, of which #4 (index 3) is budget-id
    +        // 5 columns, of which #5 (index 4) is tags-space
    +        $file   = "header1,header2,header3,header4,header5\nvalue4,value5,value6,2,more tags there\nvalueX,valueY,valueZ\nA,B,C,,\nd,e,f,1,xxx";
    +        $reader = Reader::createFromString($file);
    +        $config = ['has-headers' => false];
    +
    +        $handler = new ConfigureRolesHandler;
    +        try {
    +            $headers = $handler->getHeaders($reader, $config);
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals([], $headers);
    +    }
    +
    +    public function testGetNextData(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'role-x' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [
    +            'delimiter'   => ',',
    +            'has-headers' => true,
    +        ];
    +        $job->save();
    +
    +        // make one attachment.
    +        $att                  = new Attachment;
    +        $att->filename        = 'import_file';
    +        $att->user_id         = $this->user()->id;
    +        $att->attachable_id   = $job->id;
    +        $att->attachable_type = Attachment::class;
    +        $att->md5             = md5('hello');
    +        $att->mime            = 'fake';
    +        $att->size            = 3;
    +        $att->save();
    +
    +        $fileContent = "column1,column2,column3\nvalue1,value2,value3";
    +        // mock some helpers:
    +        $attachments = $this->mock(AttachmentHelperInterface::class);
    +        $repository  = $this->mock(ImportJobRepositoryInterface::class);
    +        $repository->shouldReceive('getConfiguration')->once()->withArgs([Mockery::any()])->andReturn($job->configuration);
    +        $repository->shouldReceive('setConfiguration')->once()->withArgs(
    +            [Mockery::any(),
    +             [
    +                 'delimiter'    => ',',
    +                 'has-headers'  => true,
    +                 'column-count' => 3,
    +             ],
    +            ]
    +        );
    +        $repository->shouldReceive('setUser')->once();
    +        $repository->shouldReceive('getAttachments')->once()->withArgs([Mockery::any()])->andReturn(new Collection([$att]));
    +        $attachments->shouldReceive('getAttachmentContent')->withArgs([Mockery::any()])->andReturn($fileContent);
    +
    +        $expected = [
    +            'examples' => [
    +                0 => ['value1'],
    +                1 => ['value2'],
    +                2 => ['value3'],
    +            ],
    +            'total'    => 3,
    +            'headers'  => ['column1', 'column2', 'column3'],
    +        ];
    +
    +        $handler = new ConfigureRolesHandler();
    +        $handler->setJob($job);
    +        try {
    +            $result = $handler->getNextData();
    +        } catch (Exception $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +        $this->assertEquals($expected['examples'], $result['examples']);
    +        $this->assertEquals($expected['total'], $result['total']);
    +        $this->assertEquals($expected['headers'], $result['headers']);
    +
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testGetReader(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'role-x' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        // make one attachment.
    +        $att                  = new Attachment;
    +        $att->filename        = 'import_file';
    +        $att->user_id         = $this->user()->id;
    +        $att->attachable_id   = $job->id;
    +        $att->attachable_type = Attachment::class;
    +        $att->md5             = md5('hello');
    +        $att->mime            = 'fake';
    +        $att->size            = 3;
    +        $att->save();
    +        $config = [
    +            'delimiter' => ',',
    +        ];
    +
    +        $fileContent = "column1,column2,column3\nvalue1,value2,value3";
    +
    +        // mock some helpers:
    +        $attachments = $this->mock(AttachmentHelperInterface::class);
    +        $repository  = $this->mock(ImportJobRepositoryInterface::class);
    +        $repository->shouldReceive('getConfiguration')->once()->withArgs([Mockery::any()])->andReturn($config);
    +        $repository->shouldReceive('setUser')->once();
    +        $repository->shouldReceive('getAttachments')->once()->withArgs([Mockery::any()])->andReturn(new Collection([$att]));
    +        $attachments->shouldReceive('getAttachmentContent')->withArgs([Mockery::any()])->andReturn($fileContent);
    +
    +        $handler = new ConfigureRolesHandler();
    +        $handler->setJob($job);
    +        try {
    +            $reader = $handler->getReader();
    +        } catch (Exception $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testIgnoreUnmappableColumns(): void
    +    {
    +        $config   = [
    +            'column-count'      => 5,
    +            'column-roles'      => [
    +                'description', // cannot be mapped.
    +                'budget-id',
    +                'sepa-cc',     // cannot be mapped.
    +                'category-id',
    +                'tags-comma',  // cannot be mapped.
    +            ],
    +            'column-do-mapping' => [
    +                0 => true,
    +                1 => true,
    +                2 => true,
    +                3 => true,
    +                4 => true,
    +            ],
    +        ];
    +        $expected = [
    +            'column-count'      => 5,
    +            'column-roles'      => [
    +                'description', // cannot be mapped.
    +                'budget-id',
    +                'sepa-cc',     // cannot be mapped.
    +                'category-id',
    +                'tags-comma',  // cannot be mapped.
    +            ],
    +            'column-do-mapping' => [
    +                0 => false,
    +                1 => true,
    +                2 => false,
    +                3 => true,
    +                4 => false,
    +            ],
    +        ];
    +        $handler  = new ConfigureRolesHandler;
    +        $this->assertEquals($expected, $handler->ignoreUnmappableColumns($config));
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testIsMappingNecessaryNo(): void
    +    {
    +        $config  = [
    +            'column-do-mapping' => [false, false, false],
    +        ];
    +        $handler = new ConfigureRolesHandler();
    +        $result  = $handler->isMappingNecessary($config);
    +        $this->assertFalse($result);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testIsMappingNecessaryYes(): void
    +    {
    +        $config  = [
    +            'column-do-mapping' => [false, true, false, false],
    +        ];
    +        $handler = new ConfigureRolesHandler();
    +        $result  = $handler->isMappingNecessary($config);
    +        $this->assertTrue($result);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testMakeExamplesUnique(): void
    +    {
    +        $lines = [
    +            ['one', 'two', '', 'three'],
    +            ['four', 'five', '', 'six'],
    +            ['one', 'three', 'X', 'three'],
    +        ];
    +
    +        $handler = new ConfigureRolesHandler;
    +        foreach ($lines as $line) {
    +            $handler->getExampleFromLine($line);
    +        }
    +        $handler->makeExamplesUnique();
    +
    +        $expected = [
    +            0 => ['one', 'four'],
    +            1 => ['two', 'five', 'three'],
    +            2 => ['X'],
    +            3 => ['three', 'six'],
    +        ];
    +        $this->assertEquals($expected, $handler->getExamples());
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testProcessSpecifics(): void
    +    {
    +        $line   = [];
    +        $config = [
    +            'specifics' => [
    +                'IngDescription'    => true,
    +                'some-bad-specific' => true,
    +            ],
    +        ];
    +
    +        $ingDescription = $this->mock(IngDescription::class);
    +        $ingDescription->shouldReceive('run')->once()->withArgs([[]])->andReturn(['a' => 'b']);
    +
    +        $handler = new ConfigureRolesHandler;
    +        $this->assertEquals(['a' => 'b'], $handler->processSpecifics($config, []));
    +
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureRolesHandler
    +     */
    +    public function testSaveColumCount(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'role-A' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $repository = $this->mock(ImportJobRepositoryInterface::class);
    +        $repository->shouldReceive('setUser');
    +        $repository->shouldReceive('setConfiguration')->once()
    +                   ->withArgs([Mockery::any(), ['column-count' => 0]]);
    +
    +        $handler = new ConfigureRolesHandler();
    +        $handler->setJob($job);
    +        $handler->saveColumCount();
    +    }
    +
    +}
    
    From cde9c4a2bcc042d98372d9ef3d7e4e8a6ca054e0 Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Fri, 11 May 2018 10:08:34 +0200
    Subject: [PATCH 062/182] Update copyright statements.
    
    ---
     app/Api/V1/Controllers/AboutController.php    |  3 +-
     app/Api/V1/Controllers/AccountController.php  |  3 +-
     app/Api/V1/Controllers/BillController.php     |  4 +-
     app/Api/V1/Controllers/Controller.php         |  4 +-
     .../V1/Controllers/TransactionController.php  |  3 +-
     app/Api/V1/Controllers/UserController.php     |  3 +-
     app/Api/V1/Requests/AccountRequest.php        |  3 +-
     app/Api/V1/Requests/BillRequest.php           |  3 +-
     app/Api/V1/Requests/Request.php               |  4 +-
     app/Api/V1/Requests/TransactionRequest.php    |  3 +-
     app/Api/V1/Requests/UserRequest.php           |  3 +-
     app/Console/Commands/CreateExport.php         |  6 ++-
     app/Console/Commands/CreateImport.php         |  6 ++-
     app/Console/Commands/DecryptAttachment.php    |  6 ++-
     app/Console/Commands/EncryptFile.php          |  6 ++-
     app/Console/Commands/Import.php               |  6 ++-
     app/Console/Commands/ScanAttachments.php      |  6 ++-
     app/Console/Commands/UpgradeDatabase.php      |  6 ++-
     .../Commands/UpgradeFireflyInstructions.php   |  6 ++-
     app/Console/Commands/UseEncryption.php        |  5 ++-
     app/Console/Commands/VerifiesAccessToken.php  |  6 ++-
     app/Console/Commands/VerifyDatabase.php       |  6 ++-
     app/Console/Kernel.php                        |  6 ++-
     app/Events/AdminRequestedTestMessage.php      |  6 ++-
     app/Events/Event.php                          |  6 ++-
     app/Events/RegisteredUser.php                 |  6 ++-
     app/Events/RequestedNewPassword.php           |  6 ++-
     app/Events/RequestedVersionCheckStatus.php    |  5 ++-
     app/Events/StoredTransactionJournal.php       |  6 ++-
     app/Events/UpdatedTransactionJournal.php      |  6 ++-
     app/Events/UserChangedEmail.php               |  6 ++-
     app/Exceptions/FireflyException.php           |  6 ++-
     app/Exceptions/Handler.php                    |  6 ++-
     app/Exceptions/NotImplementedException.php    |  6 ++-
     app/Exceptions/ValidationException.php        |  6 ++-
     app/Export/Collector/AttachmentCollector.php  |  6 ++-
     app/Export/Collector/BasicCollector.php       |  6 ++-
     app/Export/Collector/CollectorInterface.php   |  6 ++-
     app/Export/Collector/UploadCollector.php      |  6 ++-
     app/Export/Entry/Entry.php                    |  6 ++-
     app/Export/ExpandedProcessor.php              |  6 ++-
     app/Export/Exporter/BasicExporter.php         |  6 ++-
     app/Export/Exporter/CsvExporter.php           |  6 ++-
     app/Export/Exporter/ExporterInterface.php     |  6 ++-
     app/Export/ProcessorInterface.php             |  6 ++-
     app/Factory/AccountFactory.php                |  3 +-
     app/Factory/AccountMetaFactory.php            |  3 +-
     app/Factory/BillFactory.php                   |  3 +-
     app/Factory/BudgetFactory.php                 |  4 +-
     app/Factory/CategoryFactory.php               |  3 +-
     app/Factory/PiggyBankEventFactory.php         |  3 +-
     app/Factory/PiggyBankFactory.php              |  3 +-
     app/Factory/TagFactory.php                    |  3 +-
     app/Factory/TransactionCurrencyFactory.php    |  3 +-
     app/Factory/TransactionFactory.php            |  3 +-
     app/Factory/TransactionJournalFactory.php     |  3 +-
     app/Factory/TransactionJournalMetaFactory.php |  3 +-
     app/Factory/TransactionTypeFactory.php        |  3 +-
     app/Http/Middleware/Authenticate.php          |  3 +-
     app/Http/Middleware/Installer.php             | 21 +++++++++
     .../JobConfiguration/FileJobConfiguration.php |  4 +-
     app/Import/Storage/ImportArrayStorage.php     | 21 +++++++++
     app/Rules/BelongsUser.php                     |  4 +-
     app/Rules/ValidTransactions.php               |  5 ++-
     .../Import/Routine/Fake/StageFinalHandler.php | 21 +++++++++
     app/User.php                                  |  6 ++-
     config/breadcrumbs.php                        |  4 +-
     config/csv.php                                | 16 +++----
     config/firefly.php                            | 44 +++++++++----------
     config/google2fa.php                          |  5 ++-
     config/hashing.php                            | 21 +++++++++
     config/import.php                             | 30 ++++++-------
     config/twigbridge.php                         | 30 ++++++-------
     ...1_000001_create_oauth_auth_codes_table.php |  5 ++-
     ...00002_create_oauth_access_tokens_table.php |  5 ++-
     ...0003_create_oauth_refresh_tokens_table.php |  5 ++-
     ...1_01_000004_create_oauth_clients_table.php |  5 ++-
     ...te_oauth_personal_access_clients_table.php |  5 ++-
     .../2018_03_19_141348_changes_for_v472.php    | 21 +++++++++
     .../2018_04_07_210913_changes_for_v473.php    | 21 +++++++++
     .../2018_04_29_174524_changes_for_v474.php    | 21 +++++++++
     database/seeds/ConfigSeeder.php               | 21 +++++++++
     public/js/ff/import/file/configure-upload.js  | 20 +++++++++
     resources/lang/en_US/auth.php                 | 16 ++-----
     resources/lang/en_US/bank.php                 |  5 ++-
     resources/lang/en_US/breadcrumbs.php          |  5 ++-
     resources/lang/en_US/components.php           |  3 +-
     resources/lang/en_US/config.php               |  5 ++-
     resources/lang/en_US/csv.php                  |  5 ++-
     resources/lang/en_US/demo.php                 |  5 ++-
     resources/lang/en_US/firefly.php              |  5 ++-
     resources/lang/en_US/form.php                 |  5 ++-
     resources/lang/en_US/import.php               |  5 ++-
     resources/lang/en_US/intro.php                |  5 ++-
     resources/lang/en_US/list.php                 |  5 ++-
     resources/lang/en_US/pagination.php           |  5 ++-
     resources/lang/en_US/passwords.php            |  5 ++-
     resources/lang/en_US/validation.php           |  5 ++-
     98 files changed, 512 insertions(+), 214 deletions(-)
    
    diff --git a/app/Api/V1/Controllers/AboutController.php b/app/Api/V1/Controllers/AboutController.php
    index e7fcc291c3..77e63b7c64 100644
    --- a/app/Api/V1/Controllers/AboutController.php
    +++ b/app/Api/V1/Controllers/AboutController.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Api\V1\Controllers;
     
    diff --git a/app/Api/V1/Controllers/AccountController.php b/app/Api/V1/Controllers/AccountController.php
    index cff7969f6f..2f951fed3b 100644
    --- a/app/Api/V1/Controllers/AccountController.php
    +++ b/app/Api/V1/Controllers/AccountController.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Api\V1\Controllers;
     
    diff --git a/app/Api/V1/Controllers/BillController.php b/app/Api/V1/Controllers/BillController.php
    index bbf38c444d..420b01dc16 100644
    --- a/app/Api/V1/Controllers/BillController.php
    +++ b/app/Api/V1/Controllers/BillController.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Api\V1\Controllers;
     
     use FireflyIII\Api\V1\Requests\BillRequest;
    diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php
    index ba49f812aa..a212ad3029 100644
    --- a/app/Api/V1/Controllers/Controller.php
    +++ b/app/Api/V1/Controllers/Controller.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Api\V1\Controllers;
     
     use Carbon\Carbon;
    diff --git a/app/Api/V1/Controllers/TransactionController.php b/app/Api/V1/Controllers/TransactionController.php
    index 99ea62de2a..f0dbc2b246 100644
    --- a/app/Api/V1/Controllers/TransactionController.php
    +++ b/app/Api/V1/Controllers/TransactionController.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Api\V1\Controllers;
     
    diff --git a/app/Api/V1/Controllers/UserController.php b/app/Api/V1/Controllers/UserController.php
    index 1cf1bece04..f2759f9e71 100644
    --- a/app/Api/V1/Controllers/UserController.php
    +++ b/app/Api/V1/Controllers/UserController.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Api\V1\Controllers;
     
    diff --git a/app/Api/V1/Requests/AccountRequest.php b/app/Api/V1/Requests/AccountRequest.php
    index 44977d41e2..5a287b85ed 100644
    --- a/app/Api/V1/Requests/AccountRequest.php
    +++ b/app/Api/V1/Requests/AccountRequest.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Api\V1\Requests;
     
    diff --git a/app/Api/V1/Requests/BillRequest.php b/app/Api/V1/Requests/BillRequest.php
    index f2e13aaf9f..9f68a656cd 100644
    --- a/app/Api/V1/Requests/BillRequest.php
    +++ b/app/Api/V1/Requests/BillRequest.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Api\V1\Requests;
     
    diff --git a/app/Api/V1/Requests/Request.php b/app/Api/V1/Requests/Request.php
    index 509c092956..e2016dba34 100644
    --- a/app/Api/V1/Requests/Request.php
    +++ b/app/Api/V1/Requests/Request.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Api\V1\Requests;
     
     use FireflyIII\Http\Requests\Request as FireflyIIIRequest;
    diff --git a/app/Api/V1/Requests/TransactionRequest.php b/app/Api/V1/Requests/TransactionRequest.php
    index 2b7ce5c0f9..ab03ec561a 100644
    --- a/app/Api/V1/Requests/TransactionRequest.php
    +++ b/app/Api/V1/Requests/TransactionRequest.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Api\V1\Requests;
     
    diff --git a/app/Api/V1/Requests/UserRequest.php b/app/Api/V1/Requests/UserRequest.php
    index 2a1d76fe86..ba9bf54904 100644
    --- a/app/Api/V1/Requests/UserRequest.php
    +++ b/app/Api/V1/Requests/UserRequest.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Api\V1\Requests;
     
    diff --git a/app/Console/Commands/CreateExport.php b/app/Console/Commands/CreateExport.php
    index 658dce8505..336c5718ff 100644
    --- a/app/Console/Commands/CreateExport.php
    +++ b/app/Console/Commands/CreateExport.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use Carbon\Carbon;
    diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php
    index 209ed068cf..d351c05b8d 100644
    --- a/app/Console/Commands/CreateImport.php
    +++ b/app/Console/Commands/CreateImport.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use FireflyIII\Exceptions\FireflyException;
    diff --git a/app/Console/Commands/DecryptAttachment.php b/app/Console/Commands/DecryptAttachment.php
    index baf2414ba5..dca003e8b9 100644
    --- a/app/Console/Commands/DecryptAttachment.php
    +++ b/app/Console/Commands/DecryptAttachment.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
    diff --git a/app/Console/Commands/EncryptFile.php b/app/Console/Commands/EncryptFile.php
    index 0e787860e7..d074897c23 100644
    --- a/app/Console/Commands/EncryptFile.php
    +++ b/app/Console/Commands/EncryptFile.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use FireflyIII\Exceptions\FireflyException;
    diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php
    index 9a12e5988f..25ec3be10a 100644
    --- a/app/Console/Commands/Import.php
    +++ b/app/Console/Commands/Import.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use FireflyIII\Exceptions\FireflyException;
    diff --git a/app/Console/Commands/ScanAttachments.php b/app/Console/Commands/ScanAttachments.php
    index e30727488e..b23f82b1ce 100644
    --- a/app/Console/Commands/ScanAttachments.php
    +++ b/app/Console/Commands/ScanAttachments.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use Crypt;
    diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php
    index 9c3adc2536..9ad64d700d 100644
    --- a/app/Console/Commands/UpgradeDatabase.php
    +++ b/app/Console/Commands/UpgradeDatabase.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use DB;
    diff --git a/app/Console/Commands/UpgradeFireflyInstructions.php b/app/Console/Commands/UpgradeFireflyInstructions.php
    index a79e62f05f..7924e609a0 100644
    --- a/app/Console/Commands/UpgradeFireflyInstructions.php
    +++ b/app/Console/Commands/UpgradeFireflyInstructions.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use Illuminate\Console\Command;
    diff --git a/app/Console/Commands/UseEncryption.php b/app/Console/Commands/UseEncryption.php
    index e9cbde4aed..e89aff9281 100644
    --- a/app/Console/Commands/UseEncryption.php
    +++ b/app/Console/Commands/UseEncryption.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use Illuminate\Console\Command;
    diff --git a/app/Console/Commands/VerifiesAccessToken.php b/app/Console/Commands/VerifiesAccessToken.php
    index da3225a045..c03d456b53 100644
    --- a/app/Console/Commands/VerifiesAccessToken.php
    +++ b/app/Console/Commands/VerifiesAccessToken.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use FireflyIII\Repositories\User\UserRepositoryInterface;
    diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php
    index 8db87ad4dc..08ec12f3df 100644
    --- a/app/Console/Commands/VerifyDatabase.php
    +++ b/app/Console/Commands/VerifyDatabase.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console\Commands;
     
     use Crypt;
    diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
    index 5a16876766..3f24caad90 100644
    --- a/app/Console/Kernel.php
    +++ b/app/Console/Kernel.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Console;
     
     use Illuminate\Console\Scheduling\Schedule;
    diff --git a/app/Events/AdminRequestedTestMessage.php b/app/Events/AdminRequestedTestMessage.php
    index 54858f2000..7198811a0c 100644
    --- a/app/Events/AdminRequestedTestMessage.php
    +++ b/app/Events/AdminRequestedTestMessage.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Events;
     
     use FireflyIII\User;
    diff --git a/app/Events/Event.php b/app/Events/Event.php
    index 490238246a..41b3b0437c 100644
    --- a/app/Events/Event.php
    +++ b/app/Events/Event.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Events;
     
     /**
    diff --git a/app/Events/RegisteredUser.php b/app/Events/RegisteredUser.php
    index 8296bcdec5..053d0b24c5 100644
    --- a/app/Events/RegisteredUser.php
    +++ b/app/Events/RegisteredUser.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Events;
     
     use FireflyIII\User;
    diff --git a/app/Events/RequestedNewPassword.php b/app/Events/RequestedNewPassword.php
    index 566aadeed5..422cc19872 100644
    --- a/app/Events/RequestedNewPassword.php
    +++ b/app/Events/RequestedNewPassword.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Events;
     
     use FireflyIII\User;
    diff --git a/app/Events/RequestedVersionCheckStatus.php b/app/Events/RequestedVersionCheckStatus.php
    index 1147c7cea4..6173b031af 100644
    --- a/app/Events/RequestedVersionCheckStatus.php
    +++ b/app/Events/RequestedVersionCheckStatus.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Events;
     
    diff --git a/app/Events/StoredTransactionJournal.php b/app/Events/StoredTransactionJournal.php
    index 2d57502558..4db12b1b35 100644
    --- a/app/Events/StoredTransactionJournal.php
    +++ b/app/Events/StoredTransactionJournal.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Events;
     
     use FireflyIII\Models\TransactionJournal;
    diff --git a/app/Events/UpdatedTransactionJournal.php b/app/Events/UpdatedTransactionJournal.php
    index c8b08ddaa8..5c9c7f7a9b 100644
    --- a/app/Events/UpdatedTransactionJournal.php
    +++ b/app/Events/UpdatedTransactionJournal.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Events;
     
     use FireflyIII\Models\TransactionJournal;
    diff --git a/app/Events/UserChangedEmail.php b/app/Events/UserChangedEmail.php
    index 577fcd5c86..2c2d9d8580 100644
    --- a/app/Events/UserChangedEmail.php
    +++ b/app/Events/UserChangedEmail.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Events;
     
     use FireflyIII\User;
    diff --git a/app/Exceptions/FireflyException.php b/app/Exceptions/FireflyException.php
    index 2dddf3e3c7..16c24f25aa 100644
    --- a/app/Exceptions/FireflyException.php
    +++ b/app/Exceptions/FireflyException.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Exceptions;
     
     use Exception;
    diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
    index f3fc3e628c..45570ef71a 100644
    --- a/app/Exceptions/Handler.php
    +++ b/app/Exceptions/Handler.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Exceptions;
     
     use ErrorException;
    diff --git a/app/Exceptions/NotImplementedException.php b/app/Exceptions/NotImplementedException.php
    index e05cccd6fe..7254d56d34 100644
    --- a/app/Exceptions/NotImplementedException.php
    +++ b/app/Exceptions/NotImplementedException.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Exceptions;
     
     /**
    diff --git a/app/Exceptions/ValidationException.php b/app/Exceptions/ValidationException.php
    index e0443c126e..602b4df9b0 100644
    --- a/app/Exceptions/ValidationException.php
    +++ b/app/Exceptions/ValidationException.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Exceptions;
     
     /**
    diff --git a/app/Export/Collector/AttachmentCollector.php b/app/Export/Collector/AttachmentCollector.php
    index 244cc919ed..0db72ed3a2 100644
    --- a/app/Export/Collector/AttachmentCollector.php
    +++ b/app/Export/Collector/AttachmentCollector.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Export\Collector;
     
     use Carbon\Carbon;
    diff --git a/app/Export/Collector/BasicCollector.php b/app/Export/Collector/BasicCollector.php
    index 38b357ea0d..bc19f1f541 100644
    --- a/app/Export/Collector/BasicCollector.php
    +++ b/app/Export/Collector/BasicCollector.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Export\Collector;
     
     use FireflyIII\Models\ExportJob;
    diff --git a/app/Export/Collector/CollectorInterface.php b/app/Export/Collector/CollectorInterface.php
    index 11c0f53271..bd40c82f29 100644
    --- a/app/Export/Collector/CollectorInterface.php
    +++ b/app/Export/Collector/CollectorInterface.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Export\Collector;
     
     use FireflyIII\Models\ExportJob;
    diff --git a/app/Export/Collector/UploadCollector.php b/app/Export/Collector/UploadCollector.php
    index fccc32814a..dd8c71e769 100644
    --- a/app/Export/Collector/UploadCollector.php
    +++ b/app/Export/Collector/UploadCollector.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Export\Collector;
     
     use Crypt;
    diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php
    index a91b50e45f..55886a2697 100644
    --- a/app/Export/Entry/Entry.php
    +++ b/app/Export/Entry/Entry.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Export\Entry;
     
     use FireflyIII\Models\Transaction;
    diff --git a/app/Export/ExpandedProcessor.php b/app/Export/ExpandedProcessor.php
    index 33db5e518b..d8ad004f9f 100644
    --- a/app/Export/ExpandedProcessor.php
    +++ b/app/Export/ExpandedProcessor.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Export;
     
     use Crypt;
    diff --git a/app/Export/Exporter/BasicExporter.php b/app/Export/Exporter/BasicExporter.php
    index ee618c2bb2..02826171c2 100644
    --- a/app/Export/Exporter/BasicExporter.php
    +++ b/app/Export/Exporter/BasicExporter.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Export\Exporter;
     
     use FireflyIII\Models\ExportJob;
    diff --git a/app/Export/Exporter/CsvExporter.php b/app/Export/Exporter/CsvExporter.php
    index 8eec22225c..e323b94bf9 100644
    --- a/app/Export/Exporter/CsvExporter.php
    +++ b/app/Export/Exporter/CsvExporter.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Export\Exporter;
     
     use FireflyIII\Export\Entry\Entry;
    diff --git a/app/Export/Exporter/ExporterInterface.php b/app/Export/Exporter/ExporterInterface.php
    index f9c35b8d04..f10e763dab 100644
    --- a/app/Export/Exporter/ExporterInterface.php
    +++ b/app/Export/Exporter/ExporterInterface.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Export\Exporter;
     
     use FireflyIII\Models\ExportJob;
    diff --git a/app/Export/ProcessorInterface.php b/app/Export/ProcessorInterface.php
    index c255eb7713..ad8745cc7c 100644
    --- a/app/Export/ProcessorInterface.php
    +++ b/app/Export/ProcessorInterface.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Export;
     
     use Illuminate\Support\Collection;
    diff --git a/app/Factory/AccountFactory.php b/app/Factory/AccountFactory.php
    index 8c035c8548..81383d4143 100644
    --- a/app/Factory/AccountFactory.php
    +++ b/app/Factory/AccountFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/AccountMetaFactory.php b/app/Factory/AccountMetaFactory.php
    index c514e182f8..ba2172a30d 100644
    --- a/app/Factory/AccountMetaFactory.php
    +++ b/app/Factory/AccountMetaFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/BillFactory.php b/app/Factory/BillFactory.php
    index 28b4a3544d..94327af9ee 100644
    --- a/app/Factory/BillFactory.php
    +++ b/app/Factory/BillFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/BudgetFactory.php b/app/Factory/BudgetFactory.php
    index bd96712658..ba9315f4ec 100644
    --- a/app/Factory/BudgetFactory.php
    +++ b/app/Factory/BudgetFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
    +
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/CategoryFactory.php b/app/Factory/CategoryFactory.php
    index 5bc20ec485..ff80a61a7b 100644
    --- a/app/Factory/CategoryFactory.php
    +++ b/app/Factory/CategoryFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/PiggyBankEventFactory.php b/app/Factory/PiggyBankEventFactory.php
    index d55ba7dc8f..d4a586ba03 100644
    --- a/app/Factory/PiggyBankEventFactory.php
    +++ b/app/Factory/PiggyBankEventFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/PiggyBankFactory.php b/app/Factory/PiggyBankFactory.php
    index 1f8ecf617d..ce398c3697 100644
    --- a/app/Factory/PiggyBankFactory.php
    +++ b/app/Factory/PiggyBankFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/TagFactory.php b/app/Factory/TagFactory.php
    index 09e9212a27..1fdd0e74a8 100644
    --- a/app/Factory/TagFactory.php
    +++ b/app/Factory/TagFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/TransactionCurrencyFactory.php b/app/Factory/TransactionCurrencyFactory.php
    index 2196cf4594..3cee423c42 100644
    --- a/app/Factory/TransactionCurrencyFactory.php
    +++ b/app/Factory/TransactionCurrencyFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php
    index a395793694..473f2e3c37 100644
    --- a/app/Factory/TransactionFactory.php
    +++ b/app/Factory/TransactionFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php
    index 816fe9ea58..e71bb1aed5 100644
    --- a/app/Factory/TransactionJournalFactory.php
    +++ b/app/Factory/TransactionJournalFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/TransactionJournalMetaFactory.php b/app/Factory/TransactionJournalMetaFactory.php
    index 4be44a8bbe..4289b31b7a 100644
    --- a/app/Factory/TransactionJournalMetaFactory.php
    +++ b/app/Factory/TransactionJournalMetaFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Factory/TransactionTypeFactory.php b/app/Factory/TransactionTypeFactory.php
    index 8a36869407..03db2cbd13 100644
    --- a/app/Factory/TransactionTypeFactory.php
    +++ b/app/Factory/TransactionTypeFactory.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
     
     namespace FireflyIII\Factory;
     
    diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php
    index 394f04d01a..8241ca82df 100644
    --- a/app/Http/Middleware/Authenticate.php
    +++ b/app/Http/Middleware/Authenticate.php
    @@ -1,5 +1,4 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Http\Middleware;
     
     use Closure;
    diff --git a/app/Http/Middleware/Installer.php b/app/Http/Middleware/Installer.php
    index 1afa004cdf..2f8f73a654 100644
    --- a/app/Http/Middleware/Installer.php
    +++ b/app/Http/Middleware/Installer.php
    @@ -1,4 +1,25 @@
     .
    + */
    +
     declare(strict_types=1);
     
     namespace FireflyIII\Http\Middleware;
    diff --git a/app/Import/JobConfiguration/FileJobConfiguration.php b/app/Import/JobConfiguration/FileJobConfiguration.php
    index 75b97c8aaf..19764c8806 100644
    --- a/app/Import/JobConfiguration/FileJobConfiguration.php
    +++ b/app/Import/JobConfiguration/FileJobConfiguration.php
    @@ -1,5 +1,5 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Import\JobConfiguration;
     
     
    diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php
    index 294dc9c5e5..a65a3bf8a3 100644
    --- a/app/Import/Storage/ImportArrayStorage.php
    +++ b/app/Import/Storage/ImportArrayStorage.php
    @@ -1,4 +1,25 @@
     .
    + */
    +
     declare(strict_types=1);
     
     namespace FireflyIII\Import\Storage;
    diff --git a/app/Rules/BelongsUser.php b/app/Rules/BelongsUser.php
    index 91acc6cc54..c96ba66a19 100644
    --- a/app/Rules/BelongsUser.php
    +++ b/app/Rules/BelongsUser.php
    @@ -1,6 +1,4 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII\Rules;
     
     use FireflyIII\Exceptions\FireflyException;
    diff --git a/app/Rules/ValidTransactions.php b/app/Rules/ValidTransactions.php
    index d60f4cc679..888c98adf7 100644
    --- a/app/Rules/ValidTransactions.php
    +++ b/app/Rules/ValidTransactions.php
    @@ -1,6 +1,4 @@
     .
      */
     
    +declare(strict_types=1);
    +
    +
     namespace FireflyIII\Rules;
     
     use FireflyIII\Models\Transaction;
    diff --git a/app/Support/Import/Routine/Fake/StageFinalHandler.php b/app/Support/Import/Routine/Fake/StageFinalHandler.php
    index 94f48ce3f9..7f8bf81f14 100644
    --- a/app/Support/Import/Routine/Fake/StageFinalHandler.php
    +++ b/app/Support/Import/Routine/Fake/StageFinalHandler.php
    @@ -1,4 +1,25 @@
     .
    + */
    +
     declare(strict_types=1);
     
     namespace FireflyIII\Support\Import\Routine\Fake;
    diff --git a/app/User.php b/app/User.php
    index 27b9f1bea7..b9a4de5107 100644
    --- a/app/User.php
    +++ b/app/User.php
    @@ -1,8 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     namespace FireflyIII;
     
     use FireflyIII\Events\RequestedNewPassword;
    diff --git a/config/breadcrumbs.php b/config/breadcrumbs.php
    index ea323c8789..716bb21787 100644
    --- a/config/breadcrumbs.php
    +++ b/config/breadcrumbs.php
    @@ -1,6 +1,4 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
     
         /*
    diff --git a/config/csv.php b/config/csv.php
    index 8f6ff88f9a..4af268d126 100644
    --- a/config/csv.php
    +++ b/config/csv.php
    @@ -1,15 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
    +use FireflyIII\Import\Specifics\AbnAmroDescription;
    +use FireflyIII\Import\Specifics\IngDescription;
    +use FireflyIII\Import\Specifics\PresidentsChoice;
    +use FireflyIII\Import\Specifics\RabobankDescription;
    +use FireflyIII\Import\Specifics\SnsDescription;
     
     return [
     
    diff --git a/config/firefly.php b/config/firefly.php
    index 57132bc997..850190388d 100644
    --- a/config/firefly.php
    +++ b/config/firefly.php
    @@ -1,5 +1,26 @@
     .
    + */
    +
     declare(strict_types=1);
     
     use FireflyIII\Export\Exporter\CsvExporter;
    @@ -55,31 +76,10 @@ use FireflyIII\TransactionRules\Triggers\ToAccountStarts;
     use FireflyIII\TransactionRules\Triggers\TransactionType;
     use FireflyIII\TransactionRules\Triggers\UserAction;
     
    -/**
    - * firefly.php
    - * Copyright (c) 2017 thegrumpydictator@gmail.com
    - *
    - * This file is part of Firefly III.
    - *
    - * Firefly III is free software: you can redistribute it and/or modify
    - * it under the terms of the GNU General Public License as published by
    - * the Free Software Foundation, either version 3 of the License, or
    - * (at your option) any later version.
    - *
    - * Firefly III is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    - * GNU General Public License for more details.
    - *
    - * You should have received a copy of the GNU General Public License
    - * along with Firefly III. If not, see .
    - */
    -
    -
     /*
      * DO NOT EDIT THIS FILE. IT IS AUTO GENERATED.
      *
    - * ANY OPTIONS IN THIS FILE YOU CAN SAFELY EDIT CAN BE FOUND IN THE USER INTERFACE OF FIRFELY III.
    + * ANY OPTIONS IN THIS FILE YOU CAN SAFELY EDIT CAN BE FOUND IN THE USER INTERFACE OF FIREFLY III.
      */
     
     return [
    diff --git a/config/google2fa.php b/config/google2fa.php
    index 2ff91b26c2..ff1ec61523 100644
    --- a/config/google2fa.php
    +++ b/config/google2fa.php
    @@ -1,6 +1,4 @@
     .
      */
     
    +declare(strict_types=1);
    +
    +
     return [
     
         /*
    diff --git a/config/hashing.php b/config/hashing.php
    index 77a4458077..daecb9634d 100644
    --- a/config/hashing.php
    +++ b/config/hashing.php
    @@ -1,4 +1,25 @@
     .
    + */
    +
     declare(strict_types=1);
     
     return [
    diff --git a/config/import.php b/config/import.php
    index 46a0b06687..9b022e7abc 100644
    --- a/config/import.php
    +++ b/config/import.php
    @@ -1,22 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
    +use FireflyIII\Import\Configuration\BunqConfigurator;
    +use FireflyIII\Import\Configuration\SpectreConfigurator;
    +use FireflyIII\Import\JobConfiguration\FakeJobConfiguration;
    +use FireflyIII\Import\JobConfiguration\FileJobConfiguration;
    +use FireflyIII\Import\Prerequisites\BunqPrerequisites;
    +use FireflyIII\Import\Prerequisites\FakePrerequisites;
    +use FireflyIII\Import\Prerequisites\SpectrePrerequisites;
    +use FireflyIII\Import\Routine\BunqRoutine;
    +use FireflyIII\Import\Routine\FakeRoutine;
    +use FireflyIII\Import\Routine\FileRoutine;
    +use FireflyIII\Import\Routine\SpectreRoutine;
    +use FireflyIII\Support\Import\Routine\File\CSVProcessor;
     
     return [
         'enabled'       => [
    diff --git a/config/twigbridge.php b/config/twigbridge.php
    index 46aa7f0f50..f4a2c2167a 100644
    --- a/config/twigbridge.php
    +++ b/config/twigbridge.php
    @@ -1,22 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
    +use TwigBridge\Extension\Laravel\Auth;
    +use TwigBridge\Extension\Laravel\Config;
    +use TwigBridge\Extension\Laravel\Dump;
    +use TwigBridge\Extension\Laravel\Input;
    +use TwigBridge\Extension\Laravel\Session;
    +use TwigBridge\Extension\Laravel\Str;
    +use TwigBridge\Extension\Laravel\Translator;
    +use TwigBridge\Extension\Laravel\Url;
    +use TwigBridge\Extension\Loader\Facades;
    +use TwigBridge\Extension\Loader\Filters;
    +use TwigBridge\Extension\Loader\Functions;
    +use TwigBridge\Twig\Template;
     
     /**
      * Configuration options for Twig.
    diff --git a/database/migrations/2018_01_01_000001_create_oauth_auth_codes_table.php b/database/migrations/2018_01_01_000001_create_oauth_auth_codes_table.php
    index 9ea338668d..068f187e0d 100644
    --- a/database/migrations/2018_01_01_000001_create_oauth_auth_codes_table.php
    +++ b/database/migrations/2018_01_01_000001_create_oauth_auth_codes_table.php
    @@ -1,7 +1,6 @@
     .
      */
     
    +declare(strict_types=1);
    +
     use Illuminate\Database\Migrations\Migration;
     use Illuminate\Database\Schema\Blueprint;
     use Illuminate\Support\Facades\Schema;
    diff --git a/database/migrations/2018_01_01_000002_create_oauth_access_tokens_table.php b/database/migrations/2018_01_01_000002_create_oauth_access_tokens_table.php
    index 4c9574d3e8..00889896fb 100644
    --- a/database/migrations/2018_01_01_000002_create_oauth_access_tokens_table.php
    +++ b/database/migrations/2018_01_01_000002_create_oauth_access_tokens_table.php
    @@ -1,7 +1,6 @@
     .
      */
     
    +declare(strict_types=1);
    +
     use Illuminate\Database\Migrations\Migration;
     use Illuminate\Database\Schema\Blueprint;
     use Illuminate\Support\Facades\Schema;
    diff --git a/database/migrations/2018_01_01_000003_create_oauth_refresh_tokens_table.php b/database/migrations/2018_01_01_000003_create_oauth_refresh_tokens_table.php
    index db3a09a15e..1d6199d904 100644
    --- a/database/migrations/2018_01_01_000003_create_oauth_refresh_tokens_table.php
    +++ b/database/migrations/2018_01_01_000003_create_oauth_refresh_tokens_table.php
    @@ -1,7 +1,6 @@
     .
      */
     
    +declare(strict_types=1);
    +
     use Illuminate\Database\Migrations\Migration;
     use Illuminate\Database\Schema\Blueprint;
     use Illuminate\Support\Facades\Schema;
    diff --git a/database/migrations/2018_01_01_000004_create_oauth_clients_table.php b/database/migrations/2018_01_01_000004_create_oauth_clients_table.php
    index 7a93146474..cbd08eb1db 100644
    --- a/database/migrations/2018_01_01_000004_create_oauth_clients_table.php
    +++ b/database/migrations/2018_01_01_000004_create_oauth_clients_table.php
    @@ -1,7 +1,6 @@
     .
      */
     
    +declare(strict_types=1);
    +
     use Illuminate\Database\Migrations\Migration;
     use Illuminate\Database\Schema\Blueprint;
     use Illuminate\Support\Facades\Schema;
    diff --git a/database/migrations/2018_01_01_000005_create_oauth_personal_access_clients_table.php b/database/migrations/2018_01_01_000005_create_oauth_personal_access_clients_table.php
    index 9ec29ce525..e584f7fc4b 100644
    --- a/database/migrations/2018_01_01_000005_create_oauth_personal_access_clients_table.php
    +++ b/database/migrations/2018_01_01_000005_create_oauth_personal_access_clients_table.php
    @@ -1,7 +1,6 @@
     .
      */
     
    +declare(strict_types=1);
    +
     use Illuminate\Database\Migrations\Migration;
     use Illuminate\Database\Schema\Blueprint;
     use Illuminate\Support\Facades\Schema;
    diff --git a/database/migrations/2018_03_19_141348_changes_for_v472.php b/database/migrations/2018_03_19_141348_changes_for_v472.php
    index 0960d6e381..00997e749e 100644
    --- a/database/migrations/2018_03_19_141348_changes_for_v472.php
    +++ b/database/migrations/2018_03_19_141348_changes_for_v472.php
    @@ -1,4 +1,25 @@
     .
    + */
    +
     declare(strict_types=1);
     
     use Illuminate\Database\Migrations\Migration;
    diff --git a/database/migrations/2018_04_07_210913_changes_for_v473.php b/database/migrations/2018_04_07_210913_changes_for_v473.php
    index bdb3b43e0e..a6067a92e7 100644
    --- a/database/migrations/2018_04_07_210913_changes_for_v473.php
    +++ b/database/migrations/2018_04_07_210913_changes_for_v473.php
    @@ -1,4 +1,25 @@
     .
    + */
    +
     declare(strict_types=1);
     
     use Illuminate\Database\Migrations\Migration;
    diff --git a/database/migrations/2018_04_29_174524_changes_for_v474.php b/database/migrations/2018_04_29_174524_changes_for_v474.php
    index c0fa438549..cdc6208f35 100644
    --- a/database/migrations/2018_04_29_174524_changes_for_v474.php
    +++ b/database/migrations/2018_04_29_174524_changes_for_v474.php
    @@ -1,4 +1,25 @@
     .
    + */
    +
     declare(strict_types=1);
     
     use Illuminate\Database\Migrations\Migration;
    diff --git a/database/seeds/ConfigSeeder.php b/database/seeds/ConfigSeeder.php
    index e37857aa81..e5c8d250d5 100644
    --- a/database/seeds/ConfigSeeder.php
    +++ b/database/seeds/ConfigSeeder.php
    @@ -1,5 +1,26 @@
     .
    + */
    +
     use FireflyIII\Models\Configuration;
     use Illuminate\Database\Seeder;
     
    diff --git a/public/js/ff/import/file/configure-upload.js b/public/js/ff/import/file/configure-upload.js
    index d2c211980e..ec138f6f71 100644
    --- a/public/js/ff/import/file/configure-upload.js
    +++ b/public/js/ff/import/file/configure-upload.js
    @@ -1,3 +1,23 @@
    +/*
    + * configure-upload.js
    + * 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 .
    + */
    +
     $(function () {
         "use strict";
     
    diff --git a/resources/lang/en_US/auth.php b/resources/lang/en_US/auth.php
    index ccd226ea90..a8a29b2131 100644
    --- a/resources/lang/en_US/auth.php
    +++ b/resources/lang/en_US/auth.php
    @@ -1,9 +1,8 @@
     .
      */
     
    -return [
    -    /*
    -    |--------------------------------------------------------------------------
    -    | Authentication Language Lines
    -    |--------------------------------------------------------------------------
    -    |
    -    | The following language lines are used during authentication for various
    -    | messages that we need to display to the user. You are free to modify
    -    | these language lines according to your application's requirements.
    -    |
    -    */
    +declare(strict_types=1);
     
    +return [
         'failed'   => 'These credentials do not match our records.',
         'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
     ];
    diff --git a/resources/lang/en_US/bank.php b/resources/lang/en_US/bank.php
    index 8826020cbf..5d00b1e685 100644
    --- a/resources/lang/en_US/bank.php
    +++ b/resources/lang/en_US/bank.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
     ];
    diff --git a/resources/lang/en_US/breadcrumbs.php b/resources/lang/en_US/breadcrumbs.php
    index 88ebc2aa8b..3a98e01f6f 100644
    --- a/resources/lang/en_US/breadcrumbs.php
    +++ b/resources/lang/en_US/breadcrumbs.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         'home'                => 'Home',
         'edit_currency'       => 'Edit currency ":name"',
    diff --git a/resources/lang/en_US/components.php b/resources/lang/en_US/components.php
    index 50092e06fd..6299088f8e 100644
    --- a/resources/lang/en_US/components.php
    +++ b/resources/lang/en_US/components.php
    @@ -1,5 +1,4 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         // profile
         'personal_access_tokens' => 'Personal access tokens',
    diff --git a/resources/lang/en_US/config.php b/resources/lang/en_US/config.php
    index b435777385..f7a905591f 100644
    --- a/resources/lang/en_US/config.php
    +++ b/resources/lang/en_US/config.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         'html_language'    => 'en',
         'locale'           => 'en, English, en_US, en_US.utf8, en_US.UTF-8',
    diff --git a/resources/lang/en_US/csv.php b/resources/lang/en_US/csv.php
    index 622a49202a..aae109a40a 100644
    --- a/resources/lang/en_US/csv.php
    +++ b/resources/lang/en_US/csv.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
     ];
    diff --git a/resources/lang/en_US/demo.php b/resources/lang/en_US/demo.php
    index 8de0eb8fb2..05cd427835 100644
    --- a/resources/lang/en_US/demo.php
    +++ b/resources/lang/en_US/demo.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         'no_demo_text'           => 'Sorry, there is no extra demo-explanation text for this page.',
         'see_help_icon'          => 'However, the -icon in the top right corner may tell you more.',
    diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php
    index 3062b2aeab..2f3f7025b7 100644
    --- a/resources/lang/en_US/firefly.php
    +++ b/resources/lang/en_US/firefly.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         // general stuff:
         'close'                                      => 'Close',
    diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php
    index 5f8eb6d92f..39955eb731 100644
    --- a/resources/lang/en_US/form.php
    +++ b/resources/lang/en_US/form.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         // new user:
         'bank_name'                      => 'Bank name',
    diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php
    index afba73d3c9..8a07cc6320 100644
    --- a/resources/lang/en_US/import.php
    +++ b/resources/lang/en_US/import.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         // ALL breadcrumbs and subtitles:
         'index_breadcrumb'                   => 'Import data into Firefly III',
    diff --git a/resources/lang/en_US/intro.php b/resources/lang/en_US/intro.php
    index 1d442a8d48..dd24288522 100644
    --- a/resources/lang/en_US/intro.php
    +++ b/resources/lang/en_US/intro.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         // index
         'index_intro'                           => 'Welcome to the index page of Firefly III. Please take the time to walk through this intro to get a feeling of how Firefly III works.',
    diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php
    index 27036cd560..f7b99e2c1c 100644
    --- a/resources/lang/en_US/list.php
    +++ b/resources/lang/en_US/list.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         'buttons'                 => 'Buttons',
         'icon'                    => 'Icon',
    diff --git a/resources/lang/en_US/pagination.php b/resources/lang/en_US/pagination.php
    index 8bdf7ed01d..cdf4ca9a6d 100644
    --- a/resources/lang/en_US/pagination.php
    +++ b/resources/lang/en_US/pagination.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         'previous' => '« Previous',
         'next'     => 'Next »',
    diff --git a/resources/lang/en_US/passwords.php b/resources/lang/en_US/passwords.php
    index 72f4f769a7..7ab0503080 100644
    --- a/resources/lang/en_US/passwords.php
    +++ b/resources/lang/en_US/passwords.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         'password' => 'Passwords must be at least six characters and match the confirmation.',
         'user'     => 'We can\'t find a user with that e-mail address.',
    diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php
    index 46988a77d6..c22f0145b6 100644
    --- a/resources/lang/en_US/validation.php
    +++ b/resources/lang/en_US/validation.php
    @@ -1,9 +1,8 @@
     .
      */
     
    +declare(strict_types=1);
    +
     return [
         'iban'                           => 'This is not a valid IBAN.',
         'source_equals_destination'      => 'The source account equals the destination account',
    
    From c47a5379ae4369658da1a9d26c15682f079a63f6 Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Fri, 11 May 2018 10:37:13 +0200
    Subject: [PATCH 063/182] Improve code test coverage.
    
    ---
     .../File/ConfigureUploadHandler.php           |   5 +-
     .../File/ConfigureUploadHandlerTest.php       | 185 ++++++++++++++++++
     2 files changed, 187 insertions(+), 3 deletions(-)
     create mode 100644 tests/Unit/Support/Import/Configuration/File/ConfigureUploadHandlerTest.php
    
    diff --git a/app/Support/Import/Configuration/File/ConfigureUploadHandler.php b/app/Support/Import/Configuration/File/ConfigureUploadHandler.php
    index 553423aa07..640f39833a 100644
    --- a/app/Support/Import/Configuration/File/ConfigureUploadHandler.php
    +++ b/app/Support/Import/Configuration/File/ConfigureUploadHandler.php
    @@ -66,14 +66,13 @@ class ConfigureUploadHandler implements ConfigurationInterface
             // collect specifics.
             foreach (config('csv.import_specifics') as $name => $className) {
                 $specifics[$name] = [
    -                'name'        => $className::getName(),
    -                'description' => $className::getDescription(),
    +                'name'        => trans($className::getName()),
    +                'description' => trans($className::getDescription()),
                 ];
             }
     
             $data = [
                 'accounts'   => [],
    -            'specifix'   => [],
                 'delimiters' => $delimiters,
                 'specifics'  => $specifics,
             ];
    diff --git a/tests/Unit/Support/Import/Configuration/File/ConfigureUploadHandlerTest.php b/tests/Unit/Support/Import/Configuration/File/ConfigureUploadHandlerTest.php
    new file mode 100644
    index 0000000000..baab7a6b62
    --- /dev/null
    +++ b/tests/Unit/Support/Import/Configuration/File/ConfigureUploadHandlerTest.php
    @@ -0,0 +1,185 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace Tests\Unit\Support\Import\Configuration\File;
    +
    +
    +use FireflyIII\Models\ImportJob;
    +use FireflyIII\Repositories\Account\AccountRepositoryInterface;
    +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
    +use FireflyIII\Support\Import\Configuration\File\ConfigureUploadHandler;
    +use Mockery;
    +use Tests\TestCase;
    +
    +/**
    + * Class ConfigureUploadHandlerTest
    + */
    +class ConfigureUploadHandlerTest extends TestCase
    +{
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureUploadHandler
    +     */
    +    public function testConfigureJobAccount(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'upload-B' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +
    +        $data           = [
    +            'csv_import_account' => '1',
    +            'csv_delimiter'      => ',',
    +            'has_headers'        => '1',
    +            'date_format'        => 'Y-m-d',
    +            'apply_rules'        => '1',
    +            'specifics'          => ['IngDescription'],
    +        ];
    +        $expectedConfig = [
    +            'has-headers'    => true,
    +            'date-format'    => 'Y-m-d',
    +            'delimiter'      => ',',
    +            'apply-rules'    => true,
    +            'specifics'      => [
    +                'IngDescription' => 1,
    +            ],
    +            'import-account' => 1,
    +        ];
    +
    +        $repository   = $this->mock(ImportJobRepositoryInterface::class);
    +        $accountRepos = $this->mock(AccountRepositoryInterface::class);
    +        $repository->shouldReceive('setUser')->once();
    +        $accountRepos->shouldReceive('setUser')->once();
    +        $accountRepos->shouldReceive('findNull')->once()->withArgs([1])->andReturn($this->user()->accounts()->first());
    +        $repository->shouldReceive('setConfiguration')->once()->withArgs([Mockery::any(), $expectedConfig]);
    +        $repository->shouldReceive('setStage')->once()->withArgs([Mockery::any(), 'roles']);
    +
    +        $handler = new ConfigureUploadHandler;
    +        $handler->setJob($job);
    +        $result = $handler->configureJob($data);
    +        $this->assertCount(0, $result);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureUploadHandler
    +     */
    +    public function testConfigureJobNoAccount(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'upload-B' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +
    +        $data           = [
    +            'csv_import_account' => '1',
    +            'csv_delimiter'      => ',',
    +            'has_headers'        => '1',
    +            'date_format'        => 'Y-m-d',
    +            'apply_rules'        => '1',
    +            'specifics'          => ['IngDescription'],
    +        ];
    +        $expectedConfig = [
    +            'has-headers' => true,
    +            'date-format' => 'Y-m-d',
    +            'delimiter'   => ',',
    +            'apply-rules' => true,
    +            'specifics'   => [
    +                'IngDescription' => 1,
    +            ],
    +        ];
    +
    +        $repository   = $this->mock(ImportJobRepositoryInterface::class);
    +        $accountRepos = $this->mock(AccountRepositoryInterface::class);
    +        $repository->shouldReceive('setUser')->once();
    +        $accountRepos->shouldReceive('setUser')->once();
    +        $accountRepos->shouldReceive('findNull')->once()->withArgs([1])->andReturn(null);
    +        $repository->shouldReceive('setConfiguration')->once()->withArgs([Mockery::any(), $expectedConfig]);
    +
    +        $handler = new ConfigureUploadHandler;
    +        $handler->setJob($job);
    +        $result = $handler->configureJob($data);
    +        $this->assertCount(1, $result);
    +        $this->assertEquals('You have selected an invalid account to import into.', $result->get('account')[0]);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureUploadHandler
    +     */
    +    public function testGetNextData(): void
    +    {
    +        $job                = new ImportJob;
    +        $job->user_id       = $this->user()->id;
    +        $job->key           = 'upload-A' . random_int(1, 1000);
    +        $job->status        = 'new';
    +        $job->stage         = 'new';
    +        $job->provider      = 'fake';
    +        $job->file_type     = '';
    +        $job->configuration = [];
    +        $job->save();
    +
    +        $repository = $this->mock(ImportJobRepositoryInterface::class);
    +        $repository->shouldReceive('setUser');
    +        $repository->shouldReceive('setConfiguration')->once()->withArgs([Mockery::any(), ['date-format' => 'Ymd']]);
    +
    +        $handler = new ConfigureUploadHandler;
    +        $handler->setJob($job);
    +        $result   = $handler->getNextData();
    +        $expected = [
    +            'accounts'   => [],
    +            'delimiters' => [],
    +        ];
    +        // not much to compare, really.
    +        $this->assertEquals($expected['accounts'], $result['accounts']);
    +    }
    +
    +    /**
    +     * @covers \FireflyIII\Support\Import\Configuration\File\ConfigureUploadHandler
    +     */
    +    public function testGetSpecifics(): void
    +    {
    +        $array    = [
    +            'specifics' => [
    +                'IngDescription', 'BadFakeNewsThing',
    +            ],
    +        ];
    +        $expected = [
    +            'IngDescription' => 1,
    +        ];
    +
    +        $handler = new ConfigureUploadHandler;
    +        $result  = $handler->getSpecifics($array);
    +        $this->assertEquals($expected, $result);
    +    }
    +
    +}
    \ No newline at end of file
    
    From 9bb4df4cc3fd1296a7830490a741ad1188551763 Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Fri, 11 May 2018 19:56:52 +0200
    Subject: [PATCH 064/182] Split and cleanup file import routine.
    
    ---
     .../File/ConfigureMappingHandler.php          |   2 +-
     .../Import/Placeholder/ImportTransaction.php  | 221 ++++-------------
     .../Import/Routine/File/CSVProcessor.php      | 226 +++---------------
     .../Import/Routine/File/ImportableCreator.php |  70 ++++++
     .../Import/Routine/File/LineReader.php        | 173 ++++++++++++++
     .../Routine/File/MappedValuesValidator.php    | 128 ++++++++++
     .../Import/Routine/File/MappingConverger.php  | 213 +++++++++++++++++
     7 files changed, 665 insertions(+), 368 deletions(-)
     create mode 100644 app/Support/Import/Routine/File/ImportableCreator.php
     create mode 100644 app/Support/Import/Routine/File/LineReader.php
     create mode 100644 app/Support/Import/Routine/File/MappedValuesValidator.php
     create mode 100644 app/Support/Import/Routine/File/MappingConverger.php
    
    diff --git a/app/Support/Import/Configuration/File/ConfigureMappingHandler.php b/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    index 5e5025c4e2..e7c3efe693 100644
    --- a/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    +++ b/app/Support/Import/Configuration/File/ConfigureMappingHandler.php
    @@ -229,7 +229,7 @@ class ConfigureMappingHandler implements ConfigurationInterface
             $preProcessClass = config(sprintf('csv.import_roles.%s.pre-process-mapper', $column));
     
             if (null !== $hasPreProcess && true === $hasPreProcess && null !== $preProcessClass) {
    -            $name = sprintf('\\FireflyIII\\Import\\MapperPreProcess\\%s', $preProcessClass);
    +            $name = sprintf('FireflyIII\\Import\\MapperPreProcess\\%s', $preProcessClass);
             }
     
             return $name;
    diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php
    index da40907a9c..497816a51c 100644
    --- a/app/Support/Import/Placeholder/ImportTransaction.php
    +++ b/app/Support/Import/Placeholder/ImportTransaction.php
    @@ -36,71 +36,71 @@ use Log;
     class ImportTransaction
     {
         /** @var string */
    -    private $accountBic;
    +    public $accountBic;
         /** @var string */
    -    private $accountIban;
    +    public $accountIban;
         /** @var int */
    -    private $accountId;
    +    public $accountId;
         /** @var string */
    -    private $accountName;
    +    public $accountName;
         /** @var string */
    -    private $accountNumber;
    +    public $accountNumber;
         /** @var string */
    -    private $amount;
    +    public $amount;
         /** @var string */
    -    private $amountCredit;
    +    public $amountCredit;
         /** @var string */
    -    private $amountDebit;
    +    public $amountDebit;
         /** @var int */
    -    private $billId;
    +    public $billId;
         /** @var string */
    -    private $billName;
    +    public $billName;
         /** @var int */
    -    private $budgetId;
    +    public $budgetId;
         /** @var string */
    -    private $budgetName;
    +    public $budgetName;
         /** @var int */
    -    private $categoryId;
    +    public $categoryId;
         /** @var string */
    -    private $categoryName;
    +    public $categoryName;
         /** @var string */
    -    private $currencyCode;
    +    public $currencyCode;
         /** @var int */
    -    private $currencyId;
    +    public $currencyId;
         /** @var string */
    -    private $currencyName;
    +    public $currencyName;
         /** @var string */
    -    private $currencySymbol;
    +    public $currencySymbol;
         /** @var string */
    -    private $date;
    +    public $date;
         /** @var string */
    -    private $description;
    +    public $description;
         /** @var string */
    -    private $externalId;
    +    public $externalId;
         /** @var string */
    -    private $foreignAmount;
    +    public $foreignAmount;
         /** @var string */
    -    private $foreignCurrencyCode;
    +    public $foreignCurrencyCode;
         /** @var int */
    -    private $foreignCurrencyId;
    +    public $foreignCurrencyId;
         /** @var array */
    -    private $meta;
    +    public $meta;
         /** @var array */
    -    private $modifiers;
    +    public $modifiers;
         /** @var string */
    -    private $note;
    +    public $note;
         /** @var string */
    -    private $opposingBic;
    +    public $opposingBic;
         /** @var string */
    -    private $opposingIban;
    +    public $opposingIban;
         /** @var int */
    -    private $opposingId;
    +    public $opposingId;
         /** @var string */
    -    private $opposingName;
    +    public $opposingName;
         /** @var string */
    -    private $opposingNumber;
    +    public $opposingNumber;
         /** @var array */
    -    private $tags;
    +    public $tags;
     
         /**
          * ImportTransaction constructor.
    @@ -133,9 +133,11 @@ class ImportTransaction
         {
             switch ($columnValue->getRole()) {
                 default:
    +                // @codeCoverageIgnoreStart
                     throw new FireflyException(
                         sprintf('ImportTransaction cannot handle role "%s" with value "%s"', $columnValue->getRole(), $columnValue->getValue())
                     );
    +            // @codeCoverageIgnoreEnd
                 case 'account-id':
                     // could be the result of a mapping?
                     $this->accountId = $this->getMappedValue($columnValue);
    @@ -152,10 +154,10 @@ class ImportTransaction
                 case 'account-number':
                     $this->accountNumber = $columnValue->getValue();
                     break;
    -            case'amount_debit':
    +            case 'amount_debit':
                     $this->amountDebit = $columnValue->getValue();
                     break;
    -            case'amount_credit':
    +            case 'amount_credit':
                     $this->amountCredit = $columnValue->getValue();
                     break;
                 case 'amount':
    @@ -225,10 +227,10 @@ class ImportTransaction
                     $this->date = $columnValue->getValue();
                     break;
                 case 'description':
    -                $this->description .= $columnValue->getValue();
    +                $this->description = trim($this->description . ' ' . $columnValue->getValue());
                     break;
                 case 'note':
    -                $this->note .= $columnValue->getValue();
    +                $this->note = trim($this->note . ' ' . $columnValue->getValue());
                     break;
     
                 case 'opposing-id':
    @@ -246,19 +248,17 @@ class ImportTransaction
                 case 'opposing-number':
                     $this->opposingNumber = $columnValue->getValue();
                     break;
    -
                 case 'rabo-debit-credit':
                 case 'ing-debit-credit':
                     $this->modifiers[$columnValue->getRole()] = $columnValue->getValue();
                     break;
    -
                 case 'tags-comma':
    -                // todo split using pre-processor.
    -                $this->tags = $columnValue->getValue();
    +                $tags       = explode(',', $columnValue->getValue());
    +                $this->tags = array_unique(array_merge($this->tags, $tags));
                     break;
                 case 'tags-space':
    -                // todo split using pre-processor.
    -                $this->tags = $columnValue->getValue();
    +                $tags       = explode(' ', $columnValue->getValue());
    +                $this->tags = array_unique(array_merge($this->tags, $tags));
                     break;
                 case '_ignore':
                     break;
    @@ -275,13 +275,7 @@ class ImportTransaction
         public function calculateAmount(): string
         {
             Log::debug('Now in importTransaction->calculateAmount()');
    -        $info = $this->selectAmountInput();
    -
    -        if (0 === \count($info)) {
    -            Log::error('No amount information for this row.');
    -
    -            return '';
    -        }
    +        $info  = $this->selectAmountInput();
             $class = $info['class'] ?? '';
             if ('' === $class) {
                 Log::error('No amount information (conversion class) for this row.');
    @@ -331,6 +325,7 @@ class ImportTransaction
         {
             if (null === $this->foreignAmount) {
                 Log::debug('ImportTransaction holds no foreign amount info.');
    +
                 return '';
             }
             /** @var ConverterInterface $amountConverter */
    @@ -364,6 +359,7 @@ class ImportTransaction
         /**
          * This array is being used to map the account the user is using.
          *
    +     * @codeCoverageIgnore
          * @return array
          */
         public function getAccountData(): array
    @@ -377,62 +373,7 @@ class ImportTransaction
         }
     
         /**
    -     * @return int
    -     */
    -    public function getAccountId(): int
    -    {
    -        return $this->accountId;
    -    }
    -
    -    /**
    -     * @return int
    -     */
    -    public function getBillId(): int
    -    {
    -        return $this->billId;
    -    }
    -
    -    /**
    -     * @return null|string
    -     */
    -    public function getBillName(): ?string
    -    {
    -        return $this->billName;
    -    }
    -
    -    /**
    -     * @return int
    -     */
    -    public function getBudgetId(): int
    -    {
    -        return $this->budgetId;
    -    }
    -
    -    /**
    -     * @return string|null
    -     */
    -    public function getBudgetName(): ?string
    -    {
    -        return $this->budgetName;
    -    }
    -
    -    /**
    -     * @return int
    -     */
    -    public function getCategoryId(): int
    -    {
    -        return $this->categoryId;
    -    }
    -
    -    /**
    -     * @return string|null
    -     */
    -    public function getCategoryName(): ?string
    -    {
    -        return $this->categoryName;
    -    }
    -
    -    /**
    +     * @codeCoverageIgnore
          * @return array
          */
         public function getCurrencyData(): array
    @@ -445,54 +386,7 @@ class ImportTransaction
         }
     
         /**
    -     * @return int
    -     */
    -    public function getCurrencyId(): int
    -    {
    -        return $this->currencyId;
    -    }
    -
    -    /**
    -     * @return string
    -     */
    -    public function getDate(): string
    -    {
    -        return $this->date;
    -    }
    -
    -    /**
    -     * @return string
    -     */
    -    public function getDescription(): string
    -    {
    -        return $this->description;
    -    }
    -
    -    /**
    -     * @return int
    -     */
    -    public function getForeignCurrencyId(): int
    -    {
    -        return $this->foreignCurrencyId;
    -    }
    -
    -    /**
    -     * @return array
    -     */
    -    public function getMeta(): array
    -    {
    -        return $this->meta;
    -    }
    -
    -    /**
    -     * @return string
    -     */
    -    public function getNote(): string
    -    {
    -        return $this->note;
    -    }
    -
    -    /**
    +     * @codeCoverageIgnore
          * @return array
          */
         public function getOpposingAccountData(): array
    @@ -505,25 +399,6 @@ class ImportTransaction
             ];
         }
     
    -    /**
    -     * @return int
    -     */
    -    public function getOpposingId(): int
    -    {
    -        return $this->opposingId;
    -    }
    -
    -    /**
    -     * @return array
    -     */
    -    public function getTags(): array
    -    {
    -        return [];
    -
    -        // todo make sure this is an array
    -        return $this->tags;
    -    }
    -
         /**
          * Returns the mapped value if it exists in the ColumnValue object.
          *
    diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php
    index ec924af90d..53162147c1 100644
    --- a/app/Support/Import/Routine/File/CSVProcessor.php
    +++ b/app/Support/Import/Routine/File/CSVProcessor.php
    @@ -29,7 +29,6 @@ use FireflyIII\Exceptions\FireflyException;
     use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
     use FireflyIII\Models\Account;
     use FireflyIII\Models\AccountType;
    -use FireflyIII\Models\Attachment;
     use FireflyIII\Models\ImportJob;
     use FireflyIII\Models\TransactionCurrency;
     use FireflyIII\Repositories\Account\AccountRepositoryInterface;
    @@ -40,10 +39,6 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
     use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
     use FireflyIII\Support\Import\Placeholder\ColumnValue;
     use FireflyIII\Support\Import\Placeholder\ImportTransaction;
    -use Illuminate\Support\Collection;
    -use League\Csv\Exception;
    -use League\Csv\Reader;
    -use League\Csv\Statement;
     use Log;
     
     
    @@ -81,23 +76,40 @@ class CSVProcessor implements FileProcessorInterface
         {
             Log::debug('Now in CSVProcessor() run');
     
    -        // in order to actually map we also need to read the FULL file.
    -        try {
    -            $reader = $this->getReader();
    -        } catch (Exception $e) {
    -            Log::error($e->getMessage());
    -            throw new FireflyException('Cannot get reader: ' . $e->getMessage());
    -        }
    -        // get all lines from file:
    -        $lines = $this->getLines($reader);
    +        // create separate objects to handle separate tasks:
    +        /** @var LineReader $lineReader */
    +        $lineReader = app(LineReader::class);
    +        $lineReader->setImportJob($this->importJob);
    +        $lines = $lineReader->getLines();
     
    +        // convert each line into a small set of "ColumnValue" objects,
    +        // joining each with its mapped counterpart.
    +        /** @var MappingConverger $mappingConverger */
    +        $mappingConverger = app(MappingConverger::class);
    +        $mappingConverger->setImportJob($this->importJob);
    +        $converged = $mappingConverger->converge($lines);
    +
    +        // validate mapped values:
    +        /** @var MappedValuesValidator $validator */
    +        $validator    = app(MappedValuesValidator::class);
    +        $mappedValues = $validator->validate($mappingConverger->getMappedValues());
    +
    +        // make import transaction things from these objects.
    +        /** @var ImportableCreator $creator */
    +        $creator     = app(ImportableCreator::class);
    +        $importables = $creator->convertSets($converged);
    +
    +        // todo parse importables from $importables and $mappedValues
    +
    +
    +        // from here.
             // make import objects, according to their role:
    -        $importables = $this->processLines($lines);
    +        //$importables = $this->processLines($lines);
     
             // now validate all mapped values:
    -        $this->validateMappedValues();
    +        //$this->validateMappedValues();
     
    -        return $this->parseImportables($importables);
    +        //return $this->parseImportables($importables);
         }
     
         /**
    @@ -119,57 +131,6 @@ class CSVProcessor implements FileProcessorInterface
     
         }
     
    -    /**
    -     * Returns all lines from the CSV file.
    -     *
    -     * @param Reader $reader
    -     *
    -     * @return array
    -     * @throws FireflyException
    -     */
    -    private function getLines(Reader $reader): array
    -    {
    -        Log::debug('now in getLines()');
    -        $offset = isset($this->config['has-headers']) && $this->config['has-headers'] === true ? 1 : 0;
    -        try {
    -            $stmt = (new Statement)->offset($offset);
    -        } catch (Exception $e) {
    -            throw new FireflyException(sprintf('Could not execute statement: %s', $e->getMessage()));
    -        }
    -        $results = $stmt->process($reader);
    -        $lines   = [];
    -        foreach ($results as $line) {
    -            $lines[] = array_values($line);
    -        }
    -
    -        return $lines;
    -    }
    -
    -    /**
    -     * Return an instance of a CSV file reader so content of the file can be read.
    -     *
    -     * @throws \League\Csv\Exception
    -     */
    -    private function getReader(): Reader
    -    {
    -        Log::debug('Now in getReader()');
    -        $content = '';
    -        /** @var Collection $collection */
    -        $collection = $this->importJob->attachments;
    -        /** @var Attachment $attachment */
    -        foreach ($collection as $attachment) {
    -            if ($attachment->filename === 'import_file') {
    -                $content = $this->attachments->getAttachmentContent($attachment);
    -                break;
    -            }
    -        }
    -        $config = $this->repository->getConfiguration($this->importJob);
    -        $reader = Reader::createFromString($content);
    -        $reader->setDelimiter($config['delimiter']);
    -
    -        return $reader;
    -    }
    -
         /**
          * If the value in the column is mapped to a certain ID,
          * the column where this ID must be placed will change.
    @@ -496,9 +457,9 @@ class CSVProcessor implements FileProcessorInterface
             $opposingId        = $this->verifyObjectId('opposing-id', $importable->getOpposingId());
             // also needs amount to be final.
             //$account           = $this->mapAccount($accountId, $importable->getAccountData());
    -        $source      = $this->mapAssetAccount($accountId, $importable->getAccountData());
    -        $destination = $this->mapOpposingAccount($opposingId, $amount, $importable->getOpposingAccountData());
    -        $currency    = $this->mapCurrency($currencyId, $importable->getCurrencyData());
    +        $source          = $this->mapAssetAccount($accountId, $importable->getAccountData());
    +        $destination     = $this->mapOpposingAccount($opposingId, $amount, $importable->getOpposingAccountData());
    +        $currency        = $this->mapCurrency($currencyId, $importable->getCurrencyData());
             $foreignCurrency = $this->mapCurrency($foreignCurrencyId, $importable->getForeignCurrencyData());
             if (null === $currency) {
                 Log::debug(sprintf('Could not map currency, use default (%s)', $this->defaultCurrency->code));
    @@ -595,129 +556,6 @@ class CSVProcessor implements FileProcessorInterface
     
         }
     
    -    /**
    -     * Process all lines in the CSV file. Each line is processed separately.
    -     *
    -     * @param array $lines
    -     *
    -     * @return array
    -     * @throws FireflyException
    -     */
    -    private function processLines(array $lines): array
    -    {
    -        Log::debug('Now in processLines()');
    -        $processed = [];
    -        $count     = \count($lines);
    -        foreach ($lines as $index => $line) {
    -            Log::debug(sprintf('Now at line #%d of #%d', $index, $count));
    -            $processed[] = $this->processSingleLine($line);
    -        }
    -
    -        return $processed;
    -    }
    -
    -    /**
    -     * Process a single line in the CSV file.
    -     * Each column is processed separately.
    -     *
    -     * @param array $line
    -     * @param array $roles
    -     *
    -     * @return ImportTransaction
    -     * @throws FireflyException
    -     */
    -    private function processSingleLine(array $line): ImportTransaction
    -    {
    -        Log::debug('Now in processSingleLine()');
    -        $transaction = new ImportTransaction;
    -        // todo run all specifics on row.
    -        foreach ($line as $column => $value) {
    -
    -            $value        = trim($value);
    -            $originalRole = $this->config['column-roles'][$column] ?? '_ignore';
    -            Log::debug(sprintf('Now at column #%d (%s), value "%s"', $column, $originalRole, $value));
    -            if ($originalRole !== '_ignore' && \strlen($value) > 0) {
    -
    -                // is a mapped value present?
    -                $mapped = $this->config['column-mapping-config'][$column][$value] ?? 0;
    -                // the role might change.
    -                $role = $this->getRoleForColumn($column, $mapped);
    -
    -                $columnValue = new ColumnValue;
    -                $columnValue->setValue($value);
    -                $columnValue->setRole($role);
    -                $columnValue->setMappedValue($mapped);
    -                $columnValue->setOriginalRole($originalRole);
    -                $transaction->addColumnValue($columnValue);
    -            }
    -            if ('' === $value) {
    -                Log::debug('Column skipped because value is empty.');
    -            }
    -        }
    -
    -        return $transaction;
    -    }
    -
    -    /**
    -     * For each value that has been mapped, this method will check if the mapped value(s) are actually existing
    -     *
    -     * User may indicate that he wants his categories mapped to category #3, #4, #5 but if #5 is owned by another
    -     * user it will be removed.
    -     *
    -     * @throws FireflyException
    -     */
    -    private function validateMappedValues()
    -    {
    -        Log::debug('Now in validateMappedValues()');
    -        foreach ($this->mappedValues as $role => $values) {
    -            $values = array_unique($values);
    -            if (count($values) > 0) {
    -                switch ($role) {
    -                    default:
    -                        throw new FireflyException(sprintf('Cannot validate mapped values for role "%s"', $role));
    -                    case 'opposing-id':
    -                    case 'account-id':
    -                        $set                       = $this->accountRepos->getAccountsById($values);
    -                        $valid                     = $set->pluck('id')->toArray();
    -                        $this->mappedValues[$role] = $valid;
    -                        break;
    -                    case 'currency-id':
    -                    case 'foreign-currency-id':
    -                        /** @var CurrencyRepositoryInterface $repository */
    -                        $repository = app(CurrencyRepositoryInterface::class);
    -                        $repository->setUser($this->importJob->user);
    -                        $set                       = $repository->getByIds($values);
    -                        $valid                     = $set->pluck('id')->toArray();
    -                        $this->mappedValues[$role] = $valid;
    -                        break;
    -                    case 'bill-id':
    -                        /** @var BillRepositoryInterface $repository */
    -                        $repository = app(BillRepositoryInterface::class);
    -                        $repository->setUser($this->importJob->user);
    -                        $set                       = $repository->getByIds($values);
    -                        $valid                     = $set->pluck('id')->toArray();
    -                        $this->mappedValues[$role] = $valid;
    -                        break;
    -                    case 'budget-id':
    -                        /** @var BudgetRepositoryInterface $repository */
    -                        $repository = app(BudgetRepositoryInterface::class);
    -                        $repository->setUser($this->importJob->user);
    -                        $set                       = $repository->getByIds($values);
    -                        $valid                     = $set->pluck('id')->toArray();
    -                        $this->mappedValues[$role] = $valid;
    -                        break;
    -                    case 'category-id':
    -                        /** @var CategoryRepositoryInterface $repository */
    -                        $repository = app(CategoryRepositoryInterface::class);
    -                        $repository->setUser($this->importJob->user);
    -                        $set                       = $repository->getByIds($values);
    -                        $valid                     = $set->pluck('id')->toArray();
    -                        $this->mappedValues[$role] = $valid;
    -                        break;
    -                }
    -            }
    -        }
    -    }
     
         /**
          * A small function that verifies if this particular key (ID) is present in the list
    diff --git a/app/Support/Import/Routine/File/ImportableCreator.php b/app/Support/Import/Routine/File/ImportableCreator.php
    new file mode 100644
    index 0000000000..563e558e1c
    --- /dev/null
    +++ b/app/Support/Import/Routine/File/ImportableCreator.php
    @@ -0,0 +1,70 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace FireflyIII\Support\Import\Routine\File;
    +
    +use FireflyIII\Support\Import\Placeholder\ColumnValue;
    +use FireflyIII\Support\Import\Placeholder\ImportTransaction;
    +
    +/**
    + * Takes an array of arrays of ColumnValue objects and returns one (1) ImportTransaction
    + * for each line.
    + *
    + * Class ImportableCreator
    + */
    +class ImportableCreator
    +{
    +    /**
    +     * @param array $sets
    +     *
    +     * @return array
    +     * @throws \FireflyIII\Exceptions\FireflyException
    +     */
    +    public function convertSets(array $sets): array
    +    {
    +        $return = [];
    +        foreach ($sets as $set) {
    +            $return[] = $this->convertSet($set);
    +        }
    +
    +        return $return;
    +    }
    +
    +    /**
    +     * @param array $set
    +     *
    +     * @return ImportTransaction
    +     * @throws \FireflyIII\Exceptions\FireflyException
    +     */
    +    private function convertSet(array $set): ImportTransaction
    +    {
    +        $transaction = new ImportTransaction;
    +        /** @var ColumnValue $entry */
    +        foreach ($set as $entry) {
    +            $transaction->addColumnValue($entry);
    +        }
    +
    +        return $transaction;
    +    }
    +
    +}
    \ No newline at end of file
    diff --git a/app/Support/Import/Routine/File/LineReader.php b/app/Support/Import/Routine/File/LineReader.php
    new file mode 100644
    index 0000000000..36744c5d38
    --- /dev/null
    +++ b/app/Support/Import/Routine/File/LineReader.php
    @@ -0,0 +1,173 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace FireflyIII\Support\Import\Routine\File;
    +
    +use Exception;
    +use FireflyIII\Exceptions\FireflyException;
    +use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
    +use FireflyIII\Import\Specifics\SpecificInterface;
    +use FireflyIII\Models\Attachment;
    +use FireflyIII\Models\ImportJob;
    +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
    +use League\Csv\Reader;
    +use League\Csv\Statement;
    +use Log;
    +
    +/**
    + * Class LineReader
    + */
    +class LineReader
    +{
    +    /** @var AttachmentHelperInterface */
    +    private $attachments;
    +    /** @var ImportJob */
    +    private $importJob;
    +    /** @var ImportJobRepositoryInterface */
    +    private $repository;
    +
    +    /**
    +     * Grab all lines from the import job.
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    public function getLines(): array
    +    {
    +        try {
    +            $reader = $this->getReader();
    +            // @codeCoverageIgnoreStart
    +        } catch (Exception $e) {
    +            Log::error($e->getMessage());
    +            throw new FireflyException('Cannot get reader: ' . $e->getMessage());
    +        }
    +        // @codeCoverageIgnoreEnd
    +        // get all lines from file:
    +        $lines = $this->getAllLines($reader);
    +
    +        // apply specifics and return.
    +        return $this->applySpecifics($lines);
    +    }
    +
    +    /**
    +     * @param ImportJob $importJob
    +     */
    +    public function setImportJob(ImportJob $importJob): void
    +    {
    +        $this->importJob   = $importJob;
    +        $this->repository  = app(ImportJobRepositoryInterface::class);
    +        $this->attachments = app(AttachmentHelperInterface::class);
    +        $this->repository->setUser($importJob->user);
    +    }
    +
    +    /**
    +     * @param array $lines
    +     *
    +     * @return array
    +     */
    +    private function applySpecifics(array $lines): array
    +    {
    +        $config         = $this->importJob->configuration;
    +        $validSpecifics = array_keys(config('csv.import_specifics'));
    +        $specifics      = $config['specifics'] ?? [];
    +        $names          = array_keys($specifics);
    +        $toApply        = [];
    +        foreach ($names as $name) {
    +            if (!\in_array($name, $validSpecifics, true)) {
    +                continue;
    +            }
    +            $class     = config(sprintf('csv.import_specifics.%s', $name));
    +            $toApply[] = app($class);
    +        }
    +        $return = [];
    +        /** @var array $line */
    +        foreach ($lines as $line) {
    +            /** @var SpecificInterface $specific */
    +            foreach ($toApply as $specific) {
    +                $line = $specific->run($line);
    +            }
    +            $return[] = $line;
    +        }
    +
    +        return $return;
    +    }
    +
    +    /**
    +     * @param Reader $reader
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    private function getAllLines(Reader $reader): array
    +    {
    +        /** @var array $config */
    +        $config = $this->importJob->configuration;
    +        Log::debug('now in getLines()');
    +        $offset = isset($config['has-headers']) && $config['has-headers'] === true ? 1 : 0;
    +        try {
    +            $stmt = (new Statement)->offset($offset);
    +            // @codeCoverageIgnoreStart
    +        } catch (Exception $e) {
    +            throw new FireflyException(sprintf('Could not execute statement: %s', $e->getMessage()));
    +        }
    +        // @codeCoverageIgnoreEnd
    +        $results = $stmt->process($reader);
    +        $lines   = [];
    +        foreach ($results as $line) {
    +            $lines[] = array_values($line);
    +        }
    +
    +        return $lines;
    +    }
    +
    +    /**
    +     * @return Reader
    +     * @throws FireflyException
    +     */
    +    private function getReader(): Reader
    +    {
    +        Log::debug('Now in getReader()');
    +        $content    = '';
    +        $collection = $this->repository->getAttachments($this->importJob);
    +        /** @var Attachment $attachment */
    +        foreach ($collection as $attachment) {
    +            if ($attachment->filename === 'import_file') {
    +                $content = $this->attachments->getAttachmentContent($attachment);
    +                break;
    +            }
    +        }
    +        $config = $this->repository->getConfiguration($this->importJob);
    +        $reader = Reader::createFromString($content);
    +        try {
    +            $reader->setDelimiter($config['delimiter'] ?? ',');
    +            // @codeCoverageIgnoreStart
    +        } catch (\League\Csv\Exception $e) {
    +            throw new FireflyException(sprintf('Cannot set delimiter: %s', $e->getMessage()));
    +        }
    +        // @codeCoverageIgnoreEnd
    +
    +        return $reader;
    +    }
    +
    +
    +}
    \ No newline at end of file
    diff --git a/app/Support/Import/Routine/File/MappedValuesValidator.php b/app/Support/Import/Routine/File/MappedValuesValidator.php
    new file mode 100644
    index 0000000000..c2f23cb54a
    --- /dev/null
    +++ b/app/Support/Import/Routine/File/MappedValuesValidator.php
    @@ -0,0 +1,128 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace FireflyIII\Support\Import\Routine\File;
    +
    +use FireflyIII\Exceptions\FireflyException;
    +use FireflyIII\Models\ImportJob;
    +use FireflyIII\Repositories\Account\AccountRepositoryInterface;
    +use FireflyIII\Repositories\Bill\BillRepositoryInterface;
    +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
    +use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
    +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
    +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
    +use Log;
    +
    +/**
    + * Class MappedValuesValidator
    + */
    +class MappedValuesValidator
    +{
    +    /** @var AccountRepositoryInterface */
    +    private $accountRepos;
    +    /** @var BillRepositoryInterface */
    +    private $billRepos;
    +    /** @var BudgetRepositoryInterface */
    +    private $budgetRepos;
    +    /** @var CategoryRepositoryInterface */
    +    private $catRepos;
    +    /** @var CurrencyRepositoryInterface */
    +    private $currencyRepos;
    +    /** @var ImportJob */
    +    private $importJob;
    +    /** @var ImportJobRepositoryInterface */
    +    private $repository;
    +
    +    /**
    +     * @param ImportJob $importJob
    +     */
    +    public function setImportJob(ImportJob $importJob): void
    +    {
    +        $this->importJob = $importJob;
    +
    +        $this->repository    = app(ImportJobRepositoryInterface::class);
    +        $this->accountRepos  = app(AccountRepositoryInterface::class);
    +        $this->currencyRepos = app(CurrencyRepositoryInterface::class);
    +        $this->billRepos     = app(BillRepositoryInterface::class);
    +        $this->budgetRepos   = app(BudgetRepositoryInterface::class);
    +        $this->catRepos      = app(CategoryRepositoryInterface::class);
    +
    +        $this->repository->setUser($importJob->user);
    +        $this->accountRepos->setUser($importJob->user);
    +        $this->currencyRepos->setUser($importJob->user);
    +        $this->billRepos->setUser($importJob->user);
    +        $this->budgetRepos->setUser($importJob->user);
    +        $this->catRepos->setUser($importJob->user);
    +    }
    +
    +
    +    /**
    +     * @param array $mappings
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    public function validate(array $mappings): array
    +    {
    +        $return = [];
    +        Log::debug('Now in validateMappedValues()');
    +        foreach ($mappings as $role => $values) {
    +            $values = array_unique($values);
    +            if (\count($values) > 0) {
    +                switch ($role) {
    +                    default:
    +                        throw new FireflyException(sprintf('Cannot validate mapped values for role "%s"', $role));
    +                    case 'opposing-id':
    +                    case 'account-id':
    +                        $set           = $this->accountRepos->getAccountsById($values);
    +                        $valid         = $set->pluck('id')->toArray();
    +                        $return[$role] = $valid;
    +                        break;
    +                    case 'currency-id':
    +                    case 'foreign-currency-id':
    +                        $set           = $this->currencyRepos->getByIds($values);
    +                        $valid         = $set->pluck('id')->toArray();
    +                        $return[$role] = $valid;
    +                        break;
    +                    case 'bill-id':
    +                        $set           = $this->billRepos->getByIds($values);
    +                        $valid         = $set->pluck('id')->toArray();
    +                        $return[$role] = $valid;
    +                        break;
    +                    case 'budget-id':
    +                        $set           = $this->budgetRepos->getByIds($values);
    +                        $valid         = $set->pluck('id')->toArray();
    +                        $return[$role] = $valid;
    +                        break;
    +                    case 'category-id':
    +                        $set           = $this->catRepos->getByIds($values);
    +                        $valid         = $set->pluck('id')->toArray();
    +                        $return[$role] = $valid;
    +                        break;
    +                }
    +            }
    +        }
    +
    +        return $return;
    +    }
    +}
    \ No newline at end of file
    diff --git a/app/Support/Import/Routine/File/MappingConverger.php b/app/Support/Import/Routine/File/MappingConverger.php
    new file mode 100644
    index 0000000000..12a52a5279
    --- /dev/null
    +++ b/app/Support/Import/Routine/File/MappingConverger.php
    @@ -0,0 +1,213 @@
    +.
    + */
    +
    +declare(strict_types=1);
    +
    +namespace FireflyIII\Support\Import\Routine\File;
    +
    +use FireflyIII\Exceptions\FireflyException;
    +use FireflyIII\Models\ImportJob;
    +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
    +use FireflyIII\Support\Import\Placeholder\ColumnValue;
    +use Log;
    +
    +/**
    + * Class MappingConverger
    + */
    +class MappingConverger
    +{
    +    /** @var array */
    +    private $doMapping;
    +    /** @var ImportJob */
    +    private $importJob;
    +    /** @var array */
    +    private $mappedValues;
    +    /** @var array */
    +    private $mapping;
    +    /** @var ImportJobRepositoryInterface */
    +    private $repository;
    +    /** @var array */
    +    private $roles;
    +
    +    /**
    +     * Each cell in the CSV file could be linked to a mapped value. This depends on the role of
    +     * the column and the content of the cell. This method goes over all cells, and using their
    +     * associated role, will see if it has been linked to a mapped value. These mapped values
    +     * are all IDs of objects in the Firefly III database.
    +     *
    +     * If such a mapping exists the role of the cell changes to whatever the mapped value is.
    +     *
    +     * Examples:
    +     *
    +     * - Cell with content "Checking Account" and role "account-name". Mapping links "Checking Account" to account-id 2.
    +     * - Cell with content "Checking Account" and role "description". No mapping, so value and role remains the same.
    +     *
    +     * @param array $lines
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    public function converge(array $lines): array
    +    {
    +        Log::debug('Start converging process.');
    +        $collection = [];
    +        $total      = \count($lines);
    +        /** @var array $line */
    +        foreach ($lines as $lineIndex => $line) {
    +            Log::debug(sprintf('Now converging line %d out of %d.', $lineIndex + 1, $total));
    +            $set          = $this->processLine($line);
    +            $collection[] = $set;
    +        }
    +
    +        return $collection;
    +
    +    }
    +
    +    /**
    +     * @return array
    +     */
    +    public function getMappedValues(): array
    +    {
    +        return $this->mappedValues;
    +    }
    +
    +    /**
    +     * @param ImportJob $importJob
    +     */
    +    public function setImportJob(ImportJob $importJob): void
    +    {
    +        $this->importJob  = $importJob;
    +        $this->repository = app(ImportJobRepositoryInterface::class);
    +        $this->repository->setUser($importJob->user);
    +        $this->mappedValues = [];
    +        $config             = $importJob->configuration;
    +        $this->roles        = $config['column-roles'] ?? [];
    +        $this->mapping      = $config['column-mapping-config'] ?? [];
    +        $this->doMapping    = $config['column-do-mapping'] ?? [];
    +    }
    +
    +    /**
    +     * If the value in the column is mapped to a certain ID,
    +     * the column where this ID must be placed will change.
    +     *
    +     * For example, if you map role "budget-name" with value "groceries" to 1,
    +     * then that should become the budget-id. Not the name.
    +     *
    +     * @param int $column
    +     * @param int $mapped
    +     *
    +     * @return string
    +     * @throws FireflyException
    +     */
    +    private function getRoleForColumn(int $column, int $mapped): string
    +    {
    +        $role = $this->roles[$column] ?? '_ignore';
    +        if ($mapped === 0) {
    +            Log::debug(sprintf('Column #%d with role "%s" is not mapped.', $column, $role));
    +
    +            return $role;
    +        }
    +        if (!(isset($this->doMapping[$column]) && $this->doMapping[$column] === true)) {
    +
    +            return $role;
    +        }
    +        switch ($role) {
    +            default:
    +                throw new FireflyException(sprintf('Cannot indicate new role for mapped role "%s"', $role)); // @codeCoverageIgnore
    +            case 'account-id':
    +            case 'account-name':
    +            case 'account-iban':
    +            case 'account-number':
    +                $newRole = 'account-id';
    +                break;
    +            case 'bill-id':
    +            case 'bill-name':
    +                $newRole = 'bill-id';
    +                break;
    +            case 'budget-id':
    +            case 'budget-name':
    +                $newRole = 'budget-id';
    +                break;
    +            case 'currency-id':
    +            case 'currency-name':
    +            case 'currency-code':
    +            case 'currency-symbol':
    +                $newRole = 'currency-id';
    +                break;
    +            case 'category-id':
    +            case 'category-name':
    +                $newRole = 'category-id';
    +                break;
    +            case 'foreign-currency-id':
    +            case 'foreign-currency-code':
    +                $newRole = 'foreign-currency-id';
    +                break;
    +            case 'opposing-id':
    +            case 'opposing-name':
    +            case 'opposing-iban':
    +            case 'opposing-number':
    +                $newRole = 'opposing-id';
    +                break;
    +        }
    +        Log::debug(sprintf('Role was "%s", but because of mapping, role becomes "%s"', $role, $newRole));
    +
    +        // also store the $mapped values in a "mappedValues" array.
    +        $this->mappedValues[$newRole][] = $mapped;
    +
    +        return $newRole;
    +    }
    +
    +    /**
    +     * @param array $line
    +     *
    +     * @return array
    +     * @throws FireflyException
    +     */
    +    private function processLine(array $line): array
    +    {
    +        $return = [];
    +        foreach ($line as $columnIndex => $value) {
    +            $value        = trim($value);
    +            $originalRole = $this->roles[$columnIndex] ?? '_ignore';
    +            Log::debug(sprintf('Now at column #%d (%s), value "%s"', $columnIndex, $originalRole, $value));
    +            if ($originalRole !== '_ignore' && \strlen($value) > 0) {
    +
    +                // is a mapped value present?
    +                $mapped = $this->mapping[$columnIndex][$value] ?? 0;
    +                // the role might change.
    +                $role = $this->getRoleForColumn($columnIndex, $mapped);
    +
    +                $columnValue = new ColumnValue;
    +                $columnValue->setValue($value);
    +                $columnValue->setRole($role);
    +                $columnValue->setMappedValue($mapped);
    +                $columnValue->setOriginalRole($originalRole);
    +                $return[] = $columnValue;
    +            }
    +            if ('' === $value) {
    +                Log::debug('Column skipped because value is empty.');
    +            }
    +        }
    +
    +        return $return;
    +    }
    +
    +}
    \ No newline at end of file
    
    From 4d6bc5572381068034d7812b996e14c8350314c7 Mon Sep 17 00:00:00 2001
    From: James Cole 
    Date: Fri, 11 May 2018 19:58:10 +0200
    Subject: [PATCH 065/182] Update test code.
    
    ---
     .../V1/Controllers/AboutControllerTest.php    |   4 +-
     .../V1/Controllers/AccountControllerTest.php  |  22 +-
     .../Api/V1/Controllers/BillControllerTest.php |  12 +-
     .../Controllers/TransactionControllerTest.php | 115 +--
     .../Api/V1/Controllers/UserControllerTest.php |  14 +-
     tests/CreatesApplication.php                  |   2 +-
     .../Account/ReconcileControllerTest.php       |  34 +-
     .../Controllers/AccountControllerTest.php     |  28 +-
     .../Admin/ConfigurationControllerTest.php     |   4 +-
     .../Controllers/Admin/HomeControllerTest.php  |   4 +-
     .../Controllers/Admin/LinkControllerTest.php  |  26 +-
     .../Admin/UpdateControllerTest.php            |  12 +-
     .../Controllers/Admin/UserControllerTest.php  |  12 +-
     .../Controllers/AttachmentControllerTest.php  |  16 +-
     .../Auth/ForgotPasswordControllerTest.php     |   4 +-
     .../Auth/TwoFactorControllerTest.php          |  10 +-
     .../Controllers/BillControllerTest.php        |  20 +-
     .../Controllers/BudgetControllerTest.php      |  42 +-
     .../Controllers/CategoryControllerTest.php    |  28 +-
     .../Chart/AccountControllerTest.php           |  22 +-
     .../Controllers/Chart/BillControllerTest.php  |   4 +-
     .../Chart/BudgetControllerTest.php            |  22 +-
     .../Chart/BudgetReportControllerTest.php      |   6 +-
     .../Chart/CategoryControllerTest.php          |  10 +-
     .../Chart/CategoryReportControllerTest.php    |  10 +-
     .../Chart/ExpenseReportControllerTest.php     |   2 +-
     .../Chart/PiggyBankControllerTest.php         |   2 +-
     .../Chart/ReportControllerTest.php            |   6 +-
     .../Chart/TagReportControllerTest.php         |  14 +-
     .../Controllers/CurrencyControllerTest.php    |  26 +-
     .../Controllers/DebugControllerTest.php       |   2 +-
     .../Controllers/ExportControllerTest.php      |  10 +-
     .../Controllers/HelpControllerTest.php        |  10 +-
     .../Controllers/HomeControllerTest.php        |  16 +-
     .../Import/IndexControllerTest.php            |   5 +-
     .../Controllers/JavascriptControllerTest.php  |   8 +-
     .../Json/AutoCompleteControllerTest.php       |  18 +-
     .../Controllers/Json/BoxControllerTest.php    |  10 +-
     .../Json/ExchangeControllerTest.php           |   4 +-
     .../Json/FrontpageControllerTest.php          |   2 +-
     .../Controllers/Json/IntroControllerTest.php  |  10 +-
     .../Controllers/JsonControllerTest.php        |   4 +-
     .../Controllers/NewUserControllerTest.php     |   8 +-
     .../Controllers/PiggyBankControllerTest.php   |  14 +-
     .../Popup/ReportControllerTest.php            |  22 +-
     .../Controllers/PreferencesControllerTest.php |   6 +-
     .../Controllers/ProfileControllerTest.php     |  42 +-
     .../Report/AccountControllerTest.php          |   2 +-
     .../Report/BalanceControllerTest.php          |   2 +-
     .../Report/BudgetControllerTest.php           |   4 +-
     .../Report/CategoryControllerTest.php         |   6 +-
     .../Report/ExpenseControllerTest.php          |  10 +-
     .../Report/OperationsControllerTest.php       |   6 +-
     .../Controllers/ReportControllerTest.php      |  48 +-
     .../Controllers/RuleControllerTest.php        |  38 +-
     .../Controllers/RuleGroupControllerTest.php   |  20 +-
     .../Controllers/SearchControllerTest.php      |   4 +-
     .../Feature/Controllers/TagControllerTest.php |  20 +-
     .../Transaction/BulkControllerTest.php        |   6 +-
     .../Transaction/ConvertControllerTest.php     |  36 +-
     .../Transaction/LinkControllerTest.php        |  12 +-
     .../Transaction/MassControllerTest.php        |  10 +-
     .../Transaction/SingleControllerTest.php      |  70 +-
     .../Transaction/SplitControllerTest.php       |  16 +-
     .../Controllers/TransactionControllerTest.php |  18 +-
     tests/Feature/ExampleTest.php                 |   2 +-
     tests/TestCase.php                            |   9 +-
     tests/Unit/ExampleTest.php                    |   2 +-
     tests/Unit/Factory/AccountFactoryTest.php     |  24 +-
     tests/Unit/Factory/BillFactoryTest.php        |  12 +-
     tests/Unit/Factory/BudgetFactoryTest.php      |   8 +-
     tests/Unit/Factory/CategoryFactoryTest.php    |  10 +-
     .../Factory/PiggyBankEventFactoryTest.php     |   8 +-
     tests/Unit/Factory/PiggyBankFactoryTest.php   |   8 +-
     tests/Unit/Factory/TagFactoryTest.php         |   4 +-
     .../TransactionCurrencyFactoryTest.php        |  12 +-
     tests/Unit/Factory/TransactionFactoryTest.php |  85 +-
     .../Factory/TransactionJournalFactoryTest.php |   4 +-
     .../TransactionJournalMetaFactoryTest.php     |  13 +-
     .../Handlers/Events/AdminEventHandlerTest.php |   4 +-
     .../Handlers/Events/UserEventHandlerTest.php  |  18 +-
     .../Events/VersionCheckEventHandlerTest.php   |  10 +-
     tests/Unit/Helpers/AttachmentHelperTest.php   |  10 +-
     tests/Unit/Helpers/MetaPieChartTest.php       |   8 +-
     .../Import/Converter/AmountCreditTest.php     |   4 +-
     .../Unit/Import/Converter/AmountDebitTest.php |   4 +-
     tests/Unit/Import/Converter/AmountTest.php    |   4 +-
     .../Import/Converter/INGDebitCreditTest.php   |   6 +-
     .../Import/Mapper/AssetAccountIbansTest.php   |   2 +-
     .../Unit/Import/Mapper/AssetAccountsTest.php  |   2 +-
     tests/Unit/Import/Mapper/BillsTest.php        |   3 +-
     tests/Unit/Import/Mapper/BudgetsTest.php      |   2 +-
     tests/Unit/Import/Mapper/CategoriesTest.php   |   2 +-
     .../Mapper/OpposingAccountIbansTest.php       |   2 +-
     .../Import/Mapper/OpposingAccountsTest.php    |   2 +-
     tests/Unit/Import/Mapper/TagsTest.php         |   2 +-
     .../Mapper/TransactionCurrenciesTest.php      |   2 +-
     .../Import/MapperPreProcess/TagsCommaTest.php |   2 +-
     .../Import/MapperPreProcess/TagsSpaceTest.php |   2 +-
     tests/Unit/Middleware/AuthenticateTest.php    |  10 +-
     .../Middleware/AuthenticateTwoFactorTest.php  |  10 +-
     tests/Unit/Middleware/IsAdminTest.php         |   8 +-
     tests/Unit/Middleware/IsDemoUserTest.php      |   6 +-
     tests/Unit/Middleware/IsSandstormUserTest.php |   6 +-
     tests/Unit/Middleware/RangeTest.php           |   4 +-
     .../RedirectIf2FAAuthenticatedTest.php        |   6 +-
     .../RedirectIfAuthenticatedTest.php           |   4 +-
     .../Destroy/AccountDestroyServiceTest.php     |   6 +-
     .../Update/AccountUpdateServiceTest.php       |  14 +-
     .../Update/JournalUpdateServiceTest.php       |  10 +-
     .../Update/TransactionUpdateServiceTest.php   |  14 +-
     .../File/ConfigureMappingHandlerTest.php      |   3 +-
     .../Placeholder/ImportTransactionTest.php     | 748 ++++++++++++++++++
     .../Routine/File/ImportableCreatorTest.php    |  64 ++
     .../Import/Routine/File/LineReaderTest.php    | 102 +++
     .../Routine/File/MappingConvergerTest.php     | 154 ++++
     .../TransactionRules/Actions/AddTagTest.php   |   4 +-
     .../Actions/AppendDescriptionTest.php         |   2 +-
     .../Actions/AppendNotesTest.php               |   4 +-
     .../Actions/ClearBudgetTest.php               |   2 +-
     .../Actions/ClearCategoryTest.php             |   2 +-
     .../Actions/ClearNotesTest.php                |   9 +-
     .../Actions/PrependDescriptionTest.php        |   2 +-
     .../Actions/PrependNotesTest.php              |   4 +-
     .../Actions/RemoveAllTagsTest.php             |   2 +-
     .../Actions/RemoveTagTest.php                 |   4 +-
     .../Actions/SetBudgetTest.php                 |   2 +-
     .../Actions/SetCategoryTest.php               |   2 +-
     .../Actions/SetDescriptionTest.php            |   2 +-
     .../Actions/SetDestinationAccountTest.php     |  10 +-
     .../TransactionRules/Actions/SetNotesTest.php |   4 +-
     .../Actions/SetSourceAccountTest.php          |  10 +-
     .../Triggers/AmountExactlyTest.php            |   8 +-
     .../Triggers/AmountLessTest.php               |  10 +-
     .../Triggers/AmountMoreTest.php               |  12 +-
     .../Triggers/BudgetIsTest.php                 |  12 +-
     .../Triggers/CategoryIsTest.php               |  10 +-
     .../Triggers/DescriptionContainsTest.php      |  16 +-
     .../Triggers/DescriptionEndsTest.php          |  18 +-
     .../Triggers/DescriptionIsTest.php            |  12 +-
     .../Triggers/DescriptionStartsTest.php        |  16 +-
     .../Triggers/FromAccountContainsTest.php      |  10 +-
     .../Triggers/FromAccountEndsTest.php          |  12 +-
     .../Triggers/FromAccountIsTest.php            |  10 +-
     .../Triggers/FromAccountStartsTest.php        |  12 +-
     .../Triggers/HasAnyBudgetTest.php             |   8 +-
     .../Triggers/HasAnyTagTest.php                |   6 +-
     .../Triggers/HasAttachmentTest.php            |   8 +-
     .../Triggers/HasNoBudgetTest.php              |   8 +-
     .../Triggers/HasNoCategoryTest.php            |   8 +-
     .../Triggers/HasNoTagTest.php                 |   6 +-
     .../Triggers/NotesAnyTest.php                 |   8 +-
     .../Triggers/NotesAreTest.php                 |  12 +-
     .../Triggers/NotesContainTest.php             |  16 +-
     .../Triggers/NotesEmptyTest.php               |   8 +-
     .../Triggers/NotesEndTest.php                 |  12 +-
     .../Triggers/NotesStartTest.php               |  12 +-
     .../TransactionRules/Triggers/TagIsTest.php   |  12 +-
     .../Triggers/ToAccountContainsTest.php        |  10 +-
     .../Triggers/ToAccountEndsTest.php            |  12 +-
     .../Triggers/ToAccountIsTest.php              |  10 +-
     .../Triggers/ToAccountStartsTest.php          |  12 +-
     .../Triggers/TransactionTypeTest.php          |   8 +-
     .../Transformers/AccountTransformerTest.php   |  14 +-
     .../AttachmentTransformerTest.php             |   2 +-
     .../Unit/Transformers/BillTransformerTest.php |   6 +-
     .../Transformers/BudgetTransformerTest.php    |   2 +-
     .../Transformers/CategoryTransformerTest.php  |   2 +-
     .../JournalMetaTransformerTest.php            |   2 +-
     .../PiggyBankEventTransformerTest.php         |   4 +-
     .../Transformers/PiggyBankTransformerTest.php |   6 +-
     .../Unit/Transformers/TagTransformerTest.php  |   2 +-
     .../TransactionTransformerTest.php            | 122 ++-
     .../Unit/Transformers/UserTransformerTest.php |   4 +-
     174 files changed, 2138 insertions(+), 940 deletions(-)
     create mode 100644 tests/Unit/Support/Import/Placeholder/ImportTransactionTest.php
     create mode 100644 tests/Unit/Support/Import/Routine/File/ImportableCreatorTest.php
     create mode 100644 tests/Unit/Support/Import/Routine/File/LineReaderTest.php
     create mode 100644 tests/Unit/Support/Import/Routine/File/MappingConvergerTest.php
    
    diff --git a/tests/Api/V1/Controllers/AboutControllerTest.php b/tests/Api/V1/Controllers/AboutControllerTest.php
    index 68740c65fb..d3ffcb88e0 100644
    --- a/tests/Api/V1/Controllers/AboutControllerTest.php
    +++ b/tests/Api/V1/Controllers/AboutControllerTest.php
    @@ -48,7 +48,7 @@ class AboutControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\AboutController::__construct
          * @covers \FireflyIII\Api\V1\Controllers\AboutController::about
          */
    -    public function testAbout()
    +    public function testAbout(): void
         {
             // test API
             $response = $this->get('/api/v1/about');
    @@ -67,7 +67,7 @@ class AboutControllerTest extends TestCase
          *
          * @covers \FireflyIII\Api\V1\Controllers\AboutController::user
          */
    -    public function testUser()
    +    public function testUser(): void
         {
             // test API
             $response = $this->get('/api/v1/about/user');
    diff --git a/tests/Api/V1/Controllers/AccountControllerTest.php b/tests/Api/V1/Controllers/AccountControllerTest.php
    index e6c024b7dc..77586a0008 100644
    --- a/tests/Api/V1/Controllers/AccountControllerTest.php
    +++ b/tests/Api/V1/Controllers/AccountControllerTest.php
    @@ -52,7 +52,7 @@ class AccountControllerTest extends TestCase
          *
          * @covers \FireflyIII\Api\V1\Controllers\AccountController::delete
          */
    -    public function testDelete()
    +    public function testDelete(): void
         {
             // mock stuff:
             $repository    = $this->mock(AccountRepositoryInterface::class);
    @@ -76,7 +76,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\AccountController::index
          * @covers \FireflyIII\Api\V1\Controllers\AccountController::mapTypes
          */
    -    public function testIndex()
    +    public function testIndex(): void
         {
             // create stuff
             $accounts = factory(Account::class, 10)->create();
    @@ -115,7 +115,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::rules
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::getAll
          */
    -    public function testInvalidBalance()
    +    public function testInvalidBalance(): void
         {
             // mock repositories
             $repository    = $this->mock(AccountRepositoryInterface::class);
    @@ -157,7 +157,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::rules
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::getAll
          */
    -    public function testNoCreditCardData()
    +    public function testNoCreditCardData(): void
         {
             // mock repositories
             $repository    = $this->mock(AccountRepositoryInterface::class);
    @@ -199,7 +199,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::rules
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::getAll
          */
    -    public function testNoCurrencyInfo()
    +    public function testNoCurrencyInfo(): void
         {
             // mock repositories
             $repository    = $this->mock(AccountRepositoryInterface::class);
    @@ -236,7 +236,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\AccountController::show
          */
     
    -    public function testShow()
    +    public function testShow(): void
         {
             // create stuff
             $account = $this->user()->accounts()->first();
    @@ -273,7 +273,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::rules
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::getAll
          */
    -    public function testStoreNotUnique()
    +    public function testStoreNotUnique(): void
         {
             // mock repositories
             $repository    = $this->mock(AccountRepositoryInterface::class);
    @@ -315,7 +315,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::rules
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::getAll
          */
    -    public function testStoreValid()
    +    public function testStoreValid(): void
         {
             // mock repositories
             $repository    = $this->mock(AccountRepositoryInterface::class);
    @@ -359,7 +359,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::rules
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::getAll
          */
    -    public function testStoreWithCurrencyCode()
    +    public function testStoreWithCurrencyCode(): void
         {
             // mock repositories
             $repository    = $this->mock(AccountRepositoryInterface::class);
    @@ -405,7 +405,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\AccountController::update
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::rules
          */
    -    public function testUpdate()
    +    public function testUpdate(): void
         {
             // mock repositories
             $repository    = $this->mock(AccountRepositoryInterface::class);
    @@ -448,7 +448,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\AccountController::update
          * @covers \FireflyIII\Api\V1\Requests\AccountRequest::rules
          */
    -    public function testUpdateCurrencyCode()
    +    public function testUpdateCurrencyCode(): void
         {
             // mock repositories
             $repository    = $this->mock(AccountRepositoryInterface::class);
    diff --git a/tests/Api/V1/Controllers/BillControllerTest.php b/tests/Api/V1/Controllers/BillControllerTest.php
    index 7ea81bfc1a..76ab361ee3 100644
    --- a/tests/Api/V1/Controllers/BillControllerTest.php
    +++ b/tests/Api/V1/Controllers/BillControllerTest.php
    @@ -52,7 +52,7 @@ class BillControllerTest extends TestCase
          *
          * @covers \FireflyIII\Api\V1\Controllers\BillController
          */
    -    public function testDelete()
    +    public function testDelete(): void
         {
             // mock stuff:
             $repository = $this->mock(BillRepositoryInterface::class);
    @@ -73,7 +73,7 @@ class BillControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\BillController::__construct
          * @covers \FireflyIII\Api\V1\Controllers\BillController::index
          */
    -    public function testIndex()
    +    public function testIndex(): void
         {
             // create stuff
             $bills     = factory(Bill::class, 10)->create();
    @@ -99,7 +99,7 @@ class BillControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Api\V1\Controllers\BillController::show
          */
    -    public function testShow()
    +    public function testShow(): void
         {
             // create stuff
             $bill       = $this->user()->bills()->first();
    @@ -127,7 +127,7 @@ class BillControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\BillRequest::getAll
          * @covers \FireflyIII\Api\V1\Requests\BillRequest::withValidator
          */
    -    public function testStoreMinOverMax()
    +    public function testStoreMinOverMax(): void
         {
             // create stuff
             $bill       = $this->user()->bills()->first();
    @@ -172,7 +172,7 @@ class BillControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\BillRequest::authorize
          * @covers \FireflyIII\Api\V1\Requests\BillRequest::getAll
          */
    -    public function testStoreValid()
    +    public function testStoreValid(): void
         {
             // create stuff
             $bill       = $this->user()->bills()->first();
    @@ -211,7 +211,7 @@ class BillControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\BillRequest::authorize
          * @covers \FireflyIII\Api\V1\Requests\BillRequest::getAll
          */
    -    public function testUpdateValid()
    +    public function testUpdateValid(): void
         {
             // create stuff
             $bill       = $this->user()->bills()->first();
    diff --git a/tests/Api/V1/Controllers/TransactionControllerTest.php b/tests/Api/V1/Controllers/TransactionControllerTest.php
    index db4174b1b2..d4646cd950 100644
    --- a/tests/Api/V1/Controllers/TransactionControllerTest.php
    +++ b/tests/Api/V1/Controllers/TransactionControllerTest.php
    @@ -24,6 +24,7 @@ declare(strict_types=1);
     namespace Tests\Api\V1\Controllers;
     
     
    +use FireflyIII\Exceptions\FireflyException;
     use FireflyIII\Helpers\Collector\JournalCollector;
     use FireflyIII\Helpers\Collector\JournalCollectorInterface;
     use FireflyIII\Helpers\Filter\NegativeAmountFilter;
    @@ -57,7 +58,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::__construct
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::delete
          */
    -    public function testDelete()
    +    public function testDelete(): void
         {
             // mock stuff:
             $repository = $this->mock(JournalRepositoryInterface::class);
    @@ -81,7 +82,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailCurrencyCode()
    +    public function testFailCurrencyCode(): void
         {
             // mock stuff:
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -128,7 +129,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailCurrencyId()
    +    public function testFailCurrencyId(): void
         {
             // mock stuff:
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -174,7 +175,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailEmptyDescriptions()
    +    public function testFailEmptyDescriptions(): void
         {
             // mock stuff:
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -226,7 +227,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailEmptySplitDescriptions()
    +    public function testFailEmptySplitDescriptions(): void
         {
             // mock stuff:
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -284,7 +285,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          * @covers \FireflyIII\Rules\BelongsUser
          */
    -    public function testFailExpenseID()
    +    public function testFailExpenseID(): void
         {
             // mock stuff:
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -332,7 +333,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailExpenseName()
    +    public function testFailExpenseName(): void
         {
             // mock stuff:
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -379,7 +380,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailNoAsset()
    +    public function testFailNoAsset(): void
         {
             // mock stuff:
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -423,7 +424,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailNoData()
    +    public function testFailNoData(): void
         {
             // mock stuff:
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -460,7 +461,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailNoForeignCurrencyInfo()
    +    public function testFailNoForeignCurrencyInfo(): void
         {
             // mock stuff:
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -508,7 +509,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailOpposingRevenueID()
    +    public function testFailOpposingRevenueID(): void
         {
             $account  = $this->user()->accounts()->where('account_type_id', 3)->first();
             $opposing = $this->user()->accounts()->where('account_type_id', 5)->first();
    @@ -561,7 +562,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          * @covers \FireflyiII\Rules\BelongsUser
          */
    -    public function testFailOwnershipBillId()
    +    public function testFailOwnershipBillId(): void
         {
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -620,7 +621,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          * @covers \FireflyiII\Rules\BelongsUser
          */
    -    public function testFailOwnershipBillName()
    +    public function testFailOwnershipBillName(): void
         {
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -678,7 +679,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          * @covers \FireflyiII\Rules\BelongsUser
          */
    -    public function testFailOwnershipBudgetId()
    +    public function testFailOwnershipBudgetId(): void
         {
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -736,7 +737,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          * @covers \FireflyiII\Rules\BelongsUser
          */
    -    public function testFailOwnershipBudgetName()
    +    public function testFailOwnershipBudgetName(): void
         {
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -794,7 +795,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          * @covers \FireflyiII\Rules\BelongsUser
          */
    -    public function testFailOwnershipCategoryId()
    +    public function testFailOwnershipCategoryId(): void
         {
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -852,7 +853,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          * @covers \FireflyiII\Rules\BelongsUser
          */
    -    public function testFailOwnershipPiggyBankID()
    +    public function testFailOwnershipPiggyBankID(): void
         {
             // move account to other user
             $move                  = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -918,7 +919,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          * @covers \FireflyiII\Rules\BelongsUser
          */
    -    public function testFailOwnershipPiggyBankName()
    +    public function testFailOwnershipPiggyBankName(): void
         {
             // move account to other user
             $move                  = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -984,7 +985,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          * @covers \FireflyIII\Rules\BelongsUser
          */
    -    public function testFailRevenueID()
    +    public function testFailRevenueID(): void
         {
             $account      = $this->user()->accounts()->where('account_type_id', 4)->first();
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -1030,7 +1031,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailSplitDeposit()
    +    public function testFailSplitDeposit(): void
         {
             $account = $this->user()->accounts()->where('account_type_id', 3)->first();
             $second  = $this->user()->accounts()->where('account_type_id', 3)->where('id', '!=', $account->id)->first();
    @@ -1087,7 +1088,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailSplitTransfer()
    +    public function testFailSplitTransfer(): void
         {
             $account = $this->user()->accounts()->where('account_type_id', 3)->first();
             $second  = $this->user()->accounts()->where('account_type_id', 3)->where('id', '!=', $account->id)->first();
    @@ -1151,7 +1152,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testFailSplitWithdrawal()
    +    public function testFailSplitWithdrawal(): void
         {
             $account = $this->user()->accounts()->where('account_type_id', 3)->first();
             $second  = $this->user()->accounts()->where('account_type_id', 3)->where('id', '!=', $account->id)->first();
    @@ -1210,7 +1211,7 @@ class TransactionControllerTest extends TestCase
          *
          * throws \FireflyIII\Exceptions\FireflyException
          */
    -    public function testIndex()
    +    public function testIndex(): void
         {
             $accountRepos = $this->mock(AccountRepositoryInterface::class);
             $accountRepos->shouldReceive('setUser');
    @@ -1223,7 +1224,11 @@ class TransactionControllerTest extends TestCase
             $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
             $collector->setAllAssetAccounts();
             $collector->setLimit(5)->setPage(1);
    -        $paginator = $collector->getPaginatedJournals();
    +        try {
    +            $paginator = $collector->getPaginatedJournals();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
     
             // mock stuff:
             $repository = $this->mock(JournalRepositoryInterface::class);
    @@ -1260,7 +1265,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::mapTypes
          * throws \FireflyIII\Exceptions\FireflyException
          */
    -    public function testIndexWithRange()
    +    public function testIndexWithRange(): void
         {
             $accountRepos = $this->mock(AccountRepositoryInterface::class);
             $accountRepos->shouldReceive('setUser');
    @@ -1273,7 +1278,11 @@ class TransactionControllerTest extends TestCase
             $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
             $collector->setAllAssetAccounts();
             $collector->setLimit(5)->setPage(1);
    +        try {
             $paginator = $collector->getPaginatedJournals();
    +        } catch (FireflyException $e) {
    +            $this->assertTrue(false, $e->getMessage());
    +        }
     
             // mock stuff:
             $repository = $this->mock(JournalRepositoryInterface::class);
    @@ -1321,7 +1330,7 @@ class TransactionControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::show
          */
    -    public function testShowDeposit()
    +    public function testShowDeposit(): void
         {
             do {
                 // this is kind of cheating but OK.
    @@ -1382,7 +1391,7 @@ class TransactionControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::show
          */
    -    public function testShowWithdrawal()
    +    public function testShowWithdrawal(): void
         {
             do {
                 // this is kind of cheating but OK.
    @@ -1450,7 +1459,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessBillId()
    +    public function testSuccessBillId(): void
         {
             // default journal:
             $journal      = $this->user()->transactionJournals()->first();
    @@ -1491,7 +1500,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessBillName()
    +    public function testSuccessBillName(): void
         {
             // default journal:
             $journal      = $this->user()->transactionJournals()->first();
    @@ -1532,7 +1541,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessNewStoreOpposingName()
    +    public function testSuccessNewStoreOpposingName(): void
         {
             $journal      = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first();
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -1572,7 +1581,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreAccountName()
    +    public function testSuccessStoreAccountName(): void
         {
             // default journal:
             $journal      = $this->user()->transactionJournals()->first();
    @@ -1611,7 +1620,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreBasic()
    +    public function testSuccessStoreBasic(): void
         {
             // default journal:
             $journal      = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first();
    @@ -1650,7 +1659,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreBasicByName()
    +    public function testSuccessStoreBasicByName(): void
         {
             // default journal:
             $journal      = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first();
    @@ -1691,7 +1700,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreBasicDeposit()
    +    public function testSuccessStoreBasicDeposit(): void
         {
             // default journal:
             $journal      = $this->user()->transactionJournals()->where('transaction_type_id', 2)->first();
    @@ -1730,7 +1739,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreBudgetId()
    +    public function testSuccessStoreBudgetId(): void
         {
             $budget       = $this->user()->budgets()->first();
             $journal      = $this->user()->transactionJournals()->first();
    @@ -1769,7 +1778,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreBudgetName()
    +    public function testSuccessStoreBudgetName(): void
         {
             $budget       = $this->user()->budgets()->first();
             $journal      = $this->user()->transactionJournals()->first();
    @@ -1809,7 +1818,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreCategoryID()
    +    public function testSuccessStoreCategoryID(): void
         {
             $category     = $this->user()->categories()->first();
             $journal      = $this->user()->transactionJournals()->first();
    @@ -1848,7 +1857,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreCategoryName()
    +    public function testSuccessStoreCategoryName(): void
         {
             $category     = $this->user()->categories()->first();
             $journal      = $this->user()->transactionJournals()->first();
    @@ -1887,7 +1896,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreForeignAmount()
    +    public function testSuccessStoreForeignAmount(): void
         {
             $currency     = TransactionCurrency::first();
             $foreign      = TransactionCurrency::where('id', '!=', $currency->id)->first();
    @@ -1928,7 +1937,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreMetaData()
    +    public function testSuccessStoreMetaData(): void
         {
             $journal      = $this->user()->transactionJournals()->first();
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -1973,7 +1982,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreNewCategoryName()
    +    public function testSuccessStoreNewCategoryName(): void
         {
             $journal      = $this->user()->transactionJournals()->first();
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -2013,7 +2022,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreNewOpposingName()
    +    public function testSuccessStoreNewOpposingName(): void
         {
             $journal      = $this->user()->transactionJournals()->first();
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -2053,7 +2062,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreNotes()
    +    public function testSuccessStoreNotes(): void
         {
             $journal      = $this->user()->transactionJournals()->first();
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -2092,7 +2101,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreOpposingID()
    +    public function testSuccessStoreOpposingID(): void
         {
             $opposing     = $this->user()->accounts()->where('account_type_id', 4)->first();
             $journal      = $this->user()->transactionJournals()->first();
    @@ -2131,7 +2140,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreOpposingName()
    +    public function testSuccessStoreOpposingName(): void
         {
             $opposing     = $this->user()->accounts()->where('account_type_id', 4)->first();
             $journal      = $this->user()->transactionJournals()->first();
    @@ -2172,7 +2181,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStorePiggyDeposit()
    +    public function testSuccessStorePiggyDeposit(): void
         {
             $journal      = $this->user()->transactionJournals()->first();
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -2211,7 +2220,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStorePiggyId()
    +    public function testSuccessStorePiggyId(): void
         {
             $source       = $this->user()->accounts()->where('account_type_id', 3)->first();
             $dest         = $this->user()->accounts()->where('account_type_id', 3)->where('id', '!=', $source->id)->first();
    @@ -2251,7 +2260,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStorePiggyName()
    +    public function testSuccessStorePiggyName(): void
         {
             $source       = $this->user()->accounts()->where('account_type_id', 3)->first();
             $dest         = $this->user()->accounts()->where('account_type_id', 3)->where('id', '!=', $source->id)->first();
    @@ -2290,7 +2299,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreReconciled()
    +    public function testSuccessStoreReconciled(): void
         {
             $journal      = $this->user()->transactionJournals()->first();
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -2328,7 +2337,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreSplit()
    +    public function testSuccessStoreSplit(): void
         {
             $journal      = $this->user()->transactionJournals()->first();
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
    @@ -2375,7 +2384,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testSuccessStoreTags()
    +    public function testSuccessStoreTags(): void
         {
             $tags         = [
                 'TagOne' . random_int(1, 1000),
    @@ -2419,7 +2428,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::update
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testUpdateBasicDeposit()
    +    public function testUpdateBasicDeposit(): void
         {
             $account      = $this->user()->accounts()->where('account_type_id', 3)->first();
             $repository   = $this->mock(JournalRepositoryInterface::class);
    @@ -2460,7 +2469,7 @@ class TransactionControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\TransactionController::update
          * @covers \FireflyIII\Api\V1\Requests\TransactionRequest
          */
    -    public function testUpdateBasicWithdrawal()
    +    public function testUpdateBasicWithdrawal(): void
         {
             $account    = $this->user()->accounts()->where('account_type_id', 3)->first();
             $repository = $this->mock(JournalRepositoryInterface::class);
    diff --git a/tests/Api/V1/Controllers/UserControllerTest.php b/tests/Api/V1/Controllers/UserControllerTest.php
    index 3fd0e2a69d..7db9ea3925 100644
    --- a/tests/Api/V1/Controllers/UserControllerTest.php
    +++ b/tests/Api/V1/Controllers/UserControllerTest.php
    @@ -54,7 +54,7 @@ class UserControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\UserController::delete
          * @covers \FireflyIII\Api\V1\Requests\UserRequest
          */
    -    public function testDelete()
    +    public function testDelete(): void
         {
             // create a user first:
             $user = User::create(['email' => 'some@newu' . random_int(1, 1000) . 'ser.nl', 'password' => 'hello', 'blocked' => 0]);
    @@ -72,7 +72,7 @@ class UserControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\UserController::delete
          * @covers \FireflyIII\Api\V1\Requests\UserRequest
          */
    -    public function testDeleteNoAdmin()
    +    public function testDeleteNoAdmin(): void
         {
             Passport::actingAs($this->emptyUser());
     
    @@ -89,7 +89,7 @@ class UserControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\UserController::__construct
          * @covers \FireflyIII\Api\V1\Controllers\UserController::index
          */
    -    public function testIndex()
    +    public function testIndex(): void
         {
             // create stuff
             $users = factory(User::class, 10)->create();
    @@ -113,7 +113,7 @@ class UserControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Api\V1\Controllers\UserController::show
          */
    -    public function testShow()
    +    public function testShow(): void
         {
             $user = User::first();
     
    @@ -127,7 +127,7 @@ class UserControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\UserController::store
          * @covers \FireflyIII\Api\V1\Requests\UserRequest
          */
    -    public function testStoreBasic()
    +    public function testStoreBasic(): void
         {
             $data = [
                 'email'   => 'some_new@user' . random_int(1, 1000) . '.com',
    @@ -148,7 +148,7 @@ class UserControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\UserController::store
          * @covers \FireflyIII\Api\V1\Requests\UserRequest
          */
    -    public function testStoreNotUnique()
    +    public function testStoreNotUnique(): void
         {
             $data = [
                 'email'   => $this->user()->email,
    @@ -177,7 +177,7 @@ class UserControllerTest extends TestCase
          * @covers \FireflyIII\Api\V1\Controllers\UserController::update
          * @covers \FireflyIII\Api\V1\Requests\UserRequest
          */
    -    public function testUpdate()
    +    public function testUpdate(): void
         {
             // create a user first:
             $user = User::create(['email' => 'some@newu' . random_int(1, 1000) . 'ser.nl', 'password' => 'hello', 'blocked' => 0]);
    diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php
    index ee73d4b36a..712aeec11c 100644
    --- a/tests/CreatesApplication.php
    +++ b/tests/CreatesApplication.php
    @@ -36,7 +36,7 @@ trait CreatesApplication
          *
          * @return \Illuminate\Foundation\Application
          */
    -    public function createApplication()
    +    public function createApplication(): \Illuminate\Foundation\Application
         {
             $app = require __DIR__ . '/../bootstrap/app.php';
     
    diff --git a/tests/Feature/Controllers/Account/ReconcileControllerTest.php b/tests/Feature/Controllers/Account/ReconcileControllerTest.php
    index ccdfe0bd25..f549fb9a2e 100644
    --- a/tests/Feature/Controllers/Account/ReconcileControllerTest.php
    +++ b/tests/Feature/Controllers/Account/ReconcileControllerTest.php
    @@ -56,7 +56,7 @@ class ReconcileControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::edit
          */
    -    public function testEdit()
    +    public function testEdit(): void
         {
             $repository  = $this->mock(JournalRepositoryInterface::class);
             $journal     = $this->user()->transactionJournals()->where('transaction_type_id', 5)->first();
    @@ -78,7 +78,7 @@ class ReconcileControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::edit
          */
    -    public function testEditRedirect()
    +    public function testEditRedirect(): void
         {
             $journal = $this->user()->transactionJournals()->where('transaction_type_id', '!=', 5)->first();
             $this->be($this->user());
    @@ -90,7 +90,7 @@ class ReconcileControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::overview()
          */
    -    public function testOverview()
    +    public function testOverview(): void
         {
             $transactions = $this->user()->transactions()->inRandomOrder()->take(3)->get();
             $repository   = $this->mock(JournalRepositoryInterface::class);
    @@ -112,7 +112,7 @@ class ReconcileControllerTest extends TestCase
          * @covers                   \FireflyIII\Http\Controllers\Account\ReconcileController::overview()
          * @expectedExceptionMessage is not an asset account
          */
    -    public function testOverviewNotAsset()
    +    public function testOverviewNotAsset(): void
         {
             $account    = $this->user()->accounts()->where('account_type_id', '!=', 3)->first();
             $parameters = [
    @@ -131,7 +131,7 @@ class ReconcileControllerTest extends TestCase
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::reconcile()
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::redirectToOriginalAccount()
          */
    -    public function testReconcile()
    +    public function testReconcile(): void
         {
             $repository = $this->mock(CurrencyRepositoryInterface::class);
             $repository->shouldReceive('findNull')->once()->andReturn(TransactionCurrency::find(1));
    @@ -148,7 +148,7 @@ class ReconcileControllerTest extends TestCase
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::reconcile()
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::redirectToOriginalAccount()
          */
    -    public function testReconcileInitialBalance()
    +    public function testReconcileInitialBalance(): void
         {
             $transaction = Transaction::leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
                                       ->where('accounts.user_id', $this->user()->id)->where('accounts.account_type_id', 6)->first(['account_id']);
    @@ -162,7 +162,7 @@ class ReconcileControllerTest extends TestCase
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::reconcile()
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::redirectToOriginalAccount()
          */
    -    public function testReconcileNoDates()
    +    public function testReconcileNoDates(): void
         {
             $repository = $this->mock(CurrencyRepositoryInterface::class);
             $repository->shouldReceive('findNull')->once()->andReturn(TransactionCurrency::find(1));
    @@ -180,7 +180,7 @@ class ReconcileControllerTest extends TestCase
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::reconcile()
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::redirectToOriginalAccount()
          */
    -    public function testReconcileNoEndDate()
    +    public function testReconcileNoEndDate(): void
         {
             $repository = $this->mock(CurrencyRepositoryInterface::class);
             $repository->shouldReceive('findNull')->once()->andReturn(TransactionCurrency::find(1));
    @@ -198,7 +198,7 @@ class ReconcileControllerTest extends TestCase
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::reconcile()
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::redirectToOriginalAccount()
          */
    -    public function testReconcileNotAsset()
    +    public function testReconcileNotAsset(): void
         {
             $account = $this->user()->accounts()->where('account_type_id', '!=', 6)->where('account_type_id', '!=', 3)->first();
             $this->be($this->user());
    @@ -209,7 +209,7 @@ class ReconcileControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::show()
          */
    -    public function testShow()
    +    public function testShow(): void
         {
             $journal    = $this->user()->transactionJournals()->where('transaction_type_id', 5)->first();
             $repository = $this->mock(JournalRepositoryInterface::class);
    @@ -227,7 +227,7 @@ class ReconcileControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::show()
          */
    -    public function testShowSomethingElse()
    +    public function testShowSomethingElse(): void
         {
             $journal = $this->user()->transactionJournals()->where('transaction_type_id', '!=', 5)->first();
             $this->be($this->user());
    @@ -240,7 +240,7 @@ class ReconcileControllerTest extends TestCase
          * @covers       \FireflyIII\Http\Controllers\Account\ReconcileController::submit()
          * @covers       \FireflyIII\Http\Requests\ReconciliationStoreRequest
          */
    -    public function testSubmit()
    +    public function testSubmit(): void
         {
             $repository   = $this->mock(AccountRepositoryInterface::class);
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -268,7 +268,7 @@ class ReconcileControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::transactions()
          */
    -    public function testTransactions()
    +    public function testTransactions(): void
         {
             $repository = $this->mock(CurrencyRepositoryInterface::class);
             $repository->shouldReceive('findNull')->once()->andReturn(TransactionCurrency::find(1));
    @@ -281,7 +281,7 @@ class ReconcileControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\Account\ReconcileController::transactions()
          */
    -    public function testTransactionsInitialBalance()
    +    public function testTransactionsInitialBalance(): void
         {
             $transaction = Transaction::leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
                                       ->where('accounts.user_id', $this->user()->id)->where('accounts.account_type_id', 6)->first(['account_id']);
    @@ -294,7 +294,7 @@ class ReconcileControllerTest extends TestCase
          * @covers       \FireflyIII\Http\Controllers\Account\ReconcileController::update
          * @covers       \FireflyIII\Http\Requests\ReconciliationUpdateRequest
          */
    -    public function testUpdate()
    +    public function testUpdate(): void
         {
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
             $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal);
    @@ -317,7 +317,7 @@ class ReconcileControllerTest extends TestCase
          * @covers       \FireflyIII\Http\Controllers\Account\ReconcileController::update
          * @covers       \FireflyIII\Http\Requests\ReconciliationUpdateRequest
          */
    -    public function testUpdateNotReconcile()
    +    public function testUpdateNotReconcile(): void
         {
             $journal = $this->user()->transactionJournals()->where('transaction_type_id', '!=', 5)->first();
             $data    = [
    @@ -334,7 +334,7 @@ class ReconcileControllerTest extends TestCase
          * @covers       \FireflyIII\Http\Controllers\Account\ReconcileController::update
          * @covers       \FireflyIII\Http\Requests\ReconciliationUpdateRequest
          */
    -    public function testUpdateZero()
    +    public function testUpdateZero(): void
         {
             $journal = $this->user()->transactionJournals()->where('transaction_type_id', 5)->first();
             $data    = [
    diff --git a/tests/Feature/Controllers/AccountControllerTest.php b/tests/Feature/Controllers/AccountControllerTest.php
    index 4357050385..290a766cc6 100644
    --- a/tests/Feature/Controllers/AccountControllerTest.php
    +++ b/tests/Feature/Controllers/AccountControllerTest.php
    @@ -64,7 +64,7 @@ class AccountControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\AccountController::create
          */
    -    public function testCreate()
    +    public function testCreate(): void
         {
             // mock stuff
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -84,7 +84,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Http\Controllers\AccountController::delete
          * @covers \FireflyIII\Http\Controllers\Controller::rememberPreviousUri
          */
    -    public function testDelete()
    +    public function testDelete(): void
         {
             // mock stuff
             $journalRepos  = $this->mock(JournalRepositoryInterface::class);
    @@ -106,7 +106,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Http\Controllers\Controller::__construct
          * @covers \FireflyIII\Http\Controllers\Controller::getPreviousUri
          */
    -    public function testDestroy()
    +    public function testDestroy(): void
         {
             // mock stuff
             $journalRepos  = $this->mock(JournalRepositoryInterface::class);
    @@ -128,7 +128,7 @@ class AccountControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\AccountController::edit
          */
    -    public function testEdit()
    +    public function testEdit(): void
         {
             $note       = new Note();
             $note->text = 'This is a test';
    @@ -168,7 +168,7 @@ class AccountControllerTest extends TestCase
          * @param string $range
          *
          */
    -    public function testIndex(string $range)
    +    public function testIndex(string $range): void
         {
             // mock stuff
             $account       = factory(Account::class)->make();
    @@ -199,7 +199,7 @@ class AccountControllerTest extends TestCase
          *
          * @param string $range
          */
    -    public function testShow(string $range)
    +    public function testShow(string $range): void
         {
             $date = new Carbon;
             $this->session(['start' => $date, 'end' => clone $date]);
    @@ -243,7 +243,7 @@ class AccountControllerTest extends TestCase
          * @covers                   \FireflyIII\Http\Controllers\AccountController::show
          * @expectedExceptionMessage End is after start!
          */
    -    public function testShowBrokenBadDates()
    +    public function testShowBrokenBadDates(): void
         {
             // mock
             $journalRepos = $this->mock(JournalRepositoryInterface::class);
    @@ -262,7 +262,7 @@ class AccountControllerTest extends TestCase
          * @covers                   \FireflyIII\Http\Controllers\AccountController::redirectToOriginalAccount
          * @expectedExceptionMessage Expected a transaction
          */
    -    public function testShowBrokenInitial()
    +    public function testShowBrokenInitial(): void
         {
             // mock
             $journalRepos  = $this->mock(JournalRepositoryInterface::class);
    @@ -283,7 +283,7 @@ class AccountControllerTest extends TestCase
          *
          * @param string $range
          */
    -    public function testShowByDateEmpty(string $range)
    +    public function testShowByDateEmpty(string $range): void
         {
             // mock stuff
             $collector     = $this->mock(JournalCollectorInterface::class);
    @@ -318,7 +318,7 @@ class AccountControllerTest extends TestCase
          * @covers       \FireflyIII\Http\Controllers\AccountController::show
          * @covers       \FireflyIII\Http\Controllers\AccountController::redirectToOriginalAccount
          */
    -    public function testShowInitial()
    +    public function testShowInitial(): void
         {
             // mock stuff
             $journalRepos  = $this->mock(JournalRepositoryInterface::class);
    @@ -339,7 +339,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Http\Requests\AccountFormRequest
          * @covers \FireflyIII\Http\Controllers\Controller::getPreviousUri
          */
    -    public function testStore()
    +    public function testStore(): void
         {
             // mock stuff
             $journalRepos  = $this->mock(JournalRepositoryInterface::class);
    @@ -368,7 +368,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Http\Requests\AccountFormRequest
          * @covers \FireflyIII\Http\Controllers\Controller::getPreviousUri
          */
    -    public function testStoreAnother()
    +    public function testStoreAnother(): void
         {
             // mock stuff
             $journalRepos  = $this->mock(JournalRepositoryInterface::class);
    @@ -395,7 +395,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Http\Requests\AccountFormRequest
          * @covers \FireflyIII\Http\Controllers\Controller::getPreviousUri
          */
    -    public function testUpdate()
    +    public function testUpdate(): void
         {
             // mock stuff
             $journalRepos  = $this->mock(JournalRepositoryInterface::class);
    @@ -422,7 +422,7 @@ class AccountControllerTest extends TestCase
          * @covers \FireflyIII\Http\Requests\AccountFormRequest
          * @covers \FireflyIII\Http\Controllers\Controller::getPreviousUri
          */
    -    public function testUpdateAgain()
    +    public function testUpdateAgain(): void
         {
             // mock stuff
             $journalRepos  = $this->mock(JournalRepositoryInterface::class);
    diff --git a/tests/Feature/Controllers/Admin/ConfigurationControllerTest.php b/tests/Feature/Controllers/Admin/ConfigurationControllerTest.php
    index ad63174d66..0c2364dc6a 100644
    --- a/tests/Feature/Controllers/Admin/ConfigurationControllerTest.php
    +++ b/tests/Feature/Controllers/Admin/ConfigurationControllerTest.php
    @@ -49,7 +49,7 @@ class ConfigurationControllerTest extends TestCase
          * @covers \FireflyIII\Http\Controllers\Admin\ConfigurationController::index
          * @covers \FireflyIII\Http\Controllers\Admin\ConfigurationController::__construct
          */
    -    public function testIndex()
    +    public function testIndex(): void
         {
             $this->be($this->user());
     
    @@ -72,7 +72,7 @@ class ConfigurationControllerTest extends TestCase
         /**
          * @covers \FireflyIII\Http\Controllers\Admin\ConfigurationController::postIndex
          */
    -    public function testPostIndex()
    +    public function testPostIndex(): void
         {
             $falseConfig       = new Configuration;
             $falseConfig->data = false;
    diff --git a/tests/Feature/Controllers/Admin/HomeControllerTest.php b/tests/Feature/Controllers/Admin/HomeControllerTest.php
    index 74f94d125b..9d02ab892e 100644
    --- a/tests/Feature/Controllers/Admin/HomeControllerTest.php
    +++ b/tests/Feature/Controllers/Admin/HomeControllerTest.php
    @@ -49,7 +49,7 @@ class HomeControllerTest extends TestCase
          * @covers \FireflyIII\Http\Controllers\Admin\HomeController::index
          * @covers \FireflyIII\Http\Controllers\Admin\HomeController::__construct
          */
    -    public function testIndex()
    +    public function testIndex(): void
         {
             $this->be($this->user());
             $response = $this->get(route('admin.index'));
    @@ -58,7 +58,7 @@ class HomeControllerTest extends TestCase
             $response->assertSee('
    + @@ -79,6 +80,11 @@ {{ formatAmountBySymbol(piggy.left_to_save,piggy.currency_symbol,piggy.currency_dp) }} {% endif %} + {% endfor %} diff --git a/resources/views/piggy-banks/show.twig b/resources/views/piggy-banks/show.twig index 5aa3e4e5a1..6dc2addc62 100644 --- a/resources/views/piggy-banks/show.twig +++ b/resources/views/piggy-banks/show.twig @@ -26,8 +26,8 @@ @@ -40,15 +40,21 @@ - + - + - + @@ -74,7 +80,8 @@ {% endif %} From 551ff109c98eb904ee527319d94b91aabe47fe96 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 26 May 2018 07:53:32 +0200 Subject: [PATCH 125/182] Fixed #1403 --- app/Http/Controllers/BillController.php | 1 + app/Models/Bill.php | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index 8fcb18777f..bbb5a96234 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -164,6 +164,7 @@ class BillController extends Controller $preFilled = [ 'notes' => $this->billRepository->getNoteText($bill), 'transaction_currency_id' => $bill->transaction_currency_id, + 'active' => $bill->active, ]; $request->session()->flash('preFilled', $preFilled); diff --git a/app/Models/Bill.php b/app/Models/Bill.php index e42126e39d..bf4c01ff12 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -29,10 +29,14 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\Attachment; /** * Class Bill. + * + * @property bool $active + * @property int $transaction_currency_id + * @property string $amount_min + * @property string $amount_max */ class Bill extends Model { From 039e3aa34c18c722432896973a49f040bb54c531 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 26 May 2018 07:55:31 +0200 Subject: [PATCH 126/182] Fix for #1406 --- app/Console/Commands/UpgradeDatabase.php | 71 +++++++++++++++--------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 9ad64d700d..c4f31d21d2 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -161,28 +161,41 @@ class UpgradeDatabase extends Command 'order' => 2, ] ); - - // add triggers for amounts: - RuleTrigger::create( - [ - 'rule_id' => $rule->id, - 'trigger_type' => 'amount_less', - 'trigger_value' => round($bill->amount_max, $currency->decimal_places), - 'active' => 1, - 'stop_processing' => 0, - 'order' => 3, - ] - ); - RuleTrigger::create( - [ - 'rule_id' => $rule->id, - 'trigger_type' => 'amount_more', - 'trigger_value' => round($bill->amount_min, $currency->decimal_places), - 'active' => 1, - 'stop_processing' => 0, - 'order' => 4, - ] - ); + if ($bill->amount_max !== $bill->amount_min) { + // add triggers for amounts: + RuleTrigger::create( + [ + 'rule_id' => $rule->id, + 'trigger_type' => 'amount_less', + 'trigger_value' => round($bill->amount_max, $currency->decimal_places), + 'active' => 1, + 'stop_processing' => 0, + 'order' => 3, + ] + ); + RuleTrigger::create( + [ + 'rule_id' => $rule->id, + 'trigger_type' => 'amount_more', + 'trigger_value' => round($bill->amount_min, $currency->decimal_places), + 'active' => 1, + 'stop_processing' => 0, + 'order' => 4, + ] + ); + } + if($bill->amount_max === $bill->amount_min) { + RuleTrigger::create( + [ + 'rule_id' => $rule->id, + 'trigger_type' => 'amount_exactly', + 'trigger_value' => round($bill->amount_min, $currency->decimal_places), + 'active' => 1, + 'stop_processing' => 0, + 'order' => 3, + ] + ); + } // create action RuleAction::create( @@ -602,12 +615,16 @@ class UpgradeDatabase extends Command $opposing->transaction_currency_id = $currency->id; $transaction->save(); $opposing->save(); - Log::debug(sprintf('Currency for account "%s" is %s, and currency for account "%s" is also + Log::debug( + sprintf( + 'Currency for account "%s" is %s, and currency for account "%s" is also %s, so %s #%d (#%d and #%d) has been verified to be to %s exclusively.', - $opposing->account->name, $opposingCurrency->code, - $transaction->account->name, $transaction->transactionCurrency->code, - $journal->transactionType->type, $journal->id, - $transaction->id, $opposing->id, $currency->code)); + $opposing->account->name, $opposingCurrency->code, + $transaction->account->name, $transaction->transactionCurrency->code, + $journal->transactionType->type, $journal->id, + $transaction->id, $opposing->id, $currency->code + ) + ); return; } From b8bc8e2c4743fee6013424a4efd58c5691a9313f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 26 May 2018 07:58:36 +0200 Subject: [PATCH 127/182] Fix #1413 --- resources/lang/en_US/firefly.php | 1 + resources/views/new-user/index.twig | 3 +++ 2 files changed, 4 insertions(+) diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 35cf2184d8..bd4b5d4bd1 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -820,6 +820,7 @@ return [ 'language' => 'Language', 'new_savings_account' => ':bank_name savings account', 'cash_wallet' => 'Cash wallet', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Your accounts', diff --git a/resources/views/new-user/index.twig b/resources/views/new-user/index.twig index 3f7022e34a..9b5dbbcd6d 100644 --- a/resources/views/new-user/index.twig +++ b/resources/views/new-user/index.twig @@ -26,6 +26,9 @@ {{ ExpandedForm.text('bank_name') }} {{ ExpandedForm.balance('bank_balance') }} +

    + {{ 'currency_not_present'|_ }} +

    {{ 'savings_balance_text'|_ }} From 9b6766d3b2239789d91fbea60f9e98375558457f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 26 May 2018 08:04:50 +0200 Subject: [PATCH 128/182] Fix #1416 --- app/Repositories/Attachment/AttachmentRepository.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Repositories/Attachment/AttachmentRepository.php b/app/Repositories/Attachment/AttachmentRepository.php index 23cf6e9fad..36d38ade9d 100644 --- a/app/Repositories/Attachment/AttachmentRepository.php +++ b/app/Repositories/Attachment/AttachmentRepository.php @@ -24,6 +24,7 @@ namespace FireflyIII\Repositories\Attachment; use Carbon\Carbon; use Crypt; +use Exception; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Models\Attachment; use FireflyIII\Models\Note; @@ -52,7 +53,11 @@ class AttachmentRepository implements AttachmentRepositoryInterface $helper = app(AttachmentHelperInterface::class); $file = $helper->getAttachmentLocation($attachment); - unlink($file); + try { + unlink($file); + } catch (Exception $e) { + Log::error(sprintf('Could not delete file for attachment %d.', $attachment->id)); + } $attachment->delete(); return true; From dcfea2097381e7972f704577418f729fd6f57ab6 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 26 May 2018 13:14:51 +0200 Subject: [PATCH 129/182] Expand error logs. --- app/Exceptions/Handler.php | 6 ++++++ resources/views/emails/error-html.twig | 4 +++- resources/views/emails/error-text.twig | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 45570ef71a..020f131bea 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -128,14 +128,17 @@ class Handler extends ExceptionHandler */ public function report(Exception $exception) { + $doMailError = env('SEND_ERROR_MESSAGE', true); if ( ( $exception instanceof FireflyException || $exception instanceof ErrorException || $exception instanceof OAuthServerException + || $exception instanceof AuthenticationException ) && $doMailError) { + $userData = [ 'id' => 0, 'email' => 'unknown@example.com', @@ -153,6 +156,9 @@ class Handler extends ExceptionHandler 'line' => $exception->getLine(), 'code' => $exception->getCode(), 'version' => config('firefly.version'), + 'url' => Request::fullUrl(), + 'userAgent' => Request::userAgent(), + 'json' => Request::acceptsJson(), ]; // create job that will mail. diff --git a/resources/views/emails/error-html.twig b/resources/views/emails/error-html.twig index f3126c63e5..0e0f2004f0 100644 --- a/resources/views/emails/error-html.twig +++ b/resources/views/emails/error-html.twig @@ -24,7 +24,9 @@

    - The IP address related to this error is: {{ ip }} + The IP address related to this error is: {{ ip }}
    + URL is: {{ url }}
    + User agent: {{ userAgent }}

    diff --git a/resources/views/emails/error-text.twig b/resources/views/emails/error-text.twig index 56d4ea7a39..6cc100d81a 100644 --- a/resources/views/emails/error-text.twig +++ b/resources/views/emails/error-text.twig @@ -14,6 +14,8 @@ This error occured in file "{{ file }}" on line {{ line }} with code {{ code }}. {% endif %} The IP address related to this error is: {{ ip }} +URL is: {{ url }} +User agent: {{ userAgent }} The full stacktrace is below. If you think this is a bug in Firefly III, you can forward this message to thegrumpydictator@gmail.com. This can help fix From 73aef1b9a45595aa5ca1959beeeb088346d06b72 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 26 May 2018 13:55:11 +0200 Subject: [PATCH 130/182] Code for #1415 --- app/Handlers/Events/APIEventHandler.php | 80 +++++++++++++++++++ app/Mail/AccessTokenCreatedMail.php | 68 ++++++++++++++++ app/Mail/ConfirmEmailChangeMail.php | 2 +- app/Mail/OAuthTokenCreatedMail.php | 70 ++++++++++++++++ app/Mail/UndoEmailChangeMail.php | 2 +- app/Providers/EventServiceProvider.php | 53 +++++++++--- app/User.php | 1 + .../emails/access-token-created-html.twig | 13 +++ .../emails/access-token-created-text.twig | 7 ++ .../emails/oauth-client-created-html.twig | 14 ++++ .../emails/oauth-client-created-text.twig | 9 +++ 11 files changed, 308 insertions(+), 11 deletions(-) create mode 100644 app/Handlers/Events/APIEventHandler.php create mode 100644 app/Mail/AccessTokenCreatedMail.php create mode 100644 app/Mail/OAuthTokenCreatedMail.php create mode 100644 resources/views/emails/access-token-created-html.twig create mode 100644 resources/views/emails/access-token-created-text.twig create mode 100644 resources/views/emails/oauth-client-created-html.twig create mode 100644 resources/views/emails/oauth-client-created-text.twig diff --git a/app/Handlers/Events/APIEventHandler.php b/app/Handlers/Events/APIEventHandler.php new file mode 100644 index 0000000000..64aa286420 --- /dev/null +++ b/app/Handlers/Events/APIEventHandler.php @@ -0,0 +1,80 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Handlers\Events; + + +use Exception; +use FireflyIII\Mail\AccessTokenCreatedMail; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use Laravel\Passport\Events\AccessTokenCreated; +use Laravel\Passport\Token; +use Log; +use Mail; +use Request; +use Session; + +/** + * Class APIEventHandler + */ +class APIEventHandler +{ + /** + * @param AccessTokenCreated $event + * + * @return bool + */ + public function accessTokenCreated(AccessTokenCreated $event): bool + { + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + $user = $repository->findNull((int)$event->userId); + if (null === $user) { + Log::error('Access Token generated but no user associated.'); + + return true; + } + + $email = $user->email; + $ipAddress = Request::ip(); + + Log::debug(sprintf('Now in APIEventHandler::accessTokenCreated. Email is %s, IP is %s', $email, $ipAddress)); + try { + Log::debug('Trying to send message...'); + Mail::to($email)->send(new AccessTokenCreatedMail($email, $ipAddress)); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::debug('Send message failed! :('); + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + Session::flash('error', 'Possible email error: ' . $e->getMessage()); + } + Log::debug('If no error above this line, message was sent.'); + + // @codeCoverageIgnoreEnd + return true; + + + } + +} \ No newline at end of file diff --git a/app/Mail/AccessTokenCreatedMail.php b/app/Mail/AccessTokenCreatedMail.php new file mode 100644 index 0000000000..6e0e43e8a4 --- /dev/null +++ b/app/Mail/AccessTokenCreatedMail.php @@ -0,0 +1,68 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Mail; + + +use Illuminate\Bus\Queueable; +use Illuminate\Mail\Mailable; +use Illuminate\Queue\SerializesModels; +use Laravel\Passport\Token; + +/** + * Class AccessTokenCreatedMail + */ +class AccessTokenCreatedMail extends Mailable +{ + + use Queueable, SerializesModels; + + /** @var string Email address of admin */ + public $email; + /** @var string IP address of admin */ + public $ipAddress; + + /** + * AccessTokenCreatedMail constructor. + * + * @param string $email + * @param string $ipAddress + * @param Token $token + */ + public function __construct(string $email, string $ipAddress) + { + $this->email = $email; + $this->ipAddress = $ipAddress; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + return $this->view('emails.access-token-created-html')->text('emails.access-token-created-text') + ->subject('A new access token was created'); + } +} \ No newline at end of file diff --git a/app/Mail/ConfirmEmailChangeMail.php b/app/Mail/ConfirmEmailChangeMail.php index 4a0e5dd1cd..9289671ea9 100644 --- a/app/Mail/ConfirmEmailChangeMail.php +++ b/app/Mail/ConfirmEmailChangeMail.php @@ -68,6 +68,6 @@ class ConfirmEmailChangeMail extends Mailable public function build() { return $this->view('emails.confirm-email-change-html')->text('emails.confirm-email-change-text') - ->subject('Your Firefly III email address has changed.'); + ->subject('Your Firefly III email address has changed'); } } diff --git a/app/Mail/OAuthTokenCreatedMail.php b/app/Mail/OAuthTokenCreatedMail.php new file mode 100644 index 0000000000..5036c1225d --- /dev/null +++ b/app/Mail/OAuthTokenCreatedMail.php @@ -0,0 +1,70 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Mail; + +use Illuminate\Bus\Queueable; +use Illuminate\Mail\Mailable; +use Illuminate\Queue\SerializesModels; +use Laravel\Passport\Client; + + +/** + * Class OAuthTokenCreatedMail + */ +class OAuthTokenCreatedMail extends Mailable +{ + use Queueable, SerializesModels; + + /** @var Client The client */ + public $client; + /** @var string Email address of admin */ + public $email; + /** @var string IP address of admin */ + public $ipAddress; + + /** + * OAuthTokenCreatedMail constructor. + * + * @param string $email + * @param string $ipAddress + * @param Client $client + */ + public function __construct(string $email, string $ipAddress, Client $client) + { + $this->email = $email; + $this->ipAddress = $ipAddress; + $this->client = $client; + } + + /** + * Build the message. + * + * @return $this + */ + public function build(): self + { + return $this->view('emails.oauth-client-created-html')->text('emails.oauth-client-created-text') + ->subject('A new OAuth client has been created'); + } +} \ No newline at end of file diff --git a/app/Mail/UndoEmailChangeMail.php b/app/Mail/UndoEmailChangeMail.php index 94a6b5b5ab..a47d6ee9f4 100644 --- a/app/Mail/UndoEmailChangeMail.php +++ b/app/Mail/UndoEmailChangeMail.php @@ -66,6 +66,6 @@ class UndoEmailChangeMail extends Mailable public function build() { return $this->view('emails.undo-email-change-html')->text('emails.undo-email-change-text') - ->subject('Your Firefly III email address has changed.'); + ->subject('Your Firefly III email address has changed'); } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 75b5dc0921..352e357d22 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Providers; +use Exception; use FireflyIII\Events\AdminRequestedTestMessage; use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; @@ -29,10 +30,18 @@ use FireflyIII\Events\RequestedVersionCheckStatus; use FireflyIII\Events\StoredTransactionJournal; use FireflyIII\Events\UpdatedTransactionJournal; use FireflyIII\Events\UserChangedEmail; +use FireflyIII\Mail\OAuthTokenCreatedMail; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankRepetition; +use FireflyIII\Repositories\User\UserRepositoryInterface; use Illuminate\Auth\Events\Login; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; +use Laravel\Passport\Client; +use Laravel\Passport\Events\AccessTokenCreated; +use Log; +use Mail; +use Request; +use Session; /** * Class EventServiceProvider. @@ -82,6 +91,10 @@ class EventServiceProvider extends ServiceProvider UpdatedTransactionJournal::class => [ 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@processRules', ], + // API related events: + AccessTokenCreated::class => [ + 'FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated', + ], ]; /** @@ -91,14 +104,13 @@ class EventServiceProvider extends ServiceProvider public function boot() { parent::boot(); - $this->registerDeleteEvents(); $this->registerCreateEvents(); } /** * */ - protected function registerCreateEvents() + protected function registerCreateEvents(): void { // move this routine to a filter // in case of repeated piggy banks and/or other problems. @@ -112,13 +124,36 @@ class EventServiceProvider extends ServiceProvider $repetition->save(); } ); + Client::created( + function (Client $oauthClient) { + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + $user = $repository->findNull((int)$oauthClient->user_id); + if (null === $user) { + Log::error('OAuth client generated but no user associated.'); + + return; + } + + $email = $user->email; + $ipAddress = Request::ip(); + + Log::debug(sprintf('Now in EventServiceProvider::registerCreateEvents. Email is %s, IP is %s', $email, $ipAddress)); + try { + Log::debug('Trying to send message...'); + Mail::to($email)->send(new OAuthTokenCreatedMail($email, $ipAddress, $oauthClient)); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::debug('Send message failed! :('); + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + Session::flash('error', 'Possible email error: ' . $e->getMessage()); + } + Log::debug('If no error above this line, message was sent.'); + + + } + ); } - /** - * - */ - protected function registerDeleteEvents() - { - - } } diff --git a/app/User.php b/app/User.php index b52ef745cb..3e3150fd13 100644 --- a/app/User.php +++ b/app/User.php @@ -56,6 +56,7 @@ use FireflyIII\Models\Attachment; /** * Class User. * @property int $id + * @property string $email */ class User extends Authenticatable { diff --git a/resources/views/emails/access-token-created-html.twig b/resources/views/emails/access-token-created-html.twig new file mode 100644 index 0000000000..1c88099a1d --- /dev/null +++ b/resources/views/emails/access-token-created-html.twig @@ -0,0 +1,13 @@ +{% include 'emails.header-html' %} +

    + Somebody (hopefully you) just created a new Firefly III API Access Token for your user account. +

    + +

    + With this token, they can access all of your financial records through the Firefly III API. +

    + +

    + If this wasn't you, please revoke this token as soon as possible at {{ route('profile.index') }}. +

    +{% include 'emails.footer-html' %} diff --git a/resources/views/emails/access-token-created-text.twig b/resources/views/emails/access-token-created-text.twig new file mode 100644 index 0000000000..5ea41eb0fe --- /dev/null +++ b/resources/views/emails/access-token-created-text.twig @@ -0,0 +1,7 @@ +{% include 'emails.header-text' %} +Somebody (hopefully you) just created a new Firefly III API Access Token for your user account. + +With this token, they can access all of your financial records through the Firefly III API. + +If this wasn't you, please revoke this token as soon as possible at {{ route('profile.index') }}. +{% include 'emails.footer-text' %} diff --git a/resources/views/emails/oauth-client-created-html.twig b/resources/views/emails/oauth-client-created-html.twig new file mode 100644 index 0000000000..f49cd54b34 --- /dev/null +++ b/resources/views/emails/oauth-client-created-html.twig @@ -0,0 +1,14 @@ +{% include 'emails.header-html' %} +

    + Somebody (hopefully you) just created a new Firefly III API OAuth Client for your user account. It's labeled "{{ client.name }}" + and has callback URL {{ client.redirect }}. +

    + +

    + With this client, they can access all of your financial records through the Firefly III API. +

    + +

    + If this wasn't you, please revoke this client as soon as possible at {{ route('profile.index') }}. +

    +{% include 'emails.footer-html' %} diff --git a/resources/views/emails/oauth-client-created-text.twig b/resources/views/emails/oauth-client-created-text.twig new file mode 100644 index 0000000000..d74222d0f4 --- /dev/null +++ b/resources/views/emails/oauth-client-created-text.twig @@ -0,0 +1,9 @@ +{% include 'emails.header-text' %} +Somebody (hopefully you) just created a new Firefly III API OAuth Client for your user account. It's labeled "{{ client.name }}" and has callback URL: + +{{ client.redirect }} + +With this client, they can access all of your financial records through the Firefly III API. + +If this wasn't you, please revoke this client as soon as possible at {{ route('profile.index') }}. +{% include 'emails.footer-text' %} From 4ad68b7dfa3c00ceb38149e4dbeb8072ddec6b66 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 28 May 2018 19:07:06 +0200 Subject: [PATCH 131/182] Fix #1449 --- app/Http/Controllers/BudgetController.php | 35 +++++++++++++++-------- resources/views/budgets/index.twig | 2 +- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 3ab8154910..c1b6faa275 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -87,6 +87,8 @@ class BudgetController extends Controller $budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount); $largeDiff = false; $warnText = ''; + $days = 0; + $daysInMonth = 0; if (0 === bccomp($amount, '0')) { $budgetLimit = null; } @@ -95,12 +97,15 @@ class BudgetController extends Controller // otherwise, use diff between start and end. $today = new Carbon; if ($today->gte($start) && $today->lte($end)) { - $days = $end->diffInDays($today); + $days = $end->diffInDays($today); + $daysInMonth = $start->diffInDays($today); } if ($today->lte($start) || $today->gte($end)) { - $days = $start->diffInDays($end); + $days = $start->diffInDays($end); + $daysInMonth = $start->diffInDays($end); } - $days = $days === 0 ? 1 : $days; + $days = $days === 0 ? 1 : $days; + $daysInMonth = $daysInMonth === 0 ? 1 : $daysInMonth; // calculate left in budget: $spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end); @@ -146,6 +151,7 @@ class BudgetController extends Controller 'large_diff' => $largeDiff, 'left_per_day' => $leftPerDay, 'warn_text' => $warnText, + 'daysInMonth' => $daysInMonth, ] ); @@ -229,22 +235,27 @@ class BudgetController extends Controller */ public function index(Request $request, string $moment = null) { - $range = Preferences::get('viewRange', '1M')->data; - $start = session('start', new Carbon); - $end = session('end', new Carbon); - $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); - $pageSize = (int)Preferences::get('listPageSize', 50)->data; + $range = Preferences::get('viewRange', '1M')->data; + $start = session('start', new Carbon); + $end = session('end', new Carbon); + $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); + $pageSize = (int)Preferences::get('listPageSize', 50)->data; + $days = 0; + $daysInMonth = 0; // if today is between start and end, use the diff in days between end and today (days left) // otherwise, use diff between start and end. $today = new Carbon; if ($today->gte($start) && $today->lte($end)) { - $days = $end->diffInDays($today); + $days = $end->diffInDays($today); + $daysInMonth = $start->diffInDays($today); } if ($today->lte($start) || $today->gte($end)) { - $days = $start->diffInDays($end); + $days = $start->diffInDays($end); + $daysInMonth = $start->diffInDays($end); } - $days = $days === 0 ? 1 : $days; + $days = $days === 0 ? 1 : $days; + $daysInMonth = $daysInMonth === 0 ? 1 : $daysInMonth; // make date if present: if (null !== $moment || '' !== (string)$moment) { @@ -312,7 +323,7 @@ class BudgetController extends Controller return view( 'budgets.index', compact( 'available', 'currentMonth', 'next', 'nextText', 'prev', 'allBudgets', 'prevText', 'periodStart', 'periodEnd', 'days', 'page', - 'budgetInformation', + 'budgetInformation', 'daysInMonth', 'inactive', 'budgets', 'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start', 'end' ) ); diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index 71dad53b7e..476199250a 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -188,7 +188,7 @@
    - {% for journal in journals %} + {% for transaction in transactions %} {# AMOUNT #} {# category #} {# budget #} From 1962f74439ce141cbfbbffc0b220a2ce437577d4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 14:20:17 +0200 Subject: [PATCH 158/182] Update German language files [skip ci] --- resources/lang/de_DE/auth.php | 16 +- resources/lang/de_DE/bank.php | 5 +- resources/lang/de_DE/breadcrumbs.php | 5 +- resources/lang/de_DE/components.php | 3 +- resources/lang/de_DE/config.php | 5 +- resources/lang/de_DE/csv.php | 5 +- resources/lang/de_DE/demo.php | 7 +- resources/lang/de_DE/firefly.php | 32 +-- resources/lang/de_DE/form.php | 12 +- resources/lang/de_DE/import.php | 313 ++++++++++++++++----------- resources/lang/de_DE/intro.php | 5 +- resources/lang/de_DE/list.php | 10 +- resources/lang/de_DE/pagination.php | 5 +- resources/lang/de_DE/passwords.php | 5 +- resources/lang/de_DE/validation.php | 8 +- 15 files changed, 249 insertions(+), 187 deletions(-) diff --git a/resources/lang/de_DE/auth.php b/resources/lang/de_DE/auth.php index 89e83d4fc7..5973e3031e 100644 --- a/resources/lang/de_DE/auth.php +++ b/resources/lang/de_DE/auth.php @@ -1,9 +1,8 @@ . */ -return [ - /* - |-------------------------------------------------------------------------- - | Authentication Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used during authentication for various - | messages that we need to display to the user. You are free to modify - | these language lines according to your application's requirements. - | - */ +declare(strict_types=1); +return [ 'failed' => 'Falscher Benutzername und/oder falsches Passwort.', 'throttle' => 'Zu viele Anmeldeversuche. Bitte versuchen Sie es in :seconds Sekunden erneut.', ]; diff --git a/resources/lang/de_DE/bank.php b/resources/lang/de_DE/bank.php index 8826020cbf..5d00b1e685 100644 --- a/resources/lang/de_DE/bank.php +++ b/resources/lang/de_DE/bank.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/de_DE/breadcrumbs.php b/resources/lang/de_DE/breadcrumbs.php index 89da61a42c..777321a6f0 100644 --- a/resources/lang/de_DE/breadcrumbs.php +++ b/resources/lang/de_DE/breadcrumbs.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'home' => 'Startseite', 'edit_currency' => 'Währung „:name” bearbeiten', diff --git a/resources/lang/de_DE/components.php b/resources/lang/de_DE/components.php index 27cc4cdd9b..eab77f0c4f 100644 --- a/resources/lang/de_DE/components.php +++ b/resources/lang/de_DE/components.php @@ -1,5 +1,4 @@ . */ +declare(strict_types=1); + return [ // profile 'personal_access_tokens' => 'Persönliche Zugangs-Authentifizierungsschlüssel', diff --git a/resources/lang/de_DE/config.php b/resources/lang/de_DE/config.php index 51bb831575..a42da571da 100644 --- a/resources/lang/de_DE/config.php +++ b/resources/lang/de_DE/config.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'html_language' => 'de', 'locale' => 'de, Deutsch, de_DE, de_DE.utf8, de_DE.UTF-8', diff --git a/resources/lang/de_DE/csv.php b/resources/lang/de_DE/csv.php index 622a49202a..aae109a40a 100644 --- a/resources/lang/de_DE/csv.php +++ b/resources/lang/de_DE/csv.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/de_DE/demo.php b/resources/lang/de_DE/demo.php index d0dfca4076..479be2661f 100644 --- a/resources/lang/de_DE/demo.php +++ b/resources/lang/de_DE/demo.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'no_demo_text' => 'Leider gibt es keine zusätzlichen Demo-Erklärungen für diese Seite.', 'see_help_icon' => 'Vielleicht erfahren Sie mehr über das -Icon in der oberen rechten Ecke.', @@ -32,5 +33,5 @@ return [ 'currencies-index' => 'Firefly III kann mit Zahlungen in unterschiedlichen Währungen umgeben. Obwohl es den Euro standardmäßig nutzt, ist es möglich, US-Dollar oder andere Währungen zu verwenden. Eine kleine Auswahl an Währungen wird bereitgestellt, Sie können jedoch weitere hinzufügen. Eine Veränderung der Standardwährung verändert keine bestehende Buchungen. Firefly III unterstützt die Verwendung mehrerer Währungen zur gleichen Zeit.', 'transactions-index' => 'Diese Ausgaben, Einnahmen und Umbuchungen sind nicht besonders einfallsreich. Sie wurden automatisch generiert.', 'piggy-banks-index' => 'Hier wurden bereits drei Sparschweine angelegt. Der Betrag in den Sparschweinen kann über die Plus-/Minus-Buttons angepasst werden. Klicken Sie auf den Namen des Sparschweins um weitere Informationen einzusehen.', - 'import-index' => 'Natürlich kann jede CSV-Datei in Firefly III importiert werden', + 'import-index' => 'Jede CSV-Datei kann in Firefly III importiert werden. Es wird auch der Import von Daten aus Bunq und Spectre unterstützt. Weitere Banken und Finanzaggregatoren werden in Zukunft implementiert. Als Demo-Anwender können Sie jedoch nur einen „Schein”-Anbieter in Aktion erleben. Es werden einige zufällige Transaktionen generiert, um Ihnen zu zeigen, wie der Prozess funktioniert.', ]; diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index 847652b6f3..56429a99ac 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // general stuff: 'close' => 'Schließen', @@ -179,6 +180,7 @@ return [ 'authorization_request_intro' => ':client bittet um Erlaubnis, auf Ihre Finanzverwaltung zuzugreifen. Möchten Sie :client erlauben auf diese Datensätze zuzugreifen?', 'scopes_will_be_able' => 'Diese Anwendung kann:', 'button_authorize' => 'Erlauben', + 'none_in_select_list' => '(Keine)', // check for updates: 'update_check_title' => 'Nach Updates suchen', @@ -667,6 +669,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'bill_will_automatch' => 'Rechnung wird automatisch mit passenden Buchungen verknüpft', 'skips_over' => 'überschreitet', 'bill_store_error' => 'Beim Speichern Ihrer neuen Rechnung ist ein unerwarteter Fehler aufgetreten. Bitte überprüfen Sie die Protokolldateien.', + 'list_inactive_rule' => 'Inaktive Regeln', // accounts: 'details_for_asset' => 'Informationen zum Bestandskonto „:name”', @@ -802,6 +805,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'opt_group_savingAsset' => 'Sparkonten', 'opt_group_sharedAsset' => 'Gemeinsame Bestandskonten', 'opt_group_ccAsset' => 'Kreditkarten', + 'opt_group_cashWalletAsset' => 'Geldbörsen', 'notes' => 'Notizen', 'unknown_journal_error' => 'Die Buchung konnte nicht gespeichert werden. Bitte überprüfen Sie die Protokolldateien.', @@ -817,6 +821,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'language' => 'Sprache', 'new_savings_account' => ':bank_name-Sparkonto', 'cash_wallet' => 'Geldbörse', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Deine Konten', @@ -1014,6 +1019,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'remove_money_from_piggy_title' => 'Geld dem Sparschwein „:name” entnehmen', 'add' => 'Hinzufügen', 'no_money_for_piggy' => 'Sie haben kein Geld, welches Sie in dieses Sparschwein geben können.', + 'suggested_savings_per_month' => 'Suggested per month', 'remove' => 'Entfernen', 'max_amount_add' => 'Der maximale Betrag, den Sie hinzufügen können ist', @@ -1149,27 +1155,9 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'no_edit_multiple_left' => 'Sie haben keine gültigen Buchungen zur Bearbeitung ausgewählt.', 'cannot_convert_split_journal' => 'Eine Splitbuchung konnte nicht umgesetzt werden', - // import bread crumbs and titles: - 'import' => 'Importieren', - 'import_data' => 'Daten importieren', - 'import_general_index_file' => 'Datei importieren', - 'import_from_bunq' => 'Von „bunq” importieren', - 'import_using_spectre' => 'Import mit Spectre', - 'import_using_plaid' => 'Import mit Plaid', - 'import_config_bread_crumb' => 'Import einrichten', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Daten in Firefly III importieren', - 'import_index_sub_title' => 'Index', - 'import_general_index_intro' => 'Willkommen beim Importassistenten von Firefly III. Es gibt einige Möglichkeiten, Daten in Firefly III zu importieren, die hier als Schaltflächen angezeigt werden.', - 'upload_error' => 'Die hochgeladene Datei konnte nicht verarbeitet werden. Möglicherweise handelt es sich um einen ungültigen Dateityp oder eine ungültige Kodierung. Die Protokolldateien enthalten weitere Informationen.', - 'reset_import_settings_title' => 'Importeinstellungen zurücksetzen', - 'reset_import_settings_text' => 'Über diese Links können Sie Ihre Importeinstellungen für bestimmte Anbieter zurücksetzen. Dies ist nützlich, wenn fehlerhafte Einstellungen den Import von Daten verhindern.', - 'reset_settings_bunq' => 'Bunq-API-Schlüssel entfernen (lokale externe IP-Adresse und Bunq-bezogene RSA-Schlüssel).', - 'reset_settings_spectre' => '„Spectre”-Geheimnisse und -IDs entfernen. Dadurch wird auch Ihr „Spectre”-Schlüsselpaar entfernt. Denken Sie daran, die neue Version zu aktualisieren.', - 'settings_reset_for_bunq' => 'Bunq-Einstellungen zurückgesetzt', - 'settings_reset_for_spectre' => 'Spectre-Einstellungen zurückgesetzt', - + 'import_data' => 'Daten importieren', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Diese Funktion ist nicht verfügbar, wenn Sie Firefly III in einer Sandstorm.io-Umgebung verwenden.', diff --git a/resources/lang/de_DE/form.php b/resources/lang/de_DE/form.php index 1085d6f86b..3195ec20e9 100644 --- a/resources/lang/de_DE/form.php +++ b/resources/lang/de_DE/form.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // new user: 'bank_name' => 'Name der Bank', @@ -184,6 +185,13 @@ return [ 'blocked' => 'Ist blockiert?', 'blocked_code' => 'Grund für Block', + // import + 'apply_rules' => 'Regeln anwenden', + 'artist' => 'Interpret', + 'album' => 'Album', + 'song' => 'Titel', + + // admin 'domain' => 'Domain', 'single_user_mode' => 'Registrierung deaktivieren', diff --git a/resources/lang/de_DE/import.php b/resources/lang/de_DE/import.php index 7915af59a7..70ab8666db 100644 --- a/resources/lang/de_DE/import.php +++ b/resources/lang/de_DE/import.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ - // status of import: + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Daten in Firefly III importieren', + 'prerequisites_breadcrumb_fake' => 'Voraussetzungen für den Scheinimportanbieter', + 'prerequisites_breadcrumb_spectre' => 'Voraussetzungen für Spectre', + 'prerequisites_breadcrumb_bunq' => 'Voraussetzungen für bunq', + 'job_configuration_breadcrumb' => 'Konfiguration für „:key”', + 'job_status_breadcrumb' => 'Importstatus für „:key”', + 'cannot_create_for_provider' => 'Firefly III konnte keine Aufgabe für den Anbieter „:provider” erstellen.', + + // index page: + 'general_index_title' => 'Datei importieren', + 'general_index_intro' => 'Willkommen beim Importassistenten von Firefly III. Es gibt einige Möglichkeiten, Daten in Firefly III zu importieren, die hier als Schaltflächen angezeigt werden.', + // import provider strings (index): + 'button_fake' => 'Importfunktion testen', + 'button_file' => 'Datei importieren', + 'button_bunq' => 'Von „bunq” importieren', + 'button_spectre' => 'Importieren mit Spectre', + 'button_plaid' => 'Import mit Plaid', + 'button_yodlee' => 'Importieren mit Yodlee', + 'button_quovo' => 'Import mit Quovo', + // global config box (index) + 'global_config_title' => 'Allgemeine Importkonfiguration', + 'global_config_text' => 'In Zukunft wird dieses Feld Einstellungen enthalten, die für ALLE oben genannten Importanbieter gelten.', + // prerequisites box (index) + 'need_prereq_title' => 'Importvoraussetzungen', + 'need_prereq_intro' => 'Einige Importmethoden benötigen Ihre Aufmerksamkeit, bevor sie verwendet werden können. Beispielsweise benötigen sie spezielle API-Schlüssel oder Anwendungsgeheimnisse. Sie können sie hier konfigurieren. Das Symbol zeigt an, ob diese Voraussetzungen erfüllt sind.', + 'do_prereq_fake' => 'Voraussetzungen für den Scheinanbieter', + 'do_prereq_file' => 'Voraussetzungen für den Dateiimport', + 'do_prereq_bunq' => 'Voraussetzungen für den Import aus Bunq', + 'do_prereq_spectre' => 'Voraussetzungen für den Import mit Spectre', + 'do_prereq_plaid' => 'Voraussetzungen für den Import mit Plaid', + 'do_prereq_yodlee' => 'Voraussetzungen für den Import mit Yodlee', + 'do_prereq_quovo' => 'Voraussetzungen für den Import mit Quovo', + // provider config box (index) + 'can_config_title' => 'Einstellungen importieren', + 'can_config_intro' => 'Einige Importmethoden können nach Ihren Wünschen konfiguriert werden. Sie verfügen über zusätzliche Einstellungen, die Sie anpassen können.', + 'do_config_fake' => 'Konfiguration für den Scheinanbieter', + 'do_config_file' => 'Konfiguration für Dateiimporte', + 'do_config_bunq' => 'Konfiguration für den Import aus bunq', + 'do_config_spectre' => 'Konfiguration für den Import aus Spectre', + 'do_config_plaid' => 'Konfiguration für den Import aus Plaid', + 'do_config_yodlee' => 'Konfiguration für den Import aus Yodlee', + 'do_config_quovo' => 'Konfiguration für den Import aus Quovo', + + // prerequisites: + 'prereq_fake_title' => 'Voraussetzungen für einen Import vom Scheinimportanbieter', + 'prereq_fake_text' => 'Dieser Scheinanbieter benötigt einen eigenen API-Schlüssel. Dieser muss 32 Zeichen lang sein. Sie können diese hier verwenden: 123456789012345678901234567890', + 'prereq_spectre_title' => 'Voraussetzungen für einen Import durch Verwendung der Spectre-API', + 'prereq_spectre_text' => 'Um Daten über die Spectre-API (v4) zu importieren, müssen Sie Firefly III zwei geheime Werte zur Verfügung stellen. Diese können auf der Geheimnisse-Seite gefunden werden.', + 'prereq_spectre_pub' => 'Ebenso muss die Spectre-API den öffentlichen Schlüssel kennen, der unten angezeigt wird. Ohne diesen wird sie Sie nicht erkennen. Bitte geben Sie diesen öffentlichen Schlüssel auf Ihrer Geheimnisse-Seite ein.', + 'prereq_bunq_title' => 'Voraussetzungen für einen Import von bunq', + 'prereq_bunq_text' => 'Um aus „bunq” importieren zu können, benötigen Sie einen API-Schlüssel. Sie können diesen über die App bekommen. Bitte beachten Sie, dass sich die Importfunktion von „bunq” noch im BETA-Stadium befindet. Es wurde nur gegen die Sandbox-API getestet.', + 'prereq_bunq_ip' => '„bunq” benötigt Ihre öffentlich zugängliche IP-Adresse. Firefly III versuchte, diese mithilfe des ipify-Diensts auszufüllen. Stellen Sie sicher, dass diese IP-Adresse korrekt ist, da sonst der Import fehlschlägt.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Schein-API-Schlüssel erfolgreich gespeichert!', + 'prerequisites_saved_for_spectre' => 'App-ID und Geheimnis gespeichert!', + 'prerequisites_saved_for_bunq' => 'API-Schlüssel und IP gespeichert!', + + // job configuration: + 'job_config_apply_rules_title' => 'Aufgabenkonfiguration - Regeln übernehmen?', + 'job_config_apply_rules_text' => 'Sobald der Scheinanbieter ausgeführt wird, können Ihre Regeln auf die Buchungen angewendet werden. Dies erhöht die Zeit für den Import.', + 'job_config_input' => 'Ihre Eingaben', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Albumname eingeben', + 'job_config_fake_artist_text' => 'Viele Importassistent haben einige Konfigurationsschritte, die Sie ausführen müssen. Im Falle des gefälschten Importanbieters müssen Sie einige seltsame Fragen beantworten. Geben Sie in diesem Fall „David Bowie” ein, um fortzufahren.', + 'job_config_fake_song_title' => 'Titelnamen eingeben', + 'job_config_fake_song_text' => 'Nennen Sie den Song „Goldene Jahre”, um mit dem Scheinimport fortzufahren.', + 'job_config_fake_album_title' => 'Albumname eingeben', + 'job_config_fake_album_text' => 'Einige Importassistenten benötigen nach der Hälfte des Imports zusätzliche Daten. Im Falle des Scheinimportanbieter müssen Sie einige seltsame Fragen beantworten. Geben Sie „Station zu Station” ein, um fortzufahren.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Import einrichten (1/4) • Ihre Datei hochladen', + 'job_config_file_upload_text' => 'Diese Assistent wird Ihnen helfen, Dateien von Ihrer Bank in Firefly III zu importieren. ', + 'job_config_file_upload_help' => 'Wählen Sie Ihre Datei aus. Bitte stellen Sie sicher, dass die Datei UTF-8-kodiert ist.', + 'job_config_file_upload_config_help' => 'Wenn Sie zuvor Daten in Firefly III importiert haben, verfügen Sie möglicherweise über eine Konfigurationsdatei, die Ihnen die Konfigurationswerte vorgibt. Für einige Banken haben andere Benutzer freundlicherweise ihreKonfigurationsdatei zur Verfügung gestellt.', + 'job_config_file_upload_type_help' => 'Typ der hochzuladenden Datei auswählen', + 'job_config_file_upload_submit' => 'Dateien hochladen', + 'import_file_type_csv' => 'CSV (Kommagetrennte Werte)', + 'file_not_utf8' => 'Die hochgeladene Datei ist nicht als UTF-8 oder ASCII kodiert. Firefly III kann mit solchen Dateien nicht umgehen. Bitte verwenden Sie „Notepad++” oder „Sublime”, um Ihre Datei in UTF-8 zu konvertieren.', + 'job_config_uc_title' => 'Import einrichten (2/4) • Allgemeine Datei-Einstellungen', + 'job_config_uc_text' => 'Um Ihre Datei korrekt importieren zu können, überprüfen Sie bitte die folgenden Optionen.', + 'job_config_uc_header_help' => 'Markieren Sie dieses Feld, wenn die erste Zeile Ihrer CSV-Datei die Spaltenüberschriften enthält.', + 'job_config_uc_date_help' => 'Datumsformat in Ihrer Datei. Folgen Sie dem auf dieser Seite angegebenen Format. Der Standardwert analysiert Daten, die wie folgt aussehen: :dateExample.', + 'job_config_uc_delimiter_help' => 'Wählen Sie das Feldtrennzeichen, das in Ihrer Eingabedatei verwendet wird. Wenn nicht sicher, ist das Komma die sicherste Option.', + 'job_config_uc_account_help' => 'Wenn Ihre Datei KEINE Informationen über Ihr(e) Anlagenkont(o/en) enthält, wählen Sie über dieses Auswahlmenü aus, zu welchem Konto die Buchungen in der Datei gehören.', + 'job_config_uc_apply_rules_title' => 'Regeln anwenden', + 'job_config_uc_apply_rules_text' => 'Wendet Ihre Regeln auf jede importierte Buchung an. Beachten Sie, dass dies den Import erheblich verlangsamt.', + 'job_config_uc_specifics_title' => 'Bankspezifische Einstellungen', + 'job_config_uc_specifics_txt' => 'Einige Banken liefern schlecht formatierte Dateien. Firefly III kann diese automatisch korrigieren. Wenn Ihre Bank solche Dateien liefert, diese aber hier nicht aufgeführt sind, öffnen Sie bitte einen Fehlerbericht auf GitHub.', + 'job_config_uc_submit' => 'Fortsetzen', + 'invalid_import_account' => 'Sie haben ein ungültiges Konto zum Importieren ausgewählt.', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Wählen Sie Ihre Zugangsdaten', + 'job_config_spectre_login_text' => 'Firefly III hat :count bestehende Zugangsdaten in Ihrem Spectre-Konto gefunden. Von welchem möchten Sie importieren?', + 'spectre_login_status_active' => 'Aktiv', + 'spectre_login_status_inactive' => 'Inaktiv', + 'spectre_login_status_disabled' => 'Deaktiviert', + 'spectre_login_new_login' => 'Melden Sie sich bei einer anderen Bank oder einer dieser Banken mit anderen Zugangsdaten an.', + 'job_config_spectre_accounts_title' => 'Import-Konten auswählen', + 'job_config_spectre_accounts_text' => 'Sie haben „:name” (:country) gewählt. Sie haben :count Konto(s) bei diesem Anbieter. Bitte wählen Sie das/die Firefly III-Kont(o/en), auf dem/denen die Buchungen von diesen Konten gespeichert werden sollen. Denken Sie daran, dass zum Importieren von Daten sowohl das Firefly III-Konto als auch das „:name”-Konto dieselbe Währung haben müssen.', + 'spectre_no_supported_accounts' => 'Von diesem Konto können Sie nicht importieren, da die Währungen nicht übereinstimmen.', + 'spectre_do_not_import' => '(Nicht importieren)', + 'spectre_no_mapping' => 'Es scheint, dass Sie keine Konten zum Importieren ausgewählt haben.', + 'imported_from_account' => 'Von „:account” importiert', + 'spectre_account_with_number' => 'Konto :number', + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq-Konten', + 'job_config_bunq_accounts_text' => 'Dies sind jene Konten, die mit Ihrem „bunq”-Konto verknüpft sind. Bitte wählen Sie die Konten aus, von denen Sie importieren möchten, und in welches Konto die Buchungen importiert werden sollen.', + 'bunq_no_mapping' => 'Es scheint, dass Sie keine Konten ausgewählt haben.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT Code', + 'spectre_extra_key_status' => 'Status', + 'spectre_extra_key_card_type' => 'Kartentyp', + 'spectre_extra_key_account_name' => 'Kontoname', + 'spectre_extra_key_client_name' => 'Client-Name', + 'spectre_extra_key_account_number' => 'Kontonummer', + 'spectre_extra_key_blocked_amount' => 'Gesperrter Betrag', + 'spectre_extra_key_available_amount' => 'Verfügbarer Betrag', + 'spectre_extra_key_credit_limit' => 'Kreditrahmen', + 'spectre_extra_key_interest_rate' => 'Zinssatz', + 'spectre_extra_key_expiry_date' => 'Gültig bis', + 'spectre_extra_key_open_date' => 'Anfangsdatum', + 'spectre_extra_key_current_time' => 'Aktuelle Uhrzeit', + 'spectre_extra_key_current_date' => 'Aktuelles Datum', + 'spectre_extra_key_cards' => 'Karten', + 'spectre_extra_key_units' => 'Einheiten', + 'spectre_extra_key_unit_price' => 'Stückpreis', + 'spectre_extra_key_transactions_count' => 'Anzahl der Buchungen', + + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Bessere Beschreibungen im ING-Export erstellen', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Anführungszeichen aus SNS/Volksbank-Exportdateien entfernen', + 'specific_abn_name' => 'ABN•AMRO NL', + 'specific_abn_descr' => 'Behebt mögliche Probleme mit ABN•AMRO-Dateien', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Behebt mögliche Probleme mit Rabobank-Dateien', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Behebt mögliche Probleme mit PC-Dateien', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Import einrichten (3/4) • Funktion jeder Spalte festlegen', + 'job_config_roles_text' => 'Jede Spalte in Ihrer CSV-Datei enthält bestimmte Daten. Bitte geben Sie an, welche Art von Daten enthalten sind. Die Option "Daten zuordnen" bedeutet, dass jeder Eintrag in der Spalte mit einem Wert aus Ihrer der Datenbank ersetzt wird. Eine oft zugeordnete Spalte ist die Spalte, welche die IBAN des fremden Kontos enthält. Diese können leicht mit bereits angelegten IBANs in Ihrer Datenbank verglichen werden.', + 'job_config_roles_submit' => 'Fortsetzen', + 'job_config_roles_column_name' => 'Name der Spalte', + 'job_config_roles_column_example' => 'Spaltenbeispieldaten', + 'job_config_roles_column_role' => 'Bedeutung der Spalte', + 'job_config_roles_do_map_value' => 'Diese Werte zuordnen', + 'job_config_roles_no_example' => 'Keine Beispieldaten vorhanden', + 'job_config_roles_fa_warning' => 'Wenn Sie eine Spalte als mit einem Betrag in einer Fremdwährung kennzeichnen, müssen Sie auch die Spalte mit der entsprechenden Währung festlegen.', + 'job_config_roles_rwarning' => 'Markieren Sie mindestens die Spalte, die den jeweiligen Betrag enthält. Darüber hinaus sollten eine Spalte für die Beschreibung, das Datum und das Gegenkonto ausgewählt werden.', + 'job_config_roles_colum_count' => 'Spalte', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Import einrichten (4/4) - Importdaten mit Firefly III-Daten verknüpfen', + 'job_config_map_text' => 'In den folgenden Tabellen zeigt der linke Wert Informationen, die sich in Ihrer hochgeladenen Datei befinden. Es ist Ihre Aufgabe, diesen Wert, wenn möglich, einem bereits in der Datenbank vorhandenen zuzuordnen. Firefly wird sich an diese Zuordnung halten. Wenn kein Wert für die Zuordnung vorhanden ist oder Sie den bestimmten Wert nicht abbilden möchten, wählen Sie nichts aus.', + 'job_config_map_nothing' => 'Ihre Datei enthält keine Daten, die bestehenden Werten zugeordnet werden können. Klicken Sie „Import starten” um fortzufahren.', + 'job_config_field_value' => 'Feldwert', + 'job_config_field_mapped' => 'Zugeordnet zu', + 'map_do_not_map' => '(keine Zuordnung)', + 'job_config_map_submit' => 'Import starten', + + + // import status page: + 'import_with_key' => 'Mit Schlüssel „:key” importieren', 'status_wait_title' => 'Bitte warten...', 'status_wait_text' => 'Diese Box wird gleich ausgeblendet.', - 'status_fatal_title' => 'Ein schwerwiegender Fehler ist aufgetreten', - 'status_fatal_text' => 'Es ist ein schwerwiegender Fehler aufgetreten und die Importroutine kann nicht fortgeführt werden. Bitte sehen Sie sich die Erklärung in rot unten an.', - 'status_fatal_more' => 'Wenn der Fehler eine Zeitüberschreitung ist, wird der Import mittendrin gestoppt. Bei einigen Serverkonfigurationen wird lediglich der Server gestoppt, während der Import im Hintergrund ausgeführt wird. Um dies zu überprüfen, überprüfen Sie die Protokolldateien. Wenn das Problem weiterhin besteht, sollten Sie stattdessen den Import über die Befehlszeile in Erwägung ziehen.', - 'status_ready_title' => 'Der Import ist startbereit', - 'status_ready_text' => 'Der Import ist bereit zu starten. Alle Einstellungen wurden von Ihnen erledigt. Bitte laden Sie die Konfigurationsdatei herunter. Diese wird Ihnen beim Import helfen, sollte dieser nicht wie gewünscht verlaufen. Um den Import tatsächlich zu starten führen Sie den folgenden Befehl in der Konsole aus oder nutzen Sie den Web-basierten Import. Abhängig von ihrer Konfiguration wird Ihnen der Konsolenimport mehr Rückmeldungen geben.', - 'status_ready_noconfig_text' => 'Der Import ist bereit zu starten. Alle Einstellungen wurden von Ihnen erledigt. Um den Import tatsächlich zu starten führen Sie den folgenden Befehl in der Konsole aus oder nutzen Sie den Web-basierten Import. Abhängig von ihrer Konfiguration wird Ihnen der Konsolenimport mehr Rückmeldungen geben.', - 'status_ready_config' => 'Konfiguration herunterladen', - 'status_ready_start' => 'Importieren starten', - 'status_ready_share' => 'Bitte denken Sie darüber nach ihre Konfiguration herunterzuladen und in der Übersicht der Import-Einstellungen zu teilen. Dieses erlaubt es anderen Nutzern von Firefly III ihre Daten unkomplizierter zu importieren.', - 'status_job_new' => 'Die Aufgabe ist neu.', - 'status_job_configuring' => 'Der Import wird konfiguriert.', - 'status_job_configured' => 'Der Import ist konfiguriert.', - 'status_job_running' => 'Import wird ausgeführt … Bitte warten …', - 'status_job_error' => 'Ein Fehler ist aufgetreten.', - 'status_job_finished' => 'Import abgeschlossen!', 'status_running_title' => 'Import wird ausgeführt', - 'status_running_placeholder' => 'Bitte auf die Aktualisierung warten …', - 'status_finished_title' => 'Importassistent abgeschlossen', - 'status_finished_text' => 'Der Importassistent hat Ihre Daten importiert.', - 'status_errors_title' => 'Fehler beim Importieren', - 'status_errors_single' => 'Beim Import ist ein Fehler aufgetreten. Dieser scheint aber nicht schwerwiegend zu sein.', - 'status_errors_multi' => 'Beim Importieren sind einige Fehler aufgetreten. Diese scheinen aber nicht schwerwiegend zu sein.', - 'status_bread_crumb' => 'Importstatus', - 'status_sub_title' => 'Importstatus', - 'config_sub_title' => 'Import einrichten', - 'status_finished_job' => 'Die importierten :count Überweisungen finden Sie im Schlagwort :tag.', - 'status_finished_no_tag' => 'Firefly III hat keine Daten aus Ihrer Import-Datei gesammelt.', - 'import_with_key' => 'Mit Schlüssel „:key” importieren', + 'status_job_running' => 'Bitte warten, Import wird ausgeführt …', + 'status_job_storing' => 'Bitte warten, Daten werden gespeichert …', + 'status_job_rules' => 'Bitte warten, Regeln werden angewendet …', + 'status_fatal_title' => 'Schwerwiegender Fehler', + 'status_fatal_text' => 'Der Import hat einen Fehler verursacht, der nicht behoben werden konnte. Entschuldigung!', + 'status_fatal_more' => 'Diese (möglicherweise sehr kryptische) Fehlermeldung wurde durch Protokolldateien ergänzt, die Sie auf Ihrer Festplatte oder im Docker-Container finden, von dem aus Sie Firefly III ausführen.', + 'status_finished_title' => 'Importieren abgeschlossen', + 'status_finished_text' => 'Import abgeschlossen.', + 'finished_with_errors' => 'Beim Import gab es einige Fehler. Bitte prüfen Sie diese sorgfältig.', + 'unknown_import_result' => 'Unbekanntes Importergebnis', + 'result_no_transactions' => 'Es wurden keine Buchungen importiert. Vielleicht waren sie alle Duplikate, sind einfach keine Buchungen, die importiert werden könnten. Vielleicht können Ihnen die Protokolldateien sagen, was passiert ist. Wenn Sie regelmäßig Daten importieren, ist dies normal.', + 'result_one_transaction' => 'Es wurde genau eine Buchung importiert. Diese wurde unter dem Schlagwort :tag gespeichert, wo Sie diese weiter untersuchen können.', + 'result_many_transactions' => 'Firefly III hat :count Buchungen importiert. Diese wurden unter dem Schlagwort :tag gespeichert, wo Sie diese weiter untersuchen können.', - // file, upload something - 'file_upload_title' => 'Import einrichten (1/4) • Ihre Datei hochladen', - 'file_upload_text' => 'Dieser Assistent hilft Ihnen, Dateien von Ihrer Bank in Firefly III zu importieren. Bitte sehen Sie sich die Hilfeseiten in der oberen rechten Ecke an.', - 'file_upload_fields' => 'Felder', - 'file_upload_help' => 'Datei auswählen', - 'file_upload_config_help' => 'Wenn Sie bereits zuvor Daten in Firefly III importiert haben, haben Sie eventuell eine Konfigurationsdatei, welche einige Einstellungen für Sie voreinstellt. Für einige Banken haben andere Nutzer freundlicherweise bereits ihre Konfigurationsdatei zur Verfügung gestellt', - 'file_upload_type_help' => 'Wählen Sie den Typ der hochzuladenden Datei', - 'file_upload_submit' => 'Dateien hochladen', - // file, upload types - 'import_file_type_csv' => 'CSV (Kommagetrennte Werte)', + // general errors and warnings: + 'bad_job_status' => 'Um auf diese Seite zuzugreifen, darf Ihr Importauftrag nicht den Status „:status” haben.', - // file, initial config for CSV - 'csv_initial_title' => 'Import einrichten (2/4) • Grundlegende Einrichtung des CSV-Imports', - 'csv_initial_text' => 'Um Ihre Datei korrekt importieren zu können, überprüfen Sie bitte die folgenden Optionen.', - 'csv_initial_box' => 'Standard CSV Importeinstellungen', - 'csv_initial_box_title' => 'Standard CSV Importeinstellungen', - 'csv_initial_header_help' => 'Hier auswählen, wenn die erste Zeilen der CSV-Datei die Spaltenüberschriften enthält.', - 'csv_initial_date_help' => 'Datumsformat in ihrer CSV-Datei. Geben Sie das Format so an, wie es diese Seite zeigt. Die Standardeinstellung ergibt Daten die so aussehen: :dateExample.', - 'csv_initial_delimiter_help' => 'Wählen Sie das Trennzeichen, welches in ihrer Datei genutzt wird. Wenn Sie nicht sicher sind ist Komma die sicherste Option.', - 'csv_initial_import_account_help' => 'Wenn ihre CSV-Datei KEINE Informationen über ihre Bestandskont(o/n) enthält, nutzen Sie bitte diese Auswahlmenü um anzugeben, zu welchem Bestandskonto die Buchungen in der CSV-Datei gehören.', - 'csv_initial_submit' => 'Fortfahren mit Schritt 3/4', - - // file, new options: - 'file_apply_rules_title' => 'Regeln anwenden', - 'file_apply_rules_description' => 'Regeln anwenden. Beachten Sie, dass dadurch der Import erheblich verlangsamt wird.', - 'file_match_bills_title' => 'Rechnungen zuordnen', - 'file_match_bills_description' => 'Ordnen Sie Ihre Rechnungen den neu erstellten Ausgaben zu. Beachten Sie, dass dadurch der Import erheblich verlangsamt wird.', - - // file, roles config - 'csv_roles_title' => 'Import einrichten (3/4) • Funktion jeder Spalte festlegen', - 'csv_roles_text' => 'Jede Spalte in Ihrer CSV-Datei enthält bestimmte Daten. Bitte geben Sie an, welche Art von Daten enthalten sind. Die Option "Daten zuordnen" bedeutet, dass jeder Eintrag in der Spalte mit einem Wert aus Ihrer der Datenbank ersetzt wird. Eine oft zugeordnete Spalte ist die Spalte, welche die IBAN des fremden Kontos enthält. Diese können leicht mit bereits angelegten IBANs in Ihrer Datenbank verglichen werden.', - 'csv_roles_table' => 'Tabelle', - 'csv_roles_column_name' => 'Name der Spalte', - 'csv_roles_column_example' => 'Beispieldaten', - 'csv_roles_column_role' => 'Bedeutung der Spalte', - 'csv_roles_do_map_value' => 'Diese Werte zuordnen', - 'csv_roles_column' => 'Spalte', - 'csv_roles_no_example_data' => 'Keine Beispieldaten vorhanden', - 'csv_roles_submit' => 'Fortfahren mit Schritt 4/4', - - // not csv, but normal warning - 'roles_warning' => 'Markieren Sie mindestens die Spalte, die den jeweiligen Betrag enthält. Darüber hinaus sollten eine Spalte für die Beschreibung, das Datum und das Gegenkonto ausgewählt werden.', - 'foreign_amount_warning' => 'Wenn Sie eine Spalte als Fremdwährung markieren, müssen Sie auch die Spalte festlegen, welche angibt, welche Währung es ist.', - - // file, map data - 'file_map_title' => 'Import einrichten (4/4) - Importdaten mit Firefly III-Daten verknüpfen', - 'file_map_text' => 'In den folgenden Tabellen zeigt der linke Wert Informationen, die sich in Ihrer hochgeladenen Datei befinden. Es ist Ihre Aufgabe, diesen Wert, wenn möglich, einem bereits in der Datenbank vorhandenen zuzuordnen. Firefly wird sich an diese Zuordnung halten. Wenn kein Wert für die Zuordnung vorhanden ist oder Sie den bestimmten Wert nicht abbilden möchten, wählen Sie nichts aus.', - 'file_map_field_value' => 'Feldwert', - 'file_map_field_mapped_to' => 'Zugeordnet zu', - 'map_do_not_map' => '(keine Zuordnung)', - 'file_map_submit' => 'Import starten', - 'file_nothing_to_map' => 'Ihree Datei enthält keine Daten, die bestehenden Werten zugeordnet werden können. Klicken Sie "Import starten" um fortzufahren.', - - // map things. + // column roles for CSV import: 'column__ignore' => '(diese Spalte ignorieren)', 'column_account-iban' => 'Bestandskonto (IBAN)', 'column_account-id' => 'Kennung des Bestandkontos (passend zu FF3)', @@ -158,48 +261,4 @@ return [ 'column_note' => 'Notiz(en)', 'column_internal-reference' => 'Interne Referenz', - // prerequisites - 'prerequisites' => 'Voraussetzungen', - - // bunq - 'bunq_prerequisites_title' => 'Voraussetzungen für einen Import von bunq', - 'bunq_prerequisites_text' => 'Um aus „bunq” importieren zu können, benötigen Sie einen API-Schlüssel. Sie können diesen über die App bekommen. Bitte beachten Sie, dass sich die Importfunktion von „bunq” noch im BETA-Stadium befindet. Es wurde nur gegen die Sandbox-API getestet.', - 'bunq_prerequisites_text_ip' => '„Bunq” benötigt Ihre öffentlich zugängliche IP-Adresse. Firefly III versuchte, diese mithilfe des ipify-Diensts auszufüllen. Stellen Sie sicher, dass diese IP-Adresse korrekt ist, da sonst der Import fehlschlägt.', - 'bunq_do_import' => 'Ja, von diesem Konto importieren', - 'bunq_accounts_title' => 'Bunq-Konten', - 'bunq_accounts_text' => 'Dies sind jene Konten, die mit Ihrem „bunq”-Konto verknüpft sind. Bitte wählen Sie die Konten aus, von denen Sie importieren möchten, und in welches Konto die Buchungen importiert werden sollen.', - - // Spectre - 'spectre_title' => 'Importieren mit Spectre', - 'spectre_prerequisites_title' => 'Voraussetzungen für einen Import von Spectre', - 'spectre_prerequisites_text' => 'Um Daten über die Spectre-API (v4) zu importieren, müssen Sie Firefly III zwei geheime Werte zur Verfügung stellen. Diese können auf der Geheimnisse-Seite gefunden werden.', - 'spectre_enter_pub_key' => 'Der Import funktioniert nur, wenn Sie diesen öffentlichen Schlüssel auf Ihrer Sicherheitsseite eingeben.', - 'spectre_accounts_title' => 'Import-Konten auswählen', - 'spectre_accounts_text' => 'Die Konten auf der linken Seite wurde von Spectre gefunden und können für den Import verwendet werden. Ordnen Sie jeweils ein eigenes Konto zu, in das die Buchungen importiert werden sollen. Nicht ausgwählte Konten werden beim Import ignoriert.', - 'spectre_do_import' => 'Ja, von diesem Konto importieren', - 'spectre_no_supported_accounts' => 'Von diesem Konto können Sie nicht importieren, da die Währungen nicht übereinstimmen.', - - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'BIC (SWIFT) Code', - 'spectre_extra_key_status' => 'Status', - 'spectre_extra_key_card_type' => 'Art der Kreditkarte', - 'spectre_extra_key_account_name' => 'Kontoname', - 'spectre_extra_key_client_name' => 'Kundenname', - 'spectre_extra_key_account_number' => 'Kontonummer', - 'spectre_extra_key_blocked_amount' => 'Gesperrter Betrag', - 'spectre_extra_key_available_amount' => 'Verfügbarer Betrag', - 'spectre_extra_key_credit_limit' => 'Kreditrahmen', - 'spectre_extra_key_interest_rate' => 'Zinssatz', - 'spectre_extra_key_expiry_date' => 'Ablaufdatum', - 'spectre_extra_key_open_date' => 'Anfangsdatum', - 'spectre_extra_key_current_time' => 'Aktuelle Uhrzeit', - 'spectre_extra_key_current_date' => 'Aktuelles Datum', - 'spectre_extra_key_cards' => 'Karten', - 'spectre_extra_key_units' => 'Einheiten', - 'spectre_extra_key_unit_price' => 'Stückpreis', - 'spectre_extra_key_transactions_count' => 'Anzahl Transaktionen', - - // various other strings: - 'imported_from_account' => 'Von „:account” importiert', ]; diff --git a/resources/lang/de_DE/intro.php b/resources/lang/de_DE/intro.php index 8ebc5d6e6a..3ca94fb90c 100644 --- a/resources/lang/de_DE/intro.php +++ b/resources/lang/de_DE/intro.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // index 'index_intro' => 'Wilkommen auf der Startseite von Firefly III. Bitte nehmen Sie sich die Zeit, um ein Gefühl dafür zu bekommen, wie Firefly III funktioniert.', diff --git a/resources/lang/de_DE/list.php b/resources/lang/de_DE/list.php index 7e80eccc99..a59b10a529 100644 --- a/resources/lang/de_DE/list.php +++ b/resources/lang/de_DE/list.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'buttons' => 'Schaltflächen', 'icon' => 'Symbol', @@ -111,10 +112,15 @@ return [ 'sepa-cc' => 'SEPA • Verrechnungsschlüssel', 'sepa-ep' => 'SEPA • Externer Verwendungszweck', 'sepa-ci' => 'SEPA • Identifikationsnummer des Zahlungsempfängers', + 'external_id' => 'Externe Kennung', 'account_at_bunq' => 'Konto bei „bunq”', 'file_name' => 'Dateiname', 'file_size' => 'Dateigröße', 'file_type' => 'Dateityp', 'attached_to' => 'Zugewiesen zu', 'file_exists' => 'Datei existiert', + 'spectre_bank' => 'Bank', + 'spectre_last_use' => 'Letzte Anmeldung', + 'spectre_status' => 'Status', + 'bunq_payment_id' => 'bunq payment ID', ]; diff --git a/resources/lang/de_DE/pagination.php b/resources/lang/de_DE/pagination.php index bcc5ee0bee..5df96ca481 100644 --- a/resources/lang/de_DE/pagination.php +++ b/resources/lang/de_DE/pagination.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'previous' => '« Vorherige', 'next' => 'Nächste »', diff --git a/resources/lang/de_DE/passwords.php b/resources/lang/de_DE/passwords.php index 7b15def6ef..8888015525 100644 --- a/resources/lang/de_DE/passwords.php +++ b/resources/lang/de_DE/passwords.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'password' => 'Passwörter müssen mindestens 6 Zeichen lang sein und übereinstimmen.', 'user' => 'Wir können keinen Benutzer mit dieser E-Mail Adresse finden.', diff --git a/resources/lang/de_DE/validation.php b/resources/lang/de_DE/validation.php index 3ada9a3c1d..1089cc2b37 100644 --- a/resources/lang/de_DE/validation.php +++ b/resources/lang/de_DE/validation.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'iban' => 'Dies ist keine gültige IBAN.', 'source_equals_destination' => 'Das Quellkonto entspricht dem Zielkonto', @@ -109,7 +110,8 @@ return [ 'in_array' => ':attribute existiert nicht in :other.', 'present' => 'Das :attribute Feld muss vorhanden sein.', 'amount_zero' => 'Der Gesamtbetrag darf nicht Null sein', - 'secure_password' => 'Das ist kein sicheres Passwort. Bitte versuchen Sie es erneut. Weitere Informationen finden Sie unter https://goo.gl/NCh2tN', + 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'secure_password' => 'Das ist kein sicheres Passwort. Bitte versuchen Sie es erneut. Weitere Informationen finden Sie unter https://github.com/firefly-iii/help/wiki/Secure-password', 'attributes' => [ 'email' => 'E-Mail Adresse', 'description' => 'Beschreibung', From f23ee2dac54f716e457454b4eae36a5c5d93d3f0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 14:20:39 +0200 Subject: [PATCH 159/182] Update Spanish language files [skip ci] --- resources/lang/es_ES/auth.php | 16 +- resources/lang/es_ES/bank.php | 5 +- resources/lang/es_ES/breadcrumbs.php | 5 +- resources/lang/es_ES/components.php | 3 +- resources/lang/es_ES/config.php | 5 +- resources/lang/es_ES/csv.php | 5 +- resources/lang/es_ES/demo.php | 7 +- resources/lang/es_ES/firefly.php | 32 +-- resources/lang/es_ES/form.php | 12 +- resources/lang/es_ES/import.php | 313 ++++++++++++++++----------- resources/lang/es_ES/intro.php | 5 +- resources/lang/es_ES/list.php | 10 +- resources/lang/es_ES/pagination.php | 5 +- resources/lang/es_ES/passwords.php | 5 +- resources/lang/es_ES/validation.php | 8 +- 15 files changed, 249 insertions(+), 187 deletions(-) diff --git a/resources/lang/es_ES/auth.php b/resources/lang/es_ES/auth.php index b91042a71f..7a42e8dbde 100644 --- a/resources/lang/es_ES/auth.php +++ b/resources/lang/es_ES/auth.php @@ -1,9 +1,8 @@ . */ -return [ - /* - |-------------------------------------------------------------------------- - | Authentication Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used during authentication for various - | messages that we need to display to the user. You are free to modify - | these language lines according to your application's requirements. - | - */ +declare(strict_types=1); +return [ 'failed' => 'Las credenciales no coinciden con los registros.', 'throttle' => 'Demasiados intentos de inicio de sesión. Por favor reintente en :seconds segundos.', ]; diff --git a/resources/lang/es_ES/bank.php b/resources/lang/es_ES/bank.php index 8826020cbf..5d00b1e685 100644 --- a/resources/lang/es_ES/bank.php +++ b/resources/lang/es_ES/bank.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/es_ES/breadcrumbs.php b/resources/lang/es_ES/breadcrumbs.php index 39f6cc84e4..f1b9067035 100644 --- a/resources/lang/es_ES/breadcrumbs.php +++ b/resources/lang/es_ES/breadcrumbs.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'home' => 'Inicio', 'edit_currency' => 'Editar moneda ":name"', diff --git a/resources/lang/es_ES/components.php b/resources/lang/es_ES/components.php index cd7dcbdfb4..e5083bdd1e 100644 --- a/resources/lang/es_ES/components.php +++ b/resources/lang/es_ES/components.php @@ -1,5 +1,4 @@ . */ +declare(strict_types=1); + return [ // profile 'personal_access_tokens' => 'Tokens de acceso personal', diff --git a/resources/lang/es_ES/config.php b/resources/lang/es_ES/config.php index 6d084206a4..d5bebf299c 100644 --- a/resources/lang/es_ES/config.php +++ b/resources/lang/es_ES/config.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'html_language' => 'es', 'locale' => 'es, Spanish, es_ES, es_ES.utf8, es_ES.UTF-8', diff --git a/resources/lang/es_ES/csv.php b/resources/lang/es_ES/csv.php index 622a49202a..aae109a40a 100644 --- a/resources/lang/es_ES/csv.php +++ b/resources/lang/es_ES/csv.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/es_ES/demo.php b/resources/lang/es_ES/demo.php index 95caa87b6a..67a5f2fe85 100644 --- a/resources/lang/es_ES/demo.php +++ b/resources/lang/es_ES/demo.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'no_demo_text' => 'Lamentablemente no hay textos de ayuda para esta página.', 'see_help_icon' => 'Sin embargo, el ícono en la esquina superior-derecha puede tener más información.', @@ -32,5 +33,5 @@ return [ 'currencies-index' => 'Firefly III admite múltiples monedas. A pesar de que la moneda por defecto es el Euro, se puede seleccionar el Dólar de EE.UU, y muchas otras monedas. Como se puede ver se ha incluido una pequeña selección de monedas, pero puedes agregar tu propia moneda si lo deseas. Sin embargo, cambiar la moneda predeterminada no cambiará la moneda de las transacciones existentes: Firefly III admite el uso de varias monedas al mismo tiempo.', 'transactions-index' => 'Estos gastos, depósitos y transferencias no son particularmente imaginativos. Se han generado automáticamente.', 'piggy-banks-index' => 'Como puede ver, hay tres alcancías. Utilice los botones más y menos para influir en la cantidad de dinero en cada alcancía. Haga clic en el nombre de la alcancía para ver la administración de cada una.', - 'import-index' => 'Por supuesto, cualquier archivo CSV puede ser importado dentro de Firefly III', + 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', ]; diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index f4724c75bc..70fe05fe79 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // general stuff: 'close' => 'Cerrar', @@ -179,6 +180,7 @@ return [ 'authorization_request_intro' => 'El :client está solicitando permiso para acceder a su administración financiera. ¿Desea autorizar :client para acceder a estos registros?', 'scopes_will_be_able' => 'Esta aplicación podrá:', 'button_authorize' => 'Autorizar', + 'none_in_select_list' => '(none)', // check for updates: 'update_check_title' => 'Ver actualizaciones', @@ -666,6 +668,7 @@ return [ 'bill_will_automatch' => 'Bill se vinculara automáticamente a transacciones coincidentes', 'skips_over' => 'salta sobre', 'bill_store_error' => 'An unexpected error occurred while storing your new bill. Please check the log files', + 'list_inactive_rule' => 'inactive rule', // accounts: 'details_for_asset' => 'Detalles para la cuenta de activos ":name"', @@ -801,6 +804,7 @@ return [ 'opt_group_savingAsset' => 'Cuenta de ahorros', 'opt_group_sharedAsset' => 'Cuenta de activos compartidas', 'opt_group_ccAsset' => 'Tarjetas de credito', + 'opt_group_cashWalletAsset' => 'Cash wallets', 'notes' => 'Notas', 'unknown_journal_error' => 'Could not store the transaction. Please check the log files.', @@ -817,6 +821,7 @@ return [ 'language' => 'Language', 'new_savings_account' => ':bank_name savings account', 'cash_wallet' => 'Cash wallet', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Tus cuentas', @@ -1014,6 +1019,7 @@ return [ 'remove_money_from_piggy_title' => 'Quitar dinero de la alcancía ":name"', 'add' => 'Añadir', 'no_money_for_piggy' => 'Usted no tiene dinero para colocar en esta alcancía.', + 'suggested_savings_per_month' => 'Suggested per month', 'remove' => 'Eliminar', 'max_amount_add' => 'La cantidad máxima que usted puede agregar es', @@ -1149,27 +1155,9 @@ return [ 'no_edit_multiple_left' => 'Usted ha seleccionado transacciones no validas para editar.', 'cannot_convert_split_journal' => 'No se puede convertir una transacción dividida', - // import bread crumbs and titles: - 'import' => 'Importar', - 'import_data' => 'Importar datos', - 'import_general_index_file' => 'Importar un archivo', - 'import_from_bunq' => 'Importar de bunq', - 'import_using_spectre' => 'Importar usando Spectre', - 'import_using_plaid' => 'Importar usando cuadros', - 'import_config_bread_crumb' => 'Configure su importación', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Importar datos a Firefly III', - 'import_index_sub_title' => 'Índice', - 'import_general_index_intro' => 'Bienvenido a la rutina de importación de Firefly III. Hay algunas formas de importar datos a Firefly III, que se muestran aquí como botones.', - 'upload_error' => 'The file you have uploaded could not be processed. Possibly it is of an invalid file type or encoding. The log files will have more information.', - 'reset_import_settings_title' => 'Reset import configuration', - 'reset_import_settings_text' => 'You can use these links to reset your import settings for specific providers. This is useful when bad settings stop you from importing data.', - 'reset_settings_bunq' => 'Remove bunq API key, local external IP address and bunq related RSA keys.', - 'reset_settings_spectre' => 'Remove Spectre secrets and ID\'s. This will also remove your Spectre keypair. Remember to update the new one.', - 'settings_reset_for_bunq' => 'Bunq settings reset.', - 'settings_reset_for_spectre' => 'Spectre settings reset.', - + 'import_data' => 'Importar datos', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Esta función no esta disponible cuando usted esta utilizando Firefly III dentro de un ambiente Sandstorm.io.', diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index ae6ce896fc..11d94d8b7f 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // new user: 'bank_name' => 'Banco', @@ -184,6 +185,13 @@ return [ 'blocked' => '¿Está bloqueado?', 'blocked_code' => 'Razón del bloqueo', + // import + 'apply_rules' => 'Apply rules', + 'artist' => 'Artist', + 'album' => 'Album', + 'song' => 'Song', + + // admin 'domain' => 'Dominio', 'single_user_mode' => 'Deshabilitar registro de usuario', diff --git a/resources/lang/es_ES/import.php b/resources/lang/es_ES/import.php index b7b53f9877..8a812e4689 100644 --- a/resources/lang/es_ES/import.php +++ b/resources/lang/es_ES/import.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ - // status of import: + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Import data into Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', + 'job_configuration_breadcrumb' => 'Configuration for ":key"', + 'job_status_breadcrumb' => 'Import status for ":key"', + 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', + + // index page: + 'general_index_title' => 'Import a file', + 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + // import provider strings (index): + 'button_fake' => 'Fake an import', + 'button_file' => 'Import a file', + 'button_bunq' => 'Import from bunq', + 'button_spectre' => 'Import using Spectre', + 'button_plaid' => 'Import using Plaid', + 'button_yodlee' => 'Import using Yodlee', + 'button_quovo' => 'Import using Quovo', + // global config box (index) + 'global_config_title' => 'Global import configuration', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + // prerequisites box (index) + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + // provider config box (index) + 'can_config_title' => 'Import configuration', + 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'do_config_fake' => 'Configuration for the fake provider', + 'do_config_file' => 'Configuration for file imports', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', + + // prerequisites: + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + + // job configuration: + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (valores separados por comas)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', + 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', + 'job_config_uc_apply_rules_title' => 'Apply rules', + 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', + 'job_config_uc_specifics_title' => 'Bank-specific options', + 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', + 'job_config_uc_submit' => 'Continue', + 'invalid_import_account' => 'You have selected an invalid account to import into.', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Choose your login', + 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', + 'spectre_login_status_active' => 'Active', + 'spectre_login_status_inactive' => 'Inactive', + 'spectre_login_status_disabled' => 'Disabled', + 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', + 'job_config_spectre_accounts_title' => 'Select accounts to import from', + 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', + 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', + 'spectre_do_not_import' => '(do not import)', + 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'imported_from_account' => 'Imported from ":account"', + 'spectre_account_with_number' => 'Account :number', + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq accounts', + 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', + 'bunq_no_mapping' => 'It seems you have not selected any accounts.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Status', + 'spectre_extra_key_card_type' => 'Card type', + 'spectre_extra_key_account_name' => 'Account name', + 'spectre_extra_key_client_name' => 'Client name', + 'spectre_extra_key_account_number' => 'Account number', + 'spectre_extra_key_blocked_amount' => 'Blocked amount', + 'spectre_extra_key_available_amount' => 'Available amount', + 'spectre_extra_key_credit_limit' => 'Credit limit', + 'spectre_extra_key_interest_rate' => 'Interest rate', + 'spectre_extra_key_expiry_date' => 'Expiry date', + 'spectre_extra_key_open_date' => 'Open date', + 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_current_date' => 'Current date', + 'spectre_extra_key_cards' => 'Cards', + 'spectre_extra_key_units' => 'Units', + 'spectre_extra_key_unit_price' => 'Unit price', + 'spectre_extra_key_transactions_count' => 'Transaction count', + + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Create better descriptions in ING exports', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Fixes potential problems with PC files', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', + 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'job_config_roles_submit' => 'Continue', + 'job_config_roles_column_name' => 'Name of column', + 'job_config_roles_column_example' => 'Column example data', + 'job_config_roles_column_role' => 'Column data meaning', + 'job_config_roles_do_map_value' => 'Map these values', + 'job_config_roles_no_example' => 'No example data available', + 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'job_config_roles_colum_count' => 'Column', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'job_config_field_value' => 'Field value', + 'job_config_field_mapped' => 'Mapped to', + 'map_do_not_map' => '(no mapear)', + 'job_config_map_submit' => 'Start the import', + + + // import status page: + 'import_with_key' => 'Importar con la clave \':key\'', 'status_wait_title' => 'Por favor espere...', 'status_wait_text' => 'Esta caja va a desaparecer en un momento.', - 'status_fatal_title' => 'Un error fatal ha ocurrido', - 'status_fatal_text' => 'Un error fatal ocurrió, del cual la rutina de importación no se puede recuperar. Por favor, vea la explicación en rojo a continuación.', - 'status_fatal_more' => 'Si el error es de tiempo en espera, la importación se detendrá a mitad de camino. Para algunas configuraciones de servidor, es simplemente que el servidor se detuvo mientras la importación se sigue ejecutando en segundo plano. Para verificar esto, revise los archivos de logs. Si el problema persiste, considere importar a través de la linea de comando.', - 'status_ready_title' => 'La Importación esta lista para comenzar', - 'status_ready_text' => 'La importación está lista para comenzar. Toda la configuración que necesitaba hacer ya está hecha. Por favor descargue el archivo de configuración. Le ayudará con la importación en caso de que no vaya según lo planeado. Para ejecutar la importación, puede ejecutar el siguiente comando en su consola, o utilizar la importación a través de la interfaz web. Dependiendo de su configuración, la importación de la consola le ofrecerá más comentarios de ayuda.', - 'status_ready_noconfig_text' => 'La importación está lista para comenzar. Toda la configuración que necesitaba hacer ya está hecha. Para ejecutar la importación, puede ejecutar el siguiente comando en su consola, o utilizar la importación a través de la interfaz web. Dependiendo de su configuración, la importación de la consola le ofrecerá más comentarios de ayuda.', - 'status_ready_config' => 'Configuración de descarga', - 'status_ready_start' => 'Comenzar la importación', - 'status_ready_share' => 'Por favor considere descargar su configuración y compartirla en centro de configuración de importación. Esto permitirá a otros usuarios de Firefly III importar sus archivos de manera más fácil.', - 'status_job_new' => 'El trabajo es completamente nuevo.', - 'status_job_configuring' => 'La importación se está configurando.', - 'status_job_configured' => 'La importación está configurada.', - 'status_job_running' => 'La importación se está ejecutando... Por favor, espere.', - 'status_job_error' => 'El trabajo generó un error.', - 'status_job_finished' => '¡La importación ha terminado!', 'status_running_title' => 'La importación se está ejecutando', - 'status_running_placeholder' => 'Por favor espere por la actualización...', - 'status_finished_title' => 'Rutina de importación terminada', - 'status_finished_text' => 'La rutina de importación ha importado sus datos.', - 'status_errors_title' => 'Errores durante la importación', - 'status_errors_single' => 'Ha ocurrido un error durante la importación. No parece ser fatal.', - 'status_errors_multi' => 'Algunos errores ocurrieron durante la importación. No parecen ser fatales.', - 'status_bread_crumb' => 'Estado de la importación', - 'status_sub_title' => 'Estado de la importación', - 'config_sub_title' => 'Configure su importación', - 'status_finished_job' => 'Las :count transacciones importadas pueden ser encontradas en la etiqueta .', - 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', - 'import_with_key' => 'Importar con la clave \':key\'', + 'status_job_running' => 'Please wait, running the import...', + 'status_job_storing' => 'Please wait, storing data...', + 'status_job_rules' => 'Please wait, running rules...', + 'status_fatal_title' => 'Fatal error', + 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', + 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', + 'status_finished_title' => 'Import finished', + 'status_finished_text' => 'The import has finished.', + 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', + 'unknown_import_result' => 'Unknown import result', + 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', + 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', + 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', - // file, upload something - 'file_upload_title' => 'Confirguración de importación (1/4) - Suba su archivo', - 'file_upload_text' => 'Esta rutina le ayudará a importar archivos de su banco a Firefly III. Por favor revise las páginas de ayuda en la esquina superior derecha.', - 'file_upload_fields' => 'Campos', - 'file_upload_help' => 'Seleccione sus archivos', - 'file_upload_config_help' => 'Si previamente ha importado datos en Firefly III, puede tener un archivo de configuración, el cual preestablecerá valores de configuración para usted. Para algunos bancos, otros usuarios han proporcionado amablemente sus archivos de configuración', - 'file_upload_type_help' => 'Seleccione el tipo de archivo que subirá', - 'file_upload_submit' => 'Subir archivos', - // file, upload types - 'import_file_type_csv' => 'CSV (valores separados por comas)', + // general errors and warnings: + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', - // file, initial config for CSV - 'csv_initial_title' => 'Configuración de importación (2/4) - Configuración básica CSV de importación', - 'csv_initial_text' => 'Para poder importar su archivo correctamente, por favor valide las opciones a continuación.', - 'csv_initial_box' => 'Configuración de importación de CSV simple', - 'csv_initial_box_title' => 'Opciones de configuración para importación de CSV simple', - 'csv_initial_header_help' => 'Marque esta casilla si la primera fila de su archivo CSV son los títulos de las columnas.', - 'csv_initial_date_help' => 'Formato de fecha y hora en el CSV. siga un formato como esta paginaindica.El valor por defecto.', - 'csv_initial_delimiter_help' => 'Elija el delimitador de campos de su archivo de entrada. si no esta seguro, la coma es la opción.', - 'csv_initial_import_account_help' => 'Si su archivo CSV NO contiene información sobre su (s) cuenta (s) de activos. Use este menú desplegable para seleccionar a que cuenta pertenecen las transacciones en el archivo CSV.', - 'csv_initial_submit' => 'Continúe con el paso 3/4', - - // file, new options: - 'file_apply_rules_title' => 'Aplicar reglas', - 'file_apply_rules_description' => 'Aplique sus reglas. tenga en cuenta que esto reduce significativamente la importación.', - 'file_match_bills_title' => 'Unir facturas', - 'file_match_bills_description' => 'Haga coincidir sus facturas con los retiros recién creados. Tenga en cuenta que esto reduce significativamente la importación.', - - // file, roles config - 'csv_roles_title' => 'Configuración de importación (3/4) defina el rol de cada columna', - 'csv_roles_text' => 'Cada columna en su archivo CSV contiene cierto datos. Por favor indique que tipo de datos debe esperar el importador. la opción de "map" datos significa que enlazara cada entrada encontrada en la columna con un valor en su base de datos. A menudo una columna mapeada es la columna que contiene el IBAN ya presentes en su base de datos.', - 'csv_roles_table' => 'Tabla', - 'csv_roles_column_name' => 'Nombre de la columna', - 'csv_roles_column_example' => 'Datos de ejemplo de columna', - 'csv_roles_column_role' => 'Significado de los datos de la columna', - 'csv_roles_do_map_value' => 'Mapear estos valores', - 'csv_roles_column' => 'Columna', - 'csv_roles_no_example_data' => 'No hay datos de ejemplo disponibles', - 'csv_roles_submit' => 'Continúe en el paso 4/4', - - // not csv, but normal warning - 'roles_warning' => 'Por lo menos, marque una columna como la columna de importe. también es aconsejable seleccionar una columna para la descripción. la fecha y la cuenta contraria.', - 'foreign_amount_warning' => 'Si usted marca una columna que contiene un importe en una moneda extranjera, usted también debe establecer la columna que contiene que moneda es.', - - // file, map data - 'file_map_title' => 'Configuración de importación (4/4) - Conecta datos de importación a los datos de Firefly III', - 'file_map_text' => 'En las siguientes tablas, el valor de la izquierda muestra información encontrada en el Csv cargado. Es su tarea mapear este valor, si es posible, a un valor ya presente en su base de datos. Firefly Iii respetara este mapeo. Si no hay un valor hacia el cual mapear o no se desea mapear un valor especifico, no seleccione ninguno.', - 'file_map_field_value' => 'Valor del campo', - 'file_map_field_mapped_to' => 'Asignado a', - 'map_do_not_map' => '(no mapear)', - 'file_map_submit' => 'Comenzar la importación', - 'file_nothing_to_map' => 'No hay datos presentes en su archivo que pueda asignar a los valores existentes. Por favor presione "comenzar la importación" para continuar.', - - // map things. + // column roles for CSV import: 'column__ignore' => '(Ignorar esta columna)', 'column_account-iban' => 'Caja de ahorros (IBAN)', 'column_account-id' => 'Identificación de Cuenta de ingresos (coincide con FF3)', @@ -158,48 +261,4 @@ return [ 'column_note' => 'Nota (s)', 'column_internal-reference' => 'Internal reference', - // prerequisites - 'prerequisites' => 'Prerequisitos', - - // bunq - 'bunq_prerequisites_title' => 'Pre requisitos para una importación de bunq', - 'bunq_prerequisites_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'bunq_prerequisites_text_ip' => 'Bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', - 'bunq_do_import' => 'Yes, import from this account', - 'bunq_accounts_title' => 'Bunq accounts', - 'bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', - - // Spectre - 'spectre_title' => 'Importar usando Spectre', - 'spectre_prerequisites_title' => 'Pre requisitos para una importación usando Spectre', - 'spectre_prerequisites_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your secrets page.', - 'spectre_accounts_title' => 'Seleccionar cuentas para importar desde', - 'spectre_accounts_text' => 'Cada cuenta a la izquierda abajo ha sido encontrada por Spectre y puede ser importada en Firefly III. Por favor seleccione la cuenta de activo que debe contener cualquier transacción determinada. Si usted no desea importar desde una cuenta en particular, elimine el cheque de la casilla de verificación.', - 'spectre_do_import' => 'Si, importar desde esta cuenta', - 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', - - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'SWIFT', - 'spectre_extra_key_status' => 'Estatus', - 'spectre_extra_key_card_type' => 'Tipo de tarjeta', - 'spectre_extra_key_account_name' => 'Nombre de la cuenta', - 'spectre_extra_key_client_name' => 'Nombre del cliente', - 'spectre_extra_key_account_number' => 'Numero de cuenta', - 'spectre_extra_key_blocked_amount' => 'Monto bloqueado', - 'spectre_extra_key_available_amount' => 'Monto disponible', - 'spectre_extra_key_credit_limit' => 'Limite de credito', - 'spectre_extra_key_interest_rate' => 'Tasa de interés', - 'spectre_extra_key_expiry_date' => 'Fecha de vencimiento', - 'spectre_extra_key_open_date' => 'Fecha de apertura', - 'spectre_extra_key_current_time' => 'Tiempo actual', - 'spectre_extra_key_current_date' => 'Fecha actual', - 'spectre_extra_key_cards' => 'Tarjetas', - 'spectre_extra_key_units' => 'Unidades', - 'spectre_extra_key_unit_price' => 'Precio unitario', - 'spectre_extra_key_transactions_count' => 'Cuenta de transacciones', - - // various other strings: - 'imported_from_account' => 'Importado de ":account"', ]; diff --git a/resources/lang/es_ES/intro.php b/resources/lang/es_ES/intro.php index 108538f286..85e0787ae5 100644 --- a/resources/lang/es_ES/intro.php +++ b/resources/lang/es_ES/intro.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // index 'index_intro' => 'Bienvenido a la página de índice de Firefly III. Por favor tómate tu tiempo para revisar esta guía y que puedas hacerte una idea de cómo funciona Firefly III.', diff --git a/resources/lang/es_ES/list.php b/resources/lang/es_ES/list.php index 8d217d74ad..2aa21df77b 100644 --- a/resources/lang/es_ES/list.php +++ b/resources/lang/es_ES/list.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'buttons' => 'Botones', 'icon' => 'Icono', @@ -111,10 +112,15 @@ return [ 'sepa-cc' => 'SEPA Clearing Code', 'sepa-ep' => 'SEPA External Purpose', 'sepa-ci' => 'SEPA Creditor Identifier', + 'external_id' => 'External ID', 'account_at_bunq' => 'Account with bunq', 'file_name' => 'File name', 'file_size' => 'File size', 'file_type' => 'File type', 'attached_to' => 'Attached to', 'file_exists' => 'File exists', + 'spectre_bank' => 'Bank', + 'spectre_last_use' => 'Last login', + 'spectre_status' => 'Status', + 'bunq_payment_id' => 'bunq payment ID', ]; diff --git a/resources/lang/es_ES/pagination.php b/resources/lang/es_ES/pagination.php index 2d1f80dee3..faf55a8055 100644 --- a/resources/lang/es_ES/pagination.php +++ b/resources/lang/es_ES/pagination.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'previous' => '« Anterior', 'next' => 'Siguiente »', diff --git a/resources/lang/es_ES/passwords.php b/resources/lang/es_ES/passwords.php index fa4177fdd0..893ad8a347 100644 --- a/resources/lang/es_ES/passwords.php +++ b/resources/lang/es_ES/passwords.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'password' => 'Las contraseñas deben tener al menos seis caracteres y coincidir entre sí.', 'user' => 'No podemos encontrar un usuario con esa dirección de correo electrónico.', diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 551c826cd7..227ebdc572 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'iban' => 'Este no es un IBAN válido.', 'source_equals_destination' => 'The source account equals the destination account', @@ -109,7 +110,8 @@ return [ 'in_array' => 'El campo :attribute no existe en :other.', 'present' => 'El campo :attribute debe estar presente.', 'amount_zero' => 'La cantidad total no puede ser cero', - 'secure_password' => 'Esta contraseña no es segura. Por favor inténtalo de nuevo. Para más información, visita https://goo.gl/NCh2tN', + 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', 'attributes' => [ 'email' => 'dirección de correo electrónico', 'description' => 'descripcion', From ad7e564f1460170689d75508816d37293c9212fc Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 14:20:52 +0200 Subject: [PATCH 160/182] Update French language files [skip ci] --- resources/lang/fr_FR/auth.php | 16 +- resources/lang/fr_FR/bank.php | 5 +- resources/lang/fr_FR/breadcrumbs.php | 9 +- resources/lang/fr_FR/components.php | 3 +- resources/lang/fr_FR/config.php | 5 +- resources/lang/fr_FR/csv.php | 5 +- resources/lang/fr_FR/demo.php | 7 +- resources/lang/fr_FR/firefly.php | 64 +++--- resources/lang/fr_FR/form.php | 12 +- resources/lang/fr_FR/import.php | 317 ++++++++++++++++----------- resources/lang/fr_FR/intro.php | 5 +- resources/lang/fr_FR/list.php | 10 +- resources/lang/fr_FR/pagination.php | 5 +- resources/lang/fr_FR/passwords.php | 5 +- resources/lang/fr_FR/validation.php | 8 +- 15 files changed, 269 insertions(+), 207 deletions(-) diff --git a/resources/lang/fr_FR/auth.php b/resources/lang/fr_FR/auth.php index 9ba5b70756..689b6966af 100644 --- a/resources/lang/fr_FR/auth.php +++ b/resources/lang/fr_FR/auth.php @@ -1,9 +1,8 @@ . */ -return [ - /* - |-------------------------------------------------------------------------- - | Authentication Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used during authentication for various - | messages that we need to display to the user. You are free to modify - | these language lines according to your application's requirements. - | - */ +declare(strict_types=1); +return [ 'failed' => 'Ces identifiants n\'ont aucune correspondance.', 'throttle' => 'Trop de tentatives de connexion. Veuillez essayer à nouveau dans :seconds secondes.', ]; diff --git a/resources/lang/fr_FR/bank.php b/resources/lang/fr_FR/bank.php index 8826020cbf..5d00b1e685 100644 --- a/resources/lang/fr_FR/bank.php +++ b/resources/lang/fr_FR/bank.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/fr_FR/breadcrumbs.php b/resources/lang/fr_FR/breadcrumbs.php index d6cd58e38d..044aade71c 100644 --- a/resources/lang/fr_FR/breadcrumbs.php +++ b/resources/lang/fr_FR/breadcrumbs.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'home' => 'Accueil', 'edit_currency' => 'Modifier la devise "%name"', @@ -48,9 +49,9 @@ return [ 'edit_journal' => 'Éditer la transaction ":description"', 'edit_reconciliation' => 'Éditer ":description"', 'delete_journal' => 'Supprimer la transaction ":description"', - 'tags' => 'Mots-clés', + 'tags' => 'Tags', 'createTag' => 'Créer un nouveau mot-clé', 'edit_tag' => 'Éditer le mot-clé ":tag"', - 'delete_tag' => 'Supprimer le mot-clé ":tag"', + 'delete_tag' => 'Supprimer le tag ":tag"', 'delete_journal_link' => 'Supprimer le lien entre les transactions', ]; diff --git a/resources/lang/fr_FR/components.php b/resources/lang/fr_FR/components.php index 554ef63b9e..aa225dcf01 100644 --- a/resources/lang/fr_FR/components.php +++ b/resources/lang/fr_FR/components.php @@ -1,5 +1,4 @@ . */ +declare(strict_types=1); + return [ // profile 'personal_access_tokens' => 'Jeton d\'accès personnel', diff --git a/resources/lang/fr_FR/config.php b/resources/lang/fr_FR/config.php index 8a41af96fe..763721bdba 100644 --- a/resources/lang/fr_FR/config.php +++ b/resources/lang/fr_FR/config.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'html_language' => 'fr', 'locale' => 'fr, French, fr_FR, fr_FR.utf8, fr_FR.UTF-8', diff --git a/resources/lang/fr_FR/csv.php b/resources/lang/fr_FR/csv.php index 622a49202a..aae109a40a 100644 --- a/resources/lang/fr_FR/csv.php +++ b/resources/lang/fr_FR/csv.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/fr_FR/demo.php b/resources/lang/fr_FR/demo.php index e76bc8b812..94171d0aa6 100644 --- a/resources/lang/fr_FR/demo.php +++ b/resources/lang/fr_FR/demo.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'no_demo_text' => 'Désolé, il n’y a aucun texte supplémentaire de démonstration ou d\'explication pour cette page.', 'see_help_icon' => 'Cependant, l\'icône située dans le coin supérieur droit peut vous en dire plus.', @@ -32,5 +33,5 @@ return [ 'currencies-index' => 'Firefly III prend en charge plusieurs devises. Bien que l\'Euro soit la devise par défaut, cette dernière peut être changée pour le Dollar américain et de nombreuses autres devises. Comme vous pouvez le remarquer une petite sélection des monnaies a été incluse, mais vous pouvez ajouter vos propres devises si vous le souhaitez. Gardez à l\'esprit que la modification de la devise par défaut ne modifie pas la monnaie des transactions existantes : Firefly III prend en charge l’utilisation de plusieurs devises en même temps.', 'transactions-index' => 'Ces dépenses, dépôts et transferts ne sont pas particulièrement imaginatifs. Ils ont été générés automatiquement.', 'piggy-banks-index' => 'Comme vous pouvez le voir, il y a trois tirelires. Utilisez les boutons plus et moins pour influer sur le montant d’argent dans chaque tirelire. Cliquez sur le nom de la tirelire pour voir l’administration pour chaque tirelire.', - 'import-index' => 'Bien sûr, n’importe quel fichier CSV peut être importé dans Firefly III', + 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', ]; diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 8a463c3da4..2f53b6b095 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // general stuff: 'close' => 'Fermer', @@ -84,7 +85,7 @@ return [ 'two_factor_lost_intro' => 'Malheureusement, ce n’est pas quelque chose que vous pouvez réinitialiser depuis l’interface web. Vous avez deux choix.', 'two_factor_lost_fix_self' => 'Si vous exécutez votre propre instance de Firefly III, vérifiez les logs dans storage/logs pour obtenir des instructions.', 'two_factor_lost_fix_owner' => 'Dans le cas contraire, contactez le propriétaire du site par courriel :site_owner et demandez-lui de réinitialiser votre authentification à deux facteurs.', - 'warning_much_data' => ':days de données peuvent prendre un certain temps à charger.', + 'warning_much_data' => ':days jours de données peuvent prendre un certain temps à charger.', 'registered' => 'Vous avez été enregistré avec succès !', 'Default asset account' => 'Compte d’actif par défaut', 'no_budget_pointer' => 'Vous semblez n’avoir encore aucun budget. Vous devez en créer un sur la page des budgets. Les budgets peuvent vous aider à garder une trace des dépenses.', @@ -179,6 +180,7 @@ return [ 'authorization_request_intro' => ':client demande l\'autorisation d\'accéder à votre administration financière. Souhaitez-vous autoriser :client à accéder à ces enregistrements?', 'scopes_will_be_able' => 'Cette application pourra :', 'button_authorize' => 'Autoriser', + 'none_in_select_list' => '(aucun)', // check for updates: 'update_check_title' => 'Vérifier les mises à jour', @@ -267,10 +269,10 @@ return [ 'move_rule_group_down' => 'Descendre le groupe de règles', 'save_rules_by_moving' => 'Enregistrer ces règles en les déplaçant vers un autre groupe de règles :', 'make_new_rule' => 'Créer une nouvelle règle dans le groupe de règles ":title"', - 'rule_is_strict' => 'strict rule', - 'rule_is_not_strict' => 'non-strict rule', + 'rule_is_strict' => 'règle stricte', + 'rule_is_not_strict' => 'règle non stricte', 'rule_help_stop_processing' => 'Lorsque vous cochez cette case, les règles suivantes de ce groupe ne seront pas exécutées.', - 'rule_help_strict' => 'In strict rules ALL triggers must fire for the action(s) to be executed. In non-strict rules, ANY trigger is enough for the action(s) to be executed.', + 'rule_help_strict' => 'Dans les règles strictes, TOUS les déclencheurs doivent être activés pour les actions à exécuter. Dans les règles non strictes, N\'IMPORTE QUEL déclencheur est suffisant pour que l\'action soit exécutée.', 'rule_help_active' => 'Les règles inactives ne se déclencheront jamais.', 'stored_new_rule' => 'Nouvelle règle créée avec le titre ":title"', 'deleted_rule' => 'Règle supprimée avec le titre ":title"', @@ -541,7 +543,7 @@ return [ 'attachment_deleted' => 'Pièce jointe ":name" supprimée', 'attachment_updated' => 'Pièce jointe ":name" mise à jour', 'upload_max_file_size' => 'Taille maximum du fichier : :size', - 'list_all_attachments' => 'List of all attachments', + 'list_all_attachments' => 'Liste de toutes les pièces jointes', // transaction index 'title_expenses' => 'Dépenses', @@ -584,7 +586,7 @@ return [ 'converted_to_Deposit' => 'La transaction a été convertie en dépôt', 'converted_to_Transfer' => 'La transaction a été convertie en transfert', 'invalid_convert_selection' => 'Le compte que vous avez sélectionné est déjà utilisé dans cette transaction ou n\'existe pas.', - 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', + 'source_or_dest_invalid' => 'Impossible de trouver les détails de transaction corrects. La conversion n\'est pas possible.', // create new stuff: 'create_new_withdrawal' => 'Créer une nouvelle dépense', @@ -641,8 +643,8 @@ return [ 'over_budget_warn' => ' Normalement vous budgétez :amount par jour. Là c\'est :over_amount par jour.', // bills: - 'match_between_amounts' => 'Bill matches transactions between :low and :high.', - 'bill_related_rules' => 'Rules related to this bill', + 'match_between_amounts' => 'Factures correspondes à des transactions entre :low et :high.', + 'bill_related_rules' => 'Règles reliées à cette facture', 'repeats' => 'Répétitions', 'connected_journals' => 'Opérations liées', 'auto_match_on' => 'Automatiquement mis en correspondance par Firefly III', @@ -652,13 +654,13 @@ return [ 'deleted_bill' => 'Facture ":name" supprimée', 'edit_bill' => 'Modifier la facture : ":name"', 'more' => 'Plus', - 'rescan_old' => 'Run rules again, on all transactions', + 'rescan_old' => 'Exécuter les règles à nouveau, sur toutes les transactions', 'update_bill' => 'Mettre à jour la facture', 'updated_bill' => 'Facture ":name" mise à jour', 'store_new_bill' => 'Créer une nouvelle facture', 'stored_new_bill' => 'Nouvelle facture ":name" créée', 'cannot_scan_inactive_bill' => 'Les factures inactives ne peuvent pas être analysées.', - 'rescanned_bill' => 'Rescanned everything, and linked :total transaction(s) to the bill.', + 'rescanned_bill' => 'Tout a été redéfini et lié aux :total transaction(s) de la facture.', 'average_bill_amount_year' => 'Montant moyen des factures ( :year)', 'average_bill_amount_overall' => 'Montant moyen de la facture (global)', 'bill_is_active' => 'Facture en cours', @@ -666,6 +668,7 @@ return [ 'bill_will_automatch' => 'La facture sera automatiquement liée aux transactions correspondantes', 'skips_over' => 'saute', 'bill_store_error' => 'Une erreur inattendue s\'est produite lors du stockage de votre nouvelle facture. Veuillez vérifier les fichiers journaux', + 'list_inactive_rule' => 'règle inactive', // accounts: 'details_for_asset' => 'Détails pour le compte d’actif ":name"', @@ -801,6 +804,7 @@ return [ 'opt_group_savingAsset' => 'Comptes d\'épargne', 'opt_group_sharedAsset' => 'Comptes d\'actifs partagés', 'opt_group_ccAsset' => 'Cartes de crédit', + 'opt_group_cashWalletAsset' => 'Portefeuilles d\'argent', 'notes' => 'Notes', 'unknown_journal_error' => 'Impossible de stocker la transaction. Veuillez vérifier les fichiers journaux.', @@ -812,10 +816,11 @@ return [ 'savings_balance_text' => 'Firefly III créera automatiquement un compte d\'épargne pour vous. Par défaut, il n\'y aura pas d\'argent dans votre compte d\'épargne, mais si vous le dites à Firefly III, le solde sera stocké en tant que tel.', 'finish_up_new_user' => 'C\'est tout ! Vous pouvez continuer en appuyant sur Envoyer. Vous passerez à l\'index de Firefly III.', 'stored_new_accounts_new_user' => 'Super ! Vos nouveaux comptes ont été créés.', - 'set_preferred_language' => 'If you prefer to use Firefly III in another language, please indicate so here.', - 'language' => 'Language', - 'new_savings_account' => ':bank_name savings account', - 'cash_wallet' => 'Cash wallet', + 'set_preferred_language' => 'Si vous préférez utiliser Firefly III dans une autre langue, veuillez l\'indiquer ici.', + 'language' => 'Langage', + 'new_savings_account' => ':bank_name compte d\'épargne', + 'cash_wallet' => 'Porte-monnaie', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Vos comptes', @@ -964,7 +969,7 @@ return [ 'account_role_sharedAsset' => 'Compte d\'actif partagé', 'account_role_savingAsset' => 'Compte d’épargne', 'account_role_ccAsset' => 'Carte de crédit', - 'account_role_cashWalletAsset' => 'Cash wallet', + 'account_role_cashWalletAsset' => 'Porte-monnaie', 'budget_chart_click' => 'Cliquez sur le nom du budget dans le tableau ci-dessus pour voir un graphique.', 'category_chart_click' => 'Cliquez sur un nom de catégorie dans le tableau ci-dessus pour voir un graphique.', 'in_out_accounts' => 'Gagné et dépensé par compte', @@ -1013,6 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Retirer l’argent de la tirelire ":name"', 'add' => 'Ajouter', 'no_money_for_piggy' => 'Vous n\'avez pas d\'argent à placer dans cette tirelire.', + 'suggested_savings_per_month' => 'Suggested per month', 'remove' => 'Enlever', 'max_amount_add' => 'Le montant maximum que vous pouvez ajouter est', @@ -1024,7 +1030,7 @@ return [ 'events' => 'Evènements', 'target_amount' => 'Montant cible', 'start_date' => 'Date de début', - 'no_start_date' => 'No start date', + 'no_start_date' => 'Pas de date de début', 'target_date' => 'Date cible', 'no_target_date' => 'Aucune date butoir', 'table' => 'Tableau', @@ -1148,27 +1154,9 @@ return [ 'no_edit_multiple_left' => 'Vous n\'avez sélectionné aucune transaction valide à éditer.', 'cannot_convert_split_journal' => 'Vous ne pouvez pas convertir une transaction ventilée', - // import bread crumbs and titles: - 'import' => 'Import', - 'import_data' => 'Importer des données', - 'import_general_index_file' => 'Importer un fichier', - 'import_from_bunq' => 'Importer depuis bunq', - 'import_using_spectre' => 'Importer en utilisant Spectre', - 'import_using_plaid' => 'Importer en utilisant Plaid', - 'import_config_bread_crumb' => 'Configurez votre import', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Importer des données dans Firefly III', - 'import_index_sub_title' => 'Index', - 'import_general_index_intro' => 'Bienvenue dans la routine d\'importation de Firefly III. Il existe différentes façons d\'importer des données dans Firefly III, affichées ici sous forme de boutons.', - 'upload_error' => 'Le fichier que vous avez téléchargé n\'a pas pu être traité. Peut-être qu\'il s\'agit d\'un type de fichier ou d\'un encodage invalide. Plus d\'informations dans les fichiers journaux.', - 'reset_import_settings_title' => 'Reset import configuration', - 'reset_import_settings_text' => 'You can use these links to reset your import settings for specific providers. This is useful when bad settings stop you from importing data.', - 'reset_settings_bunq' => 'Remove bunq API key, local external IP address and bunq related RSA keys.', - 'reset_settings_spectre' => 'Remove Spectre secrets and ID\'s. This will also remove your Spectre keypair. Remember to update the new one.', - 'settings_reset_for_bunq' => 'Bunq settings reset.', - 'settings_reset_for_spectre' => 'Spectre settings reset.', - + 'import_data' => 'Importer des données', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Cette fonction n\'est pas disponible lorsque vous utilisez Firefly III dans un environnement Sandstorm.io.', diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php index a5c0395e57..5c57f4717a 100644 --- a/resources/lang/fr_FR/form.php +++ b/resources/lang/fr_FR/form.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // new user: 'bank_name' => 'Nom de la banque', @@ -184,6 +185,13 @@ return [ 'blocked' => 'Est bloqué?', 'blocked_code' => 'Raison du blocage', + // import + 'apply_rules' => 'Apply rules', + 'artist' => 'Artist', + 'album' => 'Album', + 'song' => 'Song', + + // admin 'domain' => 'Domaine', 'single_user_mode' => 'Désactiver le formulaire d\'inscription', diff --git a/resources/lang/fr_FR/import.php b/resources/lang/fr_FR/import.php index b12206bc30..a111bb834e 100644 --- a/resources/lang/fr_FR/import.php +++ b/resources/lang/fr_FR/import.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ - // status of import: + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Import data into Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', + 'job_configuration_breadcrumb' => 'Configuration for ":key"', + 'job_status_breadcrumb' => 'Import status for ":key"', + 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', + + // index page: + 'general_index_title' => 'Import a file', + 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + // import provider strings (index): + 'button_fake' => 'Fake an import', + 'button_file' => 'Import a file', + 'button_bunq' => 'Import from bunq', + 'button_spectre' => 'Import using Spectre', + 'button_plaid' => 'Import using Plaid', + 'button_yodlee' => 'Import using Yodlee', + 'button_quovo' => 'Import using Quovo', + // global config box (index) + 'global_config_title' => 'Global import configuration', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + // prerequisites box (index) + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + // provider config box (index) + 'can_config_title' => 'Import configuration', + 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'do_config_fake' => 'Configuration for the fake provider', + 'do_config_file' => 'Configuration for file imports', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', + + // prerequisites: + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + + // job configuration: + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (valeurs séparées par des virgules)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', + 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', + 'job_config_uc_apply_rules_title' => 'Apply rules', + 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', + 'job_config_uc_specifics_title' => 'Bank-specific options', + 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', + 'job_config_uc_submit' => 'Continue', + 'invalid_import_account' => 'You have selected an invalid account to import into.', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Choose your login', + 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', + 'spectre_login_status_active' => 'Active', + 'spectre_login_status_inactive' => 'Inactive', + 'spectre_login_status_disabled' => 'Disabled', + 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', + 'job_config_spectre_accounts_title' => 'Select accounts to import from', + 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', + 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', + 'spectre_do_not_import' => '(do not import)', + 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'imported_from_account' => 'Imported from ":account"', + 'spectre_account_with_number' => 'Account :number', + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq accounts', + 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', + 'bunq_no_mapping' => 'It seems you have not selected any accounts.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Status', + 'spectre_extra_key_card_type' => 'Card type', + 'spectre_extra_key_account_name' => 'Account name', + 'spectre_extra_key_client_name' => 'Client name', + 'spectre_extra_key_account_number' => 'Account number', + 'spectre_extra_key_blocked_amount' => 'Blocked amount', + 'spectre_extra_key_available_amount' => 'Available amount', + 'spectre_extra_key_credit_limit' => 'Credit limit', + 'spectre_extra_key_interest_rate' => 'Interest rate', + 'spectre_extra_key_expiry_date' => 'Expiry date', + 'spectre_extra_key_open_date' => 'Open date', + 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_current_date' => 'Current date', + 'spectre_extra_key_cards' => 'Cards', + 'spectre_extra_key_units' => 'Units', + 'spectre_extra_key_unit_price' => 'Unit price', + 'spectre_extra_key_transactions_count' => 'Transaction count', + + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Create better descriptions in ING exports', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Fixes potential problems with PC files', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', + 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'job_config_roles_submit' => 'Continue', + 'job_config_roles_column_name' => 'Name of column', + 'job_config_roles_column_example' => 'Column example data', + 'job_config_roles_column_role' => 'Column data meaning', + 'job_config_roles_do_map_value' => 'Map these values', + 'job_config_roles_no_example' => 'No example data available', + 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'job_config_roles_colum_count' => 'Column', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'job_config_field_value' => 'Field value', + 'job_config_field_mapped' => 'Mapped to', + 'map_do_not_map' => '(ne pas mapper)', + 'job_config_map_submit' => 'Start the import', + + + // import status page: + 'import_with_key' => 'Importer avec la clé \':key\'', 'status_wait_title' => 'Veuillez patienter...', 'status_wait_text' => 'Cette boîte disparaîtra dans un instant.', - 'status_fatal_title' => 'Une erreur fatale est survenue', - 'status_fatal_text' => 'Une erreur fatale est survenue que le traitement d\'importation ne peut pas récupérer. Voir l\'explication en rouge ci-dessous.', - 'status_fatal_more' => 'Si l\'erreur est un time-out, l\'importation sera arrêtée pendant son traitement. Pour certaines configurations de serveur, ce n\'est que le serveur qui s\'est arrêté alors que l\'importation continue de fonctionner en arrière-plan. Pour vérifier cela, consultez les fichiers journaux. Si le problème persiste, envisagez d\'importer plutôt par ligne de commande.', - 'status_ready_title' => 'L\'importation est prête à démarrer', - 'status_ready_text' => 'L\'importation est prête à démarrer. Toute la configuration requise été effectuée. Vous pouvez téléchargez le fichier de configuration. Cela vous permettra de recommencer rapidement l\'importation si tout ne fonctionnait pas comme prévu. Pour exécuter l\'importation, vous pouvez soit exécuter la commande suivante dans la console du serveur, soit exécuter l\'importation depuis cette page web. Selon votre configuration générale, l\'importation via la console vous donnera plus de détails.', - 'status_ready_noconfig_text' => 'L\'importation est prête à démarrer. Toute la configuration requise été effectuée. Pour exécuter l\'importation, vous pouvez soit exécuter la commande suivante dans la console du serveur, soit exécuter l\'importation depuis cette page web. Selon votre configuration générale, l\'importation via la console vous donnera plus de détails.', - 'status_ready_config' => 'Télécharger la configuration', - 'status_ready_start' => 'Démarrer l\'importation', - 'status_ready_share' => 'Vous pouvez télécharger votre configuration et de la partager dans le centre de configuration d\'import. Cela permettra à d\'autres utilisateurs de Firefly III d\'importer leurs fichiers plus facilement.', - 'status_job_new' => 'Le travail est tout récent.', - 'status_job_configuring' => 'L\'importation est en cours de configuration.', - 'status_job_configured' => 'L\'importation est configurée.', - 'status_job_running' => 'L\'importation est en cours... Veuillez patienter...', - 'status_job_error' => 'Le travail a généré une erreur.', - 'status_job_finished' => 'L\'importation est terminée !', 'status_running_title' => 'L\'importation est en cours d\'exécution', - 'status_running_placeholder' => 'Attendez pour une mise à jour...', - 'status_finished_title' => 'Le traitement d\'importation est terminé', - 'status_finished_text' => 'Le traitement d\'importation a importé vos données.', - 'status_errors_title' => 'Erreurs lors de l\'importation', - 'status_errors_single' => 'Une erreur est survenue lors de l\'importation. Cela ne semble pas être fatal.', - 'status_errors_multi' => 'Certaines erreurs sont survenues lors de l\'importation. Celles-ci ne semblent pas être fatales.', - 'status_bread_crumb' => 'Statut d\'importation', - 'status_sub_title' => 'Statut d\'importation', - 'config_sub_title' => 'Configurez votre importation', - 'status_finished_job' => 'The :count transactions imported can be found in tag :tag.', - 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', - 'import_with_key' => 'Importer avec la clé \':key\'', + 'status_job_running' => 'Please wait, running the import...', + 'status_job_storing' => 'Please wait, storing data...', + 'status_job_rules' => 'Please wait, running rules...', + 'status_fatal_title' => 'Fatal error', + 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', + 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', + 'status_finished_title' => 'Import finished', + 'status_finished_text' => 'The import has finished.', + 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', + 'unknown_import_result' => 'Unknown import result', + 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', + 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', + 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', - // file, upload something - 'file_upload_title' => 'Configuration de l\'importation (1/4) - Téléchargez votre fichier', - 'file_upload_text' => 'Ce traitement vous aidera à importer des fichiers de votre banque dans Firefly III. Consultez les pages d\'aide en haut à droite.', - 'file_upload_fields' => 'Champs', - 'file_upload_help' => 'Sélectionnez votre fichier', - 'file_upload_config_help' => 'Si vous avez précédemment importé des données dans Firefly III, vous avez peut-être téléchargé un fichier de configuration qui définit les relations entre les différents champs. Pour certaines banques, des utilisateurs ont bien voulu partager leur fichier ici : fichiers de configuration.', - 'file_upload_type_help' => 'Sélectionnez le type de fichier que vous allez télécharger', - 'file_upload_submit' => 'Envoyer des fichiers', - // file, upload types - 'import_file_type_csv' => 'CSV (valeurs séparées par des virgules)', + // general errors and warnings: + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', - // file, initial config for CSV - 'csv_initial_title' => 'Configuration d\'importation (2/4) - Configuration d\'importation CSV', - 'csv_initial_text' => 'Pour pouvoir importer votre fichier correctement, veuillez valider les options ci-dessous.', - 'csv_initial_box' => 'Configuration d\'importation CSV de base', - 'csv_initial_box_title' => 'Options de configuration de l\'importation CSV de base', - 'csv_initial_header_help' => 'Cochez cette case si la première ligne de votre fichier CSV contient les entêtes des colonnes.', - 'csv_initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'csv_initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'csv_initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'csv_initial_submit' => 'Passez à l’étape 3/4', - - // file, new options: - 'file_apply_rules_title' => 'Appliquer les règles', - 'file_apply_rules_description' => 'Apply your rules. Note that this slows the import significantly.', - 'file_match_bills_title' => 'Faire correspondre les factures', - 'file_match_bills_description' => 'Match your bills to newly created withdrawals. Note that this slows the import significantly.', - - // file, roles config - 'csv_roles_title' => 'Import setup (3/4) - Define each column\'s role', - 'csv_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', - 'csv_roles_table' => 'Tableau', - 'csv_roles_column_name' => 'Nom de colonne', - 'csv_roles_column_example' => 'Données d\'exemple de colonne', - 'csv_roles_column_role' => 'Signification des données de colonne', - 'csv_roles_do_map_value' => 'Mapper ces valeurs', - 'csv_roles_column' => 'Colonne', - 'csv_roles_no_example_data' => 'Aucun exemple de données disponible', - 'csv_roles_submit' => 'Passez à l’étape 4/4', - - // not csv, but normal warning - 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', - 'foreign_amount_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', - - // file, map data - 'file_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', - 'file_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', - 'file_map_field_value' => 'Valeur du champ', - 'file_map_field_mapped_to' => 'Mappé à', - 'map_do_not_map' => '(ne pas mapper)', - 'file_map_submit' => 'Démarrez l\'importation', - 'file_nothing_to_map' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', - - // map things. + // column roles for CSV import: 'column__ignore' => '(ignorer cette colonne)', 'column_account-iban' => 'Compte d’actif (IBAN)', 'column_account-id' => 'Asset account ID (matching FF3)', @@ -151,55 +254,11 @@ return [ 'column_sepa-ci' => 'SEPA Creditor Identifier', 'column_sepa-ep' => 'SEPA External Purpose', 'column_sepa-country' => 'SEPA Country Code', - 'column_tags-comma' => 'Tags (comma separated)', - 'column_tags-space' => 'Tags (space separated)', + 'column_tags-comma' => 'Tags (séparés par des virgules)', + 'column_tags-space' => 'Tags (séparé par un espace)', 'column_account-number' => 'Asset account (account number)', 'column_opposing-number' => 'Opposing account (account number)', 'column_note' => 'Note(s)', 'column_internal-reference' => 'Internal reference', - // prerequisites - 'prerequisites' => 'Prerequisites', - - // bunq - 'bunq_prerequisites_title' => 'Prerequisites for an import from bunq', - 'bunq_prerequisites_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'bunq_prerequisites_text_ip' => 'Bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', - 'bunq_do_import' => 'Yes, import from this account', - 'bunq_accounts_title' => 'Bunq accounts', - 'bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', - - // Spectre - 'spectre_title' => 'Import using Spectre', - 'spectre_prerequisites_title' => 'Prerequisites for an import using Spectre', - 'spectre_prerequisites_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your secrets page.', - 'spectre_accounts_title' => 'Select accounts to import from', - 'spectre_accounts_text' => 'Each account on the left below has been found by Spectre and can be imported into Firefly III. Please select the asset account that should hold any given transactions. If you do not wish to import from any particular account, remove the check from the checkbox.', - 'spectre_do_import' => 'Yes, import from this account', - 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', - - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'SWIFT', - 'spectre_extra_key_status' => 'Status', - 'spectre_extra_key_card_type' => 'Card type', - 'spectre_extra_key_account_name' => 'Account name', - 'spectre_extra_key_client_name' => 'Client name', - 'spectre_extra_key_account_number' => 'Account number', - 'spectre_extra_key_blocked_amount' => 'Blocked amount', - 'spectre_extra_key_available_amount' => 'Available amount', - 'spectre_extra_key_credit_limit' => 'Credit limit', - 'spectre_extra_key_interest_rate' => 'Interest rate', - 'spectre_extra_key_expiry_date' => 'Expiry date', - 'spectre_extra_key_open_date' => 'Open date', - 'spectre_extra_key_current_time' => 'Current time', - 'spectre_extra_key_current_date' => 'Current date', - 'spectre_extra_key_cards' => 'Cards', - 'spectre_extra_key_units' => 'Units', - 'spectre_extra_key_unit_price' => 'Unit price', - 'spectre_extra_key_transactions_count' => 'Transaction count', - - // various other strings: - 'imported_from_account' => 'Imported from ":account"', ]; diff --git a/resources/lang/fr_FR/intro.php b/resources/lang/fr_FR/intro.php index 2b4ed6d7fb..103c316b23 100644 --- a/resources/lang/fr_FR/intro.php +++ b/resources/lang/fr_FR/intro.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // index 'index_intro' => 'Bienvenue sur la page index de Firefly III. Veuillez prendre le temps de parcourir l\'introduction pour comprendre comment Firefly III fonctionne.', diff --git a/resources/lang/fr_FR/list.php b/resources/lang/fr_FR/list.php index 0001821206..ccafd1e385 100644 --- a/resources/lang/fr_FR/list.php +++ b/resources/lang/fr_FR/list.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'buttons' => 'Boutons', 'icon' => 'Icône', @@ -111,10 +112,15 @@ return [ 'sepa-cc' => 'Code de compensation SEPA', 'sepa-ep' => 'Objectif externe SEPA', 'sepa-ci' => 'Identifiant SEPA Creditor', + 'external_id' => 'External ID', 'account_at_bunq' => 'Compte avec bunq', 'file_name' => 'File name', 'file_size' => 'File size', 'file_type' => 'File type', 'attached_to' => 'Attached to', 'file_exists' => 'File exists', + 'spectre_bank' => 'Bank', + 'spectre_last_use' => 'Last login', + 'spectre_status' => 'Status', + 'bunq_payment_id' => 'bunq payment ID', ]; diff --git a/resources/lang/fr_FR/pagination.php b/resources/lang/fr_FR/pagination.php index d58666a2e4..b07c1ca899 100644 --- a/resources/lang/fr_FR/pagination.php +++ b/resources/lang/fr_FR/pagination.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'previous' => '« Précédent', 'next' => 'Suivant »', diff --git a/resources/lang/fr_FR/passwords.php b/resources/lang/fr_FR/passwords.php index 7a6e781409..e8f757293f 100644 --- a/resources/lang/fr_FR/passwords.php +++ b/resources/lang/fr_FR/passwords.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'password' => 'Les mots de passe doivent contenir au moins six caractères et correspondre à la confirmation.', 'user' => 'Nous ne pouvons pas trouver d\'utilisateur avec cette adresse e-mail.', diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php index 68e8799c02..6ce0d06474 100644 --- a/resources/lang/fr_FR/validation.php +++ b/resources/lang/fr_FR/validation.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'iban' => 'Il ne s\'agit pas d\'un IBAN valide.', 'source_equals_destination' => 'Le compte source est égal au compte de destination', @@ -109,7 +110,8 @@ return [ 'in_array' => 'Le champ :attribute n\'existe pas dans :other.', 'present' => 'Le champs :attribute doit être rempli.', 'amount_zero' => 'Le montant total ne peut pas être zéro', - 'secure_password' => 'Ce n’est pas un mot de passe sécurisé. S’il vous plaît essayer de nouveau. Pour plus d’informations, visitez https://goo.gl/NCh2tN', + 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', 'attributes' => [ 'email' => 'adresse email', 'description' => 'description', From aa3ed40430e21b33a1140b7fdd2a8a6a33ad33cc Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 14:21:14 +0200 Subject: [PATCH 161/182] Update Indonesian language files [skip ci] --- resources/lang/id_ID/auth.php | 16 +- resources/lang/id_ID/bank.php | 5 +- resources/lang/id_ID/breadcrumbs.php | 5 +- resources/lang/id_ID/components.php | 3 +- resources/lang/id_ID/config.php | 5 +- resources/lang/id_ID/csv.php | 5 +- resources/lang/id_ID/demo.php | 7 +- resources/lang/id_ID/firefly.php | 32 +-- resources/lang/id_ID/form.php | 12 +- resources/lang/id_ID/import.php | 313 ++++++++++++++++----------- resources/lang/id_ID/intro.php | 5 +- resources/lang/id_ID/list.php | 10 +- resources/lang/id_ID/pagination.php | 5 +- resources/lang/id_ID/passwords.php | 5 +- resources/lang/id_ID/validation.php | 8 +- 15 files changed, 249 insertions(+), 187 deletions(-) diff --git a/resources/lang/id_ID/auth.php b/resources/lang/id_ID/auth.php index d5c89b1738..a0fabdfc2b 100644 --- a/resources/lang/id_ID/auth.php +++ b/resources/lang/id_ID/auth.php @@ -1,9 +1,8 @@ . */ -return [ - /* - |-------------------------------------------------------------------------- - | Authentication Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used during authentication for various - | messages that we need to display to the user. You are free to modify - | these language lines according to your application's requirements. - | - */ +declare(strict_types=1); +return [ 'failed' => 'Kredensial ini tidak sesuai dengan catatan kami.', 'throttle' => 'Terlalu banyak upaya login. Silakan coba lagi dalam :seconds detik.', ]; diff --git a/resources/lang/id_ID/bank.php b/resources/lang/id_ID/bank.php index 8826020cbf..5d00b1e685 100644 --- a/resources/lang/id_ID/bank.php +++ b/resources/lang/id_ID/bank.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/id_ID/breadcrumbs.php b/resources/lang/id_ID/breadcrumbs.php index 37012db64f..a6e166fd57 100644 --- a/resources/lang/id_ID/breadcrumbs.php +++ b/resources/lang/id_ID/breadcrumbs.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'home' => 'Beranda', 'edit_currency' => 'Edit mata uang ":name"', diff --git a/resources/lang/id_ID/components.php b/resources/lang/id_ID/components.php index 50092e06fd..6299088f8e 100644 --- a/resources/lang/id_ID/components.php +++ b/resources/lang/id_ID/components.php @@ -1,5 +1,4 @@ . */ +declare(strict_types=1); + return [ // profile 'personal_access_tokens' => 'Personal access tokens', diff --git a/resources/lang/id_ID/config.php b/resources/lang/id_ID/config.php index 1d5d9e7592..64659774cd 100644 --- a/resources/lang/id_ID/config.php +++ b/resources/lang/id_ID/config.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'html_language' => 'id', 'locale' => 'id, Bahasa Indonesia, id_ID, id_ID.utf8, id_ID.UTF-8', diff --git a/resources/lang/id_ID/csv.php b/resources/lang/id_ID/csv.php index 622a49202a..aae109a40a 100644 --- a/resources/lang/id_ID/csv.php +++ b/resources/lang/id_ID/csv.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/id_ID/demo.php b/resources/lang/id_ID/demo.php index be9aeac6ac..a62c929dc0 100644 --- a/resources/lang/id_ID/demo.php +++ b/resources/lang/id_ID/demo.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'no_demo_text' => 'Maaf, tidak ada teks penjelasan-penjelasan tambahan laman halaman ini.', 'see_help_icon' => 'Namun, ikon di pojok kanan atas mungkin memberi tahu Anda lebih banyak.', @@ -32,5 +33,5 @@ return [ 'currencies-index' => 'Firefly III mendukung banyak mata uang. Meski default ke Euro itu bisa diatur ke US Dollar dan banyak mata uang lainnya. Seperti yang bisa Anda lihat, sejumlah kecil mata uang telah disertakan namun Anda dapat menambahkannya sendiri jika menginginkannya. Mengubah mata uang default tidak akan mengubah mata uang dari transaksi yang ada namun: Firefly III mendukung penggunaan beberapa mata uang pada saat bersamaan.', 'transactions-index' => 'Biaya ini, deposito dan transfer tidak terlalu imajinatif. Mereka telah dihasilkan secara otomatis.', 'piggy-banks-index' => 'Seperti yang bisa Anda lihat, ada tiga celengan. Gunakan tombol plus dan minus untuk mempengaruhi jumlah uang di setiap celengan. Klik nama celengan untuk melihat administrasi masing-masing celengan.', - 'import-index' => 'Tentu saja, file CSV manapun bisa diimpor ke Firefly III', + 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', ]; diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index d831542d5d..348ed93b3a 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // general stuff: 'close' => 'Dekat', @@ -179,6 +180,7 @@ return [ 'authorization_request_intro' => ':client is requesting permission to access your financial administration. Would you like to authorize :client to access these records?', 'scopes_will_be_able' => 'This application will be able to:', 'button_authorize' => 'Authorize', + 'none_in_select_list' => '(none)', // check for updates: 'update_check_title' => 'Check for updates', @@ -666,6 +668,7 @@ return [ 'bill_will_automatch' => 'Tagihan akan secara otomatis terhubung ke transaksi yang sesuai', 'skips_over' => 'melompati', 'bill_store_error' => 'An unexpected error occurred while storing your new bill. Please check the log files', + 'list_inactive_rule' => 'inactive rule', // accounts: 'details_for_asset' => 'Rincian akun aset ":name"', @@ -801,6 +804,7 @@ return [ 'opt_group_savingAsset' => 'Menyimpan akun', 'opt_group_sharedAsset' => 'Akun aset bersama', 'opt_group_ccAsset' => 'Kartu kredit', + 'opt_group_cashWalletAsset' => 'Cash wallets', 'notes' => 'Notes', 'unknown_journal_error' => 'Could not store the transaction. Please check the log files.', @@ -816,6 +820,7 @@ return [ 'language' => 'Language', 'new_savings_account' => ':bank_name savings account', 'cash_wallet' => 'Cash wallet', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Akun anda', @@ -1013,6 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Hapus uang dari celengan ":name"', 'add' => 'Menambahkan', 'no_money_for_piggy' => 'Anda tidak punya uang untuk dimasukkan ke dalam celengan ini.', + 'suggested_savings_per_month' => 'Suggested per month', 'remove' => 'Menghapus', 'max_amount_add' => 'Jumlah maksimum yang bisa Anda tambahkan adalah', @@ -1148,27 +1154,9 @@ return [ 'no_edit_multiple_left' => 'Anda tidak memilih transaksi yang sah untuk diedit.', 'cannot_convert_split_journal' => 'Tidak dapat mengonversi transaksi split', - // import bread crumbs and titles: - 'import' => 'Impor', - 'import_data' => 'Impor data', - 'import_general_index_file' => 'Impor file', - 'import_from_bunq' => 'Impor dari bunq', - 'import_using_spectre' => 'Impor menggunakan momok', - 'import_using_plaid' => 'Impor menggunakan Plaid', - 'import_config_bread_crumb' => 'Siapkan impor Anda', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Impor data ke Firefly III', - 'import_index_sub_title' => 'Indeks', - 'import_general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', - 'upload_error' => 'The file you have uploaded could not be processed. Possibly it is of an invalid file type or encoding. The log files will have more information.', - 'reset_import_settings_title' => 'Reset import configuration', - 'reset_import_settings_text' => 'You can use these links to reset your import settings for specific providers. This is useful when bad settings stop you from importing data.', - 'reset_settings_bunq' => 'Remove bunq API key, local external IP address and bunq related RSA keys.', - 'reset_settings_spectre' => 'Remove Spectre secrets and ID\'s. This will also remove your Spectre keypair. Remember to update the new one.', - 'settings_reset_for_bunq' => 'Bunq settings reset.', - 'settings_reset_for_spectre' => 'Spectre settings reset.', - + 'import_data' => 'Impor data', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Fungsi ini tidak tersedia saat Anda menggunakan Firefly III di dalam lingkungan Sandstorm.io.', diff --git a/resources/lang/id_ID/form.php b/resources/lang/id_ID/form.php index 16431078e1..6dd307210f 100644 --- a/resources/lang/id_ID/form.php +++ b/resources/lang/id_ID/form.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // new user: 'bank_name' => 'Nama Bank', @@ -184,6 +185,13 @@ return [ 'blocked' => 'Apakah diblokir?', 'blocked_code' => 'Alasan untuk blok', + // import + 'apply_rules' => 'Apply rules', + 'artist' => 'Artist', + 'album' => 'Album', + 'song' => 'Song', + + // admin 'domain' => 'Domain', 'single_user_mode' => 'Nonaktifkan pendaftaran pengguna', diff --git a/resources/lang/id_ID/import.php b/resources/lang/id_ID/import.php index a580259879..93809b1375 100644 --- a/resources/lang/id_ID/import.php +++ b/resources/lang/id_ID/import.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ - // status of import: + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Import data into Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', + 'job_configuration_breadcrumb' => 'Configuration for ":key"', + 'job_status_breadcrumb' => 'Import status for ":key"', + 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', + + // index page: + 'general_index_title' => 'Import a file', + 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + // import provider strings (index): + 'button_fake' => 'Fake an import', + 'button_file' => 'Import a file', + 'button_bunq' => 'Import from bunq', + 'button_spectre' => 'Import using Spectre', + 'button_plaid' => 'Import using Plaid', + 'button_yodlee' => 'Import using Yodlee', + 'button_quovo' => 'Import using Quovo', + // global config box (index) + 'global_config_title' => 'Global import configuration', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + // prerequisites box (index) + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + // provider config box (index) + 'can_config_title' => 'Import configuration', + 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'do_config_fake' => 'Configuration for the fake provider', + 'do_config_file' => 'Configuration for file imports', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', + + // prerequisites: + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + + // job configuration: + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (nilai yang dipisahkan koma)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', + 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', + 'job_config_uc_apply_rules_title' => 'Apply rules', + 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', + 'job_config_uc_specifics_title' => 'Bank-specific options', + 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', + 'job_config_uc_submit' => 'Continue', + 'invalid_import_account' => 'You have selected an invalid account to import into.', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Choose your login', + 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', + 'spectre_login_status_active' => 'Active', + 'spectre_login_status_inactive' => 'Inactive', + 'spectre_login_status_disabled' => 'Disabled', + 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', + 'job_config_spectre_accounts_title' => 'Select accounts to import from', + 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', + 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', + 'spectre_do_not_import' => '(do not import)', + 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'imported_from_account' => 'Imported from ":account"', + 'spectre_account_with_number' => 'Account :number', + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq accounts', + 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', + 'bunq_no_mapping' => 'It seems you have not selected any accounts.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Status', + 'spectre_extra_key_card_type' => 'Card type', + 'spectre_extra_key_account_name' => 'Account name', + 'spectre_extra_key_client_name' => 'Client name', + 'spectre_extra_key_account_number' => 'Account number', + 'spectre_extra_key_blocked_amount' => 'Blocked amount', + 'spectre_extra_key_available_amount' => 'Available amount', + 'spectre_extra_key_credit_limit' => 'Credit limit', + 'spectre_extra_key_interest_rate' => 'Interest rate', + 'spectre_extra_key_expiry_date' => 'Expiry date', + 'spectre_extra_key_open_date' => 'Open date', + 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_current_date' => 'Current date', + 'spectre_extra_key_cards' => 'Cards', + 'spectre_extra_key_units' => 'Units', + 'spectre_extra_key_unit_price' => 'Unit price', + 'spectre_extra_key_transactions_count' => 'Transaction count', + + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Create better descriptions in ING exports', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Fixes potential problems with PC files', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', + 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'job_config_roles_submit' => 'Continue', + 'job_config_roles_column_name' => 'Name of column', + 'job_config_roles_column_example' => 'Column example data', + 'job_config_roles_column_role' => 'Column data meaning', + 'job_config_roles_do_map_value' => 'Map these values', + 'job_config_roles_no_example' => 'No example data available', + 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'job_config_roles_colum_count' => 'Column', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'job_config_field_value' => 'Field value', + 'job_config_field_mapped' => 'Mapped to', + 'map_do_not_map' => '(jangan memetakan)', + 'job_config_map_submit' => 'Start the import', + + + // import status page: + 'import_with_key' => 'Impor dengan kunci \':key\'', 'status_wait_title' => 'Tolong tunggu sebentar...', 'status_wait_text' => 'Kotak ini akan hilang dalam sekejap.', - 'status_fatal_title' => 'Sebuah kesalahan fatal terjadi', - 'status_fatal_text' => 'Kesalahan fatal terjadi, dimana rutinitas impor tidak dapat dipulihkan. Silakan lihat penjelasannya di bawah ini.', - 'status_fatal_more' => 'Jika kesalahannya adalah time-out, impor akan berhenti setengah jalan. Untuk beberapa konfigurasi server, hanya server yang berhenti sementara impor terus berjalan di latar belakang. Untuk memverifikasi ini, periksa file log. Jika masalah berlanjut, pertimbangkan untuk mengimpor lebih dari baris perintah.', - 'status_ready_title' => 'Impor sudah siap untuk memulai', - 'status_ready_text' => 'Impor sudah siap dimulai. Semua konfigurasi yang perlu Anda lakukan sudah selesai. Silahkan download file konfigurasi. Ini akan membantu Anda dengan impor seandainya tidak berjalan seperti yang direncanakan. Untuk benar-benar menjalankan impor, Anda dapat menjalankan perintah berikut di konsol Anda, atau menjalankan impor berbasis web. Bergantung pada konfigurasi Anda, impor konsol akan memberi Anda lebih banyak umpan balik.', - 'status_ready_noconfig_text' => 'Impor sudah siap dimulai. Semua konfigurasi yang perlu Anda lakukan sudah selesai. Untuk benar-benar menjalankan impor, Anda dapat menjalankan perintah berikut di konsol Anda, atau menjalankan impor berbasis web. Bergantung pada konfigurasi Anda, impor konsol akan memberi Anda lebih banyak umpan balik.', - 'status_ready_config' => 'Download konfigurasi', - 'status_ready_start' => 'Mulai impor', - 'status_ready_share' => 'Harap pertimbangkan untuk mendownload konfigurasi Anda dan membagikannya di pusat konfigurasi impor. Ini akan memungkinkan pengguna Firefly III lainnya untuk mengimpor file mereka dengan lebih mudah.', - 'status_job_new' => 'Pekerjaan itu baru.', - 'status_job_configuring' => 'Impor sedang dikonfigurasi.', - 'status_job_configured' => 'Impor dikonfigurasi.', - 'status_job_running' => 'Impor sedang berjalan.. mohon menunggu..', - 'status_job_error' => 'Pekerjaan telah menimbulkan kesalahan.', - 'status_job_finished' => 'Impor telah selesai!', 'status_running_title' => 'Impor sedang berjalan', - 'status_running_placeholder' => 'Silakan tunggu update...', - 'status_finished_title' => 'Rutin impor selesai', - 'status_finished_text' => 'Rutin impor telah mengimpor data Anda.', - 'status_errors_title' => 'Kesalahan selama impor', - 'status_errors_single' => 'Terjadi kesalahan saat mengimpor. Itu tidak tampak berakibat fatal.', - 'status_errors_multi' => 'Beberapa kesalahan terjadi saat impor. Ini tidak tampak berakibat fatal.', - 'status_bread_crumb' => 'Status impor', - 'status_sub_title' => 'Status impor', - 'config_sub_title' => 'Siapkan impor Anda', - 'status_finished_job' => 'The :count transactions imported can be found in tag :tag.', - 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', - 'import_with_key' => 'Impor dengan kunci \':key\'', + 'status_job_running' => 'Please wait, running the import...', + 'status_job_storing' => 'Please wait, storing data...', + 'status_job_rules' => 'Please wait, running rules...', + 'status_fatal_title' => 'Fatal error', + 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', + 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', + 'status_finished_title' => 'Import finished', + 'status_finished_text' => 'The import has finished.', + 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', + 'unknown_import_result' => 'Unknown import result', + 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', + 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', + 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', - // file, upload something - 'file_upload_title' => 'Impor setup (1/4) - Upload file Anda', - 'file_upload_text' => 'Rutin ini akan membantu Anda mengimpor file dari bank Anda ke Firefly III. Silakan periksa halaman bantuan di pojok kanan atas.', - 'file_upload_fields' => 'Bidang', - 'file_upload_help' => 'Pilih file anda', - 'file_upload_config_help' => 'Jika sebelumnya Anda mengimpor data ke Firefly III, Anda mungkin memiliki file konfigurasi, yang akan menetapkan nilai konfigurasi untuk Anda. Untuk beberapa bank, pengguna lain dengan ramah memberikan berkas konfigurasi mereka', - 'file_upload_type_help' => 'Pilih jenis file yang akan anda upload', - 'file_upload_submit' => 'Unggah berkas', - // file, upload types - 'import_file_type_csv' => 'CSV (nilai yang dipisahkan koma)', + // general errors and warnings: + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', - // file, initial config for CSV - 'csv_initial_title' => 'Penyiapan impor (2/4) - Penyiapan impor CSV dasar', - 'csv_initial_text' => 'Untuk dapat mengimpor file Anda dengan benar, mohon validasi pilihan di bawah ini.', - 'csv_initial_box' => 'Penyiapan impor CSV dasar', - 'csv_initial_box_title' => 'Opsi penyiapan impor CSV dasar', - 'csv_initial_header_help' => 'Centang kotak ini jika baris pertama file CSV Anda adalah judul kolom.', - 'csv_initial_date_help' => 'Format waktu tanggal di CSV Anda. Ikuti format seperti laman ini menunjukkan. Nilai default akan mengurai tanggal yang terlihat seperti ini: :dateExample.', - 'csv_initial_delimiter_help' => 'Pilih pembatas lapangan yang digunakan dalam file masukan Anda. Jika tidak yakin, koma adalah pilihan teraman.', - 'csv_initial_import_account_help' => 'Jika file CSV TIDAK berisi informasi tentang akun aset Anda, gunakan dropdown ini untuk memilih akun mana yang menjadi tempat transaksi di CSV.', - 'csv_initial_submit' => 'Lanjutkan dengan langkah 3/4', - - // file, new options: - 'file_apply_rules_title' => 'Terapkan aturan', - 'file_apply_rules_description' => 'Terapkan peraturan Anda Perhatikan bahwa ini memperlambat impor secara signifikan.', - 'file_match_bills_title' => 'Cocokkan tagihan', - 'file_match_bills_description' => 'Cocokkan tagihan Anda dengan penarikan yang baru dibuat. Perhatikan bahwa ini memperlambat impor secara signifikan.', - - // file, roles config - 'csv_roles_title' => 'Pengaturan impor (3/4) - Tentukan peran masing-masing kolom', - 'csv_roles_text' => 'Setiap kolom dalam file CSV Anda berisi data tertentu. Tolong tunjukkan jenis data yang harus diharapkan oleh importir. Pilihan untuk "memetakan" data berarti Anda akan menghubungkan setiap entri yang ditemukan di kolom ke nilai di database Anda. Kolom yang sering dipetakan adalah kolom yang berisi IBAN dari akun lawan. Itu bisa dengan mudah disesuaikan dengan keberadaan IBAN di database Anda.', - 'csv_roles_table' => 'Meja', - 'csv_roles_column_name' => 'Nama kolom', - 'csv_roles_column_example' => 'Kolom contoh data', - 'csv_roles_column_role' => 'Data kolom berarti', - 'csv_roles_do_map_value' => 'Peta nilai-nilai ini', - 'csv_roles_column' => 'Kolom', - 'csv_roles_no_example_data' => 'Tidak ada data contoh yang tersedia', - 'csv_roles_submit' => 'Lanjutkan dengan langkah 4/4', - - // not csv, but normal warning - 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', - 'foreign_amount_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', - - // file, map data - 'file_map_title' => 'Pengaturan impor (4/4) - Sambungkan data impor ke data Firefly III', - 'file_map_text' => 'Pada tabel berikut, nilai kiri menunjukkan informasi yang Anda temukan di file yang Anda upload. Adalah tugas Anda untuk memetakan nilai ini, jika mungkin, ke nilai yang sudah ada di database Anda. Firefly akan menempel pada pemetaan ini. Jika tidak ada nilai untuk dipetakan, atau Anda tidak ingin memetakan nilai spesifiknya, pilih yang tidak ada.', - 'file_map_field_value' => 'Nilai lapangan', - 'file_map_field_mapped_to' => 'Dipetakan ke', - 'map_do_not_map' => '(jangan memetakan)', - 'file_map_submit' => 'Mulai impor', - 'file_nothing_to_map' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', - - // map things. + // column roles for CSV import: 'column__ignore' => '(abaikan kolom ini)', 'column_account-iban' => 'Akun aset (IBAN)', 'column_account-id' => 'Asset account ID (matching FF3)', @@ -158,48 +261,4 @@ return [ 'column_note' => 'Catatan (s)', 'column_internal-reference' => 'Internal reference', - // prerequisites - 'prerequisites' => 'Prerequisites', - - // bunq - 'bunq_prerequisites_title' => 'Prasyarat untuk impor dari bunq', - 'bunq_prerequisites_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'bunq_prerequisites_text_ip' => 'Bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', - 'bunq_do_import' => 'Yes, import from this account', - 'bunq_accounts_title' => 'Bunq accounts', - 'bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', - - // Spectre - 'spectre_title' => 'Impor menggunakan momok', - 'spectre_prerequisites_title' => 'Prasyarat untuk impor menggunakan momok', - 'spectre_prerequisites_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your secrets page.', - 'spectre_accounts_title' => 'Select accounts to import from', - 'spectre_accounts_text' => 'Each account on the left below has been found by Spectre and can be imported into Firefly III. Please select the asset account that should hold any given transactions. If you do not wish to import from any particular account, remove the check from the checkbox.', - 'spectre_do_import' => 'Yes, import from this account', - 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', - - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'SWIFT', - 'spectre_extra_key_status' => 'Status', - 'spectre_extra_key_card_type' => 'Card type', - 'spectre_extra_key_account_name' => 'Account name', - 'spectre_extra_key_client_name' => 'Client name', - 'spectre_extra_key_account_number' => 'Account number', - 'spectre_extra_key_blocked_amount' => 'Blocked amount', - 'spectre_extra_key_available_amount' => 'Available amount', - 'spectre_extra_key_credit_limit' => 'Credit limit', - 'spectre_extra_key_interest_rate' => 'Interest rate', - 'spectre_extra_key_expiry_date' => 'Expiry date', - 'spectre_extra_key_open_date' => 'Open date', - 'spectre_extra_key_current_time' => 'Current time', - 'spectre_extra_key_current_date' => 'Current date', - 'spectre_extra_key_cards' => 'Cards', - 'spectre_extra_key_units' => 'Units', - 'spectre_extra_key_unit_price' => 'Unit price', - 'spectre_extra_key_transactions_count' => 'Transaction count', - - // various other strings: - 'imported_from_account' => 'Imported from ":account"', ]; diff --git a/resources/lang/id_ID/intro.php b/resources/lang/id_ID/intro.php index 2c47c0c340..393970564b 100644 --- a/resources/lang/id_ID/intro.php +++ b/resources/lang/id_ID/intro.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // index 'index_intro' => 'Selamat datang di halaman indeks Firefly III. Mohon luangkan waktu untuk menelusuri pengantar ini melihat bagaimana Firefly III bekerja.', diff --git a/resources/lang/id_ID/list.php b/resources/lang/id_ID/list.php index ea8d946364..d64698938c 100644 --- a/resources/lang/id_ID/list.php +++ b/resources/lang/id_ID/list.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'buttons' => 'Tombol', 'icon' => 'Ikon', @@ -111,10 +112,15 @@ return [ 'sepa-cc' => 'SEPA Clearing Code', 'sepa-ep' => 'SEPA External Purpose', 'sepa-ci' => 'SEPA Creditor Identifier', + 'external_id' => 'External ID', 'account_at_bunq' => 'Account with bunq', 'file_name' => 'File name', 'file_size' => 'File size', 'file_type' => 'File type', 'attached_to' => 'Attached to', 'file_exists' => 'File exists', + 'spectre_bank' => 'Bank', + 'spectre_last_use' => 'Last login', + 'spectre_status' => 'Status', + 'bunq_payment_id' => 'bunq payment ID', ]; diff --git a/resources/lang/id_ID/pagination.php b/resources/lang/id_ID/pagination.php index 0ec6186091..b581a04610 100644 --- a/resources/lang/id_ID/pagination.php +++ b/resources/lang/id_ID/pagination.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'previous' => '« Sebelumnya', 'next' => 'Selanjutnya »', diff --git a/resources/lang/id_ID/passwords.php b/resources/lang/id_ID/passwords.php index 766e5ac204..0081716690 100644 --- a/resources/lang/id_ID/passwords.php +++ b/resources/lang/id_ID/passwords.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'password' => 'Sandi minimal harus 6 karakter dan cocok dengan konfirmasi.', 'user' => 'Kami tidak dapat menemukan pengguna dengan alamat e-mail itu.', diff --git a/resources/lang/id_ID/validation.php b/resources/lang/id_ID/validation.php index ec4bcd3a77..00be6153a5 100644 --- a/resources/lang/id_ID/validation.php +++ b/resources/lang/id_ID/validation.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'iban' => 'Ini bukan IBAN yang valid.', 'source_equals_destination' => 'The source account equals the destination account', @@ -109,7 +110,8 @@ return [ 'in_array' => 'Bidang :attribute tidak ada in :other.', 'present' => 'Bidang :attribute harus ada.', 'amount_zero' => 'Jumlah total tidak boleh nol', - 'secure_password' => 'Ini bukan kata sandi yang aman. Silahkan coba lagi. Untuk informasi lebih lanjut, kunjungi https://goo.gl/NCh2tN', + 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', 'attributes' => [ 'email' => 'email address', 'description' => 'description', From da3bd31fb8f988866b84495823f4b299c4d29b5c Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 14:21:27 +0200 Subject: [PATCH 162/182] Update Italian language files [skip ci] --- resources/lang/it_IT/auth.php | 16 +- resources/lang/it_IT/bank.php | 5 +- resources/lang/it_IT/breadcrumbs.php | 7 +- resources/lang/it_IT/components.php | 3 +- resources/lang/it_IT/config.php | 5 +- resources/lang/it_IT/csv.php | 5 +- resources/lang/it_IT/demo.php | 9 +- resources/lang/it_IT/firefly.php | 138 ++++++------ resources/lang/it_IT/form.php | 18 +- resources/lang/it_IT/import.php | 319 ++++++++++++++++----------- resources/lang/it_IT/intro.php | 9 +- resources/lang/it_IT/list.php | 38 ++-- resources/lang/it_IT/pagination.php | 5 +- resources/lang/it_IT/passwords.php | 5 +- resources/lang/it_IT/validation.php | 28 +-- 15 files changed, 336 insertions(+), 274 deletions(-) diff --git a/resources/lang/it_IT/auth.php b/resources/lang/it_IT/auth.php index bcde25e00a..c3bc250669 100644 --- a/resources/lang/it_IT/auth.php +++ b/resources/lang/it_IT/auth.php @@ -1,9 +1,8 @@ . */ -return [ - /* - |-------------------------------------------------------------------------- - | Authentication Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used during authentication for various - | messages that we need to display to the user. You are free to modify - | these language lines according to your application's requirements. - | - */ +declare(strict_types=1); +return [ 'failed' => 'Queste credenziali non corrispondono ai nostri record.', 'throttle' => 'Troppi tentativi di accesso. Per favore riprova tra :seconds secondi.', ]; diff --git a/resources/lang/it_IT/bank.php b/resources/lang/it_IT/bank.php index 8826020cbf..5d00b1e685 100644 --- a/resources/lang/it_IT/bank.php +++ b/resources/lang/it_IT/bank.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/it_IT/breadcrumbs.php b/resources/lang/it_IT/breadcrumbs.php index 92ae5d3c73..f631ee70e8 100644 --- a/resources/lang/it_IT/breadcrumbs.php +++ b/resources/lang/it_IT/breadcrumbs.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'home' => 'Home', 'edit_currency' => 'Modifica valuta ":name"', @@ -38,7 +39,7 @@ return [ 'reports' => 'Resoconti', 'search_result' => 'Risultati di ricerca per ":query"', 'withdrawal_list' => 'Spese', - 'deposit_list' => 'Entrate, entrate e depositi', + 'deposit_list' => 'Reddito, entrate e depositi', 'transfer_list' => 'Trasferimenti', 'transfers_list' => 'Trasferimenti', 'reconciliation_list' => 'Riconciliazioni', diff --git a/resources/lang/it_IT/components.php b/resources/lang/it_IT/components.php index 82f04fce8f..296fa36c83 100644 --- a/resources/lang/it_IT/components.php +++ b/resources/lang/it_IT/components.php @@ -1,5 +1,4 @@ . */ +declare(strict_types=1); + return [ // profile 'personal_access_tokens' => 'Token di accesso personale', diff --git a/resources/lang/it_IT/config.php b/resources/lang/it_IT/config.php index acac8fc155..667e347c13 100644 --- a/resources/lang/it_IT/config.php +++ b/resources/lang/it_IT/config.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'html_language' => 'it', 'locale' => 'it, Italiano, it_IT, it_IT.utf8, it_IT.UTF-8', diff --git a/resources/lang/it_IT/csv.php b/resources/lang/it_IT/csv.php index 622a49202a..aae109a40a 100644 --- a/resources/lang/it_IT/csv.php +++ b/resources/lang/it_IT/csv.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/it_IT/demo.php b/resources/lang/it_IT/demo.php index e17fd3e782..38c210d8fb 100644 --- a/resources/lang/it_IT/demo.php +++ b/resources/lang/it_IT/demo.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'no_demo_text' => 'Spiacenti, non esiste un testo dimostrativo aggiuntivo per questa pagina.', - 'see_help_icon' => 'Tuttavia, il -icon in alto a destra potrebbe dirti di più.', + 'see_help_icon' => 'Tuttavia, l\'icona in alto a destra potrebbe dirti di più.', 'index' => 'Benvenuto in Firefly III! In questa pagina ottieni una rapida panoramica delle tue finanze. Per ulteriori informazioni, controlla Account e → Account asset e, naturalmente, i budget e i resoconti. O semplicemente dai un\'occhiata in giro e vedi dove finisci.', 'accounts-index' => 'I conti degli asset sono i tuoi conti bancari personali. I conti spese sono gli account a cui si spendono soldi, come negozi e amici. I conti delle entrate sono conti da cui ricevi denaro, come il tuo lavoro, il governo o altre fonti di reddito. In questa pagina puoi modificarli o rimuoverli.', 'budgets-index' => 'Questa pagina ti mostra una panoramica dei tuoi budget. La barra in alto mostra l\'importo disponibile per essere preventivato. Questo può essere personalizzato per qualsiasi periodo facendo clic sull\'importo a destra. La quantità che hai effettivamente speso è mostrata nella barra sottostante. Di seguito sono indicate le spese per budget e ciò che hai preventivato per loro.', @@ -32,5 +33,5 @@ return [ 'currencies-index' => 'Firefly III supporta più valute. Sebbene sia impostato su Euro, può essere impostato sul dollaro USA e su molte altre valute. Come puoi vedere, è stata inclusa una piccola selezione di valute, ma puoi aggiungere la tua se lo desideri. Tuttavia, la modifica della valuta predefinita non cambierà la valuta delle transazioni esistenti: Firefly III supporta un uso di più valute allo stesso tempo.', 'transactions-index' => 'Queste spese, depositi e trasferimenti non sono particolarmente fantasiosi. Sono stati generati automaticamente.', 'piggy-banks-index' => 'Come puoi vedere, ci sono tre salvadanai. Utilizzare i pulsanti più e meno per influenzare la quantità di denaro in ogni salvadanaio. Fare clic sul nome del salvadanaio per visualizzare la gestione per ciascun salvadanaio.', - 'import-index' => 'Naturalmente, qualsiasi file CSV può essere importato in Firefly III', + 'import-index' => 'Qualsiasi file CSV può essere importato in Firefly III. Supporta anche l\'importazione di dati da bunq e Spectre. Altre banche e aggregatori finanziari saranno implementati in futuro. Tuttavia, come utente demo, puoi vedere solo il provider "fittizio" in azione. Genererà alcune transazioni casuali per mostrarti come funziona il processo.', ]; diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index 1ecb20c803..0532dcd126 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // general stuff: 'close' => 'Chiudi', @@ -44,7 +45,7 @@ return [ 'asset_account_role_help' => 'Qualsiasi opzione aggiuntiva risultante dalla tua scelta può essere impostata in seguito.', 'Opening balance' => 'Saldo di apertura', 'create_new_stuff' => 'Crea nuove cose', - 'new_withdrawal' => 'Nuova uscita', + 'new_withdrawal' => 'Nuovo prelievo', 'create_new_transaction' => 'Crea nuova transazione', 'go_to_asset_accounts' => 'Visualizza i tuoi movimenti', 'go_to_budgets' => 'Vai ai tuoi budget', @@ -54,8 +55,8 @@ return [ 'go_to_revenue_accounts' => 'Vedi i tuoi conti entrate', 'go_to_piggies' => 'Vai ai tuoi salvadanai', 'new_deposit' => 'Nuova entrata', - 'new_transfer' => 'Nuovo giroconto', - 'new_transfers' => 'Nuovi giroconti', + 'new_transfer' => 'Nuovo trasferimento', + 'new_transfers' => 'Nuovo trasferimento', 'new_asset_account' => 'Nuova attività conto', 'new_expense_account' => 'Nuova spesa conto', 'new_revenue_account' => 'Nuova entrata conto', @@ -117,9 +118,9 @@ return [ 'chart_account_in_period' => 'Grafico di tutte le transazioni per Conto ":name" fra :start E :end', 'chart_category_in_period' => 'Grafico di tutte le transazioni per Categorie ":name" fra :start e :end', 'chart_category_all' => 'Grafico di tutte le transazioni per Categoria ":name"', - 'clone_withdrawal' => 'Duplica questo uscita', + 'clone_withdrawal' => 'Duplica questo prelievo', 'clone_deposit' => 'Duplica questa entrata', - 'clone_transfer' => 'Duplica questo giroconto', + 'clone_transfer' => 'Duplica questo trasferimento', 'multi_select_no_selection' => 'Nessuno selezionato', 'multi_select_select_all' => 'Seleziona tutto', 'multi_select_n_selected' => 'selezionato', @@ -179,6 +180,7 @@ return [ 'authorization_request_intro' => ':client sta richiedendo l\'autorizzazione per accedere alla tua amministrazione finanziaria. Desideri autorizzare :client ad accedere a questi record?', 'scopes_will_be_able' => 'Questa applicazione sarà in grado di:', 'button_authorize' => 'Autorizza', + 'none_in_select_list' => '(nessuna)', // check for updates: 'update_check_title' => 'Controlla aggiornamenti', @@ -284,9 +286,9 @@ return [ 'default_rule_trigger_from_account' => 'David Bowie', 'default_rule_action_prepend' => 'Comprato il mondo da ', 'default_rule_action_set_category' => 'Grandi spese', - 'trigger' => 'Attivare', - 'trigger_value' => 'Attiva il valore', - 'stop_processing_other_triggers' => 'Interrompe l\'elaborazione di altre attivazioni', + 'trigger' => 'Trigger', + 'trigger_value' => 'Attiva al valore', + 'stop_processing_other_triggers' => 'Interrompi l\'elaborazione di altri trigger', 'add_rule_trigger' => 'Aggiungi un nuovo trigger', 'action' => 'Azione', 'action_value' => 'Valore azione', @@ -310,25 +312,25 @@ return [ // actions and triggers 'rule_trigger_user_action' => 'L\'azione dell\'utente è ":trigger_value"', - 'rule_trigger_from_account_starts_choice' => 'Il conto di origine inizia con..', + 'rule_trigger_from_account_starts_choice' => 'Il conto di origine inizia con...', 'rule_trigger_from_account_starts' => 'Il conto di origine inizia con ":trigger_value"', - 'rule_trigger_from_account_ends_choice' => 'Il conto di origine termina con..', + 'rule_trigger_from_account_ends_choice' => 'Il conto di origine termina con...', 'rule_trigger_from_account_ends' => 'Il conto di origine termina con ":trigger_value"', - 'rule_trigger_from_account_is_choice' => 'L\'account di origine è..', - 'rule_trigger_from_account_is' => 'L\'account di origine è ":trigger_value"', - 'rule_trigger_from_account_contains_choice' => 'Il conto di origine contiene..', + 'rule_trigger_from_account_is_choice' => 'Il conto di origine è...', + 'rule_trigger_from_account_is' => 'Il conto di origine è ":trigger_value"', + 'rule_trigger_from_account_contains_choice' => 'Il conto di origine contiene...', 'rule_trigger_from_account_contains' => 'Il conto di origine contiene ":trigger_value"', - 'rule_trigger_to_account_starts_choice' => 'Il conto di destinazione inizia con..', + 'rule_trigger_to_account_starts_choice' => 'Il conto di destinazione inizia con...', 'rule_trigger_to_account_starts' => 'Il conto di destinazione inizia con ":trigger_value"', - 'rule_trigger_to_account_ends_choice' => 'Il conto di destinazione termina con..', + 'rule_trigger_to_account_ends_choice' => 'Il conto di destinazione termina con...', 'rule_trigger_to_account_ends' => 'Il conto di destinazione termina con ":trigger_value"', - 'rule_trigger_to_account_is_choice' => 'Il conto di destinazione è..', + 'rule_trigger_to_account_is_choice' => 'Il conto di destinazione è...', 'rule_trigger_to_account_is' => 'Il conto di destinazione è ":trigger_value"', - 'rule_trigger_to_account_contains_choice' => 'Il conto di destinazione contiene..', + 'rule_trigger_to_account_contains_choice' => 'Il conto di destinazione contiene...', 'rule_trigger_to_account_contains' => 'Il conto di destinazione contiene ":trigger_value"', - 'rule_trigger_transaction_type_choice' => 'La transazione è di tipo..', + 'rule_trigger_transaction_type_choice' => 'La transazione è di tipo...', 'rule_trigger_transaction_type' => 'La transazione è di tipo ":trigger_value"', - 'rule_trigger_category_is_choice' => 'La categoria è..', + 'rule_trigger_category_is_choice' => 'La categoria è...', 'rule_trigger_category_is' => 'La categoria è ":trigger_value"', 'rule_trigger_amount_less_choice' => 'L\'importo è inferiore a..', 'rule_trigger_amount_less' => 'L\'importo è inferiore a :trigger_value', @@ -347,7 +349,7 @@ return [ 'rule_trigger_budget_is_choice' => 'Il budget è...', 'rule_trigger_budget_is' => 'Il budget è ":trigger_value"', 'rule_trigger_tag_is_choice' => '(A) tag è..', - 'rule_trigger_tag_is' => 'Un tag è ":trigger_value"', + 'rule_trigger_tag_is' => 'Una etichetta è ":trigger_value"', 'rule_trigger_currency_is_choice' => 'La valuta della transazione è...', 'rule_trigger_currency_is' => 'La valuta della transazione è ":trigger_value"', 'rule_trigger_has_attachments_choice' => 'Ha almeno questo molti allegati', @@ -360,8 +362,8 @@ return [ 'rule_trigger_has_any_category' => 'La transazione ha una (qualsiasi) categoria', 'rule_trigger_has_no_budget_choice' => 'Non ha un budget', 'rule_trigger_has_no_budget' => 'La transazione non ha un budget', - 'rule_trigger_has_any_budget_choice' => 'Ha un budget (qualsiasi)', - 'rule_trigger_has_any_budget' => 'La transazione ha un budget (qualsiasi)', + 'rule_trigger_has_any_budget_choice' => 'Ha un (qualsiasi) budget', + 'rule_trigger_has_any_budget' => 'La transazione ha un (qualsiasi) budget', 'rule_trigger_has_no_tag_choice' => 'Non ha etichetta(e)', 'rule_trigger_has_no_tag' => 'La transazione non ha etichetta(e)', 'rule_trigger_has_any_tag_choice' => 'Ha una o più etichette (qualsiasi)', @@ -420,7 +422,7 @@ return [ 'rule_for_bill_title' => 'Regole generata automaticamente per la bolletta ":name"', 'rule_for_bill_description' => 'Questa regola è generata automaticamente per l\'abbinamento con la bolletta ":name".', 'create_rule_for_bill' => 'Crea una nuova regola per la bolletta ":name"', - 'create_rule_for_bill_txt' => 'Contratulazioni, hai appena creato una nuova bolletta chiamata ":name"! Firefly III può automagicamente abbinare le nuove uscite a questa bolletta. Per esempio, ogni volta che paghi l\'affitto la bolletta "affitto" verrà collegata a questa spesa. In questo modo Firefly III può visualizzare con accuratezza quali bollette sono in scadenza e quali no. Per far ciò è necessario creare una nuova regola. Firefly III ha inserito al posto tuo alcuni dettagli ragionevoli. Assicurati che questi siano corretti. Se questi valori sono corretti, Firefly III automaticamente collegherà la spesa giusta alla bolletta giusta. Controlla che i trigger siano corretti e aggiungene altri se sono sbagliati.', + 'create_rule_for_bill_txt' => 'Contratulazioni, hai appena creato una nuova bolletta chiamata ":name"! Firefly III può automagicamente abbinare le nuove uscite a questa bolletta. Per esempio, ogni volta che paghi l\'affitto la bolletta "affitto" verrà collegata a questa spesa. In questo modo Firefly III può visualizzare con accuratezza quali bollette sono in scadenza e quali no. Per far ciò è necessario creare una nuova regola. Firefly III ha inserito al posto tuo alcuni dettagli ragionevoli. Assicurati che questi siano corretti. Se questi valori sono corretti, Firefly III automaticamente collegherà il prelievo giusto alla bolletta giusta. Controlla che i trigger siano corretti e aggiungene altri se sono sbagliati.', 'new_rule_for_bill_title' => 'Regola per la bolletta ":name"', 'new_rule_for_bill_description' => 'Questa regola contrassegna le transazioni per la bolletta ":name".', @@ -541,7 +543,7 @@ return [ 'attachment_deleted' => 'Allegato eliminato ":name"', 'attachment_updated' => 'Allegato aggiornato ":name"', 'upload_max_file_size' => 'Dimensione massima del file: :size', - 'list_all_attachments' => 'List of all attachments', + 'list_all_attachments' => 'Lista di tutti gli allegati', // transaction index 'title_expenses' => 'Spese', @@ -554,42 +556,42 @@ return [ // convert stuff: 'convert_is_already_type_Withdrawal' => 'Questa transazione è già un prelievo', 'convert_is_already_type_Deposit' => 'Questa transazione è già un deposito', - 'convert_is_already_type_Transfer' => 'Questa transazione è già un giroconto', + 'convert_is_already_type_Transfer' => 'Questa transazione è già un trasferimento', 'convert_to_Withdrawal' => 'Converti ":description" in un prelievo', 'convert_to_Deposit' => 'Converti ":description" in un deposito', - 'convert_to_Transfer' => 'Converti ":description" in un giroconto', - 'convert_options_WithdrawalDeposit' => 'Convertire un prelievo in un deposito', - 'convert_options_WithdrawalTransfer' => 'Convertire un prelievo in un giroconto', - 'convert_options_DepositTransfer' => 'Convertire un deposito in un giroconto', + 'convert_to_Transfer' => 'Converti ":description" in un trasferimento', + 'convert_options_WithdrawalDeposit' => 'Converti un prelievo in un deposito', + 'convert_options_WithdrawalTransfer' => 'Converti un prelievo in un trasferimento', + 'convert_options_DepositTransfer' => 'Converti un deposito in un trasferimento', 'convert_options_DepositWithdrawal' => 'Converti un deposito in un prelievo', - 'convert_options_TransferWithdrawal' => 'Convertire un giroconto in un prelievo', + 'convert_options_TransferWithdrawal' => 'Converti un trasferimento in un prelievo', 'convert_options_TransferDeposit' => 'Converti un bonifico in un deposito', 'convert_Withdrawal_to_deposit' => 'Converti questo prelievo in un deposito', - 'convert_Withdrawal_to_transfer' => 'Converti questo prelievo in un giroconto', + 'convert_Withdrawal_to_transfer' => 'Converti questo prelievo in un trasferimento', 'convert_Deposit_to_withdrawal' => 'Converti questo deposito in un prelievo', - 'convert_Deposit_to_transfer' => 'Converti questo deposito in un giroconto', - 'convert_Transfer_to_deposit' => 'Converti questo giroconto in un deposito', - 'convert_Transfer_to_withdrawal' => 'Converti questo giroconto in un prelievo', + 'convert_Deposit_to_transfer' => 'Converti questo deposito in un trasferimento', + 'convert_Transfer_to_deposit' => 'Converti questo trasferimento in un deposito', + 'convert_Transfer_to_withdrawal' => 'Converti questo trasferimento in un prelievo', 'convert_please_set_revenue_source' => 'Si prega di scegliere il conto delle entrate da dove verranno i soldi.', 'convert_please_set_asset_destination' => 'Si prega di scegliere il conto patrimoniale dove andranno i soldi.', 'convert_please_set_expense_destination' => 'Si prega di scegliere il conto spese dove andranno i soldi.', 'convert_please_set_asset_source' => 'Si prega di scegliere il conto patrimoniale da dove verranno i soldi.', 'convert_explanation_withdrawal_deposit' => 'Se converti questo prelievo in un deposito, l\'importo verrà :amount depositato in :sourceName anziché prelevato da esso.', - 'convert_explanation_withdrawal_transfer' => 'Se converti questo prelievo in un giroconto, l\'importo verrà :amount trasferito da :sourceName a un nuovo conto attività, invece di essere pagato a :destinationName.', + 'convert_explanation_withdrawal_transfer' => 'Se converti questo prelievo in un trasferimento, l\'importo verrà :amount trasferito da :sourceName a un nuovo conto attività, invece di essere pagato a :destinationName.', 'convert_explanation_deposit_withdrawal' => 'Se converti questo deposito in un prelievo, l\'importo verrà rimosso :amount da :destinationName anziché aggiunto ad esso.', - 'convert_explanation_deposit_transfer' => 'Se converti questo deposito in un giroconto, :amount verrà trasferito da un conto attivo di tua scelta in :destinationName.', - 'convert_explanation_transfer_withdrawal' => 'Se converti questo giroconto in un prelievo, l\'importo :amount andrà da :sourceName a una nuova destinazione a titolo di spesa, anziché a :destinationName come giroconto.', - 'convert_explanation_transfer_deposit' => 'Se converti questo giroconto in un deposito, :amount verrà depositato nell\'account :destinationName anziché essere trasferito lì.', + 'convert_explanation_deposit_transfer' => 'Se converti questo deposito in un trasferimento, :amount verrà trasferito da un conto attivo di tua scelta in :destinationName.', + 'convert_explanation_transfer_withdrawal' => 'Se converti questo trasferimento in un prelievo, l\'importo :amount andrà da :sourceName a una nuova destinazione a titolo di spesa, anziché a :destinationName come trasferimento.', + 'convert_explanation_transfer_deposit' => 'Se converti questo trasferimento in un deposito, :amount verrà depositato nell\'account :destinationName anziché essere trasferito lì.', 'converted_to_Withdrawal' => 'La transazione è stata convertita in un prelievo', 'converted_to_Deposit' => 'La transazione è stata convertita in un deposito', - 'converted_to_Transfer' => 'La transazione è stata convertita in un giroconto', + 'converted_to_Transfer' => 'La transazione è stata convertita in un trasferimento', 'invalid_convert_selection' => 'Tl\'account che hai selezionato è già utilizzato in questa transazione o non esiste.', 'source_or_dest_invalid' => 'Impossibile trovare i dettagli corretti della transazione. Non è possibile effettuare la conversione.', // create new stuff: 'create_new_withdrawal' => 'Crea un nuovo prelievo', 'create_new_deposit' => 'Crea una nuova entrata', - 'create_new_transfer' => 'Crea nuovo giroconto', + 'create_new_transfer' => 'Crea nuovo trasferimento', 'create_new_asset' => 'Crea un nuovo conto attività', 'create_new_expense' => 'Crea un nuovo conto di spesa', 'create_new_revenue' => 'Crea un nuovo conto di entrate', @@ -666,6 +668,7 @@ return [ 'bill_will_automatch' => 'La bolletta verrà automaticamente collegata alle transazioni corrispondenti', 'skips_over' => 'salta sopra', 'bill_store_error' => 'Si è verificato un errore imprevisto durante la memorizzazione della nuova bolletta. Controlla i file di log', + 'list_inactive_rule' => 'regola inattiva', // accounts: 'details_for_asset' => 'Dettagli per conto attività ":name"', @@ -765,16 +768,16 @@ return [ // transactions: 'update_withdrawal' => 'Aggiorna spesa', 'update_deposit' => 'Aggiorna entrata', - 'update_transfer' => 'Aggiorna giroconto', + 'update_transfer' => 'Aggiorna trasferimento', 'updated_withdrawal' => 'Spesa aggiornata ":description"', 'updated_deposit' => 'Entrata aggiornata ":description"', - 'updated_transfer' => 'Giroconto aggiornato ":description"', + 'updated_transfer' => 'Trasferimento ":description" aggiornato', 'delete_withdrawal' => 'Elimina spesa ":description"', 'delete_deposit' => 'Elimina entrata ":description"', - 'delete_transfer' => 'Elimina giroconto ":description"', + 'delete_transfer' => 'Elimina trasferimento ":description"', 'deleted_withdrawal' => 'Spesa eliminata correttamente ":description"', 'deleted_deposit' => 'Entrata eliminata correttamente ":description"', - 'deleted_transfer' => 'Giroconto eliminato correttamente ":description"', + 'deleted_transfer' => 'Trasferimento ":description" eliminato correttamente', 'stored_journal' => 'Nuova transazione creata correttamente ":description"', 'select_transactions' => 'Seleziona transazioni', 'rule_group_select_transactions' => 'Applica ":title" a transazioni', @@ -801,6 +804,7 @@ return [ 'opt_group_savingAsset' => 'Conti risparmio', 'opt_group_sharedAsset' => 'Conti risorse condivise', 'opt_group_ccAsset' => 'Carte di credito', + 'opt_group_cashWalletAsset' => 'Contanti', 'notes' => 'Note', 'unknown_journal_error' => 'Impossibile memorizzare la transazione. Controllare i file di log.', @@ -816,6 +820,7 @@ return [ 'language' => 'Lingua', 'new_savings_account' => 'Conto di risparmio :bank_name', 'cash_wallet' => 'Contanti', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'I tuoi conti', @@ -823,7 +828,7 @@ return [ 'savings' => 'Risparmi', 'newWithdrawal' => 'Nuova uscita', 'newDeposit' => 'Nuovo deposito', - 'newTransfer' => 'Nuovo giroconto', + 'newTransfer' => 'Nuovo trasferimento', 'bills_to_pay' => 'Bollette da pagare', 'per_day' => 'Al giorno', 'left_to_spend_per_day' => 'Spese al giorno', @@ -858,10 +863,10 @@ return [ 'opening_balance' => 'Saldo di apertura', 'deposit' => 'Entrata', 'account' => 'Conto', - 'transfer' => 'Giroconto', + 'transfer' => 'Trasferimento', 'Withdrawal' => 'Spesa', 'Deposit' => 'Entrata', - 'Transfer' => 'Giroconto', + 'Transfer' => 'Trasferimento', 'bill' => 'Bolletta', 'yes' => 'Si', 'no' => 'No', @@ -1013,6 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Rimuovi i soldi dal salvadanaio ":name"', 'add' => 'Aggiungi', 'no_money_for_piggy' => 'non hai soldi da mettere in questo salvadanaio.', + 'suggested_savings_per_month' => 'Suggested per month', 'remove' => 'Rimuovi', 'max_amount_add' => 'L\'importo massimo che puoi aggiungere è', @@ -1083,7 +1089,7 @@ return [ 'send_test_email' => 'Invia un messaggio di posta elettronica di prova', 'send_test_email_text' => 'Per vedere se la tua installazione è in grado di inviare e-mail, ti preghiamo di premere questo pulsante. Qui non vedrai un errore (se presente), i file di log rifletteranno eventuali errori. Puoi premere questo pulsante tutte le volte che vuoi. Non c\'è controllo dello spam. Il messaggio verrà inviato a :email e dovrebbe arrivare a breve.', 'send_message' => 'Invia messaggio', - 'send_test_triggered' => 'Il test è stato attivato. Controlla la tua casella di posta e i file di registro.', + 'send_test_triggered' => 'Il test è stato attivato. Controlla la tua casella di posta e i file di log.', // links 'journal_link_configuration' => 'Configurazione dei collegamenti di transazione', @@ -1115,7 +1121,7 @@ return [ 'journal_links' => 'Collegamenti di transazione', 'this_withdrawal' => 'Questa spesa', 'this_deposit' => 'Questa entrata', - 'this_transfer' => 'Questo giroconto', + 'this_transfer' => 'Questo trasferimento', 'overview_for_link' => 'Panoramica per tipo di collegamento ":name"', 'source_transaction' => 'Transazione di origine', 'link_description' => 'Descrizione del collegamento', @@ -1140,7 +1146,7 @@ return [ 'do_split' => 'Fai una divisione', 'split_this_withdrawal' => 'Dividi questa spesa', 'split_this_deposit' => 'Dividi questa entrata', - 'split_this_transfer' => 'Dividi questo giroconto', + 'split_this_transfer' => 'Dividi questo trasferimento', 'cannot_edit_multiple_source' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché contiene più conti di origine.', 'cannot_edit_multiple_dest' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché contiene più conti di destinazione.', 'cannot_edit_reconciled' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché è stata contrassegnata come riconciliata.', @@ -1148,27 +1154,9 @@ return [ 'no_edit_multiple_left' => 'Non hai selezionato transazioni valide da modificare.', 'cannot_convert_split_journal' => 'Impossibile convertire una transazione divisa', - // import bread crumbs and titles: - 'import' => 'Importa', - 'import_data' => 'Importa i dati', - 'import_general_index_file' => 'Importa un file', - 'import_from_bunq' => 'Importare dal bunq', - 'import_using_spectre' => 'Importa usando Spettro', - 'import_using_plaid' => 'Importa usando Plaid', - 'import_config_bread_crumb' => 'Configura la tua importazione', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Importa i dati in Firefly III', - 'import_index_sub_title' => 'Indice', - 'import_general_index_intro' => 'Benvenuti nella routine di importazione di Firefly III. Esistono alcuni modi per importare dati in Firefly III, visualizzati qui come pulsanti.', - 'upload_error' => 'Il file che hai caricato non può essere elaborato. Probabilmente è un tipo di file o una codifica non valida. I file di log avranno maggiori informazioni.', - 'reset_import_settings_title' => 'Reimposta la configurazione di importazione', - 'reset_import_settings_text' => 'Puoi utilizzare questi collegamenti per ripristinare le impostazioni di importazione per specifici fornitori. Ciò è utile quando delle impostazioni errate impediscono l\'importazione dei dati.', - 'reset_settings_bunq' => 'Rimuovi la chiave API di bunq, l\'indirizzo IP locale esterno e le chiavi RSA correlate a bunq.', - 'reset_settings_spectre' => 'Rimuovi i segreti e gli ID di Spectre. Questo rimuoverà anche la tua coppia di chiavi di Spectre. Ricordati di aggiornare quella nuova.', - 'settings_reset_for_bunq' => 'Ripristina impostazioni bunq.', - 'settings_reset_for_spectre' => 'Ripristina impostazioni Spectre.', - + 'import_data' => 'Importa i dati', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Questa funzione non è disponibile quando si utilizza Firefly III in un ambiente Sandstorm.io.', @@ -1206,10 +1194,10 @@ return [ 'no_transactions_intro_deposit' => 'non hai ancora entrate registrate. È necessario creare voci di reddito per iniziare a gestire le tue finanze.', 'no_transactions_imperative_deposit' => 'Hai ricevuto dei soldi? Dovresti scriverlo:', 'no_transactions_create_deposit' => 'Crea una entrata', - 'no_transactions_title_transfers' => 'Creiamo un giroconto!', - 'no_transactions_intro_transfers' => 'Non hai ancora giroconti. Quando sposti denaro tra i conti attività, viene registrato come giroconto.', + 'no_transactions_title_transfers' => 'Creiamo un trasferimento!', + 'no_transactions_intro_transfers' => 'Non hai ancora trasferimenti. Quando sposti denaro tra i conti attività, viene registrato come trasferimento.', 'no_transactions_imperative_transfers' => 'Hai spostato dei soldi in giro? Dovresti scriverlo:', - 'no_transactions_create_transfers' => 'Crea un giroconto', + 'no_transactions_create_transfers' => 'Crea un trasferimento', 'no_piggies_title_default' => 'Creiamo un salvadanaio!', 'no_piggies_intro_default' => 'Non hai ancora salvadanai. Puoi creare salvadanai per dividere i tuoi risparmi e tenere traccia di ciò per cui stai risparmiando.', 'no_piggies_imperative_default' => 'Hai cose per le quali stai risparmiando? Crea un salvadanaio e tieni traccia:', diff --git a/resources/lang/it_IT/form.php b/resources/lang/it_IT/form.php index bf73d83fa4..2c47c1601f 100644 --- a/resources/lang/it_IT/form.php +++ b/resources/lang/it_IT/form.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // new user: 'bank_name' => 'Nome banca', @@ -91,7 +92,7 @@ return [ 'type' => 'Tipo', 'convert_Withdrawal' => 'Converti spesa', 'convert_Deposit' => 'Converti entrata', - 'convert_Transfer' => 'Converti giroconto', + 'convert_Transfer' => 'Converti trasferimento', 'amount' => 'Importo', 'foreign_amount' => 'Importo estero', @@ -184,6 +185,13 @@ return [ 'blocked' => 'È bloccato?', 'blocked_code' => 'Motivo del blocco', + // import + 'apply_rules' => 'Applica regole', + 'artist' => 'Artista', + 'album' => 'Album', + 'song' => 'Brano', + + // admin 'domain' => 'Dominio', 'single_user_mode' => 'Disabilita registrazione utente', @@ -202,8 +210,8 @@ return [ 'client_id' => 'ID Client', 'service_secret' => 'Servizio segreto', 'app_secret' => 'App segreto', - 'app_id' => 'App ID', - 'secret' => 'Secret', + 'app_id' => 'ID dell\'app', + 'secret' => 'Segreto', 'public_key' => 'Chiave Pubblica', 'country_code' => 'Codice Nazione', 'provider_code' => 'Banca o fornitore di dati', diff --git a/resources/lang/it_IT/import.php b/resources/lang/it_IT/import.php index 7b0b61a277..d91182b40d 100644 --- a/resources/lang/it_IT/import.php +++ b/resources/lang/it_IT/import.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ - // status of import: + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Importa i dati in Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisiti per il fornitore di importazione fittizio', + 'prerequisites_breadcrumb_spectre' => 'Prerequisiti per Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisiti per bunq', + 'job_configuration_breadcrumb' => 'Configurazione per ":key"', + 'job_status_breadcrumb' => 'Stato di importazione per ":key"', + 'cannot_create_for_provider' => 'Firefly III non può creare un lavoro per il provider ":provider".', + + // index page: + 'general_index_title' => 'Importa un file', + 'general_index_intro' => 'Benvenuti nella routine di importazione di Firefly III. Esistono alcuni modi per importare dati in Firefly III, visualizzati qui come pulsanti.', + // import provider strings (index): + 'button_fake' => 'Esegui un\'importazione fittizia', + 'button_file' => 'Importa un file', + 'button_bunq' => 'Importa da bunq', + 'button_spectre' => 'Importa usando Spectre', + 'button_plaid' => 'Importa usando Plaid', + 'button_yodlee' => 'Importa usando Yodlee', + 'button_quovo' => 'Importa usando Quovo', + // global config box (index) + 'global_config_title' => 'Configurazione globale di importazione', + 'global_config_text' => 'In futuro, questo riquadro presenterà le preferenze che si applicano a TUTTI i fornitori di importazione di cui sopra.', + // prerequisites box (index) + 'need_prereq_title' => 'Prerequisiti di importazione', + 'need_prereq_intro' => 'Alcuni metodi di importazione richiedono la tua attenzione prima che possano essere utilizzati. Ad esempio, potrebbero richiedere speciali chiavi API o segreti dell\'applicazione. Puoi configurarli qui. L\'icona indica se questi prerequisiti sono stati soddisfatti.', + 'do_prereq_fake' => 'Prerequisiti per il fornitore fittizio', + 'do_prereq_file' => 'Prerequisiti per le importazioni da file', + 'do_prereq_bunq' => 'Prerequisiti per le importazioni da bunq', + 'do_prereq_spectre' => 'Prerequisiti per le importazioni usando Spectre', + 'do_prereq_plaid' => 'Prerequisiti per le importazioni usando Plaid', + 'do_prereq_yodlee' => 'Prerequisiti per le importazioni usando Yodlee', + 'do_prereq_quovo' => 'Prerequisiti per le importazioni usando Quovo', + // provider config box (index) + 'can_config_title' => 'Configurazione di importazione', + 'can_config_intro' => 'Alcuni metodi di importazione possono essere configurati a tuo piacimento. Hanno ulteriori impostazioni che puoi modificare.', + 'do_config_fake' => 'Configurazione per il fornitore fittizio', + 'do_config_file' => 'Configurazione per le importazioni da file', + 'do_config_bunq' => 'Configurazione per le importazioni da bunq', + 'do_config_spectre' => 'Configurazione per importazioni da Spectre', + 'do_config_plaid' => 'Configurazione per importazioni da Plaid', + 'do_config_yodlee' => 'Configurazione per importazioni da Yodlee', + 'do_config_quovo' => 'Configurazione per importazioni da Quovo', + + // prerequisites: + 'prereq_fake_title' => 'Prerequisiti per un\'importazione dal fornitore di importazione fittizio', + 'prereq_fake_text' => 'Questo provider fittizio richiede una chiave API fittizia. Deve contenere 32 caratteri. È possibile utilizzare questa: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisiti per un\'importazione utilizzando le API di Spectre', + 'prereq_spectre_text' => 'Per l\'importazione dei dati attraverso le API Spectre (v4), devi fornire a Firefly III due valori segreti. Questi si possono trovare nella pagina dei segreti.', + 'prereq_spectre_pub' => 'Allo stesso modo, l\'API Spectre deve conoscere la chiave pubblica che vedi qui sotto. Senza di essa, non ti riconoscerà. Per favore inserisci questa chiave pubblica nella tua pagina dei segreti.', + 'prereq_bunq_title' => 'Prerequisiti per un\'importazione da bunq', + 'prereq_bunq_text' => 'Per importare da bunq, è necessario ottenere una chiave API. Puoi farlo attraverso l\'app. Si noti che la funzione di importazione per bunq è in BETA. È stato testato solo contro l\'API sandbox.', + 'prereq_bunq_ip' => 'bunq richiede il tuo indirizzo IP esterno. Firefly III ha provato a riempire questo campo utilizzando il servizio ipify. Assicurati che questo indirizzo IP sia corretto altrimenti l\'importazione fallirà.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Chiave API fittizia memorizzata correttamente!', + 'prerequisites_saved_for_spectre' => 'ID dell\'app e segreto memorizzati!', + 'prerequisites_saved_for_bunq' => 'Chiave API e IP memorizzati!', + + // job configuration: + 'job_config_apply_rules_title' => 'Configurazione del lavoro - applicare le tue regole?', + 'job_config_apply_rules_text' => 'Una volta avviato il fornitore fittizio, le tue regole possono essere applicate alle transazioni. Questo aggiunge del tempo all\'importazione.', + 'job_config_input' => 'Il tuo input', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Inserisci il nome dell\'album', + 'job_config_fake_artist_text' => 'Molte routine di importazione presentano alcuni passaggi di configurazione da eseguire. Nel caso del fornitore di importazione fittizio, è necessario rispondere ad alcune domande strane. In questo caso, inserire "David Bowie" per continuare.', + 'job_config_fake_song_title' => 'Inserisci il nome del brano', + 'job_config_fake_song_text' => 'Menziona la canzone "Golden years" per continuare con l\'importazione fittizia.', + 'job_config_fake_album_title' => 'Inserisci il nome dell\'album', + 'job_config_fake_album_text' => 'Alcune routine di importazione richiedono dati aggiuntivi a metà dell\'importazione. Nel caso del fornitore di importazione fittizio, è necessario rispondere ad alcune domande strane. Inserire "Station to station" per continuare.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Configurazione importazione (1/4) - Carica il tuo file', + 'job_config_file_upload_text' => 'Questa routine ti aiuterà a importare i file dalla tua banca in Firefly III. ', + 'job_config_file_upload_help' => 'Seleziona il tuo file. Assicurati che il file sia codificato in UTF-8.', + 'job_config_file_upload_config_help' => 'Se hai precedentemente importato i dati in Firefly III, potresti avere un file di configurazione, che preimposterà i valori di configurazione per te. Per alcune banche, altri utenti hanno gentilmente fornito il loro file di configurazione', + 'job_config_file_upload_type_help' => 'Seleziona il tipo di file che caricherai', + 'job_config_file_upload_submit' => 'Carica i file', + 'import_file_type_csv' => 'CSV (valori separati da virgola)', + 'file_not_utf8' => 'Il file che hai caricato non è codificato come UTF-8 o ASCII. Firefly III non può gestire tali file. Utilizzare Notepad++ o Sublime per convertire il file in UTF-8.', + 'job_config_uc_title' => 'Configurazione di importazione (2/4) - Impostazione di base dei file', + 'job_config_uc_text' => 'Per poter importare correttamente il tuo file, ti preghiamo di convalidare le opzioni di seguito.', + 'job_config_uc_header_help' => 'Seleziona questa casella se la prima riga del tuo file CSV sono i titoli delle colonne.', + 'job_config_uc_date_help' => 'Formato della data e ora nel tuo file. Segui il formato indicato in questa pagina. Il valore predefinito analizzerà le date che assomigliano a questa: :dateExample.', + 'job_config_uc_delimiter_help' => 'Scegli il delimitatore di campo che viene utilizzato nel file di ingresso. Se non si è sicuri, la virgola è l\'opzione più sicura.', + 'job_config_uc_account_help' => 'Se il tuo file NON contiene informazioni sui tuoi conti di attività, utilizza questo menu a discesa per selezionare a quale conto appartengono le transazioni nel file.', + 'job_config_uc_apply_rules_title' => 'Applica regole', + 'job_config_uc_apply_rules_text' => 'Applica le tue regole ad ogni transazione importata. Si noti che questo rallenta l\'importazione in modo significativo.', + 'job_config_uc_specifics_title' => 'Opzioni specifiche della banca', + 'job_config_uc_specifics_txt' => 'Alcune banche forniscono file formattati in modo errato. Firefly III può sistemarli automaticamente. Se la tua banca rende disponibili tali file ma non è elencata qui, ti preghiamo di segnalare il problema su GitHub.', + 'job_config_uc_submit' => 'Continua', + 'invalid_import_account' => 'Hai selezionato un conto non valido su cui effettuare l\'importazione.', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Scegli il tuo login', + 'job_config_spectre_login_text' => 'Firefly III ha rilevato :count login esistenti nel tuo account Spectre. Quale vorresti usare per l\'importazione?', + 'spectre_login_status_active' => 'Attivo', + 'spectre_login_status_inactive' => 'Inattivo', + 'spectre_login_status_disabled' => 'Disabilitato', + 'spectre_login_new_login' => 'Accedi con un\'altra banca o con una di queste banche con credenziali diverse.', + 'job_config_spectre_accounts_title' => 'Seleziona i conti dai quali importare', + 'job_config_spectre_accounts_text' => 'Hai selezionato ":name" (:country). Hai :count conti disponibili da questo fornitore. Seleziona i conti attività di Firefly III in cui devono essere memorizzate le transazioni da questi conti. Ricorda che, per importare i dati, sia l\'account Firefly III sia l\'account ":name" devono avere la stessa valuta.', + 'spectre_no_supported_accounts' => 'Non puoi importare da questo conto perché le valute non corrispondono.', + 'spectre_do_not_import' => '(non importare)', + 'spectre_no_mapping' => 'Sembra che tu non abbia selezionato nessun account da cui importare.', + 'imported_from_account' => 'Importato da ":account"', + 'spectre_account_with_number' => 'Conto :number', + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'Account bunq', + 'job_config_bunq_accounts_text' => 'Questi sono i conti associati al tuo account bunq. Seleziona i conti dai quali vuoi effettuare l\'importazione e in quale conto devono essere importate le transazioni.', + 'bunq_no_mapping' => 'Sembra che tu non abbia selezionato nessun account.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Stato', + 'spectre_extra_key_card_type' => 'Tipo carta', + 'spectre_extra_key_account_name' => 'Nome conto', + 'spectre_extra_key_client_name' => 'Nome cliente', + 'spectre_extra_key_account_number' => 'Numero conto', + 'spectre_extra_key_blocked_amount' => 'Importo bloccato', + 'spectre_extra_key_available_amount' => 'Importo disponibile', + 'spectre_extra_key_credit_limit' => 'Limite di credito', + 'spectre_extra_key_interest_rate' => 'Tasso d\'interesse', + 'spectre_extra_key_expiry_date' => 'Data scadenza', + 'spectre_extra_key_open_date' => 'Data apertura', + 'spectre_extra_key_current_time' => 'Ora corrente', + 'spectre_extra_key_current_date' => 'Data corrente', + 'spectre_extra_key_cards' => 'Carte', + 'spectre_extra_key_units' => 'Unità', + 'spectre_extra_key_unit_price' => 'Prezzo unitario', + 'spectre_extra_key_transactions_count' => 'Conteggio transazioni', + + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Crea descrizioni migliori nelle esportazioni ING', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Elimina le virgolette dai file di esportazione di SNS / Volksbank', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Risolvi i possibili problemi con i file ABN AMRO', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Risolvi i possibili problemi con i file Rabobank', + 'specific_pres_name' => 'CA finanziaria scelta dal Presidente', + 'specific_pres_descr' => 'Risolvi i possibili problemi con i file da PC', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Configurazione di importazione (3/4) - Definisci il ruolo di ogni colonna', + 'job_config_roles_text' => 'Ogni colonna nel tuo file CSV contiene determinati dati. Si prega di indicare il tipo di dati che l\'importatore dovrebbe aspettarsi. L\'opzione per "mappare" i dati significa che collegherete ogni voce trovata nella colonna con un valore nel vostro database. Una colonna spesso mappata è la colonna che contiene l\'IBAN del conto. Questo può essere facilmente abbinato all\'IBAN già presente nel tuo database.', + 'job_config_roles_submit' => 'Continua', + 'job_config_roles_column_name' => 'Nome della colonna', + 'job_config_roles_column_example' => 'Dati di esempio della colonna', + 'job_config_roles_column_role' => 'Significato dei dati della colonna', + 'job_config_roles_do_map_value' => 'Mappa questi valori', + 'job_config_roles_no_example' => 'Nessun dato di esempio disponibile', + 'job_config_roles_fa_warning' => 'Se contrassegni una colonna come contenente un importo in una valuta straniera, devi anche impostare la colonna che contiene di quale valuta si tratta.', + 'job_config_roles_rwarning' => 'Come minimo, contrassegna una colonna come colonna dell\'importo. Si consiglia di selezionare anche una colonna per la descrizione, la data e il conto opposto.', + 'job_config_roles_colum_count' => 'Colonna', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Configurazione di importazione (4/4) - Collega i dati importati con i dati di Firefly III', + 'job_config_map_text' => 'Nelle seguenti tabelle, il valore a sinistra mostra le informazioni trovate nel file caricato. È tuo compito mappare questo valore, se possibile, su un valore già presente nel tuo database. Firefly si atterrà a questa mappatura. Se non ci sono valori da mappare o non si desidera mappare il valore specifico, non selezionare niente.', + 'job_config_map_nothing' => 'Non ci sono dati presenti nel tuo file che puoi mappare a valori esistenti. Si prega di premere "Inizia l\'importazione" per continuare.', + 'job_config_field_value' => 'Valore campo', + 'job_config_field_mapped' => 'Mappato a', + 'map_do_not_map' => '(non mappare)', + 'job_config_map_submit' => 'Inizia l\'importazione', + + + // import status page: + 'import_with_key' => 'Importa con chiave \':key\'', 'status_wait_title' => 'Per favore attendere...', 'status_wait_text' => 'Questa finestra si chiuderà tra un momento.', - 'status_fatal_title' => 'Si è verificato un errore irreversibile', - 'status_fatal_text' => 'Si è verificato un errore irreversibile per cui non è stato possibile ripristinare la routine di importazione. Si prega di vedere la spiegazione in rosso qui sotto.', - 'status_fatal_more' => 'Se l\'errore è un timeout, l\'importazione si sarà interrotta a metà. Per alcune configurazioni del server, o semplicemente il server che si è fermato mentre l\'importazione continua a funzionare in sottofondo. Per verificare questo, controlla il file di registro. Se il problema persiste, prendere in considerazione l\'importazione sulla riga di comando.', - 'status_ready_title' => 'L\'importazione è pronta per iniziare', - 'status_ready_text' => 'L\'importazione è pronta per iniziare. È stata eseguita tutta la configurazione necessaria. Si prega di scaricare il file di configurazione. Ti aiuterà con l\'importazione se non dovesse andare come previsto. Per eseguire effettivamente l\'importazione, è possibile eseguire il seguente comando nella console o eseguire l\'importazione basata sul Web. A seconda della configurazione, l\'importazione della console ti darà più feedback.', - 'status_ready_noconfig_text' => 'L\'importazione è pronta per iniziare. È stata eseguita tutta la configurazione necessaria. Per eseguire effettivamente l\'importazione, è possibile eseguire il seguente comando nella console o eseguire l\'importazione basata sul Web. A seconda della configurazione, l\'importazione della console ti darà più feedback.', - 'status_ready_config' => 'Scarica configurazione', - 'status_ready_start' => 'Inizia l\'importazione', - 'status_ready_share' => 'Ti preghiamo di considerare di scaricare la tua configurazione e di condividerla nel centro di configurazione dell\'importazione. Ciò consentirà ad altri utenti di Firefly III di importare i propri file più facilmente.', - 'status_job_new' => 'Il lavoro è nuovo di zecca.', - 'status_job_configuring' => 'L\'importazione è in fase di configurazione.', - 'status_job_configured' => 'L\'importazione è configurata.', - 'status_job_running' => 'L\'importazione è in esecuzione... Attendere...', - 'status_job_error' => 'Il lavoro ha generato un errore.', - 'status_job_finished' => 'L\'importazione è finita!', 'status_running_title' => 'L\'importazione è in esecuzione', - 'status_running_placeholder' => 'Si prega di effettuare un aggiornamento...', - 'status_finished_title' => 'Routine importa terminata', - 'status_finished_text' => 'La routine di importazione ha importato i tuoi dati.', - 'status_errors_title' => 'Errori durante l\'importazione', - 'status_errors_single' => 'Si è verificato un errore durante l\'importazione. Non sembra essere fatale.', - 'status_errors_multi' => 'Alcuni errori si sono verificati durante l\'importazione. Questi non sembrano essere fatali.', - 'status_bread_crumb' => 'Stato importazione', - 'status_sub_title' => 'Stato importazione', - 'config_sub_title' => 'Configura la tua importazione', - 'status_finished_job' => 'Le transazioni :count importate possono essere trovate nel tag :tag .', - 'status_finished_no_tag' => 'Firefly III non ha raccolto alcuna transazione dal tuo file di importazione.', - 'import_with_key' => 'Importa con chiave \':key\'', + 'status_job_running' => 'Attendere, importazione in corso...', + 'status_job_storing' => 'Attendere, memorizzazione dei dati...', + 'status_job_rules' => 'Attendere, applicazione delle regole...', + 'status_fatal_title' => 'Errore fatale', + 'status_fatal_text' => 'L\'importazione ha subito un errore irreversibile. Scusa!', + 'status_fatal_more' => 'Questo messaggio di errore (probabilmente molto criptico) è completato dai file di log, che puoi trovare sul tuo disco rigido, o nel contenitore Docker da cui esegui Firefly III.', + 'status_finished_title' => 'Importazione completata', + 'status_finished_text' => 'L\'importazione è finita.', + 'finished_with_errors' => 'Si sono verificati alcuni errori durante l\'importazione. Controllali attentamente.', + 'unknown_import_result' => 'Risultato di importazione sconosciuto', + 'result_no_transactions' => 'Nessuna transazione è stata importata. Forse erano tutte dei duplicati o semplicemente non c\'era nessuna transazione da importare. Forse i file di log possono dirti cosa è successo. Questo è normale se importi i dati regolarmente.', + 'result_one_transaction' => 'È stata importata esattamente una transazione. È memorizzata sotto l\'etichetta :tag dove è possibile ispezionarla ulteriormente.', + 'result_many_transactions' => 'Firefly III ha importato :count transazioni. Sono memorizzate sotto l\'etichetta :tag dove è possibile ispezionarle ulteriormente.', - // file, upload something - 'file_upload_title' => 'Importa configurazione (1/4) - Carica il tuo file', - 'file_upload_text' => 'Questa routine ti aiuterà a importare file dalla tua banca in Firefly III. Si prega di consultare le pagine di aiuto nell\'angolo in alto a destra.', - 'file_upload_fields' => 'Campi', - 'file_upload_help' => 'Seleziona il tuo file', - 'file_upload_config_help' => 'Se hai precedentemente importato i dati in Firefly III, potresti avere un file di configurazione, con preimpostato i valori di configurazione per te. Per alcune banche, altri utenti hanno gentilmente fornito il loro configurazione file ', - 'file_upload_type_help' => 'Seleziona il tipo di file che carichi', - 'file_upload_submit' => 'File caricati', - // file, upload types - 'import_file_type_csv' => 'CSV (valori separati da virgola)', + // general errors and warnings: + 'bad_job_status' => 'Per accedere a questa pagina, il tuo lavoro di importazione non può avere lo stato ":status".', - // file, initial config for CSV - 'csv_initial_title' => 'Importa configurazione (2/4) - Impostazione di importazione CSV di base', - 'csv_initial_text' => 'Per poter importare correttamente il tuo file, ti preghiamo di convalidare le opzioni di seguito.', - 'csv_initial_box' => 'Configurazione di importazione CSV di base', - 'csv_initial_box_title' => 'Opzioni di impostazione dell\'importazione CSV di base', - 'csv_initial_header_help' => 'Seleziona questa casella se la prima riga del tuo file CSV sono i titoli delle colonne.', - 'csv_initial_date_help' => 'Formato della data e ora nel tuo CSV. Segui il formato indica questa pagina. Il valore predefinito analizzerà le date che assomigliano a questo: :dateExample.', - 'csv_initial_delimiter_help' => 'Scegli il delimitatore di campo che viene utilizzato nel file di input. Se non si è sicuri, la virgola è l\'opzione più sicura.', - 'csv_initial_import_account_help' => 'Se il tuo file CSV NON contiene informazioni sui tuoi conti di attività, utilizza questo menu a discesa per selezionare a quale conto appartengono le transazioni nel CSV.', - 'csv_initial_submit' => 'Continua con il passo 3/4', - - // file, new options: - 'file_apply_rules_title' => 'Applica regole', - 'file_apply_rules_description' => 'Applica le tue regole. Si noti che questo rallenta l\'importazione in modo significativo.', - 'file_match_bills_title' => 'Abbina le bollette', - 'file_match_bills_description' => 'Abbina le tue bollette ai prelievi di nuova creazione. Si noti che questo rallenta l\'importazione in modo significativo.', - - // file, roles config - 'csv_roles_title' => 'Importa configurazione (3/4) - Definisci il ruolo di ogni colonna', - 'csv_roles_text' => 'Ogni colonna nel tuo file CSV contiene determinati dati. Si prega di indicare il tipo di dati che l\'importatore dovrebbe aspettarsi. L\'opzione per "mappare" i dati significa che collegherete ogni voce trovata nella colonna con un valore nel vostro database. Una colonna spesso mappata è la colonna che contiene l\'IBAN del conto. Questo può essere facilmente abbinato all\'IBAN già presente nel tuo database.', - 'csv_roles_table' => 'Tabella', - 'csv_roles_column_name' => 'Nome colonna', - 'csv_roles_column_example' => 'Colonna dati esempio', - 'csv_roles_column_role' => 'Significato dei dati di colonna', - 'csv_roles_do_map_value' => 'Mappa questi valori', - 'csv_roles_column' => 'Colonna', - 'csv_roles_no_example_data' => 'Nessun dato esempio disponibile', - 'csv_roles_submit' => 'Continua con il punto 4/4', - - // not csv, but normal warning - 'roles_warning' => 'Per lo meno, contrassegnare una colonna come colonna importo. Si consiglia di selezionare anche una colonna per la descrizione, la data e il conto avversario.', - 'foreign_amount_warning' => 'Se contrassegni una colonna come contenente un importo in una valuta straniera, devi anche impostare la colonna che contiene la valuta in cui si trova.', - - // file, map data - 'file_map_title' => 'Importa configurazione (4/4) - Collega i dati di importazione ai dati di Firefly III', - 'file_map_text' => 'Nelle seguenti tabelle, il valore a sinistra mostra le informazioni trovate nel file caricato. È tuo compito mappare questo valore, se possibile, su un valore già presente nel tuo database. Firefly si atterrà a questa mappatura. Se non ci sono valori da mappare o non si desidera mappare il valore specifico, selezionare nulla.', - 'file_map_field_value' => 'Valore campo', - 'file_map_field_mapped_to' => 'Mappato a', - 'map_do_not_map' => '(non mappare)', - 'file_map_submit' => 'Inizia l\'importazione', - 'file_nothing_to_map' => 'Non ci sono dati presenti nel tuo file che puoi mappare a valori esistenti. Si prega di premere "Inizia l\'importazione" per continuare.', - - // map things. + // column roles for CSV import: 'column__ignore' => '(ignora questa colonna)', 'column_account-iban' => 'Conto patrimonio (IBAN)', 'column_account-id' => 'Conto patrimonio ID (matching FF3)', @@ -147,10 +250,10 @@ return [ 'column_sepa-ct-id' => 'ID end-to-end del bonifico SEPA', 'column_sepa-ct-op' => 'Conto opposto bonifico SEPA', 'column_sepa-db' => 'Addebito diretto SEPA', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', + 'column_sepa-cc' => 'Codice Compensazione SEPA', + 'column_sepa-ci' => 'Identificativo Creditore SEPA', 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country Code', + 'column_sepa-country' => 'Codice Paese SEPA', 'column_tags-comma' => 'Etichette (separate da virgola)', 'column_tags-space' => 'Etichette (separate con spazio)', 'column_account-number' => 'Conto patrimonio (numero conto)', @@ -158,48 +261,4 @@ return [ 'column_note' => 'Nota(e)', 'column_internal-reference' => 'Riferimento interno', - // prerequisites - 'prerequisites' => 'Prerequisiti', - - // bunq - 'bunq_prerequisites_title' => 'Prerequisiti per un\'importazione da bunq', - 'bunq_prerequisites_text' => 'Per importare da bunq, è necessario ottenere una chiave API. Puoi farlo attraverso l\'applicazione.', - 'bunq_prerequisites_text_ip' => 'Bunq richiede il tuo indirizzo IP esterno. Firefly III ha provato a riempire questo campo utilizzando il servizio ipify. Assicurati che questo indirizzo IP sia corretto altrimenti l\'importazione fallirà.', - 'bunq_do_import' => 'Sì, importa da questo conto', - 'bunq_accounts_title' => 'Conti Bunq', - 'bunq_accounts_text' => 'Questi sono i conti associati al tuo conto Bunq. Seleziona i conti dai quali vuoi effettuare l\'importazione e in quale conto devono essere importate le transazioni.', - - // Spectre - 'spectre_title' => 'Importa usando uno Spectre', - 'spectre_prerequisites_title' => 'Prerequisiti per un\'importazione utilizzando Spectre', - 'spectre_prerequisites_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your secrets page.', - 'spectre_accounts_title' => 'Seleziona i conti dai quali importare', - 'spectre_accounts_text' => 'Ogni account sulla sinistra in basso è stato trovato da Spectre e può essere importato in Firefly III. Seleziona il conto attività che dovrebbe contenere una determinata transazione. Se non desideri importare da un conto specifico, rimuovi il segno di spunta dalla casella di controllo.', - 'spectre_do_import' => 'Si, importa da questo conto', - 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', - - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'SWIFT', - 'spectre_extra_key_status' => 'Stato', - 'spectre_extra_key_card_type' => 'Tipo carta', - 'spectre_extra_key_account_name' => 'Nome conto', - 'spectre_extra_key_client_name' => 'Nome cliente', - 'spectre_extra_key_account_number' => 'Numero conto', - 'spectre_extra_key_blocked_amount' => 'Importo bloccato', - 'spectre_extra_key_available_amount' => 'Importo disponibile', - 'spectre_extra_key_credit_limit' => 'Limite di credito', - 'spectre_extra_key_interest_rate' => 'Tasso d\'interesse', - 'spectre_extra_key_expiry_date' => 'Data scadenza', - 'spectre_extra_key_open_date' => 'Data apertura', - 'spectre_extra_key_current_time' => 'Ora corrente', - 'spectre_extra_key_current_date' => 'Data corrente', - 'spectre_extra_key_cards' => 'Carte', - 'spectre_extra_key_units' => 'Unità', - 'spectre_extra_key_unit_price' => 'Prezzo unità', - 'spectre_extra_key_transactions_count' => 'Conteggio delle transazioni', - - // various other strings: - 'imported_from_account' => 'Importato da ":account"', ]; diff --git a/resources/lang/it_IT/intro.php b/resources/lang/it_IT/intro.php index df49a56afa..a5b2a44c83 100644 --- a/resources/lang/it_IT/intro.php +++ b/resources/lang/it_IT/intro.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // index 'index_intro' => 'Benvenuti nella pagina indice di Firefly III. Si prega di prendersi il tempo necessario per questa introduzione per avere un\'idea di come funziona Firefly III.', @@ -120,7 +121,7 @@ return [ // create rule: 'rules_create_mandatory' => 'Scegli un titolo descrittivo e imposta quando deve essere attivata la regola.', - 'rules_create_ruletriggerholder' => 'Aggiungi tutti i trigger che desideri, ma ricorda che TUTTI i trigger devono corrispondere prima che vengano attivate azioni.', + 'rules_create_ruletriggerholder' => 'Aggiungi tutti i trigger che desideri, ma ricorda che TUTTI i trigger devono corrispondere prima che vengano attivate le azioni.', 'rules_create_test_rule_triggers' => 'Usa questo pulsante per vedere quali transazioni corrispondono alla tua regola.', 'rules_create_actions' => 'Imposta tutte le azioni che vuoi.', @@ -132,5 +133,5 @@ return [ 'currencies_index_default' => 'Firefly III ha una valuta predefinita. Puoi sempre cambiare valuta usando questi pulsanti.', // create currency - 'currencies_create_code' => 'Questo codice dovrebbe essere conforme ISO (Google per la tua nuova valuta).', + 'currencies_create_code' => 'Questo codice dovrebbe essere conforme ISO (cercalo con Google per la tua nuova valuta).', ]; diff --git a/resources/lang/it_IT/list.php b/resources/lang/it_IT/list.php index a2485211bb..4b53c93bf8 100644 --- a/resources/lang/it_IT/list.php +++ b/resources/lang/it_IT/list.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'buttons' => 'Pulsanti', 'icon' => 'Icona', @@ -68,7 +69,7 @@ return [ 'bill' => 'Conto', 'withdrawal' => 'Spesa', 'deposit' => 'Deposito', - 'transfer' => 'Giroconto', + 'transfer' => 'Trasferimento', 'type' => 'Tipo', 'completed' => 'Completato', 'iban' => 'IBAN', @@ -97,24 +98,29 @@ return [ 'number_of_transactions' => 'Numero transazioni', 'total_amount' => 'Importo totale', 'sum' => 'Somma', - 'sum_excluding_transfers' => 'Somma (esclusi i giroconti)', + 'sum_excluding_transfers' => 'Somma (esclusi i trasferimenti)', 'sum_withdrawals' => 'Somma prelievi', 'sum_deposits' => 'Somma versamenti', - 'sum_transfers' => 'Somma giroconti', + 'sum_transfers' => 'Somma dei trasferimenti', 'reconcile' => 'Riconcilia', 'account_on_spectre' => 'Conto (Spectre)', 'do_import' => 'Importo da questo conto', - 'sepa-ct-id' => 'SEPA End to End Identifier', - 'sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'sepa-db' => 'SEPA Mandate Identifier', - 'sepa-country' => 'SEPA Country', - 'sepa-cc' => 'SEPA Clearing Code', + 'sepa-ct-id' => 'Identificativo End-To-End SEPA', + 'sepa-ct-op' => 'Identificativo Conto Oppositore SEPA', + 'sepa-db' => 'Identificativo Mandato SEPA', + 'sepa-country' => 'Paese SEPA', + 'sepa-cc' => 'Codice Compensazione SEPA', 'sepa-ep' => 'SEPA External Purpose', - 'sepa-ci' => 'SEPA Creditor Identifier', + 'sepa-ci' => 'Identificativo Creditore SEPA', + 'external_id' => 'ID esterno', 'account_at_bunq' => 'Conto con Bunq', - 'file_name' => 'File name', - 'file_size' => 'File size', - 'file_type' => 'File type', - 'attached_to' => 'Attached to', - 'file_exists' => 'File exists', + 'file_name' => 'Nome del file', + 'file_size' => 'Dimensione del file', + 'file_type' => 'Tipo del file', + 'attached_to' => 'Allegato a', + 'file_exists' => 'Il file esiste', + 'spectre_bank' => 'Banca', + 'spectre_last_use' => 'Ultimo accesso', + 'spectre_status' => 'Stato', + 'bunq_payment_id' => 'bunq payment ID', ]; diff --git a/resources/lang/it_IT/pagination.php b/resources/lang/it_IT/pagination.php index b65f332398..42bde66250 100644 --- a/resources/lang/it_IT/pagination.php +++ b/resources/lang/it_IT/pagination.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'previous' => '« Precedente', 'next' => 'Avanti »', diff --git a/resources/lang/it_IT/passwords.php b/resources/lang/it_IT/passwords.php index 41f194295b..3e44682679 100644 --- a/resources/lang/it_IT/passwords.php +++ b/resources/lang/it_IT/passwords.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'password' => 'Le password devono contenere almeno sei caratteri e devono corrispondere alla conferma.', 'user' => 'Non possiamo trovare un utente con questo indirizzo e-mail.', diff --git a/resources/lang/it_IT/validation.php b/resources/lang/it_IT/validation.php index af49eb7aba..f6c286443a 100644 --- a/resources/lang/it_IT/validation.php +++ b/resources/lang/it_IT/validation.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'iban' => 'Questo non è un IBAN valido.', 'source_equals_destination' => 'Il conto di origine è uguale al conto di destinazione', @@ -109,7 +110,8 @@ return [ 'in_array' => ':attribute il campo non esiste in :other.', 'present' => ':attribute il campo deve essere presente.', 'amount_zero' => 'L\'importo totale non può essere zero', - 'secure_password' => 'Questa non è una password sicura. Per favore riprova. Per ulteriori informazioni, visitare https://goo.gl/NCh2tN', + 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'secure_password' => 'Questa non è una password sicura. Per favore riprova. Per ulteriori informazioni, visitare http://bit.ly/FF3-password-security', 'attributes' => [ 'email' => 'indirizzo email', 'description' => 'descrizione', @@ -135,15 +137,15 @@ return [ 'rule-action.3' => 'regola azione #3', 'rule-action.4' => 'regola azione #4', 'rule-action.5' => 'regola azione #5', - 'rule-trigger-value.1' => 'regola valore trigger #1', - 'rule-trigger-value.2' => 'regola valore trigger #2', - 'rule-trigger-value.3' => 'regola valore trigger #3', - 'rule-trigger-value.4' => 'regola valore trigger #4', - 'rule-trigger-value.5' => 'regola valore trigger #5', - 'rule-trigger.1' => 'esegui regola #1', - 'rule-trigger.2' => 'esegui regola #2', - 'rule-trigger.3' => 'esegui regola #3', - 'rule-trigger.4' => 'esegui regola #4', - 'rule-trigger.5' => 'esegui regola #5', + 'rule-trigger-value.1' => 'valore #1 del trigger della regola', + 'rule-trigger-value.2' => 'valore #2 del trigger della regola', + 'rule-trigger-value.3' => 'valore #3 del trigger della regola', + 'rule-trigger-value.4' => 'valore #4 del trigger della regola', + 'rule-trigger-value.5' => 'valore #5 del trigger della regola', + 'rule-trigger.1' => 'trigger #1 della regola', + 'rule-trigger.2' => 'trigger #2 della regola', + 'rule-trigger.3' => 'trigger #3 della regola', + 'rule-trigger.4' => 'trigger #4 della regola', + 'rule-trigger.5' => 'trigger #5 della regola', ], ]; From d782e289069ebb75338310bd57b1e34b8be8f7c0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 14:21:54 +0200 Subject: [PATCH 163/182] Update Dutch language files [skip ci] --- resources/lang/nl_NL/auth.php | 16 +- resources/lang/nl_NL/bank.php | 5 +- resources/lang/nl_NL/breadcrumbs.php | 5 +- resources/lang/nl_NL/components.php | 3 +- resources/lang/nl_NL/config.php | 5 +- resources/lang/nl_NL/csv.php | 5 +- resources/lang/nl_NL/demo.php | 7 +- resources/lang/nl_NL/firefly.php | 32 +-- resources/lang/nl_NL/form.php | 12 +- resources/lang/nl_NL/import.php | 313 ++++++++++++++++----------- resources/lang/nl_NL/intro.php | 5 +- resources/lang/nl_NL/list.php | 10 +- resources/lang/nl_NL/pagination.php | 5 +- resources/lang/nl_NL/passwords.php | 5 +- resources/lang/nl_NL/validation.php | 8 +- 15 files changed, 249 insertions(+), 187 deletions(-) diff --git a/resources/lang/nl_NL/auth.php b/resources/lang/nl_NL/auth.php index 948cf45eba..8ef1c2fa01 100644 --- a/resources/lang/nl_NL/auth.php +++ b/resources/lang/nl_NL/auth.php @@ -1,9 +1,8 @@ . */ -return [ - /* - |-------------------------------------------------------------------------- - | Authentication Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used during authentication for various - | messages that we need to display to the user. You are free to modify - | these language lines according to your application's requirements. - | - */ +declare(strict_types=1); +return [ 'failed' => 'Deze gegevens zijn niet correct.', 'throttle' => 'Te veel inlogpogingen. Probeer opnieuw in :seconds seconden.', ]; diff --git a/resources/lang/nl_NL/bank.php b/resources/lang/nl_NL/bank.php index 8826020cbf..5d00b1e685 100644 --- a/resources/lang/nl_NL/bank.php +++ b/resources/lang/nl_NL/bank.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/nl_NL/breadcrumbs.php b/resources/lang/nl_NL/breadcrumbs.php index c15096d016..81dd5186bc 100644 --- a/resources/lang/nl_NL/breadcrumbs.php +++ b/resources/lang/nl_NL/breadcrumbs.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'home' => 'Home', 'edit_currency' => 'Wijzig valuta ":name"', diff --git a/resources/lang/nl_NL/components.php b/resources/lang/nl_NL/components.php index 4d040c691c..c3369d4673 100644 --- a/resources/lang/nl_NL/components.php +++ b/resources/lang/nl_NL/components.php @@ -1,5 +1,4 @@ . */ +declare(strict_types=1); + return [ // profile 'personal_access_tokens' => 'Persoonlijke toegangstokens', diff --git a/resources/lang/nl_NL/config.php b/resources/lang/nl_NL/config.php index 63b564e67a..d2e66fcf86 100644 --- a/resources/lang/nl_NL/config.php +++ b/resources/lang/nl_NL/config.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'html_language' => 'nl', 'locale' => 'nl, Dutch, nl_NL, nl_NL.utf8, nl_NL.UTF-8', diff --git a/resources/lang/nl_NL/csv.php b/resources/lang/nl_NL/csv.php index 622a49202a..aae109a40a 100644 --- a/resources/lang/nl_NL/csv.php +++ b/resources/lang/nl_NL/csv.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/nl_NL/demo.php b/resources/lang/nl_NL/demo.php index 7611dbfe17..5f6ed1f0b2 100644 --- a/resources/lang/nl_NL/demo.php +++ b/resources/lang/nl_NL/demo.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'no_demo_text' => 'Sorry, er is geen extra uitleg voor deze pagina.', 'see_help_icon' => 'Maar het -icoontje kan je wellicht meer vertellen.', @@ -32,5 +33,5 @@ return [ 'currencies-index' => 'Firefly ondersteunt meerdere valuta\'s. Hoewel het standaard de Euro is kan je ook kiezen voor de US dollar of een van de vele anderen. Er is een kleine selectie valuta meegeleverd maar je kan je eigen valuta toevoegen. Het veranderen van de standaardvaluta verandert de bestaande transacties niet: Firefly III ondersteunt het gebruik van meerdere valuta op hetzelfde moment.', 'transactions-index' => 'Deze uitgaven, inkomsten en overschrijvingen zijn niet heel fantasierijk. Ze zijn automatisch gegenereerd.', 'piggy-banks-index' => 'Zoals je kan zien zijn er drie spaarpotjes. Gebruik de plus- en minknoppen om het bedrag in de spaarpotjes te veranderen. Klik op de naam van het spaarpotje om er de geschiedenis van te zien.', - 'import-index' => 'Uiteraard kan je elk CSV bestand importeren in Firefly III', + 'import-index' => 'Je kan elk CSV bestand importeren met Firefly III. Het is ook mogelijk om transacties te importeren van bunq en Spectre. Andere verzamelbedrijven worden ook geïntegreerd. De demo-gebruiker mag alleen de "nep"-importhulp gebruiken. Deze genereert een paar willekeurige transacties om te laten zien hoe het werkt.', ]; diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index e31e56d599..051852c28d 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // general stuff: 'close' => 'Sluiten', @@ -179,6 +180,7 @@ return [ 'authorization_request_intro' => ':client vraagt ​​toestemming om toegang te krijgen tot je financiële administratie. Wil je :client autoriseren om toegang te krijgen tot je gegevens?', 'scopes_will_be_able' => 'Deze applicatie krijgt toegang tot:', 'button_authorize' => 'Toestaan', + 'none_in_select_list' => '(geen)', // check for updates: 'update_check_title' => 'Op updates controleren', @@ -666,6 +668,7 @@ return [ 'bill_will_automatch' => 'Waar van toepassing wordt dit contract automatisch gekoppeld aan transacties', 'skips_over' => 'slaat over', 'bill_store_error' => 'Er ging wat fout bij het opslaan van het contract. Kijk in de logbestanden', + 'list_inactive_rule' => 'inactieve regel', // accounts: 'details_for_asset' => 'Overzicht voor betaalrekening ":name"', @@ -801,6 +804,7 @@ return [ 'opt_group_savingAsset' => 'Spaarrekeningen', 'opt_group_sharedAsset' => 'Gedeelde betaalrekeningen', 'opt_group_ccAsset' => 'Creditcards', + 'opt_group_cashWalletAsset' => 'Cash portomonees', 'notes' => 'Notities', 'unknown_journal_error' => 'Kon de transactie niet opslaan. Kijk in de logbestanden.', @@ -816,6 +820,7 @@ return [ 'language' => 'Taal', 'new_savings_account' => ':bank_name spaarrekening', 'cash_wallet' => 'Cash-rekening', + 'currency_not_present' => 'Geen zorgen als de valuta die je gewend bent er niet tussen staat. Je kan je eigen valuta maken onder Opties > Valuta.', // home page: 'yourAccounts' => 'Je betaalrekeningen', @@ -1013,6 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Haal geld uit spaarpotje ":name"', 'add' => 'Toevoegen', 'no_money_for_piggy' => 'Er is geen geld voor dit spaarpotje.', + 'suggested_savings_per_month' => 'Voorgesteld per maand', 'remove' => 'Verwijderen', 'max_amount_add' => 'Hooguit toe te voegen', @@ -1148,27 +1154,9 @@ return [ 'no_edit_multiple_left' => 'Je hebt geen geldige transacties geselecteerd.', 'cannot_convert_split_journal' => 'Kan geen gesplitste transactie omzetten', - // import bread crumbs and titles: - 'import' => 'Import', - 'import_data' => 'Importeer data', - 'import_general_index_file' => 'Importeer een bestand', - 'import_from_bunq' => 'Importeer uit bunq', - 'import_using_spectre' => 'Importeer via Spectre', - 'import_using_plaid' => 'Importeer via Plaid', - 'import_config_bread_crumb' => 'Instellen van je import', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Gegevens importeren in Firefly III', - 'import_index_sub_title' => 'Index', - 'import_general_index_intro' => 'Dit is de import-routine van Firefly III. Er zijn verschillende manieren om gegevens te importeren in Firefly III, hier als knoppen weergegeven.', - 'upload_error' => 'Het bestand dat je hebt geüpload kan niet gebruikt worden. Het is wellicht het verkeerde type of de verkeerde encoding. In de logbestanden staat meer info.', - 'reset_import_settings_title' => 'Reset importconfiguratie', - 'reset_import_settings_text' => 'Gebruik deze links om importinstellingen voor specifieke providers te resetten. Handig als verkeerde instellingen voorkomen dat je verder kan.', - 'reset_settings_bunq' => 'Verwijdert de bunq API key, je externe IP adres zoals Firefly III die kent, en de voor bunq gegenereerde RSA sleutels.', - 'reset_settings_spectre' => 'Verwijdert je Spectre secrets en ID\'s. Dit verwijdert ook je Spectre keypair. Vergeet die niet te updaten.', - 'settings_reset_for_bunq' => 'Bunq-instellingen zijn gereset.', - 'settings_reset_for_spectre' => 'Spectre-instellingen zijn gereset.', - + 'import_data' => 'Importeer data', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Deze functie werkt niet als je Firefly III gebruikt in combinatie met Sandstorm.IO.', diff --git a/resources/lang/nl_NL/form.php b/resources/lang/nl_NL/form.php index 869a935250..748af68a19 100644 --- a/resources/lang/nl_NL/form.php +++ b/resources/lang/nl_NL/form.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // new user: 'bank_name' => 'Banknaam', @@ -184,6 +185,13 @@ return [ 'blocked' => 'Is geblokkeerd?', 'blocked_code' => 'Reden voor blokkade', + // import + 'apply_rules' => 'Regels toepassen', + 'artist' => 'Artiest', + 'album' => 'Album', + 'song' => 'Nummer', + + // admin 'domain' => 'Domein', 'single_user_mode' => 'Registratie uitgeschakelen', diff --git a/resources/lang/nl_NL/import.php b/resources/lang/nl_NL/import.php index 9a76f0b19b..89381f4bf5 100644 --- a/resources/lang/nl_NL/import.php +++ b/resources/lang/nl_NL/import.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ - // status of import: + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Gegevens importeren in Firefly III', + 'prerequisites_breadcrumb_fake' => 'Vereisten voor de nep-importhulp', + 'prerequisites_breadcrumb_spectre' => 'Vereisten voor Spectre', + 'prerequisites_breadcrumb_bunq' => 'Vereisten voor bunq', + 'job_configuration_breadcrumb' => 'Instellingen voor ":key"', + 'job_status_breadcrumb' => 'Importstatus voor ":key"', + 'cannot_create_for_provider' => 'Firefly III kan niet importeren met behulp van ":provider".', + + // index page: + 'general_index_title' => 'Importeer een bestand', + 'general_index_intro' => 'Dit is de import-routine van Firefly III. Er zijn verschillende manieren om gegevens te importeren in Firefly III, hier als knoppen weergegeven.', + // import provider strings (index): + 'button_fake' => 'Nepdata importeren', + 'button_file' => 'Importeer een bestand', + 'button_bunq' => 'Importeer uit bunq', + 'button_spectre' => 'Importeer via Spectre', + 'button_plaid' => 'Importeer via Plaid', + 'button_yodlee' => 'Importeer via Spectre', + 'button_quovo' => 'Importeer via Quovo', + // global config box (index) + 'global_config_title' => 'Configuratiebestand', + 'global_config_text' => 'In de toekomst bevat dit vak voorkeuren die van toepassing zijn op ALLE bovenstaande importproviders.', + // prerequisites box (index) + 'need_prereq_title' => 'Importvereisten', + 'need_prereq_intro' => 'Sommige importmethoden hebben je aandacht nodig voor ze gebruikt kunnen worden. Ze vereisen bijvoorbeeld speciale API-sleutels of geheime waardes. Je kan ze hier instellen. Het icoontje geeft aan of deze vereisten al ingevuld zijn.', + 'do_prereq_fake' => 'Vereisten voor de nep-importhulp', + 'do_prereq_file' => 'Vereisten voor het importeren van bestanden', + 'do_prereq_bunq' => 'Vereisten voor een import van bunq', + 'do_prereq_spectre' => 'Vereisten voor een import via Spectre', + 'do_prereq_plaid' => 'Vereisten voor een import via Plaid', + 'do_prereq_yodlee' => 'Vereisten voor een import via Yodlee', + 'do_prereq_quovo' => 'Vereisten voor een import via Quovo', + // provider config box (index) + 'can_config_title' => 'Importinstellingen', + 'can_config_intro' => 'Sommige importmethodes kunnen ingesteld worden zoals jij dat wilt. Er zijn extra instellingen die je aan kan passen.', + 'do_config_fake' => 'Instellingen voor de nep-importhulp', + 'do_config_file' => 'Instellingen voor importeren van bestanden', + 'do_config_bunq' => 'Instellingen voor importeren uit bunq', + 'do_config_spectre' => 'Instellingen voor importeren uit Spectre', + 'do_config_plaid' => 'Instellingen voor importeren uit Plaid', + 'do_config_yodlee' => 'Instellingen voor importeren uit Yodlee', + 'do_config_quovo' => 'Instellingen voor importeren uit Quovo', + + // prerequisites: + 'prereq_fake_title' => 'Instellingen voor importeren uit de nep-importhulp', + 'prereq_fake_text' => 'Deze nep-provider heeft een neppe API key nodig. Deze moet 32 tekens lang zijn. Je mag deze gebruiken: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Vereisten voor een import via Spectre', + 'prereq_spectre_text' => 'Als je gegevens wilt importeren via de Spectre API (v4), moet je een aantal geheime codes bezitten. Ze zijn te vinden op de secrets pagina.', + 'prereq_spectre_pub' => 'De Spectre API moet de publieke sleutel kennen die je hieronder ziet. Zonder deze sleutel herkent Spectre je niet. Voer deze publieke sleutel in op je secrets-pagina.', + 'prereq_bunq_title' => 'Vereisten aan een import van bunq', + 'prereq_bunq_text' => 'Om te importeren vanaf bunq moet je een API key hebben. Deze kan je aanvragen in de app. Denk er aan dat deze functie in BETA is. De code is alleen getest op de sandbox API.', + 'prereq_bunq_ip' => 'bunq wilt graag je externe IP-adres weten. Firefly III heeft geprobeerd dit in te vullen met behulp van de ipify-dienst. Zorg dat je zeker weet dat dit IP-adres klopt, want anders zal de import niet werken.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Nep API-sleutel is opgeslagen!', + 'prerequisites_saved_for_spectre' => 'APP ID en secret opgeslagen!', + 'prerequisites_saved_for_bunq' => 'API-sleutel en IP opgeslagen!', + + // job configuration: + 'job_config_apply_rules_title' => 'Importinstellingen - regels toepassen?', + 'job_config_apply_rules_text' => 'Als de nep-importhulp gedraaid heeft kunnen je regels worden toegepast op de transacties. Dit kost wel tijd.', + 'job_config_input' => 'Je invoer', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Voer albumnaam in', + 'job_config_fake_artist_text' => 'Veel importroutines hebben een paar configuratiestappen die je moet doorlopen. In het geval van de nep-importhulp moet je een aantal rare vragen beantwoorden. Voer \'David Bowie\' in om door te gaan.', + 'job_config_fake_song_title' => 'Naam van nummer', + 'job_config_fake_song_text' => 'Noem het nummer "Golden years" om door te gaan met de nep import.', + 'job_config_fake_album_title' => 'Albumnaam invoeren', + 'job_config_fake_album_text' => 'Sommige importroutines vereisen extra gegevens halverwege de import. In het geval van de nep-importhulp moet je een aantal rare vragen beantwoorden. Voer "Station naar station" in om door te gaan.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Importinstellingen (1/4) - Upload je bestand', + 'job_config_file_upload_text' => 'Met deze routine kan je bestanden van je bank importeren in Firefly III. ', + 'job_config_file_upload_help' => 'Selecteer je bestand. Zorg ervoor dat het bestand UTF-8 gecodeerd is.', + 'job_config_file_upload_config_help' => 'Als je eerder gegevens hebt geïmporteerd in Firefly III, heb je wellicht een configuratiebestand, dat een aantal zaken alvast voor je kan instellen. Voor bepaalde banken hebben andere gebruikers uit de liefde van hun hart het benodigde configuratiebestand gedeeld', + 'job_config_file_upload_type_help' => 'Selecteer het type bestand dat je zal uploaden', + 'job_config_file_upload_submit' => 'Bestanden uploaden', + 'import_file_type_csv' => 'CSV (kommagescheiden waardes)', + 'file_not_utf8' => 'Het bestand dat je hebt geüpload, is niet gecodeerd als UTF-8 of ASCII. Firefly III kan dergelijke bestanden niet verwerken. Gebruik Notepad ++ of Sublime om je bestand naar UTF-8 te converteren.', + 'job_config_uc_title' => 'Importinstellingen (2/4) - Algemene importinstellingen', + 'job_config_uc_text' => 'Om je bestand goed te kunnen importeren moet je deze opties verifiëren.', + 'job_config_uc_header_help' => 'Vink hier als de eerste rij kolomtitels bevat.', + 'job_config_uc_date_help' => 'Datum/tijd formaat in jouw bestand. Volg het formaat zoals ze het op deze pagina uitleggen. Het standaardformaat ziet er zo uit: :dateExample.', + 'job_config_uc_delimiter_help' => 'Kies het veldscheidingsteken dat in jouw bestand wordt gebruikt. Als je het niet zeker weet, is de komma de beste optie.', + 'job_config_uc_account_help' => 'Als jouw CSV bestand geen referenties bevat naar jouw betaalrekening(en), geef dan hier aan om welke rekening het gaat.', + 'job_config_uc_apply_rules_title' => 'Regels toepassen', + 'job_config_uc_apply_rules_text' => 'Past je regels toe op elke geïmporteerde transactie. Merk op dat dit de import aanzienlijk vertraagt.', + 'job_config_uc_specifics_title' => 'Bank-specifieke opties', + 'job_config_uc_specifics_txt' => 'Sommige banken leveren slecht geformatteerde bestanden aan. Firefly III kan deze automatisch corrigeren. Als jouw bank dergelijke bestanden levert, maar deze hier niet wordt vermeld, open dan een issue op GitHub.', + 'job_config_uc_submit' => 'Volgende', + 'invalid_import_account' => 'Je hebt een ongeldige betaalrekening geselecteerd om in te importeren.', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Kies je login', + 'job_config_spectre_login_text' => 'Firefly III heeft :count bestaande login(s) gevonden in je Spectre-account. Welke wil je gebruiken om van te importeren?', + 'spectre_login_status_active' => 'Actief', + 'spectre_login_status_inactive' => 'Inactief', + 'spectre_login_status_disabled' => 'Uitgeschakeld', + 'spectre_login_new_login' => 'Log in via een andere bank, of via een van deze banken met andere inloggegevens.', + 'job_config_spectre_accounts_title' => 'Selecteer de rekeningen waaruit je wilt importeren', + 'job_config_spectre_accounts_text' => 'Je hebt ":name" (:country) geselecteerd. Je hebt :count rekening(en) bij deze provider. Kies de Firefly III betaalrekening(en) waar je de transacties in wilt opslaan. Denk er aan dat zowel de ":name"-rekeningen als de Firefly III rekeningen dezelfde valuta moeten hebben.', + 'spectre_no_supported_accounts' => 'Je kan niet importeren van deze rekening omdat de valuta niet overeen komt.', + 'spectre_do_not_import' => '(niet importeren)', + 'spectre_no_mapping' => 'Je hebt geen rekeningen geselecteerd om uit te importeren.', + 'imported_from_account' => 'Geïmporteerd uit ":account"', + 'spectre_account_with_number' => 'Rekening :number', + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq rekeningen', + 'job_config_bunq_accounts_text' => 'Deze rekeningen zijn geassocieerd met je bunq-account. Kies de rekeningen waar je van wilt importeren, en geef aan waar de gegevens geïmporteerd moeten worden.', + 'bunq_no_mapping' => 'Je hebt geen rekeningen geselecteerd om uit te importeren.', + 'should_download_config' => 'Download het configuratiebestand voor deze import. Dit maakt toekomstige imports veel eenvoudiger.', + 'share_config_file' => 'Als je gegevens hebt geimporteerd van een gewone bank, deel dan je configuratiebestand zodat het makkelijk is voor andere gebruikers om hun gegevens te importeren. Als je je bestand deelt deel je natuurlijk géén privé-gegevens.', + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Status', + 'spectre_extra_key_card_type' => 'Kaartsoort', + 'spectre_extra_key_account_name' => 'Rekeningnaam', + 'spectre_extra_key_client_name' => 'Klantnaam', + 'spectre_extra_key_account_number' => 'Rekeningnummer', + 'spectre_extra_key_blocked_amount' => 'Geblokkeerd bedrag', + 'spectre_extra_key_available_amount' => 'Beschikbaar bedrag', + 'spectre_extra_key_credit_limit' => 'Kredietlimiet', + 'spectre_extra_key_interest_rate' => 'Rente', + 'spectre_extra_key_expiry_date' => 'Vervaldatum', + 'spectre_extra_key_open_date' => 'Openingsdatum', + 'spectre_extra_key_current_time' => 'Huidige tijd', + 'spectre_extra_key_current_date' => 'Huidige datum', + 'spectre_extra_key_cards' => 'Kaarten', + 'spectre_extra_key_units' => 'Eenheden', + 'spectre_extra_key_unit_price' => 'Prijs per eenheid', + 'spectre_extra_key_transactions_count' => 'Transacties', + + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Maak betere beschrijvingen in de export van ING', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim citaten uit exportbestanden van SNS / Volksbank', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Lost mogelijke problemen op met ABN AMRO-bestanden', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Lost mogelijke problemen op met Rabobank txt-bestanden', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Lost mogelijke problemen op met PC bestanden', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Importinstellingen (3/4) - rol van elke kolom definiëren', + 'job_config_roles_text' => 'Elke kolom in je CSV-bestand bevat bepaalde gegevens. Gelieve aan te geven wat voor soort gegevens de import-routine kan verwachten. De optie "maak een link" betekent dat u elke vermelding in die kolom linkt aan een waarde uit je database. Een vaak gelinkte kolom is die met de IBAN-code van de tegenrekening. Die kan je dan linken aan de IBAN in jouw database.', + 'job_config_roles_submit' => 'Volgende', + 'job_config_roles_column_name' => 'Kolomnaam', + 'job_config_roles_column_example' => 'Voorbeeldgegevens', + 'job_config_roles_column_role' => 'Kolomrol', + 'job_config_roles_do_map_value' => 'Maak een link', + 'job_config_roles_no_example' => 'Geen voorbeeldgegevens', + 'job_config_roles_fa_warning' => 'Als je een kolom markeert als "vreemde valuta" moet je ook aangeven in welke kolom de valuta staat.', + 'job_config_roles_rwarning' => 'Geef minstens de kolom aan waar het bedrag in staat. Als het even kan, ook een kolom voor de omschrijving, datum en de andere rekening.', + 'job_config_roles_colum_count' => 'Kolom', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Importinstellingen (4/4) - Link importgegevens aan Firefly III-gegevens', + 'job_config_map_text' => 'In deze tabellen is de linkerwaarde een waarde uit je CSV bestand. Jij moet de link leggen, als mogelijk, met een waarde uit jouw database. Firefly houdt zich hier aan. Als er geen waarde is, selecteer dan ook niets.', + 'job_config_map_nothing' => 'Je gaat geen gegevens importeren die te mappen zijn. Klik op "Start import" om verder te gaan.', + 'job_config_field_value' => 'Veldwaarde', + 'job_config_field_mapped' => 'Gelinkt aan', + 'map_do_not_map' => '(niet linken)', + 'job_config_map_submit' => 'Start importeren', + + + // import status page: + 'import_with_key' => 'Import met code \':key\'', 'status_wait_title' => 'Momentje...', 'status_wait_text' => 'Dit vak verdwijnt zometeen.', - 'status_fatal_title' => 'Er is een fatale fout opgetreden', - 'status_fatal_text' => 'Een fatale fout opgetreden, waar de import-routine niet van terug heeft. Zie de uitleg in het rood hieronder.', - 'status_fatal_more' => 'Als de fout een time-out is, zal de import-routine halverwege gestopt zijn. Bij bepaalde serverconfiguraties is het alleen maar de server die gestopt terwijl de import-routine op de achtergrond doorloopt. Controleer de logboekbestanden om te zien wat er aan de hand is. Als het probleem zich blijft voordoen, gebruik dan de command-line opdracht.', - 'status_ready_title' => 'De import is klaar om te beginnen', - 'status_ready_text' => 'De import kan beginnen. Alle configuratie is opgeslagen. Download dit bestand. Het kan schelen als je de import opnieuw moet doen. Om daadwerkelijk te beginnen, gebruik je of het commando in je console, of de website. Afhankelijk van hoe je Firefly III hebt ingesteld, geeft de console-methode meer feedback.', - 'status_ready_noconfig_text' => 'De import kan beginnen. Alle configuratie is opgeslagen. Om daadwerkelijk te beginnen, gebruik je of het commando in je console, of de website. Afhankelijk van hoe je Firefly III hebt ingesteld, geeft de console-methode meer feedback.', - 'status_ready_config' => 'Download importconfiguratie', - 'status_ready_start' => 'Start importeren', - 'status_ready_share' => 'Overweeg om je configuratiebestand te downloaden en te delen op de configuratiebestand-wiki. Hiermee kan je het andere Firefly III gebruikers weer makkelijker maken.', - 'status_job_new' => 'De import is gloednieuw.', - 'status_job_configuring' => 'De import wordt geconfigureerd.', - 'status_job_configured' => 'De import is geconfigureerd.', - 'status_job_running' => 'De import is bezig.. Momentje..', - 'status_job_error' => 'De import heeft een fout gegenereerd.', - 'status_job_finished' => 'Het importeren is voltooid!', 'status_running_title' => 'De import is bezig', - 'status_running_placeholder' => 'Wacht even voor een update...', - 'status_finished_title' => 'Importeren is klaar', - 'status_finished_text' => 'De import is klaar.', - 'status_errors_title' => 'Fouten tijdens het importeren', - 'status_errors_single' => 'Er is een niet-fatale fout opgetreden tijdens het importeren.', - 'status_errors_multi' => 'Er zijn een aantal niet-fatale fouten opgetreden tijdens het importeren.', - 'status_bread_crumb' => 'Status van importeren', - 'status_sub_title' => 'Status van importeren', - 'config_sub_title' => 'Instellen van je import', - 'status_finished_job' => 'De :count geïmporteerde transacties kan je vinden onder tag :tag.', - 'status_finished_no_tag' => 'Er is niets uit je bestand geïmporteerd.', - 'import_with_key' => 'Import met code \':key\'', + 'status_job_running' => 'Even geduld, de import loopt...', + 'status_job_storing' => 'Even geduld, de gegevens worden opgeslagen...', + 'status_job_rules' => 'Even gedult, je regels worden toegepast...', + 'status_fatal_title' => 'Onherstelbare fout', + 'status_fatal_text' => 'De import is tegen een fout aangelopen waar-ie niet meer van terug kan komen. Excuses!', + 'status_fatal_more' => 'Deze (waarschijnlijk zeer cryptische) foutmelding wordt aangevuld door logbestanden, die je kan vinden op je harde schijf of in de Docker-container waar je Firefly III draait.', + 'status_finished_title' => 'Importeren voltooid', + 'status_finished_text' => 'Het importeren is voltooid.', + 'finished_with_errors' => 'Er zijn enkele fouten opgetreden tijdens het importeren. Beoordeel ze zorgvuldig.', + 'unknown_import_result' => 'Onbekend importresultaat', + 'result_no_transactions' => 'Er zijn geen transacties geïmporteerd. Misschien waren ze allemaal dubbel, of er zijn simpelweg geen transacties gevonden die kunnen worden geïmporteerd. Misschien kunnen de logbestanden je vertellen wat er is gebeurd. Als je regelmatig gegevens importeert, is dit normaal.', + 'result_one_transaction' => 'Precies één transactie is geïmporteerd. Je kan deze bekijken onder tag :tag.', + 'result_many_transactions' => 'Firefly III heeft :count transacties geïmporteerd. Je kan ze inspecteren onder tag :tag.', - // file, upload something - 'file_upload_title' => 'Importinstellingen (1/4) - Upload je bestand', - 'file_upload_text' => 'Deze pagina\'s helpen je bestanden van je bank te importeren in Firefly III. Gebruik de hulp-pagina\'s linksboven voor meer informatie.', - 'file_upload_fields' => 'Velden', - 'file_upload_help' => 'Selecteer je bestand', - 'file_upload_config_help' => 'Als je eerder gegevens hebt geïmporteerd in Firefly III, heb je wellicht een configuratiebestand, dat een aantal zaken alvast voor je kan instellen. Voor bepaalde banken hebben andere gebruikers uit de liefde van hun hart het benodigde configuratiebestand gedeeld', - 'file_upload_type_help' => 'Selecteer het type bestand dat je zal uploaden', - 'file_upload_submit' => 'Bestanden uploaden', - // file, upload types - 'import_file_type_csv' => 'CSV (kommagescheiden waardes)', + // general errors and warnings: + 'bad_job_status' => 'Om deze pagina te bekijken mag je import-job niet de status ":status" hebben.', - // file, initial config for CSV - 'csv_initial_title' => 'Importinstellingen (2/4) - Algemene CVS importinstellingen', - 'csv_initial_text' => 'Om je bestand goed te kunnen importeren moet je deze opties verifiëren.', - 'csv_initial_box' => 'Algemene CVS importinstellingen', - 'csv_initial_box_title' => 'Algemene CVS importinstellingen', - 'csv_initial_header_help' => 'Vink hier als de eerste rij kolomtitels bevat.', - 'csv_initial_date_help' => 'Datum/tijd formaat in jouw CSV bestand. Volg het formaat zoals ze het op deze pagina uitleggen. Het standaardformaat ziet er zo uit: :dateExample.', - 'csv_initial_delimiter_help' => 'Kies het veldscheidingsteken dat in jouw bestand wordt gebruikt. Als je het niet zeker weet, is de komma de beste optie.', - 'csv_initial_import_account_help' => 'Als jouw CSV bestand geen referenties bevat naar jouw rekening(en), geef dan hier aan om welke rekening het gaat.', - 'csv_initial_submit' => 'Ga verder met stap 3/4', - - // file, new options: - 'file_apply_rules_title' => 'Regels toepassen', - 'file_apply_rules_description' => 'Past regels toe tijdens de import. Dit vertraagt de import behoorlijk.', - 'file_match_bills_title' => 'Match contracten', - 'file_match_bills_description' => 'Checkt of bestaande contracten matchen met nieuwe uitgaves. Dit vertraagt de import behoorlijk.', - - // file, roles config - 'csv_roles_title' => 'Importinstellingen (3/4) - rol van elke kolom definiëren', - 'csv_roles_text' => 'Elke kolom in je CSV-bestand bevat bepaalde gegevens. Gelieve aan te geven wat voor soort gegevens de import-routine kan verwachten. De optie "maak een link" betekent dat u elke vermelding in die kolom linkt aan een waarde uit je database. Een vaak gelinkte kolom is die met de IBAN-code van de tegenrekening. Die kan je dan linken aan de IBAN in jouw database.', - 'csv_roles_table' => 'Tabel', - 'csv_roles_column_name' => 'Kolomnaam', - 'csv_roles_column_example' => 'Voorbeeldgegevens', - 'csv_roles_column_role' => 'Kolomrol', - 'csv_roles_do_map_value' => 'Maak een link', - 'csv_roles_column' => 'Kolom', - 'csv_roles_no_example_data' => 'Geen voorbeeldgegevens', - 'csv_roles_submit' => 'Ga verder met stap 4/4', - - // not csv, but normal warning - 'roles_warning' => 'Geef minstens de kolom aan waar het bedrag in staat. Als het even kan, ook een kolom voor de omschrijving, datum en de andere rekening.', - 'foreign_amount_warning' => 'Als je een kolom markeert als "vreemde valuta" moet je ook aangeven in welke kolom de valuta staat.', - - // file, map data - 'file_map_title' => 'Importinstellingen (4/4) - Link importgegevens aan Firefly III-gegevens', - 'file_map_text' => 'In deze tabellen is de linkerwaarde een waarde uit je CSV bestand. Jij moet de link leggen, als mogelijk, met een waarde uit jouw database. Firefly houdt zich hier aan. Als er geen waarde is, selecteer dan ook niets.', - 'file_map_field_value' => 'Veldwaarde', - 'file_map_field_mapped_to' => 'Gelinkt aan', - 'map_do_not_map' => '(niet linken)', - 'file_map_submit' => 'Start importeren', - 'file_nothing_to_map' => 'Je gaat geen gegevens importeren die te mappen zijn. Klik op "Start import" om verder te gaan.', - - // map things. + // column roles for CSV import: 'column__ignore' => '(negeer deze kolom)', 'column_account-iban' => 'Betaalrekening (IBAN)', 'column_account-id' => 'Betaalrekening (ID gelijk aan FF3)', @@ -158,48 +261,4 @@ return [ 'column_note' => 'Opmerking(en)', 'column_internal-reference' => 'Interne referentie', - // prerequisites - 'prerequisites' => 'Vereisten', - - // bunq - 'bunq_prerequisites_title' => 'Voorwaarden voor een import van bunq', - 'bunq_prerequisites_text' => 'Om te importeren vanaf bunq moet je een API key hebben. Deze kan je aanvragen in de app. Denk er aan dat deze functie in BETA is. De code is alleen getest op de sandbox API.', - 'bunq_prerequisites_text_ip' => 'Bunq wilt graag je externe IP-adres weten. Firefly III heeft geprobeerd dit in te vullen met behulp van de ipify-dienst. Zorg dat je zeker weet dat dit IP-adres klopt, want anders zal de import niet werken.', - 'bunq_do_import' => 'Ja, importeer van deze rekening', - 'bunq_accounts_title' => 'Bunq rekeningen', - 'bunq_accounts_text' => 'Dit zijn de rekeningen uit je bunq-account. Selecteer de rekeningen waaruit je wilt importeren, en welke betaalrekeningen deze transacties op terecht moeten komen.', - - // Spectre - 'spectre_title' => 'Importeer via Spectre', - 'spectre_prerequisites_title' => 'Voorwaarden voor een import via Spectre', - 'spectre_prerequisites_text' => 'Als je gegevens wilt importeren via de Spectre API (v4), moet je een aantal geheime codes bezitten. Ze zijn te vinden op de secrets pagina.', - 'spectre_enter_pub_key' => 'Het importeren werkt alleen als je deze publieke sleutel op je secrets-pagina invoert.', - 'spectre_accounts_title' => 'Selecteer de accounts waaruit je wilt importeren', - 'spectre_accounts_text' => 'Links staan de rekeningen die zijn gevonden door Spectre. Ze kunnen worden geïmporteerd in Firefly III. Selecteer er de juiste betaalrekening bij. Verwijder het vinkje als je toch niet van een bepaalde rekening wilt importeren.', - 'spectre_do_import' => 'Ja, importeer van deze rekening', - 'spectre_no_supported_accounts' => 'Je kan helaas niet vanaf deze rekening importeren omdat de valuta niet overeenkomt.', - - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'SWIFT', - 'spectre_extra_key_status' => 'Status', - 'spectre_extra_key_card_type' => 'Kaarttype', - 'spectre_extra_key_account_name' => 'Rekeningnaam', - 'spectre_extra_key_client_name' => 'Klantnaam', - 'spectre_extra_key_account_number' => 'Rekeningnummer', - 'spectre_extra_key_blocked_amount' => 'Gereserveerd bedrag', - 'spectre_extra_key_available_amount' => 'Beschikbaar bedrag', - 'spectre_extra_key_credit_limit' => 'Kredietlimiet', - 'spectre_extra_key_interest_rate' => 'Rente', - 'spectre_extra_key_expiry_date' => 'Vervaldatum', - 'spectre_extra_key_open_date' => 'Openingsdatum', - 'spectre_extra_key_current_time' => 'Huidige tijd', - 'spectre_extra_key_current_date' => 'Huidige datum', - 'spectre_extra_key_cards' => 'Kaarten', - 'spectre_extra_key_units' => 'Eenheden', - 'spectre_extra_key_unit_price' => 'Prijs per eenheid', - 'spectre_extra_key_transactions_count' => 'Transacties', - - // various other strings: - 'imported_from_account' => 'Geïmporteerd uit ":account"', ]; diff --git a/resources/lang/nl_NL/intro.php b/resources/lang/nl_NL/intro.php index 19344b11b0..777b8f8ad1 100644 --- a/resources/lang/nl_NL/intro.php +++ b/resources/lang/nl_NL/intro.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // index 'index_intro' => 'Welkom op de homepage van Firefly III. Neem even de tijd voor deze introductie zodat je Firefly III leert kennen.', diff --git a/resources/lang/nl_NL/list.php b/resources/lang/nl_NL/list.php index d33a4301c1..cd17c28646 100644 --- a/resources/lang/nl_NL/list.php +++ b/resources/lang/nl_NL/list.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'buttons' => 'Knoppen', 'icon' => 'Icoon', @@ -111,10 +112,15 @@ return [ 'sepa-cc' => 'SEPA vrijwaringscode', 'sepa-ep' => 'SEPA transactiedoeleinde', 'sepa-ci' => 'SEPA crediteuridentificatie', + 'external_id' => 'Externe ID', 'account_at_bunq' => 'Bunq-account', 'file_name' => 'Bestandsnaam', 'file_size' => 'Bestandsgrootte', 'file_type' => 'Bestandstype', 'attached_to' => 'Bijlage van', 'file_exists' => 'Bestand bestaat', + 'spectre_bank' => 'Bank', + 'spectre_last_use' => 'Laatst ingelogd', + 'spectre_status' => 'Status', + 'bunq_payment_id' => 'bunq betalings-ID', ]; diff --git a/resources/lang/nl_NL/pagination.php b/resources/lang/nl_NL/pagination.php index a1caed0187..5be4a5ad33 100644 --- a/resources/lang/nl_NL/pagination.php +++ b/resources/lang/nl_NL/pagination.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'previous' => '« Vorige', 'next' => 'Volgende »', diff --git a/resources/lang/nl_NL/passwords.php b/resources/lang/nl_NL/passwords.php index bb4c4358f6..71bea1d8ce 100644 --- a/resources/lang/nl_NL/passwords.php +++ b/resources/lang/nl_NL/passwords.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'password' => 'Wachtwoorden moeten zes karakters lang zijn, en natuurlijk 2x hetzelfde invoeren.', 'user' => 'Geen gebruiker met dat e-mailadres.', diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php index d2d1cf9fc7..d5d0537e2b 100644 --- a/resources/lang/nl_NL/validation.php +++ b/resources/lang/nl_NL/validation.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'iban' => 'Dit is niet een geldige IBAN.', 'source_equals_destination' => 'De bronrekening is gelijk aan de doelrekening', @@ -109,7 +110,8 @@ return [ 'in_array' => 'Het :attribute veld bestaat niet in :other.', 'present' => 'Het :attribute veld moet aanwezig zijn.', 'amount_zero' => 'Het totaalbedrag kan niet nul zijn', - 'secure_password' => 'Dit is geen sterk wachtwoord. Probeer het nog een keer. Zie ook: https://goo.gl/NCh2tN', + 'unique_piggy_bank_for_user' => 'De naam van de spaarpot moet uniek zijn.', + 'secure_password' => 'Dit is geen sterk wachtwoord. Probeer het nog een keer. Zie ook: http://bit.ly/FF3-password-security', 'attributes' => [ 'email' => 'e-mailadres', 'description' => 'omschrijving', From 6ce200b60d7c89a92a28c4ad5136d4f1b5868399 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 14:22:08 +0200 Subject: [PATCH 164/182] Update Polish language files [skip ci] --- resources/lang/pl_PL/auth.php | 16 +- resources/lang/pl_PL/bank.php | 5 +- resources/lang/pl_PL/breadcrumbs.php | 5 +- resources/lang/pl_PL/components.php | 3 +- resources/lang/pl_PL/config.php | 5 +- resources/lang/pl_PL/csv.php | 5 +- resources/lang/pl_PL/demo.php | 7 +- resources/lang/pl_PL/firefly.php | 32 +-- resources/lang/pl_PL/form.php | 12 +- resources/lang/pl_PL/import.php | 313 ++++++++++++++++----------- resources/lang/pl_PL/intro.php | 5 +- resources/lang/pl_PL/list.php | 10 +- resources/lang/pl_PL/pagination.php | 5 +- resources/lang/pl_PL/passwords.php | 5 +- resources/lang/pl_PL/validation.php | 8 +- 15 files changed, 249 insertions(+), 187 deletions(-) diff --git a/resources/lang/pl_PL/auth.php b/resources/lang/pl_PL/auth.php index 0ef5506167..cf2ad8e5f2 100644 --- a/resources/lang/pl_PL/auth.php +++ b/resources/lang/pl_PL/auth.php @@ -1,9 +1,8 @@ . */ -return [ - /* - |-------------------------------------------------------------------------- - | Authentication Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used during authentication for various - | messages that we need to display to the user. You are free to modify - | these language lines according to your application's requirements. - | - */ +declare(strict_types=1); +return [ 'failed' => 'Te poświadczenia nie zgadzają się z naszymi danymi.', 'throttle' => 'Zbyt wiele prób logowania. Spróbuj ponownie za :seconds sekund.', ]; diff --git a/resources/lang/pl_PL/bank.php b/resources/lang/pl_PL/bank.php index 8826020cbf..5d00b1e685 100644 --- a/resources/lang/pl_PL/bank.php +++ b/resources/lang/pl_PL/bank.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/pl_PL/breadcrumbs.php b/resources/lang/pl_PL/breadcrumbs.php index 3652e30e29..29b5e21884 100644 --- a/resources/lang/pl_PL/breadcrumbs.php +++ b/resources/lang/pl_PL/breadcrumbs.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'home' => 'Strona główna', 'edit_currency' => 'Modyfikuj walutę ":name"', diff --git a/resources/lang/pl_PL/components.php b/resources/lang/pl_PL/components.php index e34586abfd..e9a0269913 100644 --- a/resources/lang/pl_PL/components.php +++ b/resources/lang/pl_PL/components.php @@ -1,5 +1,4 @@ . */ +declare(strict_types=1); + return [ // profile 'personal_access_tokens' => 'Osobisty kod dostępowy', diff --git a/resources/lang/pl_PL/config.php b/resources/lang/pl_PL/config.php index 209a790bfc..eedc86c9af 100644 --- a/resources/lang/pl_PL/config.php +++ b/resources/lang/pl_PL/config.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'html_language' => 'pl', 'locale' => 'pl, Polish, polski, pl_PL, pl_PL.utf8, pl_PL.UTF-8', diff --git a/resources/lang/pl_PL/csv.php b/resources/lang/pl_PL/csv.php index 622a49202a..aae109a40a 100644 --- a/resources/lang/pl_PL/csv.php +++ b/resources/lang/pl_PL/csv.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/pl_PL/demo.php b/resources/lang/pl_PL/demo.php index c6deb76642..b4ca24333f 100644 --- a/resources/lang/pl_PL/demo.php +++ b/resources/lang/pl_PL/demo.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'no_demo_text' => 'Niestety, nie ma dodatkowego tekstu wyjaśniającego demo dla tej strony.', 'see_help_icon' => 'Jednakże ikona w prawym górnym rogu może powiedzieć Ci więcej.', @@ -32,5 +33,5 @@ return [ 'currencies-index' => 'Firefly obsługuje wiele walut. Chociaż domyślnie jest to euro, można go ustawić na dolara amerykańskiego i wiele innych walut. Jak widać niewielki wybór walut został uwzględniony, ale możesz dodać własne, jeśli chcesz. Zmiana domyślnej waluty nie zmieni waluty istniejących transakcji, jednak: Firefly III obsługuje korzystanie z wielu walut w tym samym czasie.', 'transactions-index' => 'Te wydatki, depozyty i transfery nie są szczególnie pomysłowe. Zostały wygenerowane automatycznie.', 'piggy-banks-index' => 'Jak widać, istnieją trzy skarbonki. Użyj przycisków plus i minus, aby wpłynąć na ilość pieniędzy w każdej skarbonce. Kliknij nazwę skarbonki, aby zobaczyć administrację każdej skarbonki.', - 'import-index' => 'Oczywiście każdy plik CSV można zaimportować do Firefly III', + 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', ]; diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index b6bb8ed4d9..c904d80cd6 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // general stuff: 'close' => 'Zamknij', @@ -179,6 +180,7 @@ return [ 'authorization_request_intro' => ':client prosi o pozwolenie na dostęp do Twojej administracji finansowej. Czy chcesz pozwolić :client na dostęp do tych danych?', 'scopes_will_be_able' => 'Ta aplikacja będzie mogła:', 'button_authorize' => 'Autoryzuj', + 'none_in_select_list' => '(none)', // check for updates: 'update_check_title' => 'Sprawdź aktualizacje', @@ -666,6 +668,7 @@ return [ 'bill_will_automatch' => 'Rachunek będzie automatycznie powiązany z pasującymi transakcjami', 'skips_over' => 'pomija', 'bill_store_error' => 'Wystąpił nieoczekiwany błąd podczas zapisywania nowego rachunku. Sprawdź pliki dziennika', + 'list_inactive_rule' => 'inactive rule', // accounts: 'details_for_asset' => 'Szczegóły konta aktywów ":name"', @@ -801,6 +804,7 @@ return [ 'opt_group_savingAsset' => 'Konta oszczędnościowe', 'opt_group_sharedAsset' => 'Współdzielone konta aktywów', 'opt_group_ccAsset' => 'Karty kredytowe', + 'opt_group_cashWalletAsset' => 'Cash wallets', 'notes' => 'Notatki', 'unknown_journal_error' => 'Nie można zapisać transakcji. Sprawdź pliki dziennika.', @@ -816,6 +820,7 @@ return [ 'language' => 'Język', 'new_savings_account' => 'Konto oszczędnościowe :bank_name', 'cash_wallet' => 'Portfel gotówkowy', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Twoje konta', @@ -1013,6 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Usuń pieniądze ze skarbonki ":name"', 'add' => 'Dodaj', 'no_money_for_piggy' => 'Nie masz pieniędzy, które mógłbyś umieścić w tej skarbonce.', + 'suggested_savings_per_month' => 'Suggested per month', 'remove' => 'Usuń', 'max_amount_add' => 'Maksymalną kwotą, którą możesz dodać jest', @@ -1148,27 +1154,9 @@ return [ 'no_edit_multiple_left' => 'Nie wybrałeś żadnych poprawnych transakcji do edycji.', 'cannot_convert_split_journal' => 'Nie można przekonwertować podzielonej transakcji', - // import bread crumbs and titles: - 'import' => 'Importuj', - 'import_data' => 'Importuj dane', - 'import_general_index_file' => 'Importuj plik', - 'import_from_bunq' => 'Importuj z bunq', - 'import_using_spectre' => 'Importuj za pomocą Spectre', - 'import_using_plaid' => 'Importuj za pomocą Plaid', - 'import_config_bread_crumb' => 'Skonfiguruj swój import', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Importuj dane do Firefly III', - 'import_index_sub_title' => 'Indeks', - 'import_general_index_intro' => 'Witamy w procedurze importu Firefly III. Istnieje kilka sposobów importowania danych do Firefly III.', - 'upload_error' => 'Przesłany plik nie może zostać przetworzony. Prawdopodobnie typ pliku lub kodowanie jest błędne. Pliki dziennika zawierają więcej informacji.', - 'reset_import_settings_title' => 'Zresetuj konfigurację importu', - 'reset_import_settings_text' => 'Możesz użyć tych linków, aby zresetować ustawienia importu dla konkretnych dostawców. Jest to przydatne, gdy złe ustawienia uniemożliwiają importowanie danych.', - 'reset_settings_bunq' => 'Usuń klucz API, lokalny zewnętrzny adres IP oraz klucze RSA związane z Bunq.', - 'reset_settings_spectre' => 'Usuń identyfikatory i sekrety Spectre. Spowoduje to również usunięcie Twojego klucza Spectre. Pamiętaj, aby zaktualizować nowy.', - 'settings_reset_for_bunq' => 'Resetowanie ustawień Bunq.', - 'settings_reset_for_spectre' => 'Resetowanie ustawień Spectre.', - + 'import_data' => 'Importuj dane', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Ta funkcja nie jest dostępna, gdy używasz Firefly III w środowisku Sandstorm.io.', diff --git a/resources/lang/pl_PL/form.php b/resources/lang/pl_PL/form.php index a776638882..e72c91843f 100644 --- a/resources/lang/pl_PL/form.php +++ b/resources/lang/pl_PL/form.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // new user: 'bank_name' => 'Nazwa banku', @@ -184,6 +185,13 @@ return [ 'blocked' => 'Jest zablokowany?', 'blocked_code' => 'Powód blokady', + // import + 'apply_rules' => 'Apply rules', + 'artist' => 'Artist', + 'album' => 'Album', + 'song' => 'Song', + + // admin 'domain' => 'Domena', 'single_user_mode' => 'Wyłącz rejestrację użytkowników', diff --git a/resources/lang/pl_PL/import.php b/resources/lang/pl_PL/import.php index d3d8583a4c..6b2aa6c068 100644 --- a/resources/lang/pl_PL/import.php +++ b/resources/lang/pl_PL/import.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ - // status of import: + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Import data into Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', + 'job_configuration_breadcrumb' => 'Configuration for ":key"', + 'job_status_breadcrumb' => 'Import status for ":key"', + 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', + + // index page: + 'general_index_title' => 'Import a file', + 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + // import provider strings (index): + 'button_fake' => 'Fake an import', + 'button_file' => 'Import a file', + 'button_bunq' => 'Import from bunq', + 'button_spectre' => 'Import using Spectre', + 'button_plaid' => 'Import using Plaid', + 'button_yodlee' => 'Import using Yodlee', + 'button_quovo' => 'Import using Quovo', + // global config box (index) + 'global_config_title' => 'Global import configuration', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + // prerequisites box (index) + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + // provider config box (index) + 'can_config_title' => 'Import configuration', + 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'do_config_fake' => 'Configuration for the fake provider', + 'do_config_file' => 'Configuration for file imports', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', + + // prerequisites: + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + + // job configuration: + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (wartości oddzielone przecinkami)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', + 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', + 'job_config_uc_apply_rules_title' => 'Apply rules', + 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', + 'job_config_uc_specifics_title' => 'Bank-specific options', + 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', + 'job_config_uc_submit' => 'Continue', + 'invalid_import_account' => 'You have selected an invalid account to import into.', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Choose your login', + 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', + 'spectre_login_status_active' => 'Active', + 'spectre_login_status_inactive' => 'Inactive', + 'spectre_login_status_disabled' => 'Disabled', + 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', + 'job_config_spectre_accounts_title' => 'Select accounts to import from', + 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', + 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', + 'spectre_do_not_import' => '(do not import)', + 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'imported_from_account' => 'Imported from ":account"', + 'spectre_account_with_number' => 'Account :number', + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq accounts', + 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', + 'bunq_no_mapping' => 'It seems you have not selected any accounts.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Status', + 'spectre_extra_key_card_type' => 'Card type', + 'spectre_extra_key_account_name' => 'Account name', + 'spectre_extra_key_client_name' => 'Client name', + 'spectre_extra_key_account_number' => 'Account number', + 'spectre_extra_key_blocked_amount' => 'Blocked amount', + 'spectre_extra_key_available_amount' => 'Available amount', + 'spectre_extra_key_credit_limit' => 'Credit limit', + 'spectre_extra_key_interest_rate' => 'Interest rate', + 'spectre_extra_key_expiry_date' => 'Expiry date', + 'spectre_extra_key_open_date' => 'Open date', + 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_current_date' => 'Current date', + 'spectre_extra_key_cards' => 'Cards', + 'spectre_extra_key_units' => 'Units', + 'spectre_extra_key_unit_price' => 'Unit price', + 'spectre_extra_key_transactions_count' => 'Transaction count', + + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Create better descriptions in ING exports', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Fixes potential problems with PC files', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', + 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'job_config_roles_submit' => 'Continue', + 'job_config_roles_column_name' => 'Name of column', + 'job_config_roles_column_example' => 'Column example data', + 'job_config_roles_column_role' => 'Column data meaning', + 'job_config_roles_do_map_value' => 'Map these values', + 'job_config_roles_no_example' => 'No example data available', + 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'job_config_roles_colum_count' => 'Column', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'job_config_field_value' => 'Field value', + 'job_config_field_mapped' => 'Mapped to', + 'map_do_not_map' => '(nie mapuj)', + 'job_config_map_submit' => 'Start the import', + + + // import status page: + 'import_with_key' => 'Import z kluczem \':key\'', 'status_wait_title' => 'Proszę czekać...', 'status_wait_text' => 'To pole za chwilę zniknie.', - 'status_fatal_title' => 'Wystąpił błąd krytyczny', - 'status_fatal_text' => 'Wystąpił błąd krytyczny, którego procedura importu nie może naprawić. Zobacz wyjaśnienie na czerwono poniżej.', - 'status_fatal_more' => 'Jeśli przekroczono limit czasu, import zostanie zatrzymany w połowie. W przypadku niektórych konfiguracji serwerów, jedynie serwer przestał odpowiadać podczas gdy importowanie nadal działa w tle. Aby to zweryfikować, należy sprawdzić pliki dziennika. Jeśli problem będzie się powtarzał, należy rozważyć Importowanie poprzez konsolę.', - 'status_ready_title' => 'Import jest gotowy do uruchomienia', - 'status_ready_text' => 'Import jest gotowy do uruchomienia. Cała konfiguracja, którą musisz wykonać, została wykonana. Proszę pobierz plik konfiguracyjny. Pomoże Ci w imporcie, jeśli nie pójdzie zgodnie z planem. Aby faktycznie uruchomić import, możesz wykonać następujące polecenie w konsoli lub uruchomić importowanie przez przeglądarkę www. W zależności od konfiguracji import przez konsolę daje więcej informacji zwrotnych.', - 'status_ready_noconfig_text' => 'Import jest gotowy do uruchomienia. Cała konfiguracja, którą musisz wykonać, została wykonana. Aby faktycznie uruchomić import, możesz wykonać następujące polecenie w konsoli lub uruchomić importowanie przez przeglądarkę www. W zależności od konfiguracji import przez konsolę daje więcej informacji zwrotnych.', - 'status_ready_config' => 'Pobierz konfigurację', - 'status_ready_start' => 'Rozpocznij Importowanie', - 'status_ready_share' => 'Rozważ pobranie konfiguracji i udostępnienie jej w centrum konfiguracyjnym portali. Umożliwi to innym użytkownikom Firefly III łatwiejsze importowanie plików.', - 'status_job_new' => 'Zadanie jest zupełnie nowe.', - 'status_job_configuring' => 'Import jest konfigurowany.', - 'status_job_configured' => 'Import jest skonfigurowany.', - 'status_job_running' => 'Import w toku... Proszę czekać..', - 'status_job_error' => 'Zadanie wygenerowało błąd.', - 'status_job_finished' => 'Importowanie zostało zakończone!', 'status_running_title' => 'Trwa importowanie', - 'status_running_placeholder' => 'Proszę czekać na aktualizację...', - 'status_finished_title' => 'Zakończono procedurę importu', - 'status_finished_text' => 'Twoje dane zostały zaimportowane.', - 'status_errors_title' => 'Błędy podczas importowania', - 'status_errors_single' => 'Wystąpił błąd podczas importowania. Nie wydaje się być krytyczny.', - 'status_errors_multi' => 'Wystąpiły błędy podczas importowania. Nie wydają się być krytyczne.', - 'status_bread_crumb' => 'Status importu', - 'status_sub_title' => 'Status importu', - 'config_sub_title' => 'Skonfiguruj import', - 'status_finished_job' => 'Zaimportowane transakcje (:count) znajdują się w tagu :tag.', - 'status_finished_no_tag' => 'Firefly III nie zaimportował żadnych transakcji z twojego pliku.', - 'import_with_key' => 'Import z kluczem \':key\'', + 'status_job_running' => 'Please wait, running the import...', + 'status_job_storing' => 'Please wait, storing data...', + 'status_job_rules' => 'Please wait, running rules...', + 'status_fatal_title' => 'Fatal error', + 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', + 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', + 'status_finished_title' => 'Import finished', + 'status_finished_text' => 'The import has finished.', + 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', + 'unknown_import_result' => 'Unknown import result', + 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', + 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', + 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', - // file, upload something - 'file_upload_title' => 'Konfiguracja importu (1/4) - Prześlij swój plik', - 'file_upload_text' => 'Ta procedura pomoże Ci importować pliki z twojego banku do Firefly III. Sprawdź stronę pomocy w prawym górnym rogu.', - 'file_upload_fields' => 'Pola', - 'file_upload_help' => 'Wybierz swój plik', - 'file_upload_config_help' => 'Jeśli wcześniej importowałeś dane do Firefly III, możesz posiadać plik konfiguracji, który wstępnie ustawi wartości parametrów konfiguracyjnych za Ciebie. Dla niektórych banków, inni użytkownicy uprzejmie dostarczyli swoje pliki konfiguracji', - 'file_upload_type_help' => 'Wybierz typ pliku, który będziesz przesyłać', - 'file_upload_submit' => 'Prześlij pliki', - // file, upload types - 'import_file_type_csv' => 'CSV (wartości oddzielone przecinkami)', + // general errors and warnings: + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', - // file, initial config for CSV - 'csv_initial_title' => 'Konfiguracja importu (2/4) - Podstawowa konfiguracja importu CSV', - 'csv_initial_text' => 'Aby móc poprawnie zaimportować plik, sprawdź poprawność poniższych opcji.', - 'csv_initial_box' => 'Podstawowa konfiguracja importu CSV', - 'csv_initial_box_title' => 'Podstawowe opcje konfiguracji importu CSV', - 'csv_initial_header_help' => 'Zaznacz to pole, jeśli pierwszy wiersz w pliku CSV to nazwy kolumn.', - 'csv_initial_date_help' => 'Format daty i czasu w pliku CSV. Format powinien być zgodny z opisem na tej stronie. Wartość domyślna będzie analizować daty, które wyglądają następująco: :dateExample.', - 'csv_initial_delimiter_help' => 'Wybierz separator pola, który jest używany w pliku wejściowym. Jeśli nie jesteś pewien, przecinek jest najbezpieczniejszym rozwiązaniem.', - 'csv_initial_import_account_help' => 'Jeśli Twój plik CSV NIE zawiera informacji o Twoich kontach aktywów, użyj tego menu, aby wybrać, do którego konta należą transakcje w pliku CSV.', - 'csv_initial_submit' => 'Przejdź do kroku 3/4', - - // file, new options: - 'file_apply_rules_title' => 'Zastosuj reguły', - 'file_apply_rules_description' => 'Zastosuj swoje reguły. Zwróć uwagę, że to znacznie spowalnia importowanie.', - 'file_match_bills_title' => 'Dopasuj rachunki', - 'file_match_bills_description' => 'Dopasuj swoje rachunki do nowo utworzonych wypłat. Zwróć uwagę, że to znacznie spowalnia importowanie.', - - // file, roles config - 'csv_roles_title' => 'Konfiguracja importu (3/4) - Zdefiniuj rolę każdej kolumny', - 'csv_roles_text' => 'Każda kolumna w pliku CSV zawiera określone dane. Proszę wskazać, jakiego rodzaju danych importer powinien oczekiwać. Opcja "mapowania" danych oznacza, że każdy wpis znaleziony w kolumnie zostanie połączony z wartością w bazie danych. Często odwzorowywana kolumna to kolumna zawierająca numer IBAN konta przeciwnego. Można go łatwo dopasować do obecnego numeru IBAN w bazie danych.', - 'csv_roles_table' => 'Tabela', - 'csv_roles_column_name' => 'Nazwa kolumny', - 'csv_roles_column_example' => 'Przykładowe dane kolumny', - 'csv_roles_column_role' => 'Znaczenie danych w kolumnie', - 'csv_roles_do_map_value' => 'Zmapuj te wartości', - 'csv_roles_column' => 'Kolumna', - 'csv_roles_no_example_data' => 'Brak przykładowych danych', - 'csv_roles_submit' => 'Przejdź do kroku 4/4', - - // not csv, but normal warning - 'roles_warning' => 'Zaznacz jedną z kolumn jako kolumnę z kwotami. Wskazane jest również wybranie kolumny dla opisu, daty oraz konta przeciwnego.', - 'foreign_amount_warning' => 'Jeśli zaznaczysz kolumnę jako zawierającą kwotę w obcej walucie, musisz także ustawić kolumnę, która zawiera kod tej waluty.', - - // file, map data - 'file_map_title' => 'Ustawienia importu (4/4) - Połącz dane importu z danymi Firefly III', - 'file_map_text' => 'W poniższych tabelach lewa wartość pokazuje informacje znalezione w przesłanym pliku. Twoim zadaniem jest zamapowanie tej wartości, jeśli to możliwe, na wartość już obecną w bazie danych. Firefly będzie trzymać się tego mapowania. Jeśli nie ma wartości do odwzorowania lub nie chcesz mapować określonej wartości, nie wybieraj niczego.', - 'file_map_field_value' => 'Wartość pola', - 'file_map_field_mapped_to' => 'Zmapowane do', - 'map_do_not_map' => '(nie mapuj)', - 'file_map_submit' => 'Rozpocznij import', - 'file_nothing_to_map' => 'W twoim pliku nie ma danych, które można by odwzorować na istniejące wartości. Naciśnij "Rozpocznij import", aby kontynuować.', - - // map things. + // column roles for CSV import: 'column__ignore' => '(zignoruj tę kolumnę)', 'column_account-iban' => 'Konto aktywów (IBAN)', 'column_account-id' => 'ID konta aktywów (z bazy FF3)', @@ -158,48 +261,4 @@ return [ 'column_note' => 'Notatki', 'column_internal-reference' => 'Internal reference', - // prerequisites - 'prerequisites' => 'Wymagania', - - // bunq - 'bunq_prerequisites_title' => 'Wymagania wstępne dla importu z bunq', - 'bunq_prerequisites_text' => 'Aby importować z Bunq, musisz uzyskać klucz API. Możesz to zrobić za pomocą aplikacji. Zwróć uwagę, że funkcja importu z Bunq jest w wersji BETA. Została przetestowana tylko przy użyciu testowej wersji API.', - 'bunq_prerequisites_text_ip' => 'Bunq musi znać zewnętrzny adres IP Twojego serwera. Firefly III próbował ustalić go używając usługi ipify. Upewnij się, że ten adres IP jest poprawny albo import się nie powiedzie.', - 'bunq_do_import' => 'Tak, importuj z tego konta', - 'bunq_accounts_title' => 'Konta Bunq', - 'bunq_accounts_text' => 'Te konta są powiązane z Twoim kontem Bunq. Wybierz konta, z których chcesz importować transakcje i na które konto mają trafić.', - - // Spectre - 'spectre_title' => 'Importuj za pomocą Spectre', - 'spectre_prerequisites_title' => 'Wymagania wstępne do importowania za pomocą Spectre', - 'spectre_prerequisites_text' => 'Aby importować dane za pomocą interfejsu Spectre API (v4), musisz dostarczyć Firefly III dwie sekretne wartości. Można je znaleźć na stronie sekretów.', - 'spectre_enter_pub_key' => 'Importowanie będzie działać tylko po wpisaniu tego klucza publicznego na stronie zabezpieczeń.', - 'spectre_accounts_title' => 'Wybierz konta do zaimportowania z', - 'spectre_accounts_text' => 'Każde konto po lewej stronie zostało znalezione przez Spectre i może zostać zaimportowane do Firefly III. Wybierz konto aktywów, które powinno zawierać dane transakcje. Jeśli nie chcesz importować z żadnego konkretnego konta, usuń zaznaczenie z pola wyboru.', - 'spectre_do_import' => 'Tak, importuj z tego konta', - 'spectre_no_supported_accounts' => 'Nie można importować z tego konta z powodu niedopasowania waluty.', - - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'Kod SWIFT', - 'spectre_extra_key_status' => 'Status', - 'spectre_extra_key_card_type' => 'Typ karty', - 'spectre_extra_key_account_name' => 'Nazwa konta', - 'spectre_extra_key_client_name' => 'Nazwa klienta', - 'spectre_extra_key_account_number' => 'Numer konta', - 'spectre_extra_key_blocked_amount' => 'Zablokowana kwota', - 'spectre_extra_key_available_amount' => 'Dostępna kwota', - 'spectre_extra_key_credit_limit' => 'Limit kredytowy', - 'spectre_extra_key_interest_rate' => 'Oprocentowanie', - 'spectre_extra_key_expiry_date' => 'Data wygaśnięcia', - 'spectre_extra_key_open_date' => 'Data otwarcia', - 'spectre_extra_key_current_time' => 'Aktualny czas', - 'spectre_extra_key_current_date' => 'Aktualna data', - 'spectre_extra_key_cards' => 'Karty', - 'spectre_extra_key_units' => 'Jednostki', - 'spectre_extra_key_unit_price' => 'Cena jednostkowa', - 'spectre_extra_key_transactions_count' => 'Liczba transakcji', - - // various other strings: - 'imported_from_account' => 'Zaimportowane z ":account"', ]; diff --git a/resources/lang/pl_PL/intro.php b/resources/lang/pl_PL/intro.php index 678cae900d..4691ce970d 100644 --- a/resources/lang/pl_PL/intro.php +++ b/resources/lang/pl_PL/intro.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // index 'index_intro' => 'Witamy na stronie domowej Firefly III. Proszę poświęć trochę czasu, aby przejść przez to wprowadzenie, aby poznać sposób działania Firefly III.', diff --git a/resources/lang/pl_PL/list.php b/resources/lang/pl_PL/list.php index f1c0f0df46..a18fe7286e 100644 --- a/resources/lang/pl_PL/list.php +++ b/resources/lang/pl_PL/list.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'buttons' => 'Przyciski', 'icon' => 'Ikona', @@ -111,10 +112,15 @@ return [ 'sepa-cc' => 'SEPA Clearing Code', 'sepa-ep' => 'SEPA External Purpose', 'sepa-ci' => 'SEPA Creditor Identifier', + 'external_id' => 'External ID', 'account_at_bunq' => 'Konto bunq', 'file_name' => 'Nazwa pliku', 'file_size' => 'Rozmiar pliku', 'file_type' => 'Typ pliku', 'attached_to' => 'Dołączony do', 'file_exists' => 'Plik istnieje', + 'spectre_bank' => 'Bank', + 'spectre_last_use' => 'Last login', + 'spectre_status' => 'Status', + 'bunq_payment_id' => 'bunq payment ID', ]; diff --git a/resources/lang/pl_PL/pagination.php b/resources/lang/pl_PL/pagination.php index af92d3252f..a05d28de8a 100644 --- a/resources/lang/pl_PL/pagination.php +++ b/resources/lang/pl_PL/pagination.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'previous' => '« Poprzednia', 'next' => 'Następna »', diff --git a/resources/lang/pl_PL/passwords.php b/resources/lang/pl_PL/passwords.php index 0d9391ebfb..56dbe0f1e0 100644 --- a/resources/lang/pl_PL/passwords.php +++ b/resources/lang/pl_PL/passwords.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'password' => 'Hasło musi zawierać przynajmniej 6 znaków i musi się zgadzać z potwierdzeniem.', 'user' => 'Nie możemy znaleźć użytkownika o podanym adresie email.', diff --git a/resources/lang/pl_PL/validation.php b/resources/lang/pl_PL/validation.php index d73aa32b3e..0ae9b71c19 100644 --- a/resources/lang/pl_PL/validation.php +++ b/resources/lang/pl_PL/validation.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'iban' => 'To nie jest prawidłowy IBAN.', 'source_equals_destination' => 'Konto źródłowe jest równe kontu docelowemu', @@ -109,7 +110,8 @@ return [ 'in_array' => 'Pole :attribute nie istnieje w :other.', 'present' => 'Pole :attribute musi być obecne.', 'amount_zero' => 'Całkowita kwota nie może być zerem', - 'secure_password' => 'To nie jest bezpieczne hasło. Proszę spróbować ponownie. Aby uzyskać więcej informacji odwiedź https://goo.gl/NCh2tN', + 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', 'attributes' => [ 'email' => 'adres e-mail', 'description' => 'opis', From 7645ef55c2a5a2e9e511261d9d843f2ea2e37d9a Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 14:22:19 +0200 Subject: [PATCH 165/182] Update PT_BR language files [skip ci] --- resources/lang/pt_BR/auth.php | 16 +- resources/lang/pt_BR/bank.php | 5 +- resources/lang/pt_BR/breadcrumbs.php | 7 +- resources/lang/pt_BR/components.php | 3 +- resources/lang/pt_BR/config.php | 5 +- resources/lang/pt_BR/csv.php | 5 +- resources/lang/pt_BR/demo.php | 7 +- resources/lang/pt_BR/firefly.php | 34 +-- resources/lang/pt_BR/form.php | 14 +- resources/lang/pt_BR/import.php | 313 ++++++++++++++++----------- resources/lang/pt_BR/intro.php | 5 +- resources/lang/pt_BR/list.php | 36 +-- resources/lang/pt_BR/pagination.php | 5 +- resources/lang/pt_BR/passwords.php | 5 +- resources/lang/pt_BR/validation.php | 12 +- 15 files changed, 267 insertions(+), 205 deletions(-) diff --git a/resources/lang/pt_BR/auth.php b/resources/lang/pt_BR/auth.php index ee4a1c829d..dc7186514c 100644 --- a/resources/lang/pt_BR/auth.php +++ b/resources/lang/pt_BR/auth.php @@ -1,9 +1,8 @@ . */ -return [ - /* - |-------------------------------------------------------------------------- - | Authentication Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used during authentication for various - | messages that we need to display to the user. You are free to modify - | these language lines according to your application's requirements. - | - */ +declare(strict_types=1); +return [ 'failed' => 'Essas credenciais não correspondem aos nossos registros.', 'throttle' => 'Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.', ]; diff --git a/resources/lang/pt_BR/bank.php b/resources/lang/pt_BR/bank.php index 8826020cbf..5d00b1e685 100644 --- a/resources/lang/pt_BR/bank.php +++ b/resources/lang/pt_BR/bank.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/pt_BR/breadcrumbs.php b/resources/lang/pt_BR/breadcrumbs.php index 4f2f721059..793571ed1c 100644 --- a/resources/lang/pt_BR/breadcrumbs.php +++ b/resources/lang/pt_BR/breadcrumbs.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'home' => 'Início', 'edit_currency' => 'Editar moeda ":name"', @@ -41,7 +42,7 @@ return [ 'deposit_list' => 'Receitas, renda e depósitos', 'transfer_list' => 'Transferências', 'transfers_list' => 'Transferências', - 'reconciliation_list' => 'Reconciliação', + 'reconciliation_list' => 'Reconciliações', 'create_withdrawal' => 'Criar uma nova retirada', 'create_deposit' => 'Criar um novo depósito', 'create_transfer' => 'Criar nova transferência', diff --git a/resources/lang/pt_BR/components.php b/resources/lang/pt_BR/components.php index ec68877f92..2e02694ebe 100644 --- a/resources/lang/pt_BR/components.php +++ b/resources/lang/pt_BR/components.php @@ -1,5 +1,4 @@ . */ +declare(strict_types=1); + return [ // profile 'personal_access_tokens' => 'Tokens de acesso pessoal', diff --git a/resources/lang/pt_BR/config.php b/resources/lang/pt_BR/config.php index 10ca3f6f95..ce507e8a05 100644 --- a/resources/lang/pt_BR/config.php +++ b/resources/lang/pt_BR/config.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'html_language' => 'pt-br', 'locale' => 'pt-br, pt_BR, pt_BR.utf8, pt_BR.UTF-8', diff --git a/resources/lang/pt_BR/csv.php b/resources/lang/pt_BR/csv.php index 622a49202a..aae109a40a 100644 --- a/resources/lang/pt_BR/csv.php +++ b/resources/lang/pt_BR/csv.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/pt_BR/demo.php b/resources/lang/pt_BR/demo.php index 9d53564571..b6960bda12 100644 --- a/resources/lang/pt_BR/demo.php +++ b/resources/lang/pt_BR/demo.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'no_demo_text' => 'Desculpe, não há nenhum texto extra de explicação para esta página.', 'see_help_icon' => 'No entanto, o -ícone no canto superior direito pode lhe dizer mais.', @@ -32,5 +33,5 @@ return [ 'currencies-index' => 'Firefly III oferece suporte a várias moedas. Embora o padrão seja o Euro, ela pode ser definida para o dólar americano e muitas outras moedas. Como você pode ver uma pequena seleção de moedas foi incluída, mas você pode adicionar suas próprias se desejar. No entanto, alterar a moeda padrão não vai mudar a moeda de transações existentes: Firefly III suporta o uso de várias moedas ao mesmo tempo.', 'transactions-index' => 'Estas despesas, depósitos e transferências não são fantasiosas. Elas foram geradas automaticamente.', 'piggy-banks-index' => 'Como você pode ver, existem três cofrinhos. Use o sinal de mais e menos botões para influenciar a quantidade de dinheiro em cada cofrinho. Clique no nome do cofrinho para ver a administração de cada cofrinho.', - 'import-index' => 'Sim, qualquer arquivo CSV pode ser importado para Firefly III', + 'import-index' => 'Qualquer arquivo CSV pode ser importado para o Firefly III. Importações de dados de bunq e Specter também são suportadas. Outros bancos e agregadores financeiros serão implementados futuramente. Como usuário de demonstração, no entanto, você só pode ver o provedor "falso" em ação. Ele irá gerar transações aleatórias para lhe mostrar como funciona o processo.', ]; diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 39de222884..988bf0b85a 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // general stuff: 'close' => 'Fechar', @@ -179,6 +180,7 @@ return [ 'authorization_request_intro' => ':client está pedindo permissão para acessar sua administração financeira. Gostaria de autorizar :client para acessar esses registros?', 'scopes_will_be_able' => 'Esta aplicação será capaz de:', 'button_authorize' => 'Autorizar', + 'none_in_select_list' => '(none)', // check for updates: 'update_check_title' => 'Verificar Atualizações', @@ -541,7 +543,7 @@ return [ 'attachment_deleted' => 'Anexo apagado ":name"', 'attachment_updated' => 'Anexo atualizado ":name"', 'upload_max_file_size' => 'Tamanho máximo do arquivo: :size', - 'list_all_attachments' => 'List of all attachments', + 'list_all_attachments' => 'Lista de todos os anexos', // transaction index 'title_expenses' => 'Despesas', @@ -666,6 +668,7 @@ return [ 'bill_will_automatch' => 'A fatura será automaticamente vinculada a transações correspondentes', 'skips_over' => 'ignorar', 'bill_store_error' => 'Um erro inesperado ocorreu ao armazenar sua nova fatura. Por favor, verifique os arquivos de log', + 'list_inactive_rule' => 'inactive rule', // accounts: 'details_for_asset' => 'Detalhes para a conta de ativo ":name"', @@ -801,6 +804,7 @@ return [ 'opt_group_savingAsset' => 'Contas de poupança', 'opt_group_sharedAsset' => 'Contas de ativos compartilhadas', 'opt_group_ccAsset' => 'Cartões de crédito', + 'opt_group_cashWalletAsset' => 'Cash wallets', 'notes' => 'Notas', 'unknown_journal_error' => 'A transação não pôde ser armazenada. Por favor, verifique os arquivos de log.', @@ -816,6 +820,7 @@ return [ 'language' => 'Idioma', 'new_savings_account' => 'Conta de poupança :bank_name', 'cash_wallet' => 'Carteira de dinheiro', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Suas contas', @@ -1013,6 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Retire o dinheiro do cofrinho ":name"', 'add' => 'Adicionar', 'no_money_for_piggy' => 'Você não tem dinheiro para colocar nessa poupança.', + 'suggested_savings_per_month' => 'Suggested per month', 'remove' => 'Remover', 'max_amount_add' => 'É a quantidade máxima que você pode adicionar é', @@ -1148,27 +1154,9 @@ return [ 'no_edit_multiple_left' => 'Você não selecionou nenhuma transação válida para editar.', 'cannot_convert_split_journal' => 'Não é possível converter uma transação dividida', - // import bread crumbs and titles: - 'import' => 'Importar', - 'import_data' => 'Importar dados', - 'import_general_index_file' => 'Importar um arquivo', - 'import_from_bunq' => 'Importar de bunq', - 'import_using_spectre' => 'Importar usando Spectre', - 'import_using_plaid' => 'Importar usando Plaid', - 'import_config_bread_crumb' => 'Configurar a sua importação', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Importar dados para o Firefly III', - 'import_index_sub_title' => 'Índice', - 'import_general_index_intro' => 'Bem-vindo à rotina de importação do Firefly III. Existem algumas maneiras de importar dados no Firefly III, exibidos aqui como botões.', - 'upload_error' => 'O arquivo que você enviou não pôde ser processado. Possivelmente trata-se de tipo ou codificação inválidos de arquivo. Os arquivos de log terão mais informações.', - 'reset_import_settings_title' => 'Redefinir configuração de importação', - 'reset_import_settings_text' => 'You can use these links to reset your import settings for specific providers. This is useful when bad settings stop you from importing data.', - 'reset_settings_bunq' => 'Remove bunq API key, local external IP address and bunq related RSA keys.', - 'reset_settings_spectre' => 'Remove Spectre secrets and ID\'s. This will also remove your Spectre keypair. Remember to update the new one.', - 'settings_reset_for_bunq' => 'Bunq settings reset.', - 'settings_reset_for_spectre' => 'Spectre settings reset.', - + 'import_data' => 'Importar dados', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Esta função não está disponível quando você está usando o Firefly III dentro de um ambiente Sandstorm.io.', diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php index aaed0019ec..f31a9d819a 100644 --- a/resources/lang/pt_BR/form.php +++ b/resources/lang/pt_BR/form.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // new user: 'bank_name' => 'Nome do banco', @@ -184,6 +185,13 @@ return [ 'blocked' => 'Está bloqueado?', 'blocked_code' => 'Razão para ser reportado', + // import + 'apply_rules' => 'Apply rules', + 'artist' => 'Artist', + 'album' => 'Album', + 'song' => 'Song', + + // admin 'domain' => 'Domínio', 'single_user_mode' => 'Desabilitar registro de usuários', @@ -203,7 +211,7 @@ return [ 'service_secret' => 'Service secret', 'app_secret' => 'App secret', 'app_id' => 'App ID', - 'secret' => 'Secret', + 'secret' => 'Segredo', 'public_key' => 'Chave pública', 'country_code' => 'Código do país', 'provider_code' => 'Banco ou provedor de dados', diff --git a/resources/lang/pt_BR/import.php b/resources/lang/pt_BR/import.php index 1c8b8af545..377665273f 100644 --- a/resources/lang/pt_BR/import.php +++ b/resources/lang/pt_BR/import.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ - // status of import: + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Import data into Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', + 'job_configuration_breadcrumb' => 'Configuration for ":key"', + 'job_status_breadcrumb' => 'Import status for ":key"', + 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', + + // index page: + 'general_index_title' => 'Import a file', + 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + // import provider strings (index): + 'button_fake' => 'Fake an import', + 'button_file' => 'Import a file', + 'button_bunq' => 'Import from bunq', + 'button_spectre' => 'Import using Spectre', + 'button_plaid' => 'Import using Plaid', + 'button_yodlee' => 'Import using Yodlee', + 'button_quovo' => 'Import using Quovo', + // global config box (index) + 'global_config_title' => 'Global import configuration', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + // prerequisites box (index) + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + // provider config box (index) + 'can_config_title' => 'Import configuration', + 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'do_config_fake' => 'Configuration for the fake provider', + 'do_config_file' => 'Configuration for file imports', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', + + // prerequisites: + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + + // job configuration: + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (valores separados por vírgula)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', + 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', + 'job_config_uc_apply_rules_title' => 'Apply rules', + 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', + 'job_config_uc_specifics_title' => 'Bank-specific options', + 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', + 'job_config_uc_submit' => 'Continue', + 'invalid_import_account' => 'You have selected an invalid account to import into.', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Choose your login', + 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', + 'spectre_login_status_active' => 'Active', + 'spectre_login_status_inactive' => 'Inactive', + 'spectre_login_status_disabled' => 'Disabled', + 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', + 'job_config_spectre_accounts_title' => 'Select accounts to import from', + 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', + 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', + 'spectre_do_not_import' => '(do not import)', + 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'imported_from_account' => 'Imported from ":account"', + 'spectre_account_with_number' => 'Account :number', + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq accounts', + 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', + 'bunq_no_mapping' => 'It seems you have not selected any accounts.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Status', + 'spectre_extra_key_card_type' => 'Card type', + 'spectre_extra_key_account_name' => 'Account name', + 'spectre_extra_key_client_name' => 'Client name', + 'spectre_extra_key_account_number' => 'Account number', + 'spectre_extra_key_blocked_amount' => 'Blocked amount', + 'spectre_extra_key_available_amount' => 'Available amount', + 'spectre_extra_key_credit_limit' => 'Credit limit', + 'spectre_extra_key_interest_rate' => 'Interest rate', + 'spectre_extra_key_expiry_date' => 'Expiry date', + 'spectre_extra_key_open_date' => 'Open date', + 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_current_date' => 'Current date', + 'spectre_extra_key_cards' => 'Cards', + 'spectre_extra_key_units' => 'Units', + 'spectre_extra_key_unit_price' => 'Unit price', + 'spectre_extra_key_transactions_count' => 'Transaction count', + + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Create better descriptions in ING exports', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Fixes potential problems with PC files', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', + 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'job_config_roles_submit' => 'Continue', + 'job_config_roles_column_name' => 'Name of column', + 'job_config_roles_column_example' => 'Column example data', + 'job_config_roles_column_role' => 'Column data meaning', + 'job_config_roles_do_map_value' => 'Map these values', + 'job_config_roles_no_example' => 'No example data available', + 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'job_config_roles_colum_count' => 'Column', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'job_config_field_value' => 'Field value', + 'job_config_field_mapped' => 'Mapped to', + 'map_do_not_map' => '(não mapear)', + 'job_config_map_submit' => 'Start the import', + + + // import status page: + 'import_with_key' => 'Importar com a chave \':key\'', 'status_wait_title' => 'Por favor espere...', 'status_wait_text' => 'Esta caixa desaparecerá em um instante.', - 'status_fatal_title' => 'Ocorreu um erro fatal', - 'status_fatal_text' => 'Ocorreu um erro fatal, cuja rotina de importação não pode se recuperar. Veja a explicação em vermelho abaixo.', - 'status_fatal_more' => 'Se o erro for um tempo limite, a importação terá parado a meio caminho. Para algumas configurações do servidor, é apenas o servidor que parou enquanto a importação continua em execução em segundo plano. Para verificar isso, confira os arquivos de log. Se o problema persistir, considere importação na linha de comando em vez disso.', - 'status_ready_title' => 'A importação está pronta para começar', - 'status_ready_text' => 'A importação está pronta para começar. Toda a configuração que você precisava fazer foi feita. Faça o download do arquivo de configuração. Isso irá ajudá-lo com a importação se não for como planejado. Para realmente executar a importação, você pode executar o seguinte comando no seu console ou executar a importação baseada na web. Dependendo da sua configuração, a importação do console lhe dará mais comentários.', - 'status_ready_noconfig_text' => 'A importação está pronta para começar. Toda a configuração que você precisava fazer foi feita. Para realmente executar a importação, você pode executar o seguinte comando no seu console ou executar a importação baseada na web. Dependendo da sua configuração, a importação do console lhe dará mais comentários.', - 'status_ready_config' => 'Download da configuração', - 'status_ready_start' => 'Iniciar a importação', - 'status_ready_share' => 'Por favor, considere baixar sua configuração e compartilhá-la no centro de configuração de importação. Isso permitirá que outros usuários do Firefly III importem seus arquivos mais facilmente.', - 'status_job_new' => 'O trabalho é novo.', - 'status_job_configuring' => 'A importação está sendo configurada.', - 'status_job_configured' => 'A importação está configurada.', - 'status_job_running' => 'A importação está em execução.. Aguarde..', - 'status_job_error' => 'O trabalho gerou um erro.', - 'status_job_finished' => 'A importação terminou!', 'status_running_title' => 'A importação está em execução', - 'status_running_placeholder' => 'Por favor, aguarde uma atualização...', - 'status_finished_title' => 'Rotina de importação concluída', - 'status_finished_text' => 'A rotina de importação importou seus dados.', - 'status_errors_title' => 'Erros durante a importação', - 'status_errors_single' => 'Ocorreu um erro durante a importação. Não parece ser fatal.', - 'status_errors_multi' => 'Alguns erros ocorreram durante a importação. Estes não parecem ser fatais.', - 'status_bread_crumb' => 'Status de importação', - 'status_sub_title' => 'Status de importação', - 'config_sub_title' => 'Configurar a sua importação', - 'status_finished_job' => 'As transações de :count importadas podem ser encontradas na tag :tag.', - 'status_finished_no_tag' => 'Firefly III não coletou nenhuma transação de seu arquivo de importação.', - 'import_with_key' => 'Importar com a chave \':key\'', + 'status_job_running' => 'Please wait, running the import...', + 'status_job_storing' => 'Please wait, storing data...', + 'status_job_rules' => 'Please wait, running rules...', + 'status_fatal_title' => 'Fatal error', + 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', + 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', + 'status_finished_title' => 'Import finished', + 'status_finished_text' => 'The import has finished.', + 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', + 'unknown_import_result' => 'Unknown import result', + 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', + 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', + 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', - // file, upload something - 'file_upload_title' => 'Configuração de importação (1/4) - Carregar seu arquivo', - 'file_upload_text' => 'Esta rotina irá ajudá-lo a importar arquivos do seu banco para o Firefly III. Por favor, confira as páginas de ajuda no canto superior direito.', - 'file_upload_fields' => 'Campos', - 'file_upload_help' => 'Selecione seu arquivo', - 'file_upload_config_help' => 'Se você já importou dados no Firefly III, você pode ter um arquivo de configuração, que irá predefinir os valores de configuração para você. Para alguns bancos, outros usuários forneceram gentilmente o arquivo arquivo de configuração', - 'file_upload_type_help' => 'Selecione o tipo de arquivo que você fará o upload', - 'file_upload_submit' => 'Upload de arquivos', - // file, upload types - 'import_file_type_csv' => 'CSV (valores separados por vírgula)', + // general errors and warnings: + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', - // file, initial config for CSV - 'csv_initial_title' => 'Configuração de importação (2/4) - Configuração de importação CSV básica', - 'csv_initial_text' => 'Para ser capaz de importar o arquivo corretamente, por favor valide as opções abaixo.', - 'csv_initial_box' => 'Configuração básica de importação CSV', - 'csv_initial_box_title' => 'Opções básicas de configuração de importação CSV', - 'csv_initial_header_help' => 'Marque esta caixa se a primeira linha do seu arquivo CSV for os títulos das colunas.', - 'csv_initial_date_help' => 'Formato de data e hora em seu CSV. Siga o formato como indica esta página. O valor padrão analisará datas que se parecem com isso: :dateExample.', - 'csv_initial_delimiter_help' => 'Escolha o delimitador de campo que é usado em seu arquivo de entrada. Se não tiver certeza, a vírgula é a opção mais segura.', - 'csv_initial_import_account_help' => 'Se o seu arquivo CSV NÃO contém informações sobre sua(s) conta(s) de ativo(s), use este combobox para selecionar para qual conta pertencem as transações no CSV.', - 'csv_initial_submit' => 'Continue com o passo 3/4', - - // file, new options: - 'file_apply_rules_title' => 'Aplicar regras', - 'file_apply_rules_description' => 'Aplique suas regras. Observe que isso reduz significativamente a velocidade da importação.', - 'file_match_bills_title' => 'Correspondência de contas', - 'file_match_bills_description' => 'Combine suas contas para retiradas recém-criadas. Observe que isso diminui significativamente a velocidade de importação.', - - // file, roles config - 'csv_roles_title' => 'Configuração de importação (3/4) - Definir o papel de cada coluna', - 'csv_roles_text' => 'Cada coluna no seu arquivo CSV contém certos dados. Por favor, indique que tipo de dados, o importador deve esperar. A opção "mapear" dados significa que você vai ligar cada entrada encontrada na coluna para um valor em seu banco de dados. Uma coluna mapeada muitas vezes é a coluna que contém o IBAN da conta oposta. Isso pode ser facilmente combinado para o IBAN já presente em seu banco de dados.', - 'csv_roles_table' => 'Tabela', - 'csv_roles_column_name' => 'Nome da coluna', - 'csv_roles_column_example' => 'Dados de exemplo da coluna', - 'csv_roles_column_role' => 'Significado dos dados da coluna', - 'csv_roles_do_map_value' => 'Mapear estes valores', - 'csv_roles_column' => 'Coluna', - 'csv_roles_no_example_data' => 'Não há dados de exemplo disponíveis', - 'csv_roles_submit' => 'Continue com o passo 4/4', - - // not csv, but normal warning - 'roles_warning' => 'No mínimo, marque uma coluna como a coluna de quantidade. É aconselhável também selecionar uma coluna para a descrição, data e a conta oposta.', - 'foreign_amount_warning' => 'Se você marcar uma coluna como contendo um valor em uma moeda estrangeira, você também deve definir a coluna que contém qual moeda é.', - - // file, map data - 'file_map_title' => 'Configuração de importação (4/4) - Conecte dados de importação aos dados do Firefly III', - 'file_map_text' => 'Nas tabelas a seguir, o valor à esquerda mostra informações encontradas no seu arquivo carregado. É sua tarefa mapear esse valor, se possível, para um valor já presente em seu banco de dados. O Firefly vai se ater a esse mapeamento. Se não há nenhum valor para mapear, ou não quer mapear o valor específico, não selecione nada.', - 'file_map_field_value' => 'Valor do campo', - 'file_map_field_mapped_to' => 'Mapeado para', - 'map_do_not_map' => '(não mapear)', - 'file_map_submit' => 'Iniciar a importação', - 'file_nothing_to_map' => 'Não há dados presentes no seu arquivo que você possa mapear para os valores existentes. Pressione "Iniciar a importação" para continuar.', - - // map things. + // column roles for CSV import: 'column__ignore' => '(ignorar esta coluna)', 'column_account-iban' => 'Conta de Ativo (IBAN)', 'column_account-id' => 'ID da Conta de Ativo (correspondente FF3)', @@ -158,48 +261,4 @@ return [ 'column_note' => 'Nota(s)', 'column_internal-reference' => 'Referência interna', - // prerequisites - 'prerequisites' => 'Pré-requisitos', - - // bunq - 'bunq_prerequisites_title' => 'Pré-requisitos para uma importação de bunq', - 'bunq_prerequisites_text' => 'Para importar do bunq, você precisa obter uma chave de API. Você pode fazer isso através do aplicativo. Por favor, note que a função de importação para bunq está em BETA. Só foi testado em relação à API do sandbox.', - 'bunq_prerequisites_text_ip' => 'Bunq requer seu endereço IP externo. O Firefly III tentou preencher isso usando o serviço ipify. Certifique-se de que esse endereço IP esteja correto ou a importação falhará.', - 'bunq_do_import' => 'Sim, importe desta conta', - 'bunq_accounts_title' => 'Contas Bunq', - 'bunq_accounts_text' => 'Estas são as contas associadas à sua conta bunq. Por favor, selecione as contas das quais você deseja importar e em qual conta as transações devem ser importadas.', - - // Spectre - 'spectre_title' => 'Importar usando Spectre', - 'spectre_prerequisites_title' => 'Pré-requisitos para uma importação usando Spectre', - 'spectre_prerequisites_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your secrets page.', - 'spectre_accounts_title' => 'Selecione as contas a serem importadas', - 'spectre_accounts_text' => 'Cada conta à esquerda abaixo foi encontrada pela Spectre e pode ser importada para Firefly III. Por favor selecione a conta de ativo que deve armazenar as transações. Se você não deseja importar de qualquer conta específica, desmarque a caixa de seleção.', - 'spectre_do_import' => 'Sim, importe a partir desta conta', - 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', - - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'SWIFT', - 'spectre_extra_key_status' => 'Status', - 'spectre_extra_key_card_type' => 'Tipo de Cartão', - 'spectre_extra_key_account_name' => 'Nome da Conta', - 'spectre_extra_key_client_name' => 'Nome do cliente', - 'spectre_extra_key_account_number' => 'Número da conta', - 'spectre_extra_key_blocked_amount' => 'Montante bloqueado', - 'spectre_extra_key_available_amount' => 'Montante disponível', - 'spectre_extra_key_credit_limit' => 'Limite de crédito', - 'spectre_extra_key_interest_rate' => 'Taxa de juros', - 'spectre_extra_key_expiry_date' => 'Data de vencimento', - 'spectre_extra_key_open_date' => 'Data de abertura', - 'spectre_extra_key_current_time' => 'Hora atual', - 'spectre_extra_key_current_date' => 'Data atual', - 'spectre_extra_key_cards' => 'Cartões', - 'spectre_extra_key_units' => 'Unidades', - 'spectre_extra_key_unit_price' => 'Preço unitário', - 'spectre_extra_key_transactions_count' => 'Contagem de transações', - - // various other strings: - 'imported_from_account' => 'Importado de ":account"', ]; diff --git a/resources/lang/pt_BR/intro.php b/resources/lang/pt_BR/intro.php index 3881118023..48b99c3d4d 100644 --- a/resources/lang/pt_BR/intro.php +++ b/resources/lang/pt_BR/intro.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // index 'index_intro' => 'Bem-vindo à página de inicial do Firefly III. Por favor, aproveite esta introdução para verificar como funciona o Firefly III.', diff --git a/resources/lang/pt_BR/list.php b/resources/lang/pt_BR/list.php index a6f816549b..f65a5eee64 100644 --- a/resources/lang/pt_BR/list.php +++ b/resources/lang/pt_BR/list.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'buttons' => 'Botões', 'icon' => 'Ícone', @@ -104,17 +105,22 @@ return [ 'reconcile' => 'Pago', 'account_on_spectre' => 'Conta (Spectre)', 'do_import' => 'Importar desta conta', - 'sepa-ct-id' => 'SEPA End to End Identifier', - 'sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'sepa-db' => 'SEPA Mandate Identifier', - 'sepa-country' => 'SEPA Country', - 'sepa-cc' => 'SEPA Clearing Code', - 'sepa-ep' => 'SEPA External Purpose', - 'sepa-ci' => 'SEPA Creditor Identifier', - 'account_at_bunq' => 'Account with bunq', - 'file_name' => 'File name', - 'file_size' => 'File size', - 'file_type' => 'File type', - 'attached_to' => 'Attached to', - 'file_exists' => 'File exists', + 'sepa-ct-id' => 'SEPA Identificador end-to-end', + 'sepa-ct-op' => 'SEPA Identificador de conta de contrária', + 'sepa-db' => 'SEPA Identificador de Mandato', + 'sepa-country' => 'SEPA País', + 'sepa-cc' => 'SEPA Código de Compensação', + 'sepa-ep' => 'SEPA Finalidade Externa', + 'sepa-ci' => 'SEPA Identificador do Credor', + 'external_id' => 'External ID', + 'account_at_bunq' => 'Loja com bunq', + 'file_name' => 'Nome do arquivo', + 'file_size' => 'Tamanho do Arquivo', + 'file_type' => 'Tipo do arquivo', + 'attached_to' => 'Anexado a', + 'file_exists' => 'Arquivo já existe', + 'spectre_bank' => 'Bank', + 'spectre_last_use' => 'Last login', + 'spectre_status' => 'Status', + 'bunq_payment_id' => 'bunq payment ID', ]; diff --git a/resources/lang/pt_BR/pagination.php b/resources/lang/pt_BR/pagination.php index 4a6ef00749..af96c3851f 100644 --- a/resources/lang/pt_BR/pagination.php +++ b/resources/lang/pt_BR/pagination.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'previous' => '« Anterior', 'next' => 'Próximo »', diff --git a/resources/lang/pt_BR/passwords.php b/resources/lang/pt_BR/passwords.php index 9bc624014f..d5e1ac0558 100644 --- a/resources/lang/pt_BR/passwords.php +++ b/resources/lang/pt_BR/passwords.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'password' => 'A senha precisa ter no mínimo seis caracteres e tem que ser igual à confirmação de senha.', 'user' => 'Não foi possível encontrar o usuário com este e-mail.', diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php index 3f019d5122..841f7c2d55 100644 --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'iban' => 'Este não é um válido IBAN.', - 'source_equals_destination' => 'The source account equals the destination account', + 'source_equals_destination' => 'A conta de origem é igual à conta de destino', 'unique_account_number_for_user' => 'Parece que este número de conta já está em uso.', - 'unique_iban_for_user' => 'It looks like this IBAN is already in use.', + 'unique_iban_for_user' => 'Parece que este IBAN já está em uso.', 'deleted_user' => 'Devido a restrições de segurança, você não pode registrar usando este endereço de e-mail.', 'rule_trigger_value' => 'Este valor é inválido para o disparo selecionado.', 'rule_action_value' => 'Este valor é inválido para a ação selecionada.', @@ -109,7 +110,8 @@ return [ 'in_array' => 'O campo :attribute não existe em :other.', 'present' => 'O campo :attribute deve estar presente.', 'amount_zero' => 'A quantidade total não pode ser zero', - 'secure_password' => 'Esta não é uma senha segura. Por favor, tente novamente. Para mais informações, visite https://goo.gl/NCh2tN', + 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', 'attributes' => [ 'email' => 'endereço de e-mail', 'description' => 'descrição', From 1e10a6ce1b42b8f98997230550dfde85d16592dd Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 14:22:37 +0200 Subject: [PATCH 166/182] Update Russian language files [skip ci] --- resources/lang/ru_RU/auth.php | 16 +- resources/lang/ru_RU/bank.php | 5 +- resources/lang/ru_RU/breadcrumbs.php | 5 +- resources/lang/ru_RU/components.php | 3 +- resources/lang/ru_RU/config.php | 5 +- resources/lang/ru_RU/csv.php | 5 +- resources/lang/ru_RU/demo.php | 7 +- resources/lang/ru_RU/firefly.php | 32 +-- resources/lang/ru_RU/form.php | 12 +- resources/lang/ru_RU/import.php | 313 ++++++++++++++++----------- resources/lang/ru_RU/intro.php | 5 +- resources/lang/ru_RU/list.php | 10 +- resources/lang/ru_RU/pagination.php | 5 +- resources/lang/ru_RU/passwords.php | 5 +- resources/lang/ru_RU/validation.php | 8 +- 15 files changed, 249 insertions(+), 187 deletions(-) diff --git a/resources/lang/ru_RU/auth.php b/resources/lang/ru_RU/auth.php index 1d79c2f2d3..9dbede848c 100644 --- a/resources/lang/ru_RU/auth.php +++ b/resources/lang/ru_RU/auth.php @@ -1,9 +1,8 @@ . */ -return [ - /* - |-------------------------------------------------------------------------- - | Authentication Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used during authentication for various - | messages that we need to display to the user. You are free to modify - | these language lines according to your application's requirements. - | - */ +declare(strict_types=1); +return [ 'failed' => 'Неправильный адрес электронной почты или пароль.', 'throttle' => 'Слишком много попыток входа. Пожалуйста, попробуйте снова через :seconds секунд.', ]; diff --git a/resources/lang/ru_RU/bank.php b/resources/lang/ru_RU/bank.php index 8826020cbf..5d00b1e685 100644 --- a/resources/lang/ru_RU/bank.php +++ b/resources/lang/ru_RU/bank.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/ru_RU/breadcrumbs.php b/resources/lang/ru_RU/breadcrumbs.php index 0ef4f707f2..41a0facb3a 100644 --- a/resources/lang/ru_RU/breadcrumbs.php +++ b/resources/lang/ru_RU/breadcrumbs.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'home' => 'Главная', 'edit_currency' => 'Редактирование валюты ":name"', diff --git a/resources/lang/ru_RU/components.php b/resources/lang/ru_RU/components.php index 2bf8a35763..8f542b54d8 100644 --- a/resources/lang/ru_RU/components.php +++ b/resources/lang/ru_RU/components.php @@ -1,5 +1,4 @@ . */ +declare(strict_types=1); + return [ // profile 'personal_access_tokens' => 'Персональный токен для доступа', diff --git a/resources/lang/ru_RU/config.php b/resources/lang/ru_RU/config.php index 65d4328335..a524e3644c 100644 --- a/resources/lang/ru_RU/config.php +++ b/resources/lang/ru_RU/config.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'html_language' => 'ru', 'locale' => 'ru, Russian, ru_RU, ru_RU.utf8, ru_RU.UTF-8', diff --git a/resources/lang/ru_RU/csv.php b/resources/lang/ru_RU/csv.php index 622a49202a..aae109a40a 100644 --- a/resources/lang/ru_RU/csv.php +++ b/resources/lang/ru_RU/csv.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/ru_RU/demo.php b/resources/lang/ru_RU/demo.php index 45ef6068bf..293ba441f0 100644 --- a/resources/lang/ru_RU/demo.php +++ b/resources/lang/ru_RU/demo.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'no_demo_text' => 'Извините, но для этой страницы нет дополнительного пояснения.', 'see_help_icon' => 'Воспользуйтесь значком в правом верхнем углу, чтобы узнать больше.', @@ -32,5 +33,5 @@ return [ 'currencies-index' => 'Firefly III поддерживает несколько валют. Хотя по умолчанию используется Евро, вы можете сделать основной валютой доллары США или любую другую валюту. Как вы видите, несколько валют уже есть в списке, но вы можете добавить любую другую, если это требуется. Обратите внимание, что выбор новой валюты по умолчанию не повлияет на уже существующие транзакции: Firefly III поддерживает одновременное использование нескольких валют.', 'transactions-index' => 'Эти расходы, доходы и переводы не очень интересны. Они были созданы автоматически.', 'piggy-banks-index' => 'Как вы можете видеть, здесь есть три копилки. Используйте кнопки «плюс» и «минус», чтобы влиять на количество денег в каждой копилке. Нажмите название копилки, чтобы увидеть её настройки.', - 'import-index' => 'Конечно, любой CSV-файл может быть импортирован в Firefly III', + 'import-index' => 'В Firefly III можно импортировать любой CSV-файл. Также поддерживается импорт данных из bunq и Spectre. Другие банки и финансовые агрегаторы будут реализованы в будущем. Однако, как демо-пользователь, вы можете видеть только как работает «поддельный»-провайдер. Он будет генерировать некоторые случайные транзакции, чтобы показать вам, как работает этот процесс.', ]; diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index 0d0352cf61..382410ca5c 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // general stuff: 'close' => 'Закрыть', @@ -179,6 +180,7 @@ return [ 'authorization_request_intro' => ':client запрашивает доступ к управлению вашими финансами. Вы хотите разрешить :client доступ к этой информации?', 'scopes_will_be_able' => 'Это приложение будет иметь возможность:', 'button_authorize' => 'Авторизация', + 'none_in_select_list' => '(нет)', // check for updates: 'update_check_title' => 'Проверить обновления', @@ -666,6 +668,7 @@ return [ 'bill_will_automatch' => 'Счёт будет автоматически связан с подходящими транзакциями', 'skips_over' => 'пропустить', 'bill_store_error' => 'При создании вашего нового счёта на оплату произошла неожиданная ошибка. Пожалуйста, проверьте log-файлы', + 'list_inactive_rule' => 'неактивное правило', // accounts: 'details_for_asset' => 'Детали по основному счёту ":name"', @@ -801,6 +804,7 @@ return [ 'opt_group_savingAsset' => 'Сберегательные счета', 'opt_group_sharedAsset' => 'Общие основные счета', 'opt_group_ccAsset' => 'Кредитные карты', + 'opt_group_cashWalletAsset' => 'Кошельки с наличными', 'notes' => 'Заметки', 'unknown_journal_error' => 'Не удалось сохранить транзакцию. Пожалуйста, проверьте log-файлы.', @@ -816,6 +820,7 @@ return [ 'language' => 'Язык', 'new_savings_account' => 'сберегательный счёт в :bank_name', 'cash_wallet' => 'Кошелёк с наличными', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Ваши счета', @@ -1013,6 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Удалить деньги из копилки ":name"', 'add' => 'Добавить', 'no_money_for_piggy' => 'У вас нет денег, чтобы положить в этот копилку.', + 'suggested_savings_per_month' => 'Suggested per month', 'remove' => 'Удалить', 'max_amount_add' => 'Максимальная сумма, которую вы можете добавить:', @@ -1148,27 +1154,9 @@ return [ 'no_edit_multiple_left' => 'Вы выбрали для редактирования некорректную транзакцию.', 'cannot_convert_split_journal' => 'Невозможно преобразовать раздельную транзакцию', - // import bread crumbs and titles: - 'import' => 'Импорт', - 'import_data' => 'Импорт данных', - 'import_general_index_file' => 'Импортировать файл', - 'import_from_bunq' => 'Импорт из bunq', - 'import_using_spectre' => 'Импорт с использованием Spectre', - 'import_using_plaid' => 'Импорт с использованием Plaid', - 'import_config_bread_crumb' => 'Настройте свой импорт', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Импорт данных в Firefly III', - 'import_index_sub_title' => 'Главная страница', - 'import_general_index_intro' => 'Добро пожаловать в инструмент импорта Firefly III. Существует несколько способов импорта данных в Firefly III, отображаемых здесь в виде кнопок.', - 'upload_error' => 'Невозможно обработать загруженный вами файл. Возможно, это неправильный тип файла или кодировка. Дополнительная информация содержится в log-файлах.', - 'reset_import_settings_title' => 'Сбросить настройки импорта', - 'reset_import_settings_text' => 'Вы можете использовать эти ссылки для сброса настроек импорта для определенных поставщиков. Это полезно, когда плохие настройки не позволяют вам импортировать данные.', - 'reset_settings_bunq' => 'Удалите ключ API bunq, локальный внешний IP-адрес и ключи RSA, связанные с bunq.', - 'reset_settings_spectre' => 'Удалить ID клиента Spectre и секретные ключи. Также будет удалён ваш Spectre keypair. Не забудьте обновить его.', - 'settings_reset_for_bunq' => 'Сброс настроек Bunq.', - 'settings_reset_for_spectre' => 'Сброс настроек Spectre.', - + 'import_data' => 'Импорт данных', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Эта функция недоступна, если вы используете Firefly III в среде Sandstorm.io.', diff --git a/resources/lang/ru_RU/form.php b/resources/lang/ru_RU/form.php index 5c9646a382..a4b4ef54cf 100644 --- a/resources/lang/ru_RU/form.php +++ b/resources/lang/ru_RU/form.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // new user: 'bank_name' => 'Название банка', @@ -184,6 +185,13 @@ return [ 'blocked' => 'Заблокирован?', 'blocked_code' => 'Причина блокировки', + // import + 'apply_rules' => 'Применить правила', + 'artist' => 'Исполнитель', + 'album' => 'Альбом', + 'song' => 'Композиция', + + // admin 'domain' => 'Домен', 'single_user_mode' => 'Отключить регистрацию пользователей', diff --git a/resources/lang/ru_RU/import.php b/resources/lang/ru_RU/import.php index bf6e0fe5ca..457e18aed2 100644 --- a/resources/lang/ru_RU/import.php +++ b/resources/lang/ru_RU/import.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ - // status of import: + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Импорт данных в Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', + 'job_configuration_breadcrumb' => 'Конфигурация для ":key"', + 'job_status_breadcrumb' => 'Статус импорта для ":key"', + 'cannot_create_for_provider' => 'Firefly III не может создать задачу для ":provider"-провайдера.', + + // index page: + 'general_index_title' => 'Импортировать файл', + 'general_index_intro' => 'Добро пожаловать в инструмент импорта Firefly III. Существует несколько способов импорта данных в Firefly III, отображаемых здесь в виде кнопок.', + // import provider strings (index): + 'button_fake' => 'Поддельный (демо) импорт', + 'button_file' => 'Импортировать файл', + 'button_bunq' => 'Импорт из bunq', + 'button_spectre' => 'Импорт с использованием Spectre', + 'button_plaid' => 'Импорт с использованием Plaid', + 'button_yodlee' => 'Импорт с использованием Yodlee', + 'button_quovo' => 'Импорт с использованием Quovo', + // global config box (index) + 'global_config_title' => 'Глобальные настройки импорта', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + // prerequisites box (index) + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + // provider config box (index) + 'can_config_title' => 'Импорт конфигурации', + 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'do_config_fake' => 'Configuration for the fake provider', + 'do_config_file' => 'Configuration for file imports', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Конфигурация для импорта из Spectre', + 'do_config_plaid' => 'Конфигурация для импорта из Plaid', + 'do_config_yodlee' => 'Конфигурация для импорта из Yodlee', + 'do_config_quovo' => 'Конфигурация для импорта из Quovo', + + // prerequisites: + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Ключ Fake API успешно сохранен!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + + // job configuration: + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Загрузить файлы', + 'import_file_type_csv' => 'CSV (значения, разделенные запятыми)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', + 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', + 'job_config_uc_apply_rules_title' => 'Apply rules', + 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', + 'job_config_uc_specifics_title' => 'Bank-specific options', + 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', + 'job_config_uc_submit' => 'Продолжить', + 'invalid_import_account' => 'You have selected an invalid account to import into.', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Choose your login', + 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', + 'spectre_login_status_active' => 'Active', + 'spectre_login_status_inactive' => 'Inactive', + 'spectre_login_status_disabled' => 'Disabled', + 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', + 'job_config_spectre_accounts_title' => 'Select accounts to import from', + 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', + 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', + 'spectre_do_not_import' => '(do not import)', + 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'imported_from_account' => 'Imported from ":account"', + 'spectre_account_with_number' => 'Account :number', + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq accounts', + 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', + 'bunq_no_mapping' => 'It seems you have not selected any accounts.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Status', + 'spectre_extra_key_card_type' => 'Card type', + 'spectre_extra_key_account_name' => 'Account name', + 'spectre_extra_key_client_name' => 'Client name', + 'spectre_extra_key_account_number' => 'Account number', + 'spectre_extra_key_blocked_amount' => 'Blocked amount', + 'spectre_extra_key_available_amount' => 'Available amount', + 'spectre_extra_key_credit_limit' => 'Credit limit', + 'spectre_extra_key_interest_rate' => 'Interest rate', + 'spectre_extra_key_expiry_date' => 'Expiry date', + 'spectre_extra_key_open_date' => 'Open date', + 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_current_date' => 'Current date', + 'spectre_extra_key_cards' => 'Cards', + 'spectre_extra_key_units' => 'Units', + 'spectre_extra_key_unit_price' => 'Unit price', + 'spectre_extra_key_transactions_count' => 'Transaction count', + + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Create better descriptions in ING exports', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Fixes potential problems with PC files', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', + 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'job_config_roles_submit' => 'Continue', + 'job_config_roles_column_name' => 'Name of column', + 'job_config_roles_column_example' => 'Column example data', + 'job_config_roles_column_role' => 'Column data meaning', + 'job_config_roles_do_map_value' => 'Map these values', + 'job_config_roles_no_example' => 'No example data available', + 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'job_config_roles_colum_count' => 'Column', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'job_config_field_value' => 'Field value', + 'job_config_field_mapped' => 'Mapped to', + 'map_do_not_map' => '(не сопоставлено)', + 'job_config_map_submit' => 'Start the import', + + + // import status page: + 'import_with_key' => 'Импорт с ключем \':key\'', 'status_wait_title' => 'Пожалуйста, подождите...', 'status_wait_text' => 'Это сообщение исчезнет через мгновение.', - 'status_fatal_title' => 'Произошла критическая ошибка', - 'status_fatal_text' => 'Произошла фатальная ошибка, из-за которой невозможно восстановить процедуру импорта. Пожалуйста, ознакомьтесь с пояснением в красном блоке ниже.', - 'status_fatal_more' => 'Если ошибка вызывает тайм-аут, импорт остановится на полпути. Для некоторых конфигураций серверов это означает, что сервер остановился, хотя импорт продолжает работать в фоновом режиме. Чтобы проверить, так ли это, проверьте лог-файл. Если проблема не устранена, попробуйте запустить импорт из командной строки.', - 'status_ready_title' => 'Импорт готов к запуску', - 'status_ready_text' => 'Импорт готов к запуску. Все необходимые настройки были сделаны. Пожалуйста, загрузите файл конфигурации. Это поможет повторно запустить импорт, если что-то пойдет не так, как планировалось. Чтобы непосредственно запустить импорт, вы можете либо выполнить следующую команду в консоли, либо запустить веб-импорт. В зависимости от вашей конфигурации импорт с помощью консоли может быть более информативен.', - 'status_ready_noconfig_text' => 'Импорт готов к запуску. Все необходимые настройки были сделаны. Чтобы непосредственно запустить импорт, вы можете либо выполнить следующую команду в консоли, либо запустить веб-импорт. В зависимости от вашей конфигурации импорт с помощью консоли может быть более информативен.', - 'status_ready_config' => 'Загрузить конфигурацию', - 'status_ready_start' => 'Начать импорт', - 'status_ready_share' => 'Пожалуйста, рассмотрите возможность загрузки вашей конфигурации в центр импорта конфигураций. Это позволит другим пользователям Firefly III проще импортировать свои файлы.', - 'status_job_new' => 'Новая задача.', - 'status_job_configuring' => 'Импорт настроен.', - 'status_job_configured' => 'Импорт настроен.', - 'status_job_running' => 'Импорт запущен. Пожалуйста, подождите...', - 'status_job_error' => 'Это задание вызвало ошибку.', - 'status_job_finished' => 'Импорт завершен!', 'status_running_title' => 'Выполняется импорт', - 'status_running_placeholder' => 'Пожалуйста, дождитесь, пока страница обновится...', - 'status_finished_title' => 'Процедура импорта завершена', - 'status_finished_text' => 'Ваши данные были импортированы.', - 'status_errors_title' => 'Ошибки во время импорта', - 'status_errors_single' => 'Во время импорта произошла ошибка. Однако, она не привела к фатальным последствиям.', - 'status_errors_multi' => 'Во время импорта произошли ошибки. Однако, они не привели к фатальным последствиям.', - 'status_bread_crumb' => 'Статус импорта', - 'status_sub_title' => 'Статус импорта', - 'config_sub_title' => 'Настройте свой импорт', - 'status_finished_job' => 'Всего :count транзакций было импортировано. Они могу быть найдены по метке :tag.', - 'status_finished_no_tag' => 'Firefly III не собрал никаких транзакций из вашего файла импорта.', - 'import_with_key' => 'Импорт с ключем \':key\'', + 'status_job_running' => 'Please wait, running the import...', + 'status_job_storing' => 'Please wait, storing data...', + 'status_job_rules' => 'Please wait, running rules...', + 'status_fatal_title' => 'Fatal error', + 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', + 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', + 'status_finished_title' => 'Import finished', + 'status_finished_text' => 'The import has finished.', + 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', + 'unknown_import_result' => 'Unknown import result', + 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', + 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', + 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', - // file, upload something - 'file_upload_title' => 'Настройка импорта (1/4) - Загрузите ваш файл', - 'file_upload_text' => 'Эта процедура поможет вам импортировать файлы из вашего банка в Firefly III. Пожалуйста, прочитайте справку, доступную в правом верхнем углу этой страницы.', - 'file_upload_fields' => 'Поля', - 'file_upload_help' => 'Выберите файл', - 'file_upload_config_help' => 'Если вы ранее импортировали данные в Firefly III, у вас может быть файл конфигурации, который позволит вам загрузить готовые настойки. Для некоторых банков другие пользователи любезно предоставили свои файлы конфигурации', - 'file_upload_type_help' => 'Выберите тип загружаемого файла', - 'file_upload_submit' => 'Загрузить файлы', - // file, upload types - 'import_file_type_csv' => 'CSV (значения, разделенные запятыми)', + // general errors and warnings: + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', - // file, initial config for CSV - 'csv_initial_title' => 'Настройка импорта (2/4) - Основные настройки CSV-импорта', - 'csv_initial_text' => 'Чтобы импорт данных прошёл успешно, пожалуйста проверьте несколько параметров.', - 'csv_initial_box' => 'Основные параметры импорта CSV', - 'csv_initial_box_title' => 'Основные параметры импорта CSV', - 'csv_initial_header_help' => 'Установите этот флажок, если первая строка CSV-файла содержит заголовки столбцов.', - 'csv_initial_date_help' => 'Формат даты и времени в вашем CSV-файле. Придерживайтесь формата, описанного на этой странице. По умолчанию дату будут анализироваться на соответствие такому формату: :dateExample.', - 'csv_initial_delimiter_help' => 'Выберите разделитель полей, который используется в вашем файле. Если вы не уверены, помните, что запятая - это самый безопасный вариант.', - 'csv_initial_import_account_help' => 'Если ваш CSV-файл НЕ СОДЕРЖИТ информацию о ваших счетах, используйте этот выпадающий список, чтобы выбрать, к какому счёту относятся транзакции в CVS-файле.', - 'csv_initial_submit' => 'Перейти к шагу 3/4', - - // file, new options: - 'file_apply_rules_title' => 'Применить правила', - 'file_apply_rules_description' => 'Применить ваши правила. Обратите внимание, что это значительно замедляет импорт.', - 'file_match_bills_title' => 'Соответствующие счета к оплате', - 'file_match_bills_description' => 'Сопоставление свои счета к оплате с вновь созданными расходами. Помните, что это может существенно замедлить импорт.', - - // file, roles config - 'csv_roles_title' => 'Настройка импорта (3/4). Определите роль каждого столбца', - 'csv_roles_text' => 'Каждый столбец в файле CSV содержит определённые данные. Укажите, какие данные должен ожидать импортер. Опция «сопоставить» данные привяжет каждую запись, найденную в столбце, к значению в вашей базе данных. Часто отображаемый столбец - это столбец, содержащий IBAN спонсорского счёта. Его можно легко сопоставить с существующим в вашей базе данных IBAN.', - 'csv_roles_table' => 'Таблица', - 'csv_roles_column_name' => 'Название столбца', - 'csv_roles_column_example' => 'Пример данных в столбце', - 'csv_roles_column_role' => 'Значение в столбце', - 'csv_roles_do_map_value' => 'Сопоставьте эти значения', - 'csv_roles_column' => 'Столбец', - 'csv_roles_no_example_data' => 'Нет доступных данных для примера', - 'csv_roles_submit' => 'Перейти к шагу 4/4', - - // not csv, but normal warning - 'roles_warning' => 'Пожалуйста, отметьте хотя бы один столбец как столбец с суммой. Также целесообразно выбрать столбец для описания, даты и спонсорского счёта.', - 'foreign_amount_warning' => 'Если вы пометите этот столбец, как содержащий сумму в иностранной валюте, вы также должны указать столбец, который указывает, какая именно это валюта.', - - // file, map data - 'file_map_title' => 'Настройки импорта (4/4) - Сопоставление данных импорта с данными Firefly III', - 'file_map_text' => 'В следующих таблицах значение слева отображает информацию, найденную в загруженном файле. Ваша задача - сопоставить это значение (если это возможно) со значением, уже имеющимся в вашей базе данных. Firefly будет придерживаться этого сопоставления. Если для сопоставления нет значения или вы не хотите отображать определённое значение, ничего не выбирайте.', - 'file_map_field_value' => 'Значение поля', - 'file_map_field_mapped_to' => 'Сопоставлено с', - 'map_do_not_map' => '(не сопоставлено)', - 'file_map_submit' => 'Начать импорт', - 'file_nothing_to_map' => 'В вашем файле нет данных, которые можно сопоставить с существующими значениями. Нажмите «Начать импорт», чтобы продолжить.', - - // map things. + // column roles for CSV import: 'column__ignore' => '(игнорировать этот столбец)', 'column_account-iban' => 'Счет актива (IBAN)', 'column_account-id' => 'ID основного счёта (соответствующий FF3)', @@ -158,48 +261,4 @@ return [ 'column_note' => 'Примечания', 'column_internal-reference' => 'Внутренняя ссылка', - // prerequisites - 'prerequisites' => 'Требования', - - // bunq - 'bunq_prerequisites_title' => 'Требования для импорта из bunq', - 'bunq_prerequisites_text' => 'Чтобы импортировать из bunq, вам нужно получить ключ API. Вы можете сделать это через приложение. Обратите внимание, что функция импорта для bunq находится в бета-тестирования. Было протестировано только API песочницы (sandbox).', - 'bunq_prerequisites_text_ip' => 'Для работы с Bunq необходимо указать ваш внешний IP-адрес. Firefly III попытался узнать ваш адрес с помощью службы ipify. Убедитесь, что этот IP-адрес верен, иначе импорт не будет выполнен.', - 'bunq_do_import' => 'Да, импортировать с этого счёта', - 'bunq_accounts_title' => 'Счета Bunq', - 'bunq_accounts_text' => 'Эти счета связаны с вашей учётной записью bunq. Выберите счета, данные о о которых вы хотите импортировать, и счёт, на который будут импортированы транзакции.', - - // Spectre - 'spectre_title' => 'Импорт с использованием Spectre', - 'spectre_prerequisites_title' => 'Требования для импорта с использованием Spectre', - 'spectre_prerequisites_text' => 'Чтобы импортировать данные с помощью Spectre API (v4), вы должны предоставить Firefly III два секретных значения. Их можно найти на странице secrets page.', - 'spectre_enter_pub_key' => 'Импорт будет работать только если вы введёте этот ключ безопасности на странице с ключами в своём аккаунте.', - 'spectre_accounts_title' => 'Выберите счёта, с которых будет производиться импорт', - 'spectre_accounts_text' => 'Каждый счёт в списке слева был найден в в Spectre и может быть импортирован в Firefly III. Выберите основной счёт, на котором нужно сохранить импортируемые транзакции. Если вы не хотите импортировать данные с какого-либо конкретного счёта, снимите соответствующий флажок.', - 'spectre_do_import' => 'Да, импортировать с этого счёта', - 'spectre_no_supported_accounts' => 'Вы не можете импортировать с этого счёта из-за несоответствия валюты.', - - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'SWIFT', - 'spectre_extra_key_status' => 'Статус', - 'spectre_extra_key_card_type' => 'Тип карты', - 'spectre_extra_key_account_name' => 'Название счёта', - 'spectre_extra_key_client_name' => 'Имя клиента', - 'spectre_extra_key_account_number' => 'Номер счёта', - 'spectre_extra_key_blocked_amount' => 'Заблокированная сумма', - 'spectre_extra_key_available_amount' => 'Доступная сумма', - 'spectre_extra_key_credit_limit' => 'Кредитный лимит', - 'spectre_extra_key_interest_rate' => 'Процентная ставка', - 'spectre_extra_key_expiry_date' => 'Дата окончания', - 'spectre_extra_key_open_date' => 'Дата открытия', - 'spectre_extra_key_current_time' => 'Текущее время', - 'spectre_extra_key_current_date' => 'Текущая дата', - 'spectre_extra_key_cards' => 'Карты', - 'spectre_extra_key_units' => 'Единицы', - 'spectre_extra_key_unit_price' => 'Цена за единицу', - 'spectre_extra_key_transactions_count' => 'Количество транзакций', - - // various other strings: - 'imported_from_account' => 'Импортировано со счёта ":account"', ]; diff --git a/resources/lang/ru_RU/intro.php b/resources/lang/ru_RU/intro.php index 40f7a5dd48..8f21f6457e 100644 --- a/resources/lang/ru_RU/intro.php +++ b/resources/lang/ru_RU/intro.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // index 'index_intro' => 'Добро пожаловать на стартовую страницу Firefly III. Пожалуйста, найдите время, чтобы ознакомиться с этим кратким введением в возможности Firefly III.', diff --git a/resources/lang/ru_RU/list.php b/resources/lang/ru_RU/list.php index effe3f6184..ad3e77cc87 100644 --- a/resources/lang/ru_RU/list.php +++ b/resources/lang/ru_RU/list.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'buttons' => 'Кнопки', 'icon' => 'Значок', @@ -111,10 +112,15 @@ return [ 'sepa-cc' => 'Код очистки SEPA', 'sepa-ep' => 'Внешняя цель SEPA', 'sepa-ci' => 'Идентификатор кредитора SEPA', + 'external_id' => 'Внешний ID', 'account_at_bunq' => 'Счёт с bunq', 'file_name' => 'Имя файла', 'file_size' => 'Размер файла', 'file_type' => 'Тип файла', 'attached_to' => 'Прикреплено к', 'file_exists' => 'Файл существует', + 'spectre_bank' => 'Bank', + 'spectre_last_use' => 'Last login', + 'spectre_status' => 'Status', + 'bunq_payment_id' => 'bunq payment ID', ]; diff --git a/resources/lang/ru_RU/pagination.php b/resources/lang/ru_RU/pagination.php index 686f3d513f..55ea0144e6 100644 --- a/resources/lang/ru_RU/pagination.php +++ b/resources/lang/ru_RU/pagination.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'previous' => '« Предыдущие', 'next' => 'Следующие »', diff --git a/resources/lang/ru_RU/passwords.php b/resources/lang/ru_RU/passwords.php index 331c788036..b54116eda5 100644 --- a/resources/lang/ru_RU/passwords.php +++ b/resources/lang/ru_RU/passwords.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'password' => 'Пароль должен содержать не менее 6 символов. Пароль и его подтверждение должны совпадать.', 'user' => 'Мы не можем найти пользователя с таким e-mail.', diff --git a/resources/lang/ru_RU/validation.php b/resources/lang/ru_RU/validation.php index 0aa6c7686d..9d551d5344 100644 --- a/resources/lang/ru_RU/validation.php +++ b/resources/lang/ru_RU/validation.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'iban' => 'Это некорректный IBAN.', 'source_equals_destination' => 'Счёт источник и счёт назначения совпадают', @@ -109,7 +110,8 @@ return [ 'in_array' => 'Поле :attribute не существует в :other.', 'present' => 'Поле :attribute должно быть заполнено.', 'amount_zero' => 'Общее количество не может быть равно нулю', - 'secure_password' => 'Это не безопасный пароль. Попробуйте еще раз. Для получения справки посетите https://goo.gl/NCh2tN', + 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', 'attributes' => [ 'email' => '"Адрес электронной почты"', 'description' => '"Описание"', From 2a05cc382f66cc9bdb4289f50a2a90f5b560e606 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 14:22:53 +0200 Subject: [PATCH 167/182] Update Turkish language files [skip ci] --- resources/lang/tr_TR/auth.php | 16 +- resources/lang/tr_TR/bank.php | 5 +- resources/lang/tr_TR/breadcrumbs.php | 5 +- resources/lang/tr_TR/components.php | 3 +- resources/lang/tr_TR/config.php | 5 +- resources/lang/tr_TR/csv.php | 5 +- resources/lang/tr_TR/demo.php | 7 +- resources/lang/tr_TR/firefly.php | 32 +-- resources/lang/tr_TR/form.php | 12 +- resources/lang/tr_TR/import.php | 313 ++++++++++++++++----------- resources/lang/tr_TR/intro.php | 5 +- resources/lang/tr_TR/list.php | 10 +- resources/lang/tr_TR/pagination.php | 5 +- resources/lang/tr_TR/passwords.php | 5 +- resources/lang/tr_TR/validation.php | 8 +- 15 files changed, 249 insertions(+), 187 deletions(-) diff --git a/resources/lang/tr_TR/auth.php b/resources/lang/tr_TR/auth.php index e51cdeef23..a000fd4e5f 100644 --- a/resources/lang/tr_TR/auth.php +++ b/resources/lang/tr_TR/auth.php @@ -1,9 +1,8 @@ . */ -return [ - /* - |-------------------------------------------------------------------------- - | Authentication Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used during authentication for various - | messages that we need to display to the user. You are free to modify - | these language lines according to your application's requirements. - | - */ +declare(strict_types=1); +return [ 'failed' => 'Bu kimlik bilgileri kayıtlarımızla uyuşmuyor.', 'throttle' => 'Fazla sayıda oturum açma girişimi. Lütfen :seconds saniye sonra tekrar deneyiniz.', ]; diff --git a/resources/lang/tr_TR/bank.php b/resources/lang/tr_TR/bank.php index 8826020cbf..5d00b1e685 100644 --- a/resources/lang/tr_TR/bank.php +++ b/resources/lang/tr_TR/bank.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/tr_TR/breadcrumbs.php b/resources/lang/tr_TR/breadcrumbs.php index 17bc230d6a..82d66d4c65 100644 --- a/resources/lang/tr_TR/breadcrumbs.php +++ b/resources/lang/tr_TR/breadcrumbs.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'home' => 'Anasayfa', 'edit_currency' => '":name" para birimini düzenle', diff --git a/resources/lang/tr_TR/components.php b/resources/lang/tr_TR/components.php index 50092e06fd..6299088f8e 100644 --- a/resources/lang/tr_TR/components.php +++ b/resources/lang/tr_TR/components.php @@ -1,5 +1,4 @@ . */ +declare(strict_types=1); + return [ // profile 'personal_access_tokens' => 'Personal access tokens', diff --git a/resources/lang/tr_TR/config.php b/resources/lang/tr_TR/config.php index 7c3694a155..4bc8b0175d 100644 --- a/resources/lang/tr_TR/config.php +++ b/resources/lang/tr_TR/config.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'html_language' => 'tr', 'locale' => 'tr, Turkish, tr_TR, tr_TR.utf8, tr_TR.S.UTF-8', diff --git a/resources/lang/tr_TR/csv.php b/resources/lang/tr_TR/csv.php index 622a49202a..aae109a40a 100644 --- a/resources/lang/tr_TR/csv.php +++ b/resources/lang/tr_TR/csv.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ ]; diff --git a/resources/lang/tr_TR/demo.php b/resources/lang/tr_TR/demo.php index 5c4ee00a0d..f60723c7a3 100644 --- a/resources/lang/tr_TR/demo.php +++ b/resources/lang/tr_TR/demo.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'no_demo_text' => 'Maalesef, bu sayfa için daha fazla demo açıklama metni yok.', 'see_help_icon' => 'Ancak, sağ üst köşedeki simge size daha fazla bilgi verebilir.', @@ -32,5 +33,5 @@ return [ 'currencies-index' => 'Firefly III, birden fazla para birimini destekliyor. Euro varsayılan olmasına rağmen, ABD Doları ve diğer birçok para birimine ayarlanabilir. Gördüğünüz gibi küçük bir para birimi seçeneği dedahil edilmiştir ancak isterseniz kendi para biriminizi ekleyebilirsiniz. Varsayılan para birimi değiştirilebilir ancak mevcut işlemlerin para birimi değiştirilemez: Firefly III, aynı anda birden çok para biriminin kullanılmasını destekler.', 'transactions-index' => 'Bu masraflar, mevduatlar ve transferler için özellikle yaratıcı değildir. Bunlar otomatik olarak oluşturuldu.', 'piggy-banks-index' => 'Gördüğünüz gibi, üç tane banka var. Her domuzcuk bankasındaki para miktarını değiştirmek için artı ve eksi düğmelerini kullanın. Her domuzcuk bankasının yönetimini görmek için domuzcuk\'un üzerine tıklayın.', - 'import-index' => 'Elbette, herhangi bir CVS dosyası Firefly III\'e aktarılabilir', + 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', ]; diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index 43138bb0b3..32747c7c17 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // general stuff: 'close' => 'Kapat', @@ -179,6 +180,7 @@ return [ 'authorization_request_intro' => ':client is requesting permission to access your financial administration. Would you like to authorize :client to access these records?', 'scopes_will_be_able' => 'This application will be able to:', 'button_authorize' => 'Authorize', + 'none_in_select_list' => '(none)', // check for updates: 'update_check_title' => 'Check for updates', @@ -667,6 +669,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'bill_will_automatch' => 'Fatura uygun işlemlere otomatik olarak bağlandı', 'skips_over' => 'atla', 'bill_store_error' => 'An unexpected error occurred while storing your new bill. Please check the log files', + 'list_inactive_rule' => 'inactive rule', // accounts: 'details_for_asset' => '":name" Varlık hesabı ayrıntıları', @@ -802,6 +805,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'opt_group_savingAsset' => 'Tasarruf Hesapları', 'opt_group_sharedAsset' => 'Paylaşılan varlık hesapları', 'opt_group_ccAsset' => 'Kredi Kartı', + 'opt_group_cashWalletAsset' => 'Cash wallets', 'notes' => 'Notes', 'unknown_journal_error' => 'Could not store the transaction. Please check the log files.', @@ -817,6 +821,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'language' => 'Language', 'new_savings_account' => ':bank_name savings account', 'cash_wallet' => 'Cash wallet', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Hesaplarınız', @@ -1014,6 +1019,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'remove_money_from_piggy_title' => '":name" kumbarasından para çek', 'add' => 'Ekle', 'no_money_for_piggy' => 'Bu kumbaraya koyacak paran yok.', + 'suggested_savings_per_month' => 'Suggested per month', 'remove' => 'Kaldır', 'max_amount_add' => 'Ekleyebileceğiniz azami tutar', @@ -1149,27 +1155,9 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'no_edit_multiple_left' => 'Düzenlemek için geçerli İşlemler seçmediniz.', 'cannot_convert_split_journal' => 'Bölünmüş bir İşlemi dönüştüremezsiniz', - // import bread crumbs and titles: - 'import' => 'İçe aktar', - 'import_data' => 'Veriyi içe aktar', - 'import_general_index_file' => 'Bir Dosyayı içe aktar', - 'import_from_bunq' => 'Bunq\'dan aktar', - 'import_using_spectre' => 'Spectre kullanarak içe aktar', - 'import_using_plaid' => 'Palid kullanarak içe aktar', - 'import_config_bread_crumb' => 'Aktarımınızı oluşturunuz', - - // import index page: + // Import page (general strings only) 'import_index_title' => 'Firefly III\'e veri aktarma', - 'import_index_sub_title' => 'İndeks', - 'import_general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', - 'upload_error' => 'The file you have uploaded could not be processed. Possibly it is of an invalid file type or encoding. The log files will have more information.', - 'reset_import_settings_title' => 'Reset import configuration', - 'reset_import_settings_text' => 'You can use these links to reset your import settings for specific providers. This is useful when bad settings stop you from importing data.', - 'reset_settings_bunq' => 'Remove bunq API key, local external IP address and bunq related RSA keys.', - 'reset_settings_spectre' => 'Remove Spectre secrets and ID\'s. This will also remove your Spectre keypair. Remember to update the new one.', - 'settings_reset_for_bunq' => 'Bunq settings reset.', - 'settings_reset_for_spectre' => 'Spectre settings reset.', - + 'import_data' => 'Veriyi içe aktar', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Bir Sandstorm.io ortamında Firefly III kullanıyorsanız, bu işlev kullanılamaz.', diff --git a/resources/lang/tr_TR/form.php b/resources/lang/tr_TR/form.php index 0304b33981..ac3f7c5de7 100644 --- a/resources/lang/tr_TR/form.php +++ b/resources/lang/tr_TR/form.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // new user: 'bank_name' => 'Banka adı', @@ -184,6 +185,13 @@ return [ 'blocked' => 'Engellendi mi?', 'blocked_code' => 'Blok nedeni', + // import + 'apply_rules' => 'Apply rules', + 'artist' => 'Artist', + 'album' => 'Album', + 'song' => 'Song', + + // admin 'domain' => 'Alan adı', 'single_user_mode' => 'Kullanıcı kaydını devre dışı bırak', diff --git a/resources/lang/tr_TR/import.php b/resources/lang/tr_TR/import.php index 1d7658750c..dd1ecba61d 100644 --- a/resources/lang/tr_TR/import.php +++ b/resources/lang/tr_TR/import.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ - // status of import: + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Import data into Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', + 'job_configuration_breadcrumb' => 'Configuration for ":key"', + 'job_status_breadcrumb' => 'Import status for ":key"', + 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', + + // index page: + 'general_index_title' => 'Import a file', + 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + // import provider strings (index): + 'button_fake' => 'Fake an import', + 'button_file' => 'Import a file', + 'button_bunq' => 'Import from bunq', + 'button_spectre' => 'Import using Spectre', + 'button_plaid' => 'Import using Plaid', + 'button_yodlee' => 'Import using Yodlee', + 'button_quovo' => 'Import using Quovo', + // global config box (index) + 'global_config_title' => 'Global import configuration', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + // prerequisites box (index) + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + // provider config box (index) + 'can_config_title' => 'Import configuration', + 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', + 'do_config_fake' => 'Configuration for the fake provider', + 'do_config_file' => 'Configuration for file imports', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', + + // prerequisites: + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + + // job configuration: + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (virgülle ayrılmış değerler)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', + 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', + 'job_config_uc_apply_rules_title' => 'Apply rules', + 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', + 'job_config_uc_specifics_title' => 'Bank-specific options', + 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', + 'job_config_uc_submit' => 'Continue', + 'invalid_import_account' => 'You have selected an invalid account to import into.', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Choose your login', + 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', + 'spectre_login_status_active' => 'Active', + 'spectre_login_status_inactive' => 'Inactive', + 'spectre_login_status_disabled' => 'Disabled', + 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', + 'job_config_spectre_accounts_title' => 'Select accounts to import from', + 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', + 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', + 'spectre_do_not_import' => '(do not import)', + 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'imported_from_account' => 'Imported from ":account"', + 'spectre_account_with_number' => 'Account :number', + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq accounts', + 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', + 'bunq_no_mapping' => 'It seems you have not selected any accounts.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Status', + 'spectre_extra_key_card_type' => 'Card type', + 'spectre_extra_key_account_name' => 'Account name', + 'spectre_extra_key_client_name' => 'Client name', + 'spectre_extra_key_account_number' => 'Account number', + 'spectre_extra_key_blocked_amount' => 'Blocked amount', + 'spectre_extra_key_available_amount' => 'Available amount', + 'spectre_extra_key_credit_limit' => 'Credit limit', + 'spectre_extra_key_interest_rate' => 'Interest rate', + 'spectre_extra_key_expiry_date' => 'Expiry date', + 'spectre_extra_key_open_date' => 'Open date', + 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_current_date' => 'Current date', + 'spectre_extra_key_cards' => 'Cards', + 'spectre_extra_key_units' => 'Units', + 'spectre_extra_key_unit_price' => 'Unit price', + 'spectre_extra_key_transactions_count' => 'Transaction count', + + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Create better descriptions in ING exports', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Fixes potential problems with PC files', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', + 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'job_config_roles_submit' => 'Continue', + 'job_config_roles_column_name' => 'Name of column', + 'job_config_roles_column_example' => 'Column example data', + 'job_config_roles_column_role' => 'Column data meaning', + 'job_config_roles_do_map_value' => 'Map these values', + 'job_config_roles_no_example' => 'No example data available', + 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'job_config_roles_colum_count' => 'Column', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'job_config_field_value' => 'Field value', + 'job_config_field_mapped' => 'Mapped to', + 'map_do_not_map' => '(eşleştirme)', + 'job_config_map_submit' => 'Start the import', + + + // import status page: + 'import_with_key' => '\':key\' ile içe aktarın', 'status_wait_title' => 'Lütfen bekleyin...', 'status_wait_text' => 'Bu kutu bir dakika içinde kaybolacak.', - 'status_fatal_title' => 'Önemli bir hata oluştu', - 'status_fatal_text' => 'İçe aktarma rutininin kurtaramadığı önemli bir hata oluştu. Lütfen aşağıdaki kırmızı renkli açıklamaları okuyun.', - 'status_fatal_more' => 'Eğer hata zaman aşımı ise, içe aktarma yarısında durdurulur. Bazı sunucu ayarlarında sadece sunucu durdurulurken içe aktarım arka planda devam eder. Bunu sağlamak için kayıt dosyalarını kontrol edin. Eğer sorun devam ederse komut satırı üzerinden içe aktarımı deneyin.', - 'status_ready_title' => 'İçe aktarım başlamaya hazır', - 'status_ready_text' => 'İçe aktarım başlamaya hazır. Yapmanız gereken tüm ayarlar yapıldı. Lütfen ayar dosyasını indirin. İçe aktarım planlandığı gibi gitmezse size yardım edecektir. İçe aktarımı başlatmak için takip eden komutu konsolunuza girebilir ya da web tabanlı içe aktarımı kullanabilirsiniz. Ayarlarınıza bağlı olarak göre konsol içe aktarımı size daha fazla geri bildirim verecektir.', - 'status_ready_noconfig_text' => 'İçe aktarım başlamaya hazır. Yapmanız gereken tüm ayarlar yapıldı. İçe aktarımı başlatmak için takip eden komutu konsolunuza girebilir ya da web tabanlı içe aktarımı kullanabilirsiniz. Ayarlarınıza bağlı olarak göre konsol içe aktarımı size daha fazla geri bildirim verecektir.', - 'status_ready_config' => 'Yapılandırmayı indir', - 'status_ready_start' => 'İçe aktarmayı başlat', - 'status_ready_share' => 'Lütfen ayarlarınızı indirmeyi ve onu içe aktarım ayarları merkezinde paylaşmayı düşünün. Bu diğer kullanıcılarının Firefly III\'ün dosyalarını daha kolay içe aktarmasına olanak tanır.', - 'status_job_new' => 'Yeni iş.', - 'status_job_configuring' => 'İçe aktarım ayarlanıyor.', - 'status_job_configured' => 'İçe aktarım ayarlandı.', - 'status_job_running' => 'Alma işlemi çalışıyor... Lütfen bekleyin..', - 'status_job_error' => 'İş bir hata üretti.', - 'status_job_finished' => 'Alma işlemi tamamlandı!', 'status_running_title' => 'İçe aktarma işlemi sürüyor', - 'status_running_placeholder' => 'Güncelleme için lütfen bekleyin...', - 'status_finished_title' => 'İçe aktarma rutini tamamlandı', - 'status_finished_text' => 'İçe aktarma rutini verilerinizi içe aktardı.', - 'status_errors_title' => 'İçe aktarım sırasında hata', - 'status_errors_single' => 'İçe aktarım sırasında bir hata oluştu. Önemli gibi görünmüyor.', - 'status_errors_multi' => 'İçe aktarım sırasında hatalar oluştu. Önemli gibi görünmüyorlar.', - 'status_bread_crumb' => 'Aktarma durumu', - 'status_sub_title' => 'Aktarma durumu', - 'config_sub_title' => 'Hesabınızı oluşturunuz', - 'status_finished_job' => 'The :count transactions imported can be found in tag :tag.', - 'status_finished_no_tag' => 'Firefly III has not collected any transactions from your import file.', - 'import_with_key' => '\':key\' ile içe aktarın', + 'status_job_running' => 'Please wait, running the import...', + 'status_job_storing' => 'Please wait, storing data...', + 'status_job_rules' => 'Please wait, running rules...', + 'status_fatal_title' => 'Fatal error', + 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', + 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', + 'status_finished_title' => 'Import finished', + 'status_finished_text' => 'The import has finished.', + 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', + 'unknown_import_result' => 'Unknown import result', + 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', + 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', + 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', - // file, upload something - 'file_upload_title' => 'Ayarları aktar (1/4) - Dosyalarınızı yükelyin', - 'file_upload_text' => 'Bu yöntem dosyalarınızı bankanızdan Firefly III\'e aktarmanıza yardımcı olur. Lütfen sağ üst köşedeki yardımı kontrol edin.', - 'file_upload_fields' => 'Alanlar', - 'file_upload_help' => 'Dosyanızı seçin', - 'file_upload_config_help' => 'Eğer Firefly III\'e daha önce veri aktardıysanız, ayarları sizin için önceden ayarlayacak bir ayar dosyasına sahip olabilirsiniz. Diğer kullanıcılar baı bankalar için kendi ayar dosyalarını sağlayabilirler', - 'file_upload_type_help' => 'Yükleyeceğiniz dosya türünü seçin', - 'file_upload_submit' => 'Dosyaları yükle', - // file, upload types - 'import_file_type_csv' => 'CSV (virgülle ayrılmış değerler)', + // general errors and warnings: + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', - // file, initial config for CSV - 'csv_initial_title' => 'Ayarları aktar (2/4) - Temel CSV aktarım ayarları', - 'csv_initial_text' => 'Dosyanızı doğru bir şekilde içe aktarabilmek için lütfen aşağıdaki seçenekleri doğrulayın.', - 'csv_initial_box' => 'Temel CSV aktarım ayarları', - 'csv_initial_box_title' => 'Temel CSV aktarım ayarları seçenekleri', - 'csv_initial_header_help' => 'CSV dosyanızın ilk satırları sütun başlıklarıysa bu kutuyu işaretleyin.', - 'csv_initial_date_help' => 'CSV dosyanızda ki zaman biçimi. Bu sayfanın gösterdiği biçimi kontrol takip edin. Varsayılan değer şu şekilde görülen tarihleri ayrıştırır: :dateExample.', - 'csv_initial_delimiter_help' => 'Gir dosyanızda kullanılan alan sınırlayıcıyı seçin. Emin değilseniz, virgül en güvenli seçenektir.', - 'csv_initial_import_account_help' => 'Eğer CSV dosyanız aktif hesabınızla ilgili bilgi içermiyorsa, CSV\'de bulunan işlemlerin hangi hesaba ait olduğunu bu açılan kutudan seçiniz.', - 'csv_initial_submit' => '3/4 adım ile devam et', - - // file, new options: - 'file_apply_rules_title' => 'Kuralları uygula', - 'file_apply_rules_description' => 'Kurallarınızı kabul ediniz. Bunun önemli ölçüde içe aktarmayı yavaşlattığını unutmayın.', - 'file_match_bills_title' => 'Faturaları eşleştirin', - 'file_match_bills_description' => 'Faturalarınızı yeni oluşturulan çekimlerle eşleştirin. Bunun önemli ölçüde içe aktarmayı yavaşlatacağını unutmayın.', - - // file, roles config - 'csv_roles_title' => 'Ayarları aktar (3/4) - Her sütunun görevini belirleyin', - 'csv_roles_text' => 'CSV dosyanızdaki her sütun belirli verileri içerir. Lütfen aktarıcının ne tür bir veri beklemesi gerektiğini belirtin. Verileri "planla" seçeneği sütunda bulunan her girdinin veri tabanınınızdaki bir değer ile bağlantılanması anlamına gelir. Genellikle planlanan sütun karşı hesabın IBAN numarasının olduğu sütundur. Bu veri tabanınızda bulunan IBAN\'larla kolayca eşleştirilebilir.', - 'csv_roles_table' => 'Tablo', - 'csv_roles_column_name' => 'Sütun adı', - 'csv_roles_column_example' => 'Sütun örneği verileri', - 'csv_roles_column_role' => 'Sütun veri ortalaması', - 'csv_roles_do_map_value' => 'Değerlerin haritası', - 'csv_roles_column' => 'Sütun', - 'csv_roles_no_example_data' => 'Örnek veri yok', - 'csv_roles_submit' => '4/4 adım ile devam et', - - // not csv, but normal warning - 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', - 'foreign_amount_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', - - // file, map data - 'file_map_title' => 'Ayarları aktar (4/4) - İçe aktarım verilerini Firefly III verilerine bağlayın', - 'file_map_text' => 'Takip eden tabloda, sol değer yüklediğiniz dosyada bulunan bilgileri gösterir. Bu değeri eşlemek sizin göreviniz, eğer mümkünse veritabanınızda bulunan bir değerle. Firefly bu eşlemeye bağlı kalacak. Eğer eşleştirilecek değer yoksa ya da belirli bir değer ile eşleştirmek istemiyorsanız hiçbir şey seçmeyin.', - 'file_map_field_value' => 'Alan değeri', - 'file_map_field_mapped_to' => 'Eşleşti', - 'map_do_not_map' => '(eşleştirme)', - 'file_map_submit' => 'İçe aktarmaya başla', - 'file_nothing_to_map' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', - - // map things. + // column roles for CSV import: 'column__ignore' => '(bu sütünu yok say)', 'column_account-iban' => 'Öğe hesabı (IBAN)', 'column_account-id' => 'Asset account ID (matching FF3)', @@ -158,48 +261,4 @@ return [ 'column_note' => 'Not(lar)', 'column_internal-reference' => 'Internal reference', - // prerequisites - 'prerequisites' => 'Prerequisites', - - // bunq - 'bunq_prerequisites_title' => 'Bunq\'dan içeri aktarım için şartlar', - 'bunq_prerequisites_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'bunq_prerequisites_text_ip' => 'Bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', - 'bunq_do_import' => 'Yes, import from this account', - 'bunq_accounts_title' => 'Bunq accounts', - 'bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', - - // Spectre - 'spectre_title' => 'Spectre kullanarak içe aktar', - 'spectre_prerequisites_title' => 'Spectre kullanarak içe aktarma için ön koşullar', - 'spectre_prerequisites_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your secrets page.', - 'spectre_accounts_title' => 'Select accounts to import from', - 'spectre_accounts_text' => 'Each account on the left below has been found by Spectre and can be imported into Firefly III. Please select the asset account that should hold any given transactions. If you do not wish to import from any particular account, remove the check from the checkbox.', - 'spectre_do_import' => 'Yes, import from this account', - 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', - - // keys from "extra" array: - 'spectre_extra_key_iban' => 'IBAN', - 'spectre_extra_key_swift' => 'SWIFT', - 'spectre_extra_key_status' => 'Status', - 'spectre_extra_key_card_type' => 'Card type', - 'spectre_extra_key_account_name' => 'Account name', - 'spectre_extra_key_client_name' => 'Client name', - 'spectre_extra_key_account_number' => 'Account number', - 'spectre_extra_key_blocked_amount' => 'Blocked amount', - 'spectre_extra_key_available_amount' => 'Available amount', - 'spectre_extra_key_credit_limit' => 'Credit limit', - 'spectre_extra_key_interest_rate' => 'Interest rate', - 'spectre_extra_key_expiry_date' => 'Expiry date', - 'spectre_extra_key_open_date' => 'Open date', - 'spectre_extra_key_current_time' => 'Current time', - 'spectre_extra_key_current_date' => 'Current date', - 'spectre_extra_key_cards' => 'Cards', - 'spectre_extra_key_units' => 'Units', - 'spectre_extra_key_unit_price' => 'Unit price', - 'spectre_extra_key_transactions_count' => 'Transaction count', - - // various other strings: - 'imported_from_account' => 'Imported from ":account"', ]; diff --git a/resources/lang/tr_TR/intro.php b/resources/lang/tr_TR/intro.php index ed21bd0c27..6e4291b465 100644 --- a/resources/lang/tr_TR/intro.php +++ b/resources/lang/tr_TR/intro.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ // index 'index_intro' => 'Firefly III indeks sayfasına hoşgeldiniz. Firefly III\'nin nasıl çalıştığını öğrenmek için lütfen bu tanıtımı izleyin.', diff --git a/resources/lang/tr_TR/list.php b/resources/lang/tr_TR/list.php index 016da06ac2..4b2516e5f6 100644 --- a/resources/lang/tr_TR/list.php +++ b/resources/lang/tr_TR/list.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'buttons' => 'Tuşlar', 'icon' => 'Simge', @@ -111,10 +112,15 @@ return [ 'sepa-cc' => 'SEPA Clearing Code', 'sepa-ep' => 'SEPA External Purpose', 'sepa-ci' => 'SEPA Creditor Identifier', + 'external_id' => 'External ID', 'account_at_bunq' => 'Account with bunq', 'file_name' => 'File name', 'file_size' => 'File size', 'file_type' => 'File type', 'attached_to' => 'Attached to', 'file_exists' => 'File exists', + 'spectre_bank' => 'Bank', + 'spectre_last_use' => 'Last login', + 'spectre_status' => 'Status', + 'bunq_payment_id' => 'bunq payment ID', ]; diff --git a/resources/lang/tr_TR/pagination.php b/resources/lang/tr_TR/pagination.php index 4b82910d66..5022bed68c 100644 --- a/resources/lang/tr_TR/pagination.php +++ b/resources/lang/tr_TR/pagination.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'previous' => '« Önceki', 'next' => 'Sonraki »', diff --git a/resources/lang/tr_TR/passwords.php b/resources/lang/tr_TR/passwords.php index a013bf7294..3a312f2358 100644 --- a/resources/lang/tr_TR/passwords.php +++ b/resources/lang/tr_TR/passwords.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'password' => 'Şifreniz en az altı karakter olmalıdır ve onayla eşleşmelidir.', 'user' => 'Bu e-posta adresine sahip bir kullanıcı bulunamadı.', diff --git a/resources/lang/tr_TR/validation.php b/resources/lang/tr_TR/validation.php index 00adb5581e..7a4374c4d5 100644 --- a/resources/lang/tr_TR/validation.php +++ b/resources/lang/tr_TR/validation.php @@ -1,9 +1,8 @@ . */ +declare(strict_types=1); + return [ 'iban' => 'Bu IBAN geçerli değilrdir.', 'source_equals_destination' => 'The source account equals the destination account', @@ -109,7 +110,8 @@ return [ 'in_array' => ':attribute alanı :other içinde olamaz.', 'present' => ':attribute alanı mevcut olmalıdır.', 'amount_zero' => 'Toplam tutar sıfır olamaz', - 'secure_password' => 'Güvenli bir şifre değildir. Lütfen tekrar deneyin. Daha fazla bilgi için https://goo.gl/NCh2tN adresini ziyaret edin', + 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', 'attributes' => [ 'email' => 'email address', 'description' => 'description', From a7b8470d9ea5f9e6c16320fbc9419fa9cef10967 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 22:04:52 +0200 Subject: [PATCH 168/182] Expand test coverage. --- app/Factory/TransactionFactory.php | 4 +- app/Helpers/Attachments/AttachmentHelper.php | 5 +- app/Http/Controllers/AccountController.php | 15 +- app/Http/Controllers/BudgetController.php | 1 + app/Http/Controllers/DebugController.php | 33 ++-- app/Http/Controllers/HomeController.php | 4 +- .../Controllers/Import/IndexController.php | 3 +- app/Http/Controllers/JavascriptController.php | 8 +- .../Json/AutoCompleteController.php | 29 ++-- app/Http/Controllers/ProfileController.php | 4 +- app/Http/Controllers/RuleController.php | 24 ++- app/Models/Rule.php | 1 + app/Models/RuleTrigger.php | 4 +- app/Models/Transaction.php | 1 + app/Models/TransactionCurrency.php | 1 + resources/views/debug.twig | 1 + .../Controllers/AccountControllerTest.php | 86 +++++++++- .../Controllers/AttachmentControllerTest.php | 20 ++- .../Controllers/BillControllerTest.php | 151 ++++++++++++++++-- .../Controllers/BudgetControllerTest.php | 125 ++++++++++++++- .../Chart/AccountControllerTest.php | 4 +- .../Import/IndexControllerTest.php | 37 +++++ .../Import/JobStatusControllerTest.php | 2 +- .../Controllers/JavascriptControllerTest.php | 36 ++++- .../Json/AutoCompleteControllerTest.php | 44 ++++- .../Controllers/Json/BoxControllerTest.php | 85 +++++++++- .../Controllers/NewUserControllerTest.php | 39 ++++- .../Controllers/PiggyBankControllerTest.php | 54 ++++--- .../Controllers/ProfileControllerTest.php | 111 ++++++++++++- .../Controllers/RuleControllerTest.php | 105 ++++++++---- .../Controllers/TransactionControllerTest.php | 87 +++++++--- 31 files changed, 951 insertions(+), 173 deletions(-) diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index eb2244ca15..c9c787d092 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -56,10 +56,10 @@ class TransactionFactory $currencyId = $data['currency_id'] ?? null; $currencyId = isset($data['currency']) ? $data['currency']->id : $currencyId; if ('' === $data['amount']) { - throw new FireflyException('Amount is an empty string, which Firefly III cannot handle. Apologies.'); + throw new FireflyException('Amount is an empty string, which Firefly III cannot handle. Apologies.'); // @codeCoverageIgnore } if (null === $currencyId) { - throw new FireflyException('Cannot store transaction without currency information.'); + throw new FireflyException('Cannot store transaction without currency information.'); // @codeCoverageIgnore } $data['foreign_amount'] = '' === (string)$data['foreign_amount'] ? null : $data['foreign_amount']; Log::debug(sprintf('Create transaction for account #%d ("%s") with amount %s', $data['account']->id, $data['account']->name, $data['amount'])); diff --git a/app/Helpers/Attachments/AttachmentHelper.php b/app/Helpers/Attachments/AttachmentHelper.php index 906b7e39fe..72a00bab0c 100644 --- a/app/Helpers/Attachments/AttachmentHelper.php +++ b/app/Helpers/Attachments/AttachmentHelper.php @@ -25,6 +25,7 @@ namespace FireflyIII\Helpers\Attachments; use Crypt; use FireflyIII\Models\Attachment; use Illuminate\Contracts\Encryption\DecryptException; +use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; @@ -66,6 +67,8 @@ class AttachmentHelper implements AttachmentHelperInterface } /** + * @codeCoverageIgnore + * * @param Attachment $attachment * * @return string @@ -74,7 +77,7 @@ class AttachmentHelper implements AttachmentHelperInterface { try { $content = Crypt::decrypt($this->uploadDisk->get(sprintf('at-%d.data', $attachment->id))); - } catch (DecryptException $e) { + } catch (DecryptException|FileNotFoundException $e) { Log::error(sprintf('Could not decrypt data of attachment #%d', $attachment->id)); return ''; diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index b132445c70..db68a5c841 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -210,18 +210,7 @@ class AccountController extends Controller $request->session()->flash('preFilled', $preFilled); - return view( - 'accounts.edit', - compact( - 'account', - 'currency', - 'subTitle', - 'subTitleIcon', - 'what', - 'roles', - 'preFilled' - ) - ); + return view('accounts.edit', compact('account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles', 'preFilled')); } /** @@ -340,7 +329,7 @@ class AccountController extends Controller public function showAll(Request $request, Account $account) { if (AccountType::INITIAL_BALANCE === $account->accountType->type) { - return $this->redirectToOriginalAccount($account); + return $this->redirectToOriginalAccount($account); // @codeCoverageIgnore } $end = new Carbon; $today = new Carbon; diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 59f1ec0071..ed199483d5 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -96,6 +96,7 @@ class BudgetController extends Controller // if today is between start and end, use the diff in days between end and today (days left) // otherwise, use diff between start and end. $today = new Carbon; + Log::debug(sprintf('Start is %s, end is %s, today is %s', $start->format('Y-m-d'), $end->format('Y-m-d'),$today->format('Y-m-d'))); if ($today->gte($start) && $today->lte($end)) { $days = $end->diffInDays($today); $daysInMonth = $start->diffInDays($today); diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 4a56acf427..11f52acf5c 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -66,6 +66,7 @@ class DebugController extends Controller $userAgent = $request->header('user-agent'); $isSandstorm = var_export(env('IS_SANDSTORM', 'unknown'), true); $isDocker = var_export(env('IS_DOCKER', 'unknown'), true); + $toSandbox = var_export(env('BUNQ_USE_SANDBOX', 'unknown'), true); $trustedProxies = env('TRUSTED_PROXIES', '(none)'); $displayErrors = ini_get('display_errors'); $errorReporting = $this->errorReporting((int)ini_get('error_reporting')); @@ -96,40 +97,24 @@ class DebugController extends Controller if (null !== $logFile) { try { $logContent = file_get_contents($logFile); + // @codeCoverageIgnoreStart } catch (Exception $e) { // don't care Log::debug(sprintf('Could not read log file. %s', $e->getMessage())); } + // @codeCoverageIgnoreEnd } } } // last few lines - $logContent = 'Truncated from this point <----|' . substr($logContent, -4096); + $logContent = 'Truncated from this point <----|' . substr($logContent, -8192); return view( - 'debug', - compact( - 'phpVersion', - 'extensions', 'localeAttempts', - 'appEnv', - 'appDebug', - 'appLog', - 'appLogLevel', - 'now', - 'packages', - 'drivers', - 'currentDriver', - 'userAgent', - 'displayErrors', - 'errorReporting', - 'phpOs', - 'interface', - 'logContent', - 'cacheDriver', - 'isDocker', - 'isSandstorm', - 'trustedProxies' - ) + 'debug', compact( + 'phpVersion', 'extensions', 'localeAttempts', 'appEnv', 'appDebug', 'appLog', 'appLogLevel', 'now', 'packages', 'drivers', 'currentDriver', + 'userAgent', 'displayErrors', 'errorReporting', 'phpOs', 'interface', 'logContent', 'cacheDriver', 'isDocker', 'isSandstorm', 'trustedProxies', + 'toSandbox' + ) ); } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index ba32b7e7ba..993d3273e5 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -133,10 +133,12 @@ class HomeController extends Controller Log::debug('Call twig:clean...'); try { Artisan::call('twig:clean'); + // @codeCoverageIgnoreStart } catch (Exception $e) { - // dont care + // don't care Log::debug('Called twig:clean.'); } + // @codeCoverageIgnoreEnd Log::debug('Call view:clear...'); Artisan::call('view:clear'); Log::debug('Done! Redirecting...'); diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index c416cb8c98..a6fe9d3167 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -156,8 +156,9 @@ class IndexController extends Controller public function download(ImportJob $job): LaravelResponse { Log::debug('Now in download()', ['job' => $job->key]); - $config = $job->configuration; + $config = $this->repository->getConfiguration($job); // This is CSV import specific: + $config['delimiter'] = $config['delimiter'] ?? ','; $config['delimiter'] = "\t" === $config['delimiter'] ? 'tab' : $config['delimiter']; // this prevents private information from escaping diff --git a/app/Http/Controllers/JavascriptController.php b/app/Http/Controllers/JavascriptController.php index d47992d9ed..0ebb2d5192 100644 --- a/app/Http/Controllers/JavascriptController.php +++ b/app/Http/Controllers/JavascriptController.php @@ -29,6 +29,7 @@ use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Http\Request; +use Illuminate\Http\Response; use Log; use Preferences; @@ -68,9 +69,9 @@ class JavascriptController extends Controller /** * @param CurrencyRepositoryInterface $repository * - * @return \Illuminate\Http\Response + * @return Response */ - public function currencies(CurrencyRepositoryInterface $repository) + public function currencies(CurrencyRepositoryInterface $repository): Response { $currencies = $repository->get(); $data = ['currencies' => []]; @@ -102,7 +103,8 @@ class JavascriptController extends Controller } /** @var TransactionCurrency $currency */ $currency = $currencyRepository->findNull($currencyId); - if (0 === $currencyId) { + if (null === $currency) { + /** @var TransactionCurrency $currency */ $currency = app('amount')->getDefaultCurrency(); } diff --git a/app/Http/Controllers/Json/AutoCompleteController.php b/app/Http/Controllers/Json/AutoCompleteController.php index b01069520e..6ff6c92e1d 100644 --- a/app/Http/Controllers/Json/AutoCompleteController.php +++ b/app/Http/Controllers/Json/AutoCompleteController.php @@ -35,6 +35,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Support\CacheProperties; +use Illuminate\Http\JsonResponse; /** * Class AutoCompleteController. @@ -47,7 +48,7 @@ class AutoCompleteController extends Controller * * @param AccountRepositoryInterface $repository * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function allAccounts(AccountRepositoryInterface $repository) { @@ -64,9 +65,9 @@ class AutoCompleteController extends Controller /** * @param JournalCollectorInterface $collector * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ - public function allTransactionJournals(JournalCollectorInterface $collector) + public function allTransactionJournals(JournalCollectorInterface $collector): JsonResponse { $collector->setLimit(250)->setPage(1); $return = array_unique($collector->getJournals()->pluck('description')->toArray()); @@ -80,9 +81,9 @@ class AutoCompleteController extends Controller * * @param BillRepositoryInterface $repository * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ - public function bills(BillRepositoryInterface $repository) + public function bills(BillRepositoryInterface $repository): JsonResponse { $return = array_unique( $repository->getActiveBills()->pluck('name')->toArray() @@ -95,7 +96,7 @@ class AutoCompleteController extends Controller /** * @param BudgetRepositoryInterface $repository * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function budgets(BudgetRepositoryInterface $repository) { @@ -110,7 +111,7 @@ class AutoCompleteController extends Controller * * @param CategoryRepositoryInterface $repository * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function categories(CategoryRepositoryInterface $repository) { @@ -123,7 +124,7 @@ class AutoCompleteController extends Controller /** * @param CurrencyRepositoryInterface $repository * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function currencyNames(CurrencyRepositoryInterface $repository) { @@ -138,7 +139,7 @@ class AutoCompleteController extends Controller * * @param AccountRepositoryInterface $repository * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function expenseAccounts(AccountRepositoryInterface $repository) { @@ -163,7 +164,7 @@ class AutoCompleteController extends Controller * @param JournalCollectorInterface $collector * @param TransactionJournal $except * - * @return \Illuminate\Http\JsonResponse|mixed + * @return JsonResponse|mixed */ public function journalsWithId(JournalCollectorInterface $collector, TransactionJournal $except) { @@ -195,7 +196,7 @@ class AutoCompleteController extends Controller /** * @param AccountRepositoryInterface $repository * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function revenueAccounts(AccountRepositoryInterface $repository) { @@ -220,7 +221,7 @@ class AutoCompleteController extends Controller * * @param TagRepositoryInterface $tagRepository * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function tags(TagRepositoryInterface $tagRepository) { @@ -234,7 +235,7 @@ class AutoCompleteController extends Controller * @param JournalCollectorInterface $collector * @param string $what * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function transactionJournals(JournalCollectorInterface $collector, string $what) { @@ -251,7 +252,7 @@ class AutoCompleteController extends Controller /** * @param JournalRepositoryInterface $repository * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function transactionTypes(JournalRepositoryInterface $repository) { diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 12c9923f6c..581f88b35d 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -424,7 +424,7 @@ class ProfileController extends Controller /** * */ - private function createOAuthKeys() + private function createOAuthKeys(): void { $rsa = new RSA(); $keys = $rsa->createKey(4096); @@ -437,11 +437,13 @@ class ProfileController extends Controller if (file_exists($publicKey) || file_exists($privateKey)) { return; } + // @codeCoverageIgnoreStart Log::alert('NO OAuth keys were found. They have been created.'); file_put_contents($publicKey, array_get($keys, 'publickey')); file_put_contents($privateKey, array_get($keys, 'privatekey')); } + // @codeCoverageIgnoreEnd /** * @return string diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php index 297768f9e1..98a781017c 100644 --- a/app/Http/Controllers/RuleController.php +++ b/app/Http/Controllers/RuleController.php @@ -352,12 +352,12 @@ class RuleController extends Controller // redirect to show bill. if ($request->get('return_to_bill') === 'true' && (int)$request->get('bill_id') > 0) { - return redirect(route('bills.show', [(int)$request->get('bill_id')])); + return redirect(route('bills.show', [(int)$request->get('bill_id')])); // @codeCoverageIgnore } // redirect to new bill creation. if ((int)$request->get('bill_id') > 0) { - return redirect($this->getPreviousUri('bills.create.uri')); + return redirect($this->getPreviousUri('bills.create.uri')); // @codeCoverageIgnore } @@ -403,10 +403,12 @@ class RuleController extends Controller $matcher->setTriggers($triggers); try { $matchingTransactions = $matcher->findTransactionsByTriggers(); + // @codeCoverageIgnoreStart } catch (FireflyException $exception) { Log::error(sprintf('Could not grab transactions in testTriggers(): %s', $exception->getMessage())); Log::error($exception->getTraceAsString()); } + // @codeCoverageIgnoreStart // Warn the user if only a subset of transactions is returned @@ -422,10 +424,12 @@ class RuleController extends Controller $view = 'ERROR, see logs.'; try { $view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render(); + // @codeCoverageIgnoreStart } catch (Throwable $exception) { Log::error(sprintf('Could not render view in testTriggers(): %s', $exception->getMessage())); Log::error($exception->getTraceAsString()); } + // @codeCoverageIgnoreEnd return response()->json(['html' => $view, 'warning' => $warning]); } @@ -461,10 +465,12 @@ class RuleController extends Controller $matcher->setRule($rule); try { $matchingTransactions = $matcher->findTransactionsByRule(); + // @codeCoverageIgnoreStart } catch (FireflyException $exception) { Log::error(sprintf('Could not grab transactions in testTriggersByRule(): %s', $exception->getMessage())); Log::error($exception->getTraceAsString()); } + // @codeCoverageIgnoreEnd // Warn the user if only a subset of transactions is returned $warning = ''; @@ -479,10 +485,12 @@ class RuleController extends Controller $view = 'ERROR, see logs.'; try { $view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render(); + // @codeCoverageIgnoreStart } catch (Throwable $exception) { Log::error(sprintf('Could not render view in testTriggersByRule(): %s', $exception->getMessage())); Log::error($exception->getTraceAsString()); } + // @codeCoverageIgnoreEnd return response()->json(['html' => $view, 'warning' => $warning]); } @@ -587,10 +595,12 @@ class RuleController extends Controller 'count' => 1, ] )->render(); + // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::error(sprintf('Throwable was thrown in getActionsForBill(): %s', $e->getMessage())); Log::error($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd return $actions; } @@ -620,10 +630,12 @@ class RuleController extends Controller 'count' => $count, ] )->render(); + // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::debug(sprintf('Throwable was thrown in getCurrentActions(): %s', $e->getMessage())); Log::error($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd ++$index; } @@ -655,10 +667,12 @@ class RuleController extends Controller 'count' => $count, ] )->render(); + // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::debug(sprintf('Throwable was thrown in getCurrentTriggers(): %s', $e->getMessage())); Log::error($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd ++$index; } } @@ -691,10 +705,12 @@ class RuleController extends Controller 'count' => $count, ] )->render(); + // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::debug(sprintf('Throwable was thrown in getPreviousActions(): %s', $e->getMessage())); Log::error($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd ++$newIndex; } @@ -727,10 +743,12 @@ class RuleController extends Controller 'count' => $count, ] )->render(); + // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::debug(sprintf('Throwable was thrown in getPreviousTriggers(): %s', $e->getMessage())); Log::error($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd ++$newIndex; } @@ -786,10 +804,12 @@ class RuleController extends Controller 'count' => 4, ] )->render(); + // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::debug(sprintf('Throwable was thrown in getTriggersForBill(): %s', $e->getMessage())); Log::debug($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd return $triggers; } diff --git a/app/Models/Rule.php b/app/Models/Rule.php index ac3cc0e992..e6e65ba73e 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -34,6 +34,7 @@ use FireflyIII\Models\RuleAction; * Class Rule. * @property bool $stop_processing * @property int $id + * @property \Illuminate\Support\Collection $ruleTriggers */ class Rule extends Model { diff --git a/app/Models/RuleTrigger.php b/app/Models/RuleTrigger.php index 3b68656a91..58265d0d1c 100644 --- a/app/Models/RuleTrigger.php +++ b/app/Models/RuleTrigger.php @@ -23,10 +23,12 @@ declare(strict_types=1); namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; -use FireflyIII\Models\Rule; /** * Class RuleTrigger. + * + * @property string $trigger_value + * @property string $trigger_type */ class RuleTrigger extends Model { diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index bdd5253e1c..b591f1d956 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -71,6 +71,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string $description * @property bool $is_split * @property int $attachmentCount + * @property int $transaction_currency_id */ class Transaction extends Model { diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index ef74e55f19..439962a257 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -31,6 +31,7 @@ use FireflyIII\Models\TransactionJournal; * Class TransactionCurrency. * * @property string $code + * @property int $decimal_places * */ class TransactionCurrency extends Model diff --git a/resources/views/debug.twig b/resources/views/debug.twig index 7b8c0a26cb..7d57662635 100644 --- a/resources/views/debug.twig +++ b/resources/views/debug.twig @@ -38,6 +38,7 @@ Debug information generated at {{ now }} for Firefly III version **{{ FF_VERSION {% if SANDSTORM == true %}| Sandstorm anon? | {% if SANDSTORM_ANON == true %}yes{% else %}no{% endif %} |{% endif %} | Is Sandstorm (.env) | {{ isSandstorm }} | | Is Docker (.env) | {{ isDocker }} | +| bunq uses sandbox | {{ toSandbox }} | | Trusted proxies (.env) | {{ trustedProxies }} | | User agent | {{ userAgent }} | | Loaded extensions | {{ extensions }} | diff --git a/tests/Feature/Controllers/AccountControllerTest.php b/tests/Feature/Controllers/AccountControllerTest.php index 290a766cc6..290b47e3b9 100644 --- a/tests/Feature/Controllers/AccountControllerTest.php +++ b/tests/Feature/Controllers/AccountControllerTest.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace Tests\Feature\Controllers; +use Amount; use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Account; @@ -159,6 +160,41 @@ class AccountControllerTest extends TestCase $response->assertSee($note->text); } + /** + * @covers \FireflyIII\Http\Controllers\AccountController + */ + public function testEditNull(): void + { + $note = new Note(); + $note->text = 'This is a test'; + // mock stuff + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $repository = $this->mock(CurrencyRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + Amount::shouldReceive('getDefaultCurrency')->andReturn(TransactionCurrency::find(2)); + $repository->shouldReceive('findNull')->once()->andReturn(null); + $repository->shouldReceive('get')->andReturn(new Collection); + $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); + $accountRepos->shouldReceive('getNote')->andReturn($note)->once(); + $accountRepos->shouldReceive('getOpeningBalanceAmount')->andReturnNull(); + $accountRepos->shouldReceive('getOpeningBalanceDate')->andReturnNull(); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1'); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'accountNumber'])->andReturn('123'); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'accountRole'])->andReturn('defaultAsset'); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'ccType'])->andReturn(''); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'ccMonthlyPaymentDate'])->andReturn(''); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'BIC'])->andReturn('BIC'); + + $this->be($this->user()); + $account = $this->user()->accounts()->where('account_type_id', 3)->whereNull('deleted_at')->first(); + $response = $this->get(route('accounts.edit', [$account->id])); + $response->assertStatus(200); + // has bread crumb + $response->assertSee('
    {{ trans('import.file_map_field_value') }}{{ trans('import.file_map_field_mapped_to') }}{{ trans('import.job_config_field_value') }}{{ trans('import.job_config_field_mapped') }}
    - {{ Form.select('mapping['~field.index~']['~option~']', + {{ Form.select('mapping['~index~']['~option~']', field.options, - job.configuration['column-mapping-config'][field.index][option], {class: 'form-control'}) }} + importJob.configuration['column-mapping-config'][index][option], {class: 'form-control'}) }}
    {{ 'target_amount'|_ }}{{ piggyBank.targetamount|formatAmount }} + {{ formatAmountBySymbol(piggy.target_amount, piggy.currency_symbol, piggy.currency_dp) }} +
    {{ 'saved_so_far'|_ }}{{ currentRelevantRepAmount(piggyBank)|formatAmount }} + {{ formatAmountBySymbol(piggy.current_amount, piggy.currency_symbol, piggy.currency_dp) }} +
    {{ 'left_to_save'|_ }}{{ (piggyBank.targetamount - currentRelevantRepAmount(piggyBank))|formatAmount }} + {{ formatAmountBySymbol(piggy.left_to_save, piggy.currency_symbol, piggy.currency_dp) }} +
    {{ 'start_date'|_ }}
    {{ 'suggested_amount'|_ }} - {{ suggestedMonthlyAmount(piggyBank)|formatAmount }} + {{ formatAmountBySymbol(piggy.save_per_month, piggy.currency_symbol, piggy.currency_dp) }} +
    From 0a007b1e6e8fe69b206f8e89b997b024b652834f Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 29 May 2018 06:30:25 +0200 Subject: [PATCH 132/182] Fix menu view. --- app/Http/Controllers/AccountController.php | 6 ++++-- app/Support/Twig/General.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 4c15c0406b..b132445c70 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -297,8 +297,10 @@ class AccountController extends Controller throw new FireflyException('End is after start!'); // @codeCoverageIgnore } + + $what = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type)); // used for menu $today = new Carbon; - $subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type); + $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type)); $page = (int)$request->get('page'); $pageSize = (int)Preferences::get('listPageSize', 50)->data; $currencyId = (int)$this->repository->getMetaValue($account, 'currency_id'); @@ -320,7 +322,7 @@ class AccountController extends Controller return view( 'accounts.show', - compact('account', 'showAll', 'currency', 'today', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', 'chartUri') + compact('account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', 'chartUri') ); } diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 66548bd11b..a2945e833d 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -106,7 +106,7 @@ class General extends Twig_Extension $what = $args[2]; // name of the route. $activeWhat = $context['what'] ?? false; - if ($what === $activeWhat && !(false === strpos(Route::getCurrentRoute()->getName(), $route))) { + if ($what === $activeWhat && !(false === stripos(Route::getCurrentRoute()->getName(), $route))) { return 'active'; } From 5b4967acb9ed931563ab057a6d1490504cd5ba19 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 29 May 2018 07:09:11 +0200 Subject: [PATCH 133/182] Changes in error handler --- app/Exceptions/Handler.php | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 020f131bea..caef99ffa1 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -131,14 +131,22 @@ class Handler extends ExceptionHandler $doMailError = env('SEND_ERROR_MESSAGE', true); if ( - ( - $exception instanceof FireflyException - || $exception instanceof ErrorException - || $exception instanceof OAuthServerException - || $exception instanceof AuthenticationException - ) - && $doMailError) { + // if the user wants us to mail: + $doMailError === true && + (( + // and if is one of these error instances + $exception instanceof FireflyException + || $exception instanceof ErrorException + || $exception instanceof OAuthServerException + ) + || ( + // or this one, but it's a JSON exception. + $exception instanceof AuthenticationException + && Request::acceptsJson() === true + )) + ) { + // then, send email $userData = [ 'id' => 0, 'email' => 'unknown@example.com', @@ -157,7 +165,7 @@ class Handler extends ExceptionHandler 'code' => $exception->getCode(), 'version' => config('firefly.version'), 'url' => Request::fullUrl(), - 'userAgent' => Request::userAgent(), + 'userAgent' => Request::userAgent(), 'json' => Request::acceptsJson(), ]; From 3de36901b8797ed153d15149999f19a0a4a9c5ba Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 29 May 2018 07:25:04 +0200 Subject: [PATCH 134/182] Fix #1425 --- app/Helpers/Collector/JournalCollector.php | 2 + app/Helpers/Filter/TransactionViewFilter.php | 80 +++++++++++++++++++ .../Transaction/MassController.php | 17 ++-- app/Models/Transaction.php | 7 +- .../Support/TransactionServiceTrait.php | 4 +- .../Internal/Update/JournalUpdateService.php | 10 ++- .../Update/TransactionUpdateService.php | 1 + app/Transformers/TransactionTransformer.php | 4 + resources/views/transactions/mass/edit.twig | 71 ++++++++-------- 9 files changed, 146 insertions(+), 50 deletions(-) create mode 100644 app/Helpers/Filter/TransactionViewFilter.php diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php index 79e69f923c..427da305d9 100644 --- a/app/Helpers/Collector/JournalCollector.php +++ b/app/Helpers/Collector/JournalCollector.php @@ -32,6 +32,7 @@ use FireflyIII\Helpers\Filter\NegativeAmountFilter; use FireflyIII\Helpers\Filter\OpposingAccountFilter; use FireflyIII\Helpers\Filter\PositiveAmountFilter; use FireflyIII\Helpers\Filter\SplitIndicatorFilter; +use FireflyIII\Helpers\Filter\TransactionViewFilter; use FireflyIII\Helpers\Filter\TransferFilter; use FireflyIII\Models\AccountType; use FireflyIII\Models\Budget; @@ -768,6 +769,7 @@ class JournalCollector implements JournalCollectorInterface NegativeAmountFilter::class => new NegativeAmountFilter, SplitIndicatorFilter::class => new SplitIndicatorFilter, CountAttachmentsFilter::class => new CountAttachmentsFilter, + TransactionViewFilter::class => new TransactionViewFilter, ]; Log::debug(sprintf('Will run %d filters on the set.', \count($this->filters))); foreach ($this->filters as $enabled) { diff --git a/app/Helpers/Filter/TransactionViewFilter.php b/app/Helpers/Filter/TransactionViewFilter.php new file mode 100644 index 0000000000..ea83a62e75 --- /dev/null +++ b/app/Helpers/Filter/TransactionViewFilter.php @@ -0,0 +1,80 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Helpers\Filter; + +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionType; +use Illuminate\Support\Collection; +use Log; + +/** + * Class TransactionViewFilter. + * + * This filter removes the entry with a negative amount when it's a withdrawal + * And the positive amount when it's a deposit or transfer + * + * This is used in the mass-edit routine. + * + */ +class TransactionViewFilter implements FilterInterface +{ + /** + * @param Collection $set + * + * @return Collection + */ + public function filter(Collection $set): Collection + { + return $set->filter( + function (Transaction $transaction) { + // remove if amount is less than zero and type is withdrawal. + if ($transaction->transaction_type_type === TransactionType::WITHDRAWAL && 1 === bccomp($transaction->transaction_amount, '0')) { + Log::debug( + sprintf( + 'Filtered #%d because amount is %f and type is %s.', $transaction->id, $transaction->transaction_amount, + $transaction->transaction_type_type + ) + ); + + return null; + } + + if ($transaction->transaction_type_type === TransactionType::DEPOSIT && -1 === bccomp($transaction->transaction_amount, '0')) { + Log::debug( + sprintf( + 'Filtered #%d because amount is %f and type is %s.', $transaction->id, $transaction->transaction_amount, + $transaction->transaction_type_type + ) + ); + + return null; + } + Log::debug( + sprintf('#%d: amount is %f and type is %s.', $transaction->id, $transaction->transaction_amount, $transaction->transaction_type_type) + ); + + return $transaction; + } + ); + } +} diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index e404300f61..8b78a9ccb9 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -25,6 +25,7 @@ namespace FireflyIII\Http\Controllers\Transaction; use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Filter\NegativeAmountFilter; +use FireflyIII\Helpers\Filter\TransactionViewFilter; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\MassDeleteJournalRequest; use FireflyIII\Http\Requests\MassEditJournalRequest; @@ -145,22 +146,24 @@ class MassController extends Controller $collector->setUser(auth()->user()); $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); $collector->setJournals($journals); - $collector->addFilter(NegativeAmountFilter::class); - $transactions = $collector->getJournals(); + $collector->addFilter(TransactionViewFilter::class); + $collection = $collector->getJournals(); // add some filters: // transform to array - $journals = $transactions->map( + $transactions = $collection->map( function (Transaction $transaction) use ($transformer) { - $result = $transformer->transform($transaction); - - return $result; + $transaction= $transformer->transform($transaction); + // make sure amount is positive: + $transaction['amount'] = app('steam')->positive((string)$transaction['amount']); + $transaction['foreign_amount'] = app('steam')->positive((string)$transaction['foreign_amount']); + return $transaction; } ); - return view('transactions.mass.edit', compact('journals', 'subTitle', 'accounts', 'budgets')); + return view('transactions.mass.edit', compact('transactions', 'subTitle', 'accounts', 'budgets')); } /** diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 14ed95095b..bdd5253e1c 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -27,11 +27,6 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Models\Category; -use FireflyIII\Models\Budget; -use FireflyIII\Models\Account; /** * Class Transaction. @@ -49,12 +44,14 @@ use FireflyIII\Models\Account; * @property string $account_iban * @property string $account_number * @property string $account_bic + * @property string $account_type * @property string $account_currency_code * @property int $opposing_account_id * @property string $opposing_account_name * @property string $opposing_account_iban * @property string $opposing_account_number * @property string $opposing_account_bic + * @property string $opposing_account_type * @property string $opposing_currency_code * @property int $transaction_budget_id * @property string $transaction_budget_name diff --git a/app/Services/Internal/Support/TransactionServiceTrait.php b/app/Services/Internal/Support/TransactionServiceTrait.php index 3c02b991cd..06fe1fe99c 100644 --- a/app/Services/Internal/Support/TransactionServiceTrait.php +++ b/app/Services/Internal/Support/TransactionServiceTrait.php @@ -99,9 +99,9 @@ trait TransactionServiceTrait * @param int|null $accountId * @param string|null $accountName * - * @return Account + * @return Account|null */ - public function findAccount(?string $expectedType, ?int $accountId, ?string $accountName): Account + public function findAccount(?string $expectedType, ?int $accountId, ?string $accountName): ?Account { $accountId = (int)$accountId; $accountName = (string)$accountName; diff --git a/app/Services/Internal/Update/JournalUpdateService.php b/app/Services/Internal/Update/JournalUpdateService.php index 3964818fd5..8dde0112c2 100644 --- a/app/Services/Internal/Update/JournalUpdateService.php +++ b/app/Services/Internal/Update/JournalUpdateService.php @@ -57,7 +57,7 @@ class JournalUpdateService $service = app(TransactionUpdateService::class); $service->setUser($journal->user); - // create transactions + // create transactions: /** @var TransactionFactory $factory */ $factory = app(TransactionFactory::class); $factory->setUser($journal->user); @@ -105,6 +105,12 @@ class JournalUpdateService // connect tags: $this->connectTags($journal, $data); + // remove category from journal: + $journal->categories()->sync([]); + + // remove budgets from journal: + $journal->budgets()->sync([]); + // update or create custom fields: // store date meta fields (if present): $this->storeMeta($journal, $data, 'interest_date'); @@ -162,6 +168,8 @@ class JournalUpdateService foreach ($journal->transactions as $transaction) { $service->updateCategory($transaction, $category); } + // make journal empty: + $journal->categories()->sync([]); return $journal; } diff --git a/app/Services/Internal/Update/TransactionUpdateService.php b/app/Services/Internal/Update/TransactionUpdateService.php index c2f94d08e3..ee8bcaf9d9 100644 --- a/app/Services/Internal/Update/TransactionUpdateService.php +++ b/app/Services/Internal/Update/TransactionUpdateService.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Services\Internal\Update; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Transaction; use FireflyIII\Services\Internal\Support\TransactionServiceTrait; use FireflyIII\User; diff --git a/app/Transformers/TransactionTransformer.php b/app/Transformers/TransactionTransformer.php index 1956162c06..60a5f75f16 100644 --- a/app/Transformers/TransactionTransformer.php +++ b/app/Transformers/TransactionTransformer.php @@ -31,6 +31,7 @@ use FireflyIII\Models\TransactionType; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\TransformerAbstract; +use Log; use Symfony\Component\HttpFoundation\ParameterBag; /** @@ -214,6 +215,7 @@ class TransactionTransformer extends TransformerAbstract // switch on type for consistency switch ($transaction->transaction_type_type) { case TransactionType::WITHDRAWAL: + Log::debug(sprintf('%d is a withdrawal', $transaction->journal_id)); $data['source_id'] = $transaction->account_id; $data['source_name'] = $transaction->account_name; $data['source_iban'] = $transaction->account_iban; @@ -222,6 +224,8 @@ class TransactionTransformer extends TransformerAbstract $data['destination_name'] = $transaction->opposing_account_name; $data['destination_iban'] = $transaction->opposing_account_iban; $data['destination_type'] = $transaction->opposing_account_type; + Log::debug(sprintf('source_id / account_id is %d', $transaction->account_id)); + Log::debug(sprintf('source_name / account_name is "%s"', $transaction->account_name)); break; case TransactionType::DEPOSIT: case TransactionType::TRANSFER: diff --git a/resources/views/transactions/mass/edit.twig b/resources/views/transactions/mass/edit.twig index 1da5e566db..d6fcaff658 100644 --- a/resources/views/transactions/mass/edit.twig +++ b/resources/views/transactions/mass/edit.twig @@ -1,7 +1,7 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.render(Route.getCurrentRoute.getName, journals) }} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, transactions) }} {% endblock %} {% block content %} @@ -29,90 +29,91 @@ {{ trans('list.category') }} {{ trans('list.budget') }}
    {# LINK TO EDIT FORM #} - - + {# DESCRIPTION #} + placeholder="{{ transaction.description }}" name="description[{{ transaction.journal_id }}]" + type="text" value="{{ transaction.description }}">
    - {{ journal.currency_symbol }} - - + {{ transaction.currency_symbol }} + +
    - {% if journal.foreign_amount %} + {% if transaction.foreign_amount %} {# insert foreign data #}
    - {{ journal.foreign_currency.symbol }} - - + {{ transaction.foreign_currency_symbol }} + +
    {% endif %}
    {# DATE #} + name="date[{{ transaction.journal_id }}]" type="date" value="{{ transaction.date }}"> {# SOURCE ACCOUNT ID FOR TRANSFER OR WITHDRAWAL #} - {% if journal.transaction_type_type == 'Transfer' or journal.transaction_type_type == 'Withdrawal' %} - {% for account in accounts %} - + + {% endfor %} {% else %} {# SOURCE ACCOUNT NAME FOR DEPOSIT #} - + {% endif %} - {% if journal.transaction_type_type == 'Transfer' or journal.transaction_type_type == 'Deposit' %} + {% if transaction.type == 'Transfer' or transaction.type == 'Deposit' %} {# DESTINATION ACCOUNT NAME FOR TRANSFER AND DEPOSIT #} - {% for account in accounts %} - {% endfor %} {% else %} + {# DESTINATION ACCOUNT NAME FOR EXPENSE #} - + {% endif %} - + - {% if journal.transaction_type_type == 'Withdrawal' %} - {% for budget in budgets %} - {% endfor %} From 10abd7b0aebb1773043a4556a862ff71c2d61803 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 29 May 2018 18:31:48 +0200 Subject: [PATCH 135/182] Delete account meta data when field is made empty. --- app/Services/Internal/Support/AccountServiceTrait.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Services/Internal/Support/AccountServiceTrait.php b/app/Services/Internal/Support/AccountServiceTrait.php index e4a48fced5..a32fb802f9 100644 --- a/app/Services/Internal/Support/AccountServiceTrait.php +++ b/app/Services/Internal/Support/AccountServiceTrait.php @@ -346,6 +346,9 @@ trait AccountServiceTrait Log::debug(sprintf('Updated meta-field "%s":"%s" for #%d ("%s") ', $field, $data[$field], $account->id, $account->name)); } } + if (null !== $entry && isset($data[$field]) && \strlen((string)$data[$field]) === 0) { + $entry->delete(); + } } } From fbb9d7c6b4c908f56e96666f9d42b8bfb7d01f66 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 29 May 2018 18:33:43 +0200 Subject: [PATCH 136/182] It's about expecting JSON, not accepting it. --- app/Exceptions/Handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index caef99ffa1..d66d42a274 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -143,7 +143,7 @@ class Handler extends ExceptionHandler || ( // or this one, but it's a JSON exception. $exception instanceof AuthenticationException - && Request::acceptsJson() === true + && Request::expectsJson() === true )) ) { // then, send email From e9e771e57bc6e3ef0aa87b0fe1a2aa74f13d865e Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 30 May 2018 18:04:00 +0200 Subject: [PATCH 137/182] List 100 entries for bunq. #1443 --- app/Support/Import/Routine/Bunq/StageImportDataHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Support/Import/Routine/Bunq/StageImportDataHandler.php b/app/Support/Import/Routine/Bunq/StageImportDataHandler.php index cba91d0bd1..24ec36257a 100644 --- a/app/Support/Import/Routine/Bunq/StageImportDataHandler.php +++ b/app/Support/Import/Routine/Bunq/StageImportDataHandler.php @@ -284,7 +284,7 @@ class StageImportDataHandler // make request: /** @var Payment $paymentRequest */ $paymentRequest = app(Payment::class); - $result = $paymentRequest->listing($bunqAccountId); + $result = $paymentRequest->listing($bunqAccountId, ['count' => 100]); // loop result: /** @var BunqPayment $payment */ foreach ($result->getValue() as $payment) { From f263795a99460317d4307dd55a6614f385b2402d Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 30 May 2018 18:04:23 +0200 Subject: [PATCH 138/182] Add config entries for tests --- config/import.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/import.php b/config/import.php index 17cfde17f9..ed49fa8417 100644 --- a/config/import.php +++ b/config/import.php @@ -45,6 +45,7 @@ return [ 'plaid' => false, 'quovo' => false, 'yodlee' => false, + 'bad' => false, // always disabled ], // demo user can use these import providers (when enabled): 'allowed_for_demo' => [ @@ -87,7 +88,7 @@ return [ 'yodlee' => false, ], // some providers may need extra configuration per job - 'has_job_config' => [ + 'has_job_config' => [ 'fake' => true, 'file' => true, 'bunq' => true, From c339a183b9a2aacd52086e658624c6f37fbdbc0f Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 30 May 2018 18:04:43 +0200 Subject: [PATCH 139/182] Fix code coverage and a test #1443 --- .../Controllers/Import/IndexController.php | 8 +- .../Import/IndexControllerTest.php | 129 +++++++++++++++--- 2 files changed, 119 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index 567c386911..8d471992a5 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -71,7 +71,7 @@ class IndexController extends Controller */ public function create(string $importProvider) { - Log::debug(sprintf('Will create job for provider %s', $importProvider)); + Log::debug(sprintf('Will create job for provider "%s"', $importProvider)); // can only create "fake" for demo user. $providers = array_keys($this->getProviders()); if (!\in_array($importProvider, $providers, true)) { @@ -91,11 +91,13 @@ class IndexController extends Controller Log::debug('Provider has no prerequisites. Continue.'); // if job provider also has no configuration: if ($hasConfig === false) { + // @codeCoverageIgnoreStart Log::debug('Provider needs no configuration for job. Job is ready to start.'); $this->repository->updateStatus($importJob, 'ready_to_run'); Log::debug('Redirect to status-page.'); return redirect(route('import.job.status.index', [$importJob->key])); + // @codeCoverageIgnoreEnd } // update job to say "has_prereq". @@ -127,11 +129,13 @@ class IndexController extends Controller // update job to say "has_prereq". $this->repository->setStatus($importJob, 'has_prereq'); if ($hasConfig === false) { + // @codeCoverageIgnoreStart Log::debug('Provider has no configuration. Job is ready to start.'); $this->repository->updateStatus($importJob, 'ready_to_run'); Log::debug('Redirect to status-page.'); return redirect(route('import.job.status.index', [$importJob->key])); + // @codeCoverageIgnoreEnd } Log::debug('Job has configuration. Redirect to job-config.'); // Otherwise just redirect to job configuration. @@ -182,7 +186,7 @@ class IndexController extends Controller } if ($isDemoUser === false && $allowedForUser === false && $isDebug === false) { //Log::debug('User is not demo and this provider is not allowed for such users. NEXT!'); - continue; + continue; // @codeCoverageIgnore } $providers[$providerName] = [ diff --git a/tests/Feature/Controllers/Import/IndexControllerTest.php b/tests/Feature/Controllers/Import/IndexControllerTest.php index 05d9932c44..c053ed74c4 100644 --- a/tests/Feature/Controllers/Import/IndexControllerTest.php +++ b/tests/Feature/Controllers/Import/IndexControllerTest.php @@ -22,7 +22,10 @@ declare(strict_types=1); namespace Tests\Feature\Controllers\Import; +use FireflyIII\Import\Prerequisites\BunqPrerequisites; use FireflyIII\Import\Prerequisites\FakePrerequisites; +use FireflyIII\Import\Prerequisites\FilePrerequisites; +use FireflyIII\Import\Prerequisites\SpectrePrerequisites; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; @@ -51,20 +54,58 @@ class IndexControllerTest extends TestCase /** * @covers \FireflyIII\Http\Controllers\Import\IndexController */ - public function testCreateFake(): void + public function testCreateBadJob(): void { // mock stuff: - $repository = $this->mock(ImportJobRepositoryInterface::class); - $userRepository = $this->mock(UserRepositoryInterface::class); - $fakePrerequisites = $this->mock(FakePrerequisites::class); + $repository = $this->mock(ImportJobRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + $fakePrerequisites = $this->mock(FakePrerequisites::class); + $bunqPrerequisites = $this->mock(BunqPrerequisites::class); + $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); // fake job: $importJob = new ImportJob; $importJob->provider = 'fake'; $importJob->key = 'fake_job_1'; - // mock call: - $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(),'demo'])->andReturn(true)->once(); + // mock calls: + $fakePrerequisites->shouldReceive('setUser')->once(); + $bunqPrerequisites->shouldReceive('setUser')->once(); + $spectrePrerequisites->shouldReceive('setUser')->once(); + $fakePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $bunqPrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $spectrePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + + $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(false)->once(); + + $this->be($this->user()); + $response = $this->get(route('import.create', ['bad'])); + $response->assertStatus(302); + // expect a redirect to index + $response->assertSessionHas('warning', 'Firefly III cannot create a job for the "bad"-provider.'); + $response->assertRedirect(route('import.index')); + } + + /** + * @covers \FireflyIII\Http\Controllers\Import\IndexController + */ + public function testCreateFake(): void + { + // mock stuff: + $repository = $this->mock(ImportJobRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + $fakePrerequisites = $this->mock(FakePrerequisites::class); + $bunqPrerequisites = $this->mock(BunqPrerequisites::class); + $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); + $filePrerequisites = $this->mock(FilePrerequisites::class); + + // fake job: + $importJob = new ImportJob; + $importJob->provider = 'fake'; + $importJob->key = 'fake_job_1'; + + // mock calls + $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(true)->once(); $repository->shouldReceive('create')->withArgs(['fake'])->andReturn($importJob); $fakePrerequisites->shouldReceive('isComplete')->twice()->andReturn(false); $fakePrerequisites->shouldReceive('setUser')->twice(); @@ -77,16 +118,18 @@ class IndexControllerTest extends TestCase $response->assertRedirect(route('import.prerequisites.index', ['fake', 'fake_job_1'])); } - /** * @covers \FireflyIII\Http\Controllers\Import\IndexController */ public function testCreateFakeNoPrereq(): void { // mock stuff: - $repository = $this->mock(ImportJobRepositoryInterface::class); - $fakePrerequisites = $this->mock(FakePrerequisites::class); - $userRepository = $this->mock(UserRepositoryInterface::class); + $repository = $this->mock(ImportJobRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + $fakePrerequisites = $this->mock(FakePrerequisites::class); + $bunqPrerequisites = $this->mock(BunqPrerequisites::class); + $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); + $filePrerequisites = $this->mock(FilePrerequisites::class); // fake job: $importJob = new ImportJob; @@ -94,7 +137,7 @@ class IndexControllerTest extends TestCase $importJob->key = 'fake_job_2'; // mock call: - $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(),'demo'])->andReturn(true)->once(); + $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(true)->once(); $repository->shouldReceive('create')->withArgs(['fake'])->andReturn($importJob); $fakePrerequisites->shouldReceive('isComplete')->twice()->andReturn(true); $fakePrerequisites->shouldReceive('setUser')->twice(); @@ -108,6 +151,46 @@ class IndexControllerTest extends TestCase $response->assertRedirect(route('import.job.configuration.index', ['fake_job_2'])); } + /** + * @covers \FireflyIII\Http\Controllers\Import\IndexController + */ + public function testCreateFileHasNoPrereq(): void + { + // mock stuff: + $repository = $this->mock(ImportJobRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + $fakePrerequisites = $this->mock(FakePrerequisites::class); + $bunqPrerequisites = $this->mock(BunqPrerequisites::class); + $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); + $filePrerequisites = $this->mock(FilePrerequisites::class); + + // fake job: + $importJob = new ImportJob; + $importJob->provider = 'file'; + $importJob->key = 'file_job_1'; + + // mock calls + $fakePrerequisites->shouldReceive('setUser')->once(); + $bunqPrerequisites->shouldReceive('setUser')->once(); + $spectrePrerequisites->shouldReceive('setUser')->once(); + + $fakePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $bunqPrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $spectrePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + + $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(false)->once(); + $repository->shouldReceive('create')->withArgs(['file'])->andReturn($importJob); + + $repository->shouldReceive('setStatus')->withArgs([Mockery::any(), 'has_prereq'])->andReturn($importJob)->once(); + + + $this->be($this->user()); + $response = $this->get(route('import.create', ['file'])); + $response->assertStatus(302); + // expect a redirect to prerequisites + $response->assertRedirect(route('import.job.configuration.index', ['file_job_1'])); + } + /** * @covers \FireflyIII\Http\Controllers\Import\IndexController */ @@ -116,11 +199,21 @@ class IndexControllerTest extends TestCase $this->be($this->user()); // fake stuff: - $userRepository = $this->mock(UserRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + $fakePrerequisites = $this->mock(FakePrerequisites::class); + $bunqPrerequisites = $this->mock(BunqPrerequisites::class); + $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); + $filePrerequisites = $this->mock(FilePrerequisites::class); // call methods: $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(false); + $fakePrerequisites->shouldReceive('setUser')->once(); + $bunqPrerequisites->shouldReceive('setUser')->once(); + $spectrePrerequisites->shouldReceive('setUser')->once(); + $fakePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $bunqPrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $spectrePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); $response = $this->get(route('import.index')); $response->assertStatus(200); @@ -135,12 +228,16 @@ class IndexControllerTest extends TestCase $this->be($this->user()); // fake stuff: - $fake = $this->mock(FakePrerequisites::class); - $userRepository = $this->mock(UserRepositoryInterface::class); + $fakePrerequisites = $this->mock(FakePrerequisites::class); + $bunqPrerequisites = $this->mock(BunqPrerequisites::class); + $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); + $filePrerequisites = $this->mock(FilePrerequisites::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + // call methods: - $fake->shouldReceive('setUser')->once(); - $fake->shouldReceive('isComplete')->once()->andReturn(true); + $fakePrerequisites->shouldReceive('setUser')->once(); + $fakePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(true); From dc77d8edda3616ef714067938e650946870a53e8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 30 May 2018 18:04:53 +0200 Subject: [PATCH 140/182] Extra upgrade instructions. --- config/upgrade.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/upgrade.php b/config/upgrade.php index 5348b1c26c..58def7e7f9 100644 --- a/config/upgrade.php +++ b/config/upgrade.php @@ -29,12 +29,15 @@ return [ '4.6.3' => 'This will be the last version to require PHP7.0. Future versions will require PHP7.1 minimum.', '4.6.4' => 'This version of Firefly III requires PHP7.1.', '4.7.3' => 'This version of Firefly III handles bills differently. See http://bit.ly/FF3-new-bills for more information.', + '4.7.4' => 'This version of Firefly III has a new import routine. See http://bit.ly/FF3-new-import for more information.', ], 'install' => [ '4.3' => 'Welcome to Firefly! Make sure you follow the installation guide. If you need more help, please check Github or the Firefly III website. The installation guide has a FAQ which you should check out as well.', '4.6.3' => 'This will be the last version to require PHP7.0. Future versions will require PHP7.1 minimum.', '4.6.4' => 'This version of Firefly III requires PHP7.1.', + '4.7.3' => 'This version of Firefly III handles bills differently. See http://bit.ly/FF3-new-bills for more information.', + '4.7.4' => 'This version of Firefly III has a new import routine. See http://bit.ly/FF3-new-import for more information.', ], ], ]; From b33ca786ae4d0978a8ab43d366ab0a33f55eacf1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 30 May 2018 18:05:06 +0200 Subject: [PATCH 141/182] use cases for rules tested. --- .../Actions/SetDestinationAccount.php | 5 ++++- .../Factory/TransactionCurrencyFactoryTest.php | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/TransactionRules/Actions/SetDestinationAccount.php b/app/TransactionRules/Actions/SetDestinationAccount.php index 2ba82a4c7e..1ad5fb1a46 100644 --- a/app/TransactionRules/Actions/SetDestinationAccount.php +++ b/app/TransactionRules/Actions/SetDestinationAccount.php @@ -102,6 +102,9 @@ class SetDestinationAccount implements ActionInterface // update destination transaction with new destination account: // get destination transaction: $transaction = $journal->transactions()->where('amount', '>', 0)->first(); + if(null === $transaction) { + return true; + } $transaction->account_id = $this->newDestinationAccount->id; $transaction->save(); $journal->touch(); @@ -131,7 +134,7 @@ class SetDestinationAccount implements ActionInterface /** * */ - private function findExpenseAccount() + private function findExpenseAccount(): void { $account = $this->repository->findByName($this->action->action_value, [AccountType::EXPENSE]); if (null === $account) { diff --git a/tests/Unit/Factory/TransactionCurrencyFactoryTest.php b/tests/Unit/Factory/TransactionCurrencyFactoryTest.php index 5c3a6e45ea..2805aba8cc 100644 --- a/tests/Unit/Factory/TransactionCurrencyFactoryTest.php +++ b/tests/Unit/Factory/TransactionCurrencyFactoryTest.php @@ -92,6 +92,20 @@ class TransactionCurrencyFactoryTest extends TestCase $this->assertEquals($currency->id, $result->id); } + /** + * submit ID = 1000 + * + * @covers \FireflyIII\Factory\TransactionCurrencyFactory + */ + public function testFindByBadID(): void + { + $currency = TransactionCurrency::inRandomOrder()->whereNull('deleted_at')->first(); + /** @var TransactionCurrencyFactory $factory */ + $factory = app(TransactionCurrencyFactory::class); + $result = $factory->find(1000, $currency->code); + $this->assertEquals($currency->id, $result->id); + } + /** * @covers \FireflyIII\Factory\TransactionCurrencyFactory */ From 49e302e1bcf7b906056d87dc8fe3e2692fd87916 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 30 May 2018 18:36:21 +0200 Subject: [PATCH 142/182] Reinstate ability to download config. --- .../Controllers/Import/IndexController.php | 34 +++++++++++++++++++ .../Import/JobStatusController.php | 28 ++++++++++----- public/js/ff/import/status_v2.js | 5 +-- resources/lang/en_US/import.php | 2 ++ resources/views/import/status.twig | 9 +++-- routes/web.php | 3 ++ 6 files changed, 68 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index 8d471992a5..c416cb8c98 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -25,8 +25,10 @@ namespace FireflyIII\Http\Controllers\Import; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Import\Prerequisites\PrerequisitesInterface; +use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; +use Illuminate\Http\Response as LaravelResponse; use Log; use View; @@ -138,11 +140,43 @@ class IndexController extends Controller // @codeCoverageIgnoreEnd } Log::debug('Job has configuration. Redirect to job-config.'); + // Otherwise just redirect to job configuration. return redirect(route('import.job.configuration.index', [$importJob->key])); } + /** + * Generate a JSON file of the job's configuration and send it to the user. + * + * @param ImportJob $job + * + * @return LaravelResponse + */ + public function download(ImportJob $job): LaravelResponse + { + Log::debug('Now in download()', ['job' => $job->key]); + $config = $job->configuration; + // This is CSV import specific: + $config['delimiter'] = "\t" === $config['delimiter'] ? 'tab' : $config['delimiter']; + + // this prevents private information from escaping + $config['column-mapping-config'] = []; + $result = json_encode($config, JSON_PRETTY_PRINT); + $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); + /** @var LaravelResponse $response */ + $response = response($result, 200); + $response->header('Content-disposition', 'attachment; filename=' . $name) + ->header('Content-Type', 'application/json') + ->header('Content-Description', 'File Transfer') + ->header('Connection', 'Keep-Alive') + ->header('Expires', '0') + ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->header('Pragma', 'public') + ->header('Content-Length', \strlen($result)); + + return $response; + } /** * General import index. diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index 3e7e7258ff..0fbe1884c9 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -80,13 +80,23 @@ class JobStatusController extends Controller { $count = \count($importJob->transactions); $json = [ - 'status' => $importJob->status, - 'errors' => $importJob->errors, - 'count' => $count, - 'tag_id' => $importJob->tag_id, - 'tag_name' => null === $importJob->tag_id ? null : $importJob->tag->tag, - 'report_txt' => trans('import.unknown_import_result'), + 'status' => $importJob->status, + 'errors' => $importJob->errors, + 'count' => $count, + 'tag_id' => $importJob->tag_id, + 'tag_name' => null === $importJob->tag_id ? null : $importJob->tag->tag, + 'report_txt' => trans('import.unknown_import_result'), + 'download_config' => false, + 'download_config_text' => '', ]; + + if ($importJob->provider === 'file') { + $json['download_config'] = true; + $json['download_config_text'] + = trans('import.should_download_config', ['route' => route('import.job.download', [$importJob->key])]) . ' ' + . trans('import.share_config_file'); + } + // if count is zero: if (null !== $importJob->tag_id) { $count = $importJob->tag->transactionJournals->count(); @@ -114,13 +124,15 @@ class JobStatusController extends Controller public function start(ImportJob $importJob): JsonResponse { // catch impossible status: - $allowed = ['ready_to_run', 'need_job_config','error']; // todo remove error + $allowed = ['ready_to_run', 'need_job_config', 'error']; // todo remove error if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { Log::error('Job is not ready.'); $this->repository->setStatus($importJob, 'error'); - return response()->json(['status' => 'NOK', 'message' => sprintf('JobStatusController::start expects status "ready_to_run" instead of "%s".', $importJob->status)]); + return response()->json( + ['status' => 'NOK', 'message' => sprintf('JobStatusController::start expects status "ready_to_run" instead of "%s".', $importJob->status)] + ); } $importProvider = $importJob->provider; $key = sprintf('import.routine.%s', $importProvider); diff --git a/public/js/ff/import/status_v2.js b/public/js/ff/import/status_v2.js index b5d230eee4..d5cafb34a7 100644 --- a/public/js/ff/import/status_v2.js +++ b/public/js/ff/import/status_v2.js @@ -113,8 +113,9 @@ function showJobResults(data) { console.error(element); $('#import-status-errors').append($('
  • ').text(element)); }); - - + } + if(data.download_config) { + $('#import-status-download').append($('').html(data.download_config_text)); } // show success box. diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 5cedcebb1f..feb4f58645 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -131,6 +131,8 @@ return [ 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/views/import/status.twig b/resources/views/import/status.twig index 4cc0744180..900c3a9de1 100644 --- a/resources/views/import/status.twig +++ b/resources/views/import/status.twig @@ -139,6 +139,9 @@

    {{ trans('import.status_finished_text') }} +

    +

    +

      @@ -176,9 +179,9 @@ var jobStorageStartUri = '{{ route('import.job.store', [importJob.key]) }}'; // import is running: - var langImportRunning = '{{ trans('import.status_job_running') }}'; - var langImportStoring = '{{ trans('import.status_job_storing') }}'; - var langImportRules = '{{ trans('import.status_job_rules') }}'; + var langImportRunning = '{{ trans('import.status_job_running')|escape('js') }}'; + var langImportStoring = '{{ trans('import.status_job_storing')|escape('js') }}'; + var langImportRules = '{{ trans('import.status_job_rules')|escape('js') }}'; // some useful translations. {#var langImportTimeOutError = '(time out thing)';#} diff --git a/routes/web.php b/routes/web.php index b42c46a96c..7c98292c2a 100755 --- a/routes/web.php +++ b/routes/web.php @@ -464,6 +464,9 @@ Route::group( Route::any('job/start/{importJob}', ['uses' => 'Import\JobStatusController@start', 'as' => 'job.start']); Route::any('job/store/{importJob}', ['uses' => 'Import\JobStatusController@store', 'as' => 'job.store']); + // download config: + Route::get('download/{importJob}', ['uses' => 'Import\IndexController@download', 'as' => 'job.download']); + // import method prerequisites: # # From 68e7d45f63beeaf8b05eb08001491ff135306b4c Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 30 May 2018 18:38:39 +0200 Subject: [PATCH 143/182] Fix #1451 --- app/Http/Controllers/BudgetController.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index c1b6faa275..59f1ec0071 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -243,6 +243,17 @@ class BudgetController extends Controller $days = 0; $daysInMonth = 0; + // make date if present: + if (null !== $moment || '' !== (string)$moment) { + try { + $start = new Carbon($moment); + $end = app('navigation')->endOfPeriod($start, $range); + } catch (Exception $e) { + // start and end are already defined. + Log::debug('start and end are already defined.'); + } + } + // if today is between start and end, use the diff in days between end and today (days left) // otherwise, use diff between start and end. $today = new Carbon; @@ -257,16 +268,7 @@ class BudgetController extends Controller $days = $days === 0 ? 1 : $days; $daysInMonth = $daysInMonth === 0 ? 1 : $daysInMonth; - // make date if present: - if (null !== $moment || '' !== (string)$moment) { - try { - $start = new Carbon($moment); - $end = app('navigation')->endOfPeriod($start, $range); - } catch (Exception $e) { - // start and end are already defined. - Log::debug('start and end are already defined.'); - } - } + $next = clone $end; $next->addDay(); $prev = clone $start; From fb07c681329e48c24815e5bc9a12976c28477d0e Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 31 May 2018 20:04:19 +0200 Subject: [PATCH 144/182] Updated tests --- tests/Feature/Controllers/PiggyBankControllerTest.php | 6 ++++++ tests/Unit/Transformers/PiggyBankTransformerTest.php | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/tests/Feature/Controllers/PiggyBankControllerTest.php b/tests/Feature/Controllers/PiggyBankControllerTest.php index 16565539bf..8ee5612be5 100644 --- a/tests/Feature/Controllers/PiggyBankControllerTest.php +++ b/tests/Feature/Controllers/PiggyBankControllerTest.php @@ -205,6 +205,7 @@ class PiggyBankControllerTest extends TestCase $repository->shouldReceive('getCurrentAmount')->andReturn('10'); $repository->shouldReceive('setUser'); $repository->shouldReceive('correctOrder'); + $repository->shouldReceive('getSuggestedMonthlyAmount')->andReturn('1'); Steam::shouldReceive('balance')->twice()->andReturn('1'); @@ -332,8 +333,13 @@ class PiggyBankControllerTest extends TestCase // mock stuff $repository = $this->mock(PiggyBankRepositoryInterface::class); $journalRepos = $this->mock(JournalRepositoryInterface::class); + $repository->shouldReceive('setUser')->once(); $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); $repository->shouldReceive('getEvents')->andReturn(new Collection); + $repository->shouldReceive('getSuggestedMonthlyAmount')->andReturn('1'); + $repository->shouldReceive('getCurrentAmount')->andReturn('1'); + + $this->be($this->user()); $response = $this->get(route('piggy-banks.show', [1])); diff --git a/tests/Unit/Transformers/PiggyBankTransformerTest.php b/tests/Unit/Transformers/PiggyBankTransformerTest.php index 3bfd20df9a..6115fff42a 100644 --- a/tests/Unit/Transformers/PiggyBankTransformerTest.php +++ b/tests/Unit/Transformers/PiggyBankTransformerTest.php @@ -50,6 +50,8 @@ class PiggyBankTransformerTest extends TestCase $repository = $this->mock(PiggyBankRepositoryInterface::class); $repository->shouldReceive('setUser')->once(); $repository->shouldReceive('getCurrentAmount')->andReturn('12.34')->once(); + $repository->shouldReceive('getSuggestedMonthlyAmount')->andReturn('12.34')->once(); + // make new account and piggy $account = Account::create( @@ -94,10 +96,12 @@ class PiggyBankTransformerTest extends TestCase $currencyRepos->shouldReceive('setUser')->once(); $currencyRepos->shouldReceive('findNull')->withArgs([1])->andReturn(TransactionCurrency::find(1))->once(); + // mock repository: $repository = $this->mock(PiggyBankRepositoryInterface::class); $repository->shouldReceive('setUser')->once(); $repository->shouldReceive('getCurrentAmount')->andReturn('12.34')->once(); + $repository->shouldReceive('getSuggestedMonthlyAmount')->andReturn('12.34')->once(); // make new account and piggy $account = Account::create( @@ -155,6 +159,7 @@ class PiggyBankTransformerTest extends TestCase $repository = $this->mock(PiggyBankRepositoryInterface::class); $repository->shouldReceive('setUser')->once(); $repository->shouldReceive('getCurrentAmount')->andReturn('12.34')->once(); + $repository->shouldReceive('getSuggestedMonthlyAmount')->andReturn('12.34')->once(); // make new account and piggy $account = Account::create( From 895ab9c5d846b7e65d4a8791cefcb2586afc4358 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 31 May 2018 20:34:32 +0200 Subject: [PATCH 145/182] Updated lock file. --- composer.lock | 292 ++++++++++++++++++++++++++------------------------ 1 file changed, 153 insertions(+), 139 deletions(-) diff --git a/composer.lock b/composer.lock index 4888f55028..e41746e1c7 100644 --- a/composer.lock +++ b/composer.lock @@ -113,7 +113,7 @@ "payment", "sepa" ], - "time": "2018-03-21T09:07:39+00:00" + "time": "2018-05-30T09:01:50+00:00" }, { "name": "davejamesmiller/laravel-breadcrumbs", @@ -1358,16 +1358,16 @@ }, { "name": "laravelcollective/html", - "version": "v5.6.8", + "version": "v5.6.9", "source": { "type": "git", "url": "https://github.com/LaravelCollective/html.git", - "reference": "42c0854e00d6bb3346883d122b444fbf15a13ecb" + "reference": "fda9d3dad85ecea609ef9c6323d6923536cf5643" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LaravelCollective/html/zipball/42c0854e00d6bb3346883d122b444fbf15a13ecb", - "reference": "42c0854e00d6bb3346883d122b444fbf15a13ecb", + "url": "https://api.github.com/repos/LaravelCollective/html/zipball/fda9d3dad85ecea609ef9c6323d6923536cf5643", + "reference": "fda9d3dad85ecea609ef9c6323d6923536cf5643", "shasum": "" }, "require": { @@ -1422,7 +1422,7 @@ ], "description": "HTML and Form Builders for the Laravel Framework", "homepage": "https://laravelcollective.com", - "time": "2018-05-10T17:15:15+00:00" + "time": "2018-05-30T16:09:07+00:00" }, { "name": "lcobucci/jwt", @@ -2801,16 +2801,16 @@ }, { "name": "symfony/console", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "058f120b8e06ebcd7b211de5ffae07b2db00fbdd" + "reference": "2d5d973bf9933d46802b01010bd25c800c87c242" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/058f120b8e06ebcd7b211de5ffae07b2db00fbdd", - "reference": "058f120b8e06ebcd7b211de5ffae07b2db00fbdd", + "url": "https://api.github.com/repos/symfony/console/zipball/2d5d973bf9933d46802b01010bd25c800c87c242", + "reference": "2d5d973bf9933d46802b01010bd25c800c87c242", "shasum": "" }, "require": { @@ -2838,7 +2838,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -2865,20 +2865,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-05-16T09:05:32+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/css-selector", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "0383a1a4eb1ffcac28719975d3e01bfa14be8ab3" + "reference": "03ac71606ecb0b0ce792faa17d74cc32c2949ef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/0383a1a4eb1ffcac28719975d3e01bfa14be8ab3", - "reference": "0383a1a4eb1ffcac28719975d3e01bfa14be8ab3", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/03ac71606ecb0b0ce792faa17d74cc32c2949ef4", + "reference": "03ac71606ecb0b0ce792faa17d74cc32c2949ef4", "shasum": "" }, "require": { @@ -2887,7 +2887,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -2918,20 +2918,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2018-05-11T15:58:37+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/debug", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "4e7c98de67cc4171d4c986554e09a511da40f3d8" + "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/4e7c98de67cc4171d4c986554e09a511da40f3d8", - "reference": "4e7c98de67cc4171d4c986554e09a511da40f3d8", + "url": "https://api.github.com/repos/symfony/debug/zipball/449f8b00b28ab6e6912c3e6b920406143b27193b", + "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b", "shasum": "" }, "require": { @@ -2947,7 +2947,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -2974,20 +2974,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-05-16T09:05:32+00:00" + "time": "2018-05-16T14:33:22+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "63353a71073faf08f62caab4e6889b06a787f07b" + "reference": "2391ed210a239868e7256eb6921b1bd83f3087b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/63353a71073faf08f62caab4e6889b06a787f07b", - "reference": "63353a71073faf08f62caab4e6889b06a787f07b", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2391ed210a239868e7256eb6921b1bd83f3087b5", + "reference": "2391ed210a239868e7256eb6921b1bd83f3087b5", "shasum": "" }, "require": { @@ -3010,7 +3010,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3037,20 +3037,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-04-06T07:35:43+00:00" + "time": "2018-04-06T07:35:57+00:00" }, { "name": "symfony/finder", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8c633f5a815903a1fe6e3fc135f207267a8a79af" + "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8c633f5a815903a1fe6e3fc135f207267a8a79af", - "reference": "8c633f5a815903a1fe6e3fc135f207267a8a79af", + "url": "https://api.github.com/repos/symfony/finder/zipball/087e2ee0d74464a4c6baac4e90417db7477dc238", + "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238", "shasum": "" }, "require": { @@ -3059,7 +3059,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3086,20 +3086,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-05-16T09:05:32+00:00" + "time": "2018-05-16T14:33:22+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "39f2b7c681fa6c323e43b8e30ccb6e714e7d2f00" + "reference": "a916c88390fb861ee21f12a92b107d51bb68af99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/39f2b7c681fa6c323e43b8e30ccb6e714e7d2f00", - "reference": "39f2b7c681fa6c323e43b8e30ccb6e714e7d2f00", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a916c88390fb861ee21f12a92b107d51bb68af99", + "reference": "a916c88390fb861ee21f12a92b107d51bb68af99", "shasum": "" }, "require": { @@ -3107,12 +3107,13 @@ "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { + "predis/predis": "~1.0", "symfony/expression-language": "~3.4|~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3139,34 +3140,34 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-05-16T09:05:32+00:00" + "time": "2018-05-25T14:55:38+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "aeae9c66976daf8a33c7be7c5e6314b39092b1b0" + "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aeae9c66976daf8a33c7be7c5e6314b39092b1b0", - "reference": "aeae9c66976daf8a33c7be7c5e6314b39092b1b0", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", + "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", "shasum": "" }, "require": { "php": "^7.1.3", "psr/log": "~1.0", "symfony/debug": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/http-foundation": "~3.4.4|~4.0.4", + "symfony/event-dispatcher": "~4.1", + "symfony/http-foundation": "~4.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/config": "<3.4", - "symfony/dependency-injection": "<3.4.5|<4.0.5,>=4", - "symfony/var-dumper": "<3.4", + "symfony/dependency-injection": "<4.1", + "symfony/var-dumper": "<4.1", "twig/twig": "<1.34|<2.4,>=2" }, "provide": { @@ -3178,7 +3179,7 @@ "symfony/config": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "^3.4.5|^4.0.5", + "symfony/dependency-injection": "^4.1", "symfony/dom-crawler": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", @@ -3187,7 +3188,7 @@ "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0", - "symfony/var-dumper": "~3.4|~4.0" + "symfony/var-dumper": "~4.1" }, "suggest": { "symfony/browser-kit": "", @@ -3199,7 +3200,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3226,7 +3227,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-05-21T14:02:31+00:00" + "time": "2018-05-30T12:52:34+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3507,16 +3508,16 @@ }, { "name": "symfony/process", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3621fa74d0576a6f89d63bc44fabd376711bd0b0" + "reference": "73445bd33b0d337c060eef9652b94df72b6b3434" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3621fa74d0576a6f89d63bc44fabd376711bd0b0", - "reference": "3621fa74d0576a6f89d63bc44fabd376711bd0b0", + "url": "https://api.github.com/repos/symfony/process/zipball/73445bd33b0d337c060eef9652b94df72b6b3434", + "reference": "73445bd33b0d337c060eef9652b94df72b6b3434", "shasum": "" }, "require": { @@ -3525,7 +3526,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3552,7 +3553,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-16T09:05:32+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -3616,16 +3617,16 @@ }, { "name": "symfony/routing", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "e8833b64b139926cbe1610d53722185e55c18e44" + "reference": "180b51c66d10f09e562c9ebc395b39aacb2cf8a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/e8833b64b139926cbe1610d53722185e55c18e44", - "reference": "e8833b64b139926cbe1610d53722185e55c18e44", + "url": "https://api.github.com/repos/symfony/routing/zipball/180b51c66d10f09e562c9ebc395b39aacb2cf8a2", + "reference": "180b51c66d10f09e562c9ebc395b39aacb2cf8a2", "shasum": "" }, "require": { @@ -3657,7 +3658,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3690,20 +3691,20 @@ "uri", "url" ], - "time": "2018-05-16T14:21:07+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/translation", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "e1f5863d0a9e79cfec7f031421ced3fe1d403e66" + "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/e1f5863d0a9e79cfec7f031421ced3fe1d403e66", - "reference": "e1f5863d0a9e79cfec7f031421ced3fe1d403e66", + "url": "https://api.github.com/repos/symfony/translation/zipball/16328f5b217cebc8dd4adfe4aeeaa8c377581f5a", + "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a", "shasum": "" }, "require": { @@ -3718,6 +3719,7 @@ "require-dev": { "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/finder": "~2.8|~3.0|~4.0", "symfony/intl": "~3.4|~4.0", @@ -3731,7 +3733,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3758,20 +3760,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2018-05-21T10:09:47+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "3c34cf3f4bbac9e003d9325225e9ef1a49180a18" + "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3c34cf3f4bbac9e003d9325225e9ef1a49180a18", - "reference": "3c34cf3f4bbac9e003d9325225e9ef1a49180a18", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/bc88ad53e825ebacc7b190bbd360781fce381c64", + "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64", "shasum": "" }, "require": { @@ -3780,20 +3782,26 @@ "symfony/polyfill-php72": "~1.5" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" }, "require-dev": { "ext-iconv": "*", + "symfony/process": "~3.4|~4.0", "twig/twig": "~1.34|~2.4" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump" + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" }, + "bin": [ + "Resources/bin/var-dump-server" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3827,7 +3835,7 @@ "debug", "dump" ], - "time": "2018-04-26T16:12:06+00:00" + "time": "2018-04-29T07:56:09+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -3993,16 +4001,16 @@ }, { "name": "zendframework/zend-diactoros", - "version": "1.7.1", + "version": "1.7.2", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "bf26aff803a11c5cc8eb7c4878a702c403ec67f1" + "reference": "741e7a571836f038de731105f4742ca8a164e43a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/bf26aff803a11c5cc8eb7c4878a702c403ec67f1", - "reference": "bf26aff803a11c5cc8eb7c4878a702c403ec67f1", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/741e7a571836f038de731105f4742ca8a164e43a", + "reference": "741e7a571836f038de731105f4742ca8a164e43a", "shasum": "" }, "require": { @@ -4022,7 +4030,8 @@ "extra": { "branch-alias": { "dev-master": "1.7.x-dev", - "dev-develop": "1.8.x-dev" + "dev-develop": "1.8.x-dev", + "dev-release-2.0": "2.0.x-dev" } }, "autoload": { @@ -4041,7 +4050,7 @@ "psr", "psr-7" ], - "time": "2018-02-26T15:44:50+00:00" + "time": "2018-05-29T16:53:08+00:00" } ], "packages-dev": [ @@ -4496,25 +4505,28 @@ }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "478465659fd987669df0bd8a9bf22a8710e5f1b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/478465659fd987669df0bd8a9bf22a8710e5f1b6", + "reference": "478465659fd987669df0bd8a9bf22a8710e5f1b6", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "phpunit/phpunit": "^7.1" }, "type": "library", "autoload": { @@ -4537,7 +4549,7 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" + "time": "2018-05-29T17:25:09+00:00" }, { "name": "phar-io/manifest", @@ -4941,16 +4953,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "6.0.4", + "version": "6.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "52187754b0eed0b8159f62a6fa30073327e8c2ca" + "reference": "4cab20a326d14de7575a8e235c70d879b569a57a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/52187754b0eed0b8159f62a6fa30073327e8c2ca", - "reference": "52187754b0eed0b8159f62a6fa30073327e8c2ca", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4cab20a326d14de7575a8e235c70d879b569a57a", + "reference": "4cab20a326d14de7575a8e235c70d879b569a57a", "shasum": "" }, "require": { @@ -5000,7 +5012,7 @@ "testing", "xunit" ], - "time": "2018-04-29T14:59:09+00:00" + "time": "2018-05-28T11:49:20+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5270,16 +5282,16 @@ }, { "name": "phpunit/phpunit-mock-objects", - "version": "6.1.1", + "version": "6.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157" + "reference": "f9756fd4f43f014cb2dca98deeaaa8ce5500a36e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/70c740bde8fd9ea9ea295be1cd875dd7b267e157", - "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/f9756fd4f43f014cb2dca98deeaaa8ce5500a36e", + "reference": "f9756fd4f43f014cb2dca98deeaaa8ce5500a36e", "shasum": "" }, "require": { @@ -5322,7 +5334,7 @@ "mock", "xunit" ], - "time": "2018-04-11T04:50:36+00:00" + "time": "2018-05-29T13:54:20+00:00" }, { "name": "roave/security-advisories", @@ -5330,12 +5342,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "79ef6566b9c7fe6c1b6f6d12e5729d5cb545ac69" + "reference": "4d93302eb93402083f5abe72002fe8dc35e12dae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/79ef6566b9c7fe6c1b6f6d12e5729d5cb545ac69", - "reference": "79ef6566b9c7fe6c1b6f6d12e5729d5cb545ac69", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/4d93302eb93402083f5abe72002fe8dc35e12dae", + "reference": "4d93302eb93402083f5abe72002fe8dc35e12dae", "shasum": "" }, "conflict": { @@ -5419,22 +5431,24 @@ "symfony/dependency-injection": ">=2,<2.0.17", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2", - "symfony/http-foundation": ">=2,<2.3.27|>=2.4,<2.5.11|>=2.6,<2.6.6", + "symfony/http-foundation": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/routing": ">=2,<2.0.19", - "symfony/security": ">=2,<2.0.25|>=2.1,<2.1.13|>=2.2,<2.2.9|>=2.3,<2.3.37|>=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8.23,<2.8.25|>=3.2.10,<3.2.12|>=3.3.3,<3.3.5", - "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.6|>=2.8.23,<2.8.25|>=3,<3.0.6|>=3.2.10,<3.2.12|>=3.3.3,<3.3.5", - "symfony/security-csrf": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/security": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/serializer": ">=2,<2.0.11", - "symfony/symfony": ">=2,<2.3.41|>=2.4,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/symfony": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/translation": ">=2,<2.0.17", "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", + "thelia/thelia": ">=2.1,<2.1.2|>=2.1.0-beta1,<2.1.3", "titon/framework": ">=0,<9.9.99", "twig/twig": "<1.20", "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.22|>=8,<8.7.5", @@ -5486,7 +5500,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-05-21T07:43:38+00:00" + "time": "2018-05-30T02:58:56+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -6053,7 +6067,7 @@ }, { "name": "symfony/class-loader", - "version": "v3.4.10", + "version": "v3.4.11", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", @@ -6109,16 +6123,16 @@ }, { "name": "symfony/config", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "aaef656e99c5396d6118970abd1ceb11fa74551d" + "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/aaef656e99c5396d6118970abd1ceb11fa74551d", - "reference": "aaef656e99c5396d6118970abd1ceb11fa74551d", + "url": "https://api.github.com/repos/symfony/config/zipball/5ceefc256caecc3e25147c4e5b933de71d0020c4", + "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4", "shasum": "" }, "require": { @@ -6141,7 +6155,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -6168,20 +6182,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-05-16T09:05:32+00:00" + "time": "2018-05-16T14:33:22+00:00" }, { "name": "symfony/filesystem", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "7a69e728e9f0044958c2fd7d72bfe5e7bd1a4d04" + "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/7a69e728e9f0044958c2fd7d72bfe5e7bd1a4d04", - "reference": "7a69e728e9f0044958c2fd7d72bfe5e7bd1a4d04", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", + "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", "shasum": "" }, "require": { @@ -6191,7 +6205,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -6218,20 +6232,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-05-16T09:05:32+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "6795ffa2f8eebedac77f045aa62c0c10b2763042" + "reference": "07463bbbbbfe119045a24c4a516f92ebd2752784" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6795ffa2f8eebedac77f045aa62c0c10b2763042", - "reference": "6795ffa2f8eebedac77f045aa62c0c10b2763042", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/07463bbbbbfe119045a24c4a516f92ebd2752784", + "reference": "07463bbbbbfe119045a24c4a516f92ebd2752784", "shasum": "" }, "require": { @@ -6240,7 +6254,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -6267,20 +6281,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-02-19T16:50:22+00:00" + "time": "2018-02-19T16:51:42+00:00" }, { "name": "symfony/yaml", - "version": "v4.0.10", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "048b1be5fb96e73ff1d065f5e7e23f84415ac907" + "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/048b1be5fb96e73ff1d065f5e7e23f84415ac907", - "reference": "048b1be5fb96e73ff1d065f5e7e23f84415ac907", + "url": "https://api.github.com/repos/symfony/yaml/zipball/80e4bfa9685fc4a09acc4a857ec16974a9cd944e", + "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e", "shasum": "" }, "require": { @@ -6299,7 +6313,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -6326,7 +6340,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-05-07T07:12:24+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "theseer/tokenizer", From f2928e3d7d6acddffc46b0f4a4ed1d6892a8efd2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 31 May 2018 20:34:49 +0200 Subject: [PATCH 146/182] Possible fix for #1453 --- app/Http/Controllers/RuleGroupController.php | 6 ++++++ app/Models/RuleGroup.php | 1 + 2 files changed, 7 insertions(+) diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php index 3c65632544..360f73eb82 100644 --- a/app/Http/Controllers/RuleGroupController.php +++ b/app/Http/Controllers/RuleGroupController.php @@ -129,11 +129,17 @@ class RuleGroupController extends Controller { $subTitle = trans('firefly.edit_rule_group', ['title' => $ruleGroup->title]); + $preFilled = [ + 'active' => $ruleGroup->active, + ]; + + // put previous url in session if not redirect from store (not "return_to_edit"). if (true !== session('rule-groups.edit.fromUpdate')) { $this->rememberPreviousUri('rule-groups.edit.uri'); } session()->forget('rule-groups.edit.fromUpdate'); + session()->flash('preFilled', $preFilled); return view('rules.rule-group.edit', compact('ruleGroup', 'subTitle')); } diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index f16c6079ae..cfe4ae2c35 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -30,6 +30,7 @@ use FireflyIII\Models\Rule; /** * Class RuleGroup. + * @property bool $active */ class RuleGroup extends Model { From 2ba6fa0ddad87798ece1270c552752a955ce2f58 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 31 May 2018 21:13:07 +0200 Subject: [PATCH 147/182] Add description to debug logging. #1443 --- app/Support/Import/Routine/File/ImportableConverter.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Support/Import/Routine/File/ImportableConverter.php b/app/Support/Import/Routine/File/ImportableConverter.php index 845fd63ee8..557c07648b 100644 --- a/app/Support/Import/Routine/File/ImportableConverter.php +++ b/app/Support/Import/Routine/File/ImportableConverter.php @@ -130,6 +130,7 @@ class ImportableConverter */ private function convertSingle(ImportTransaction $importable): array { + Log::debug(sprintf('Description is: "%s"', $importable->description)); $amount = $importable->calculateAmount(); $foreignAmount = $importable->calculateForeignAmount(); if ('' === $amount) { @@ -208,6 +209,8 @@ class ImportableConverter $date = new Carbon; } + + $dateStr = $date->format('Y-m-d'); return [ From f1fe90fce00359ae3a782bb1c31eb27d16cb170d Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 31 May 2018 21:30:25 +0200 Subject: [PATCH 148/182] Fix #1455 --- app/Factory/TransactionFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index c7944e8198..eb2244ca15 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -143,7 +143,7 @@ class TransactionFactory } // set budget: - if ($journal->transactionType->type === TransactionType::TRANSFER) { + if ($journal->transactionType->type !== TransactionType::WITHDRAWAL) { $data['budget_id'] = null; $data['budget_name'] = null; } From 34fd8cf7514bf66f8fb20f50b53cc041d60b35a4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 31 May 2018 21:48:09 +0200 Subject: [PATCH 149/182] Fix #1442 --- .../Transaction/SplitController.php | 35 ++++++++----------- app/Http/Requests/SplitJournalFormRequest.php | 34 +++++++++--------- app/Models/TransactionJournal.php | 1 + public/js/ff/transactions/split/edit.js | 12 +++---- 4 files changed, 38 insertions(+), 44 deletions(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 2dfc57325c..aa25f47fdc 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -29,8 +29,6 @@ use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\SplitJournalFormRequest; -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; @@ -100,22 +98,18 @@ class SplitController extends Controller if ($this->isOpeningBalance($journal)) { return $this->redirectToAccount($journal); // @codeCoverageIgnore } + // basic fields: + $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); + $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); + $subTitleIcon = 'fa-pencil'; - $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); - $currencies = $this->currencies->get(); + // lists and collections + $currencies = $this->currencies->get(); + $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); + + // other fields $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; - $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); $preFilled = $this->arrayFromJournal($request, $journal); - $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); - $subTitleIcon = 'fa-pencil'; - $accountList = $this->accounts->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]); - $accountArray = []; - // account array to display currency info: - /** @var Account $account */ - foreach ($accountList as $account) { - $accountArray[$account->id] = $account; - $accountArray[$account->id]['currency_id'] = (int)$this->accounts->getMetaValue($account, 'currency_id'); - } // put previous url in session if not redirect from store (not "return_to_edit"). if (true !== session('transactions.edit-split.fromUpdate')) { @@ -126,7 +120,7 @@ class SplitController extends Controller return view( 'transactions.split.edit', compact( 'subTitleIcon', 'currencies', 'optionalFields', 'preFilled', 'subTitle', 'uploadSize', 'budgets', - 'journal', 'accountArray' + 'journal' ) ); } @@ -146,8 +140,7 @@ class SplitController extends Controller // keep current bill: $data['bill_id'] = $journal->bill_id; - - $journal = $this->repository->update($journal, $data); + $journal = $this->repository->update($journal, $data); /** @var array $files */ $files = $request->hasFile('attachments') ? $request->file('attachments') : null; @@ -283,9 +276,9 @@ class SplitController extends Controller } // take some info from first transaction, that should at least exist. $array[$index] = $row; - $array[$index]['currency_id'] = $array[0]['transaction_currency_id']; - $array[$index]['currency_code'] = $array[0]['transaction_currency_code'] ?? ''; - $array[$index]['currency_symbol'] = $array[0]['transaction_currency_symbol'] ?? ''; + $array[$index]['currency_id'] = $array[0]['currency_id']; + $array[$index]['currency_code'] = $array[0]['currency_code'] ?? ''; + $array[$index]['currency_symbol'] = $array[0]['currency_symbol'] ?? ''; $array[$index]['foreign_amount'] = round($array[0]['foreign_destination_amount'] ?? '0', 12); $array[$index]['foreign_currency_id'] = $array[0]['foreign_currency_id']; $array[$index]['foreign_currency_code'] = $array[0]['foreign_currency_code']; diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php index 207754df5b..ce99023a1f 100644 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -109,23 +109,23 @@ class SplitJournalFormRequest extends Request public function rules(): array { return [ - 'what' => 'required|in:withdrawal,deposit,transfer', - 'journal_description' => 'required|between:1,255', - 'id' => 'numeric|belongsToUser:transaction_journals,id', - 'journal_source_account_id' => 'numeric|belongsToUser:accounts,id', - 'journal_source_account_name.*' => 'between:1,255', - 'journal_currency_id' => 'required|exists:transaction_currencies,id', - 'date' => 'required|date', - 'interest_date' => 'date|nullable', - 'book_date' => 'date|nullable', - 'process_date' => 'date|nullable', - 'transactions.*.transaction_description' => 'required|between:1,255', - 'transactions.*.destination_account_id' => 'numeric|belongsToUser:accounts,id', - 'transactions.*.destination_name' => 'between:1,255|nullable', - 'transactions.*.amount' => 'required|numeric', - 'transactions.*.budget_id' => 'belongsToUser:budgets,id', - 'transactions.*.category_name' => 'between:1,255|nullable', - 'transactions.*.piggy_bank_id' => 'between:1,255|nullable', + 'what' => 'required|in:withdrawal,deposit,transfer', + 'journal_description' => 'required|between:1,255', + 'id' => 'numeric|belongsToUser:transaction_journals,id', + 'journal_source_account_id' => 'numeric|belongsToUser:accounts,id', + 'journal_source_account_name.*' => 'between:1,255', + 'journal_currency_id' => 'required|exists:transaction_currencies,id', + 'date' => 'required|date', + 'interest_date' => 'date|nullable', + 'book_date' => 'date|nullable', + 'process_date' => 'date|nullable', + 'transactions.*.transaction_description' => 'required|between:1,255', + 'transactions.*.destination_account_id' => 'numeric|belongsToUser:accounts,id', + 'transactions.*.destination_name' => 'between:1,255|nullable', + 'transactions.*.amount' => 'required|numeric', + 'transactions.*.budget_id' => 'belongsToUser:budgets,id', + 'transactions.*.category_name' => 'between:1,255|nullable', + 'transactions.*.piggy_bank_id' => 'between:1,255|nullable', ]; } diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 6432d371b4..40595f644d 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -51,6 +51,7 @@ use FireflyIII\Models\Attachment; * Class TransactionJournal. * * @property User $user + * @property int $bill_id */ class TransactionJournal extends Model { diff --git a/public/js/ff/transactions/split/edit.js b/public/js/ff/transactions/split/edit.js index 9b5aae1e99..a99a39f651 100644 --- a/public/js/ff/transactions/split/edit.js +++ b/public/js/ff/transactions/split/edit.js @@ -33,12 +33,12 @@ $(document).ready(function () { $.getJSON('json/expense-accounts').done(function (data) { destAccounts = data; - $('input[name$="destination_account_name]"]').typeahead({source: destAccounts, autoSelect: false}); + $('input[name$="destination_name]"]').typeahead({source: destAccounts, autoSelect: false}); }); $.getJSON('json/revenue-accounts').done(function (data) { srcAccounts = data; - $('input[name$="source_account_name]"]').typeahead({source: srcAccounts, autoSelect: false}); + $('input[name$="source_name]"]').typeahead({source: srcAccounts, autoSelect: false}); }); $.getJSON('json/categories').done(function (data) { @@ -123,11 +123,11 @@ function cloneDivRow() { source.find('input[name$="][amount]"]').val("").on('change', calculateBothSums); source.find('input[name$="][foreign_amount]"]').val("").on('change', calculateBothSums); if (destAccounts.length > 0) { - source.find('input[name$="destination_account_name]"]').typeahead({source: destAccounts, autoSelect: false}); + source.find('input[name$="destination_name]"]').typeahead({source: destAccounts, autoSelect: false}); } if (srcAccounts.length > 0) { - source.find('input[name$="source_account_name]"]').typeahead({source: srcAccounts, autoSelect: false}); + source.find('input[name$="source_name]"]').typeahead({source: srcAccounts, autoSelect: false}); } if (categories.length > 0) { source.find('input[name$="category_name]"]').typeahead({source: categories, autoSelect: false}); @@ -186,12 +186,12 @@ function resetDivSplits() { input.attr('name', 'transactions[' + i + '][transaction_description]'); }); // ends with ][destination_account_name] - $.each($('input[name$="][destination_account_name]"]'), function (i, v) { + $.each($('input[name$="][destination_name]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][destination_account_name]'); }); // ends with ][source_account_name] - $.each($('input[name$="][source_account_name]"]'), function (i, v) { + $.each($('input[name$="][source_name]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][source_account_name]'); }); From d1b2e63950e516f474df1b224c28caa52262b824 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 31 May 2018 22:33:42 +0200 Subject: [PATCH 150/182] Small fixes for import routine. --- .../Controllers/Import/JobStatusController.php | 7 +++++-- app/Http/Controllers/TagController.php | 18 ++++-------------- app/Import/Storage/ImportArrayStorage.php | 10 ++++++++-- app/Models/Rule.php | 2 ++ app/Models/Tag.php | 2 ++ 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index 0fbe1884c9..d512e82196 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -105,11 +105,14 @@ class JobStatusController extends Controller $json['report_txt'] = trans('import.result_no_transactions'); } if ($count === 1 && null !== $importJob->tag_id) { - $json['report_txt'] = trans('import.result_one_transaction', ['route' => route('tags.show', [$importJob->tag_id]), 'tag' => $importJob->tag->tag]); + $json['report_txt'] = trans( + 'import.result_one_transaction', ['route' => route('tags.show', [$importJob->tag_id, 'all']), 'tag' => $importJob->tag->tag] + ); } if ($count > 1 && null !== $importJob->tag_id) { $json['report_txt'] = trans( - 'import.result_many_transactions', ['count' => $count, 'route' => route('tags.show', [$importJob->tag_id]), 'tag' => $importJob->tag->tag] + 'import.result_many_transactions', + ['count' => $count, 'route' => route('tags.show', [$importJob->tag_id, 'all']), 'tag' => $importJob->tag->tag] ); } diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index 294d364014..96dbe6d289 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -36,14 +36,6 @@ use View; /** * Class TagController. - * - * Remember: a balancingAct takes at most one expense and one transfer. - * an advancePayment takes at most one expense, infinite deposits and NO transfers. - * - * transaction can only have one advancePayment OR balancingAct. - * Other attempts to put in such a tag are blocked. - * also show an error when editing a tag and it becomes either - * of these two types. Or rather, block editing of the tag. */ class TagController extends Controller { @@ -79,7 +71,6 @@ class TagController extends Controller { $subTitle = trans('firefly.new_tag'); $subTitleIcon = 'fa-tag'; - $apiKey = env('GOOGLE_MAPS_API_KEY', ''); // put previous url in session if not redirect from store (not "create another"). if (true !== session('tags.create.fromStore')) { @@ -87,7 +78,7 @@ class TagController extends Controller } session()->forget('tags.create.fromStore'); - return view('tags.create', compact('subTitle', 'subTitleIcon', 'apiKey')); + return view('tags.create', compact('subTitle', 'subTitleIcon')); } /** @@ -134,7 +125,6 @@ class TagController extends Controller { $subTitle = trans('firefly.edit_tag', ['tag' => $tag->tag]); $subTitleIcon = 'fa-tag'; - $apiKey = env('GOOGLE_MAPS_API_KEY', ''); // put previous url in session if not redirect from store (not "return_to_edit"). if (true !== session('tags.edit.fromUpdate')) { @@ -142,7 +132,7 @@ class TagController extends Controller } session()->forget('tags.edit.fromUpdate'); - return view('tags.edit', compact('tag', 'subTitle', 'subTitleIcon', 'apiKey')); + return view('tags.edit', compact('tag', 'subTitle', 'subTitleIcon')); } /** @@ -201,7 +191,6 @@ class TagController extends Controller $start = null; $end = null; $periods = new Collection; - $apiKey = env('GOOGLE_MAPS_API_KEY', ''); $path = route('tags.show', [$tag->id]); // prep for "all" view. @@ -310,7 +299,8 @@ class TagController extends Controller // get first and last tag date from tag: $range = Preferences::get('viewRange', '1M')->data; $start = app('navigation')->startOfPeriod($this->repository->firstUseDate($tag), $range); - $end = app('navigation')->startOfPeriod($this->repository->lastUseDate($tag), $range); + $end = app('navigation')->endOfPeriod($this->repository->lastUseDate($tag), $range); + // properties for entries with their amounts. $cache = new CacheProperties; $cache->addProperty($start); diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 3cb5ccfebd..6db0712dd7 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -144,15 +144,19 @@ class ImportArrayStorage */ private function countTransfers(): void { + Log::debug('Now in count transfers.'); /** @var array $array */ $array = $this->importJob->transactions; $count = 0; - foreach ($array as $transaction) { + foreach ($array as $index => $transaction) { if (strtolower(TransactionType::TRANSFER) === $transaction['type']) { $count++; + Log::debug(sprintf('Row #%d is a transfer, increase count to %d', ($index + 1), $count)); } } + Log::debug('Count is zero.'); if ($count > 0) { + Log::debug(sprintf('Count is %d', $count)); $this->checkForTransfers = true; // get users transfers. Needed for comparison. @@ -199,6 +203,7 @@ class ImportArrayStorage */ private function getTransfers(): void { + app('preferences')->mark(); /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); $collector->setUser($this->importJob->user); @@ -408,7 +413,8 @@ class ImportArrayStorage $requiredHits = count($transaction['transactions']) * 4; $totalHits = 0; Log::debug(sprintf('Required hits for transfer comparison is %d', $requiredHits)); - + Log::debug(sprintf('Array has %d transactions.', \count($transaction['transactions']))); + Log::debug(sprintf('System has %d existing transfers', \count($this->transfers))); // loop over each split: foreach ($transaction['transactions'] as $current) { diff --git a/app/Models/Rule.php b/app/Models/Rule.php index ed2d1161bb..ac3cc0e992 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -32,6 +32,8 @@ use FireflyIII\Models\RuleAction; /** * Class Rule. + * @property bool $stop_processing + * @property int $id */ class Rule extends Model { diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 4a5f176e73..cb7861d845 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -33,6 +33,8 @@ use FireflyIII\Models\TransactionJournal; /** * Class Tag. * @property Collection $transactionJournals + * @property string $tag + * @property int $id */ class Tag extends Model { From 3fbe851a0b01366b6feb9a396ae71b1e3bd4b710 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 05:23:57 +0200 Subject: [PATCH 151/182] Include external ID with import. --- app/Support/Import/Routine/File/ImportableConverter.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Support/Import/Routine/File/ImportableConverter.php b/app/Support/Import/Routine/File/ImportableConverter.php index 557c07648b..5084f24743 100644 --- a/app/Support/Import/Routine/File/ImportableConverter.php +++ b/app/Support/Import/Routine/File/ImportableConverter.php @@ -210,7 +210,6 @@ class ImportableConverter } - $dateStr = $date->format('Y-m-d'); return [ @@ -235,7 +234,7 @@ class ImportableConverter 'due_date' => $importable->meta['date-due'] ?? null, 'payment_date' => $importable->meta['date-payment'] ?? null, 'invoice_date' => $importable->meta['date-invoice'] ?? null, - // todo external ID + 'external_id' => $importable->externalId, // journal data: 'description' => $importable->description, From df87d03f32b34ca1180a626bb7f78b04a20354e9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 05:49:33 +0200 Subject: [PATCH 152/182] FF3 will apply rules when importing from bunq #1443 --- app/Import/Storage/ImportStorage.php | 389 ------------------ app/Import/Storage/ImportSupport.php | 306 -------------- .../Bunq/NewBunqJobHandler.php | 18 +- .../Bunq/NewBunqJobHandlerTest.php | 67 +++ 4 files changed, 82 insertions(+), 698 deletions(-) delete mode 100644 app/Import/Storage/ImportStorage.php delete mode 100644 app/Import/Storage/ImportSupport.php create mode 100644 tests/Unit/Support/Import/JobConfiguration/Bunq/NewBunqJobHandlerTest.php diff --git a/app/Import/Storage/ImportStorage.php b/app/Import/Storage/ImportStorage.php deleted file mode 100644 index c1d307f7b8..0000000000 --- a/app/Import/Storage/ImportStorage.php +++ /dev/null @@ -1,389 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Import\Storage; - -use Exception; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Factory\TransactionJournalFactory; -use FireflyIII\Import\Object\ImportJournal; -use FireflyIII\Models\ImportJob; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use Illuminate\Support\Collection; -use Log; -use Preferences; - -/** - * @codeCoverageIgnore - * @deprecated - * Is capable of storing individual ImportJournal objects. - * Adds 7 steps per object stored: - * 1. get all import data from import journal - * 2. is not a duplicate - * 3. create the journal - * 4. store journal - * 5. run rules - * 6. run bills - * 7. finished storing object - * - * Class ImportStorage. - */ -class ImportStorage -{ - use ImportSupport; - - /** @var Collection */ - public $errors; - /** @var Collection */ - public $journals; - /** @var int */ - protected $defaultCurrencyId = 1; - /** @var ImportJob */ - protected $job; // yes, hard coded - /** @var JournalRepositoryInterface */ - protected $journalRepository; - /** @var ImportJobRepositoryInterface */ - protected $repository; - /** @var Collection */ - protected $rules; - /** @var bool */ - private $applyRules = false; - /** @var string */ - private $dateFormat = 'Ymd'; - /** @var TransactionJournalFactory */ - private $factory; - /** @var Collection */ - private $objects; - /** @var int */ - private $total = 0; - /** @var array */ - private $transfers = []; - - /** - * ImportStorage constructor. - */ - public function __construct() - { - $this->objects = new Collection; - $this->journals = new Collection; - $this->errors = new Collection; - - } - - /** - * @param string $dateFormat - */ - public function setDateFormat(string $dateFormat) - { - $this->dateFormat = $dateFormat; - } - - /** - * @param ImportJob $job - */ - public function setJob(ImportJob $job) - { - $this->repository = app(ImportJobRepositoryInterface::class); - $this->journalRepository = app(JournalRepositoryInterface::class); - $this->repository->setUser($job->user); - $this->journalRepository->setUser($job->user); - $this->factory = app(TransactionJournalFactory::class); - $this->factory->setUser($job->user); - - $config = $this->repository->getConfiguration($job); - $currency = app('amount')->getDefaultCurrencyByUser($job->user); - $this->defaultCurrencyId = $currency->id; - $this->job = $job; - $this->transfers = $this->getTransfers(); - $this->applyRules = $config['apply-rules'] ?? false; - - if (true === $this->applyRules) { - Log::debug('applyRules seems to be true, get the rules.'); - $this->rules = $this->getRules(); - } - - Log::debug(sprintf('Value of apply rules is %s', var_export($this->applyRules, true))); - } - - /** - * @param Collection $objects - */ - public function setObjects(Collection $objects) - { - $this->objects = $objects; - $this->total = $objects->count(); - } - - /** - * Do storage of import objects. Is the main function. - * - * @return bool - */ - public function store(): bool - { - $this->objects->each( - function (ImportJournal $importJournal, int $index) { - try { - $this->storeImportJournal($index, $importJournal); - $this->addStep(); - } catch (Exception $e) { - $this->errors->push($e->getMessage()); - Log::error(sprintf('Cannot import row #%d because: %s', $index, $e->getMessage())); - Log::error($e->getTraceAsString()); - } - } - ); - Log::info('ImportStorage has finished.'); - - return true; - } - - /** - * @param int $index - * @param ImportJournal $importJournal - * - * @return bool - * - * @throws FireflyException - */ - protected function storeImportJournal(int $index, ImportJournal $importJournal): bool - { - Log::debug(sprintf('Going to store object #%d/%d with description "%s"', $index + 1, $this->total, $importJournal->getDescription())); - $assetAccount = $importJournal->asset->getAccount(); - $amount = $importJournal->getAmount(); - $foreignAmount = $importJournal->getForeignAmount(); - $currencyId = $this->getCurrencyId($importJournal); - $foreignCurrencyId = $this->getForeignCurrencyId($importJournal, $currencyId); - $date = $importJournal->getDate($this->dateFormat)->format('Y-m-d'); - $opposingAccount = $this->getOpposingAccount($importJournal->opposing, $assetAccount->id, $amount); - $transactionType = $this->getTransactionType($amount, $opposingAccount); - $this->addStep(); - - /** - * Check for double transfer. - */ - $parameters = [ - 'type' => $transactionType, - 'description' => $importJournal->getDescription(), - 'amount' => $amount, - 'date' => $date, - 'asset' => $assetAccount->name, - 'opposing' => $opposingAccount->name, - ]; - if ($this->isDoubleTransfer($parameters) || $this->hashAlreadyImported($importJournal->hash)) { - // throw error - $message = sprintf('Detected a possible duplicate, skip this one (hash: %s).', $importJournal->hash); - Log::error($message, $parameters); - - // add five steps to keep the pace: - $this->addSteps(5); - - throw new FireflyException($message); - } - - /** - * Search for journals with the same external ID. - * - */ - - - unset($parameters); - $this->addStep(); - - $budget = $importJournal->budget->getBudget(); - $category = $importJournal->category->getCategory(); - $bill = $importJournal->bill->getBill(); - $source = $assetAccount; - $destination = $opposingAccount; - - // switch account arounds when the transaction type is a deposit. - if ($transactionType === TransactionType::DEPOSIT) { - [$destination, $source] = [$source, $destination]; - } - // switch accounts around when the amount is negative and it's a transfer. - // credits to @NyKoF - if ($transactionType === TransactionType::TRANSFER && -1 === bccomp($amount, '0')) { - [$destination, $source] = [$source, $destination]; - } - Log::debug( - sprintf('Will make #%s (%s) the source and #%s (%s) the destination.', $source->id, $source->name, $destination->id, $destination->name) - ); - - $data = [ - 'user' => $this->job->user_id, - 'type' => $transactionType, - 'date' => $importJournal->getDate($this->dateFormat), - 'description' => $importJournal->getDescription(), - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'bill_id' => null === $bill ? null : $bill->id, - 'bill_name' => null, - 'tags' => $importJournal->tags, - 'interest_date' => $importJournal->getMetaDate('interest_date'), - 'book_date' => $importJournal->getMetaDate('book_date'), - 'process_date' => $importJournal->getMetaDate('process_date'), - 'due_date' => $importJournal->getMetaDate('due_date'), - 'payment_date' => $importJournal->getMetaDate('payment_date'), - 'invoice_date' => $importJournal->getMetaDate('invoice_date'), - 'internal_reference' => $importJournal->metaFields['internal_reference'] ?? null, - 'notes' => $importJournal->notes, - 'external_id' => $importJournal->getExternalId(), - 'sepa-cc' => $importJournal->getMetaString('sepa-cc'), - 'sepa-ct-op' => $importJournal->getMetaString('sepa-ct-op'), - 'sepa-ct-id' => $importJournal->getMetaString('sepa-ct-id'), - 'sepa-db' => $importJournal->getMetaString('sepa-db'), - 'sepa-country' => $importJournal->getMetaString('sepa-country'), - 'sepa-ep' => $importJournal->getMetaString('sepa-ep'), - 'sepa-ci' => $importJournal->getMetaString('sepa-ci'), - 'importHash' => $importJournal->hash, - 'transactions' => [ - // single transaction: - [ - 'description' => null, - 'amount' => $amount, - 'currency_id' => $currencyId, - 'currency_code' => null, - 'foreign_amount' => $foreignAmount, - 'foreign_currency_id' => $foreignCurrencyId, - 'foreign_currency_code' => null, - 'budget_id' => null === $budget ? null : $budget->id, - 'budget_name' => null, - 'category_id' => null === $category ? null : $category->id, - 'category_name' => null, - 'source_id' => $source->id, - 'source_name' => null, - 'destination_id' => $destination->id, - 'destination_name' => null, - 'reconciled' => false, - 'identifier' => 0, - ], - ], - ]; - $factoryJournal = null; - try { - $factoryJournal = $this->factory->create($data); - $this->journals->push($factoryJournal); - } catch (FireflyException $e) { - Log::error(sprintf('Could not use factory to store journal: %s', $e->getMessage())); - Log::error($e->getTraceAsString()); - } - - // double add step because "match bills" no longer happens. - $this->addStep(); - $this->addStep(); - - // run rules if config calls for it: - if (true === $this->applyRules && null !== $factoryJournal) { - Log::info('Will apply rules to this journal.'); - $this->applyRules($factoryJournal); - } - Preferences::setForUser($this->job->user, 'lastActivity', microtime()); - - if (!(true === $this->applyRules)) { - Log::info('Will NOT apply rules to this journal.'); - } - - // double add step because some other extra thing was removed here. - $this->addStep(); - $this->addStep(); - - Log::info( - sprintf( - 'Imported new journal #%d: "%s", amount %s %s.', $factoryJournal->id, $factoryJournal->description, $factoryJournal->transactionCurrency->code, - $amount - ) - ); - - return true; - } - - /** - * Shorthand method. - */ - private function addStep() - { - $this->repository->addStepsDone($this->job, 1); - } - - /** - * Shorthand method - * - * @param int $steps - */ - private function addSteps(int $steps) - { - $this->repository->addStepsDone($this->job, $steps); - } - - /** - * @param array $parameters - * - * @return bool - */ - private function isDoubleTransfer(array $parameters): bool - { - Log::debug('Check if is a double transfer.'); - if (TransactionType::TRANSFER !== $parameters['type']) { - Log::debug(sprintf('Is a %s, not a transfer so no.', $parameters['type'])); - - return false; - } - - $amount = app('steam')->positive($parameters['amount']); - $names = [$parameters['asset'], $parameters['opposing']]; - - sort($names); - - foreach ($this->transfers as $transfer) { - $hits = 0; - if ($parameters['description'] === $transfer['description']) { - ++$hits; - Log::debug(sprintf('Description "%s" equals "%s", hits = %d', $parameters['description'], $transfer['description'], $hits)); - } - if ($names === $transfer['names']) { - ++$hits; - Log::debug(sprintf('Involved accounts, "%s" equals "%s", hits = %d', implode(',', $names), implode(',', $transfer['names']), $hits)); - } - if (0 === bccomp($amount, $transfer['amount'])) { - ++$hits; - Log::debug(sprintf('Amount %s equals %s, hits = %d', $amount, $transfer['amount'], $hits)); - } - if ($parameters['date'] === $transfer['date']) { - ++$hits; - Log::debug(sprintf('Date %s equals %s, hits = %d', $parameters['date'], $transfer['date'], $hits)); - } - // number of hits is 4? Then it's a match - if (4 === $hits) { - Log::error( - 'There already is a transfer imported with these properties. Compare existing with new. ', - ['existing' => $transfer, 'new' => $parameters] - ); - - return true; - } - } - - return false; - } -} diff --git a/app/Import/Storage/ImportSupport.php b/app/Import/Storage/ImportSupport.php deleted file mode 100644 index f7a52914a7..0000000000 --- a/app/Import/Storage/ImportSupport.php +++ /dev/null @@ -1,306 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Import\Storage; - -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Import\Object\ImportAccount; -use FireflyIII\Import\Object\ImportJournal; -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Bill; -use FireflyIII\Models\ImportJob; -use FireflyIII\Models\Rule; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionJournalMeta; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\TransactionRules\Processor; -use Illuminate\Database\Query\JoinClause; -use Illuminate\Support\Collection; -use Log; - -/** - * @codeCoverageIgnore - * @deprecated - * Trait ImportSupport. - * - * @property int $defaultCurrencyId - * @property ImportJob $job - * @property JournalRepositoryInterface $journalRepository; - * @property Collection $rules - */ -trait ImportSupport -{ - /** - * @param TransactionJournal $journal - * - * @return bool - */ - protected function applyRules(TransactionJournal $journal): bool - { - if ($this->rules->count() > 0) { - $this->rules->each( - function (Rule $rule) use ($journal) { - Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id)); - $processor = Processor::make($rule); - $processor->handleTransactionJournal($journal); - if ($rule->stop_processing) { - return false; - } - - return true; - } - ); - } - - return true; - } - - /** - * This method finds out what the import journal's currency should be. The account itself - * is favoured (and usually it stops there). If no preference is found, the journal has a say - * and thirdly the default currency is used. - * - * @param ImportJournal $importJournal - * - * @return int - * - * @throws FireflyException - */ - private function getCurrencyId(ImportJournal $importJournal): int - { - // start with currency pref of account, if any: - $account = $importJournal->asset->getAccount(); - $currencyId = (int)$account->getMeta('currency_id'); - if ($currencyId > 0) { - return $currencyId; - } - - // use given currency - $currency = $importJournal->currency->getTransactionCurrency(); - if (null !== $currency) { - return $currency->id; - } - - // backup to default - $currency = $this->defaultCurrencyId; - - return $currency; - } - - /** - * The foreign currency is only returned when the journal has a different value from the - * currency id (see other method). - * - * @param ImportJournal $importJournal - * @param int $currencyId - * - * @see ImportSupport::getCurrencyId - * - * @return int|null - */ - private function getForeignCurrencyId(ImportJournal $importJournal, int $currencyId): ?int - { - // use given currency by import journal. - $currency = $importJournal->foreignCurrency->getTransactionCurrency(); - if (null !== $currency && (int)$currency->id !== (int)$currencyId) { - return $currency->id; - } - - // return null, because no different: - return null; - } - - /** - * The search for the opposing account is complex. Firstly, we forbid the ImportAccount to resolve into the asset - * account to prevent a situation where the transaction flows from A to A. Given the amount, we "expect" the opposing - * account to be an expense or a revenue account. However, the mapping given by the user may return something else - * entirely (usually an asset account). So whatever the expectation, the result may be anything. - * - * When the result does not match the expected type (a negative amount cannot be linked to a revenue account) the next step - * will return an error. - * - * @param ImportAccount $account - * @param int $forbiddenAccount - * @param string $amount - * - * @see ImportSupport::getTransactionType - * - * @return Account - * - * @throws FireflyException - */ - private function getOpposingAccount(ImportAccount $account, int $forbiddenAccount, string $amount): Account - { - $account->setForbiddenAccountId($forbiddenAccount); - if (bccomp($amount, '0') === -1) { - Log::debug(sprintf('%s is negative, create opposing expense account.', $amount)); - $account->setExpectedType(AccountType::EXPENSE); - - return $account->getAccount(); - } - Log::debug(sprintf('%s is positive, create opposing revenue account.', $amount)); - // amount is positive, it's a deposit, opposing is an revenue: - $account->setExpectedType(AccountType::REVENUE); - - return $account->getAccount(); - } - - /** - * @return Collection - */ - private function getRules(): Collection - { - $set = Rule::distinct() - ->where('rules.user_id', $this->job->user->id) - ->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id') - ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') - ->where('rule_groups.active', 1) - ->where('rule_triggers.trigger_type', 'user_action') - ->where('rule_triggers.trigger_value', 'store-journal') - ->where('rules.active', 1) - ->orderBy('rule_groups.order', 'ASC') - ->orderBy('rules.order', 'ASC') - ->get(['rules.*', 'rule_groups.order']); - Log::debug(sprintf('Found %d user rules.', $set->count())); - - return $set; - } - - /** - * Given the amount and the opposing account its easy to define which kind of transaction type should be associated with the new - * import. This may however fail when there is an unexpected mismatch between the transaction type and the opposing account. - * - * @param string $amount - * @param Account $account - * - * @return string - *x - * - * @throws FireflyException - * - * @see ImportSupport::getOpposingAccount() - */ - private function getTransactionType(string $amount, Account $account): string - { - $transactionType = TransactionType::WITHDRAWAL; - // amount is negative, it's a withdrawal, opposing is an expense: - if (bccomp($amount, '0') === -1) { - $transactionType = TransactionType::WITHDRAWAL; - } - - if (1 === bccomp($amount, '0')) { - $transactionType = TransactionType::DEPOSIT; - } - - // if opposing is an asset account, it's a transfer: - if (AccountType::ASSET === $account->accountType->type) { - Log::debug(sprintf('Opposing account #%d %s is an asset account, make transfer.', $account->id, $account->name)); - $transactionType = TransactionType::TRANSFER; - } - - // verify that opposing account is of the correct type: - if (AccountType::EXPENSE === $account->accountType->type && TransactionType::WITHDRAWAL !== $transactionType) { - $message = 'This row is imported as a withdrawal but opposing is an expense account. This cannot be!'; - Log::error($message); - throw new FireflyException($message); - } - - return $transactionType; - } - - /** - * This method returns a collection of the current transfers in the system and some meta data for - * this set. This can later be used to see if the journal that firefly is trying to import - * is not already present. - * - * @return array - */ - private function getTransfers(): array - { - $set = TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin( - 'transactions AS source', - function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 'source.transaction_journal_id')->where('source.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions AS destination', - function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 'destination.transaction_journal_id')->where( - 'destination.amount', - '>', - 0 - ); - } - ) - ->leftJoin('accounts as source_accounts', 'source.account_id', '=', 'source_accounts.id') - ->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id') - ->where('transaction_journals.user_id', $this->job->user_id) - ->where('transaction_types.type', TransactionType::TRANSFER) - ->get( - ['transaction_journals.id', 'transaction_journals.encrypted', 'transaction_journals.description', - 'source_accounts.name as source_name', 'destination_accounts.name as destination_name', 'destination.amount', - 'transaction_journals.date',] - ); - $array = []; - /** @var TransactionJournal $entry */ - foreach ($set as $entry) { - $original = [app('steam')->tryDecrypt($entry->source_name), app('steam')->tryDecrypt($entry->destination_name)]; - sort($original); - $array[] = [ - 'names' => $original, - 'amount' => $entry->amount, - 'date' => $entry->date->format('Y-m-d'), - 'description' => $entry->description, - ]; - } - - return $array; - } - - /** - * Checks if the import journal has not been imported before. - * - * @param string $hash - * - * @return bool - */ - private function hashAlreadyImported(string $hash): bool - { - $json = json_encode($hash); - /** @var TransactionJournalMeta $entry */ - $entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') - ->where('data', $json) - ->where('name', 'importHash') - ->first(); - if (null !== $entry) { - Log::error(sprintf('A journal with hash %s has already been imported (spoiler: it\'s journal #%d)', $hash, $entry->transaction_journal_id)); - - return true; - } - - return false; - } -} diff --git a/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php b/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php index 3d929d3db6..2f3f208c93 100644 --- a/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php +++ b/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php @@ -24,15 +24,19 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\JobConfiguration\Bunq; use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use Illuminate\Support\MessageBag; use Log; /** - * @codeCoverageIgnore * Class NewBunqJobHandler */ class NewBunqJobHandler implements BunqJobConfigurationInterface { + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; /** * Return true when this stage is complete. @@ -41,12 +45,16 @@ class NewBunqJobHandler implements BunqJobConfigurationInterface */ public function configurationComplete(): bool { - Log::debug('NewBunqJobHandler::configurationComplete always returns true.'); + // simply set the job configuration "apply-rules" to true. + $config = $this->repository->getConfiguration($this->importJob); + $config['apply-rules'] = true; + $this->repository->setConfiguration($this->importJob, $config); return true; } /** + * @codeCoverageIgnore * Store the job configuration. * * @param array $data @@ -61,6 +69,7 @@ class NewBunqJobHandler implements BunqJobConfigurationInterface } /** + * @codeCoverageIgnore * Get data for config view. * * @return array @@ -73,6 +82,7 @@ class NewBunqJobHandler implements BunqJobConfigurationInterface } /** + * @codeCoverageIgnore * Get the view for this stage. * * @return string @@ -91,6 +101,8 @@ class NewBunqJobHandler implements BunqJobConfigurationInterface */ public function setImportJob(ImportJob $importJob): void { - Log::debug('NewBunqJobHandler::setImportJob does nothing.'); + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); } } \ No newline at end of file diff --git a/tests/Unit/Support/Import/JobConfiguration/Bunq/NewBunqJobHandlerTest.php b/tests/Unit/Support/Import/JobConfiguration/Bunq/NewBunqJobHandlerTest.php new file mode 100644 index 0000000000..45f7fe62a9 --- /dev/null +++ b/tests/Unit/Support/Import/JobConfiguration/Bunq/NewBunqJobHandlerTest.php @@ -0,0 +1,67 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Support\Import\JobConfiguration\Bunq; + + +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Support\Import\JobConfiguration\Bunq\NewBunqJobHandler; +use Mockery; +use Tests\TestCase; + +class NewBunqJobHandlerTest extends TestCase +{ + /** + * @covers \FireflyIII\Support\Import\JobConfiguration\Bunq\NewBunqJobHandler + */ + public function testCC(): void + { + $job = new ImportJob; + $job->user_id = $this->user()->id; + $job->key = 'cXha' . random_int(1, 1000); + $job->status = 'new'; + $job->stage = 'new'; + $job->provider = 'bunq'; + $job->file_type = ''; + $job->configuration = []; + $job->save(); + + // expected config: + $expected = [ + 'apply-rules' => true, + ]; + + // mock stuff + $repository = $this->mock(ImportJobRepositoryInterface::class); + // mock calls + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('getConfiguration')->andReturn([])->once(); + $repository->shouldReceive('setConfiguration')->withArgs([Mockery::any(), $expected])->once(); + + $handler = new NewBunqJobHandler(); + $handler->setImportJob($job); + $this->assertTrue($handler->configurationComplete()); + } + +} \ No newline at end of file From 66fa73aea471ce4861c1147b98856e1159a1e56c Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 05:49:41 +0200 Subject: [PATCH 153/182] Fix tests. --- .../Controllers/Transaction/SplitControllerTest.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/Feature/Controllers/Transaction/SplitControllerTest.php b/tests/Feature/Controllers/Transaction/SplitControllerTest.php index 785eeae4e9..37277fcbd0 100644 --- a/tests/Feature/Controllers/Transaction/SplitControllerTest.php +++ b/tests/Feature/Controllers/Transaction/SplitControllerTest.php @@ -97,9 +97,6 @@ class SplitControllerTest extends TestCase $journalRepos->shouldReceive('getJournalTotal')->andReturn('0'); $journalRepos->shouldReceive('getJournalCategoryName')->andReturn('Some'); - // mock for new account list and for account array - $accountRepos->shouldReceive('getAccountsByType') - ->withArgs([[AccountType::ASSET, AccountType::DEFAULT]])->andReturn(new Collection([$account]))->twice(); $currencyRepository->shouldReceive('get')->once()->andReturn(new Collection); $budgetRepository->shouldReceive('getActiveBudgets')->andReturn(new Collection); @@ -138,10 +135,6 @@ class SplitControllerTest extends TestCase $currencyRepository->shouldReceive('get')->once()->andReturn(new Collection); $budgetRepository->shouldReceive('getActiveBudgets')->andReturn(new Collection); - // mock for new account list and for account array - $accountRepos->shouldReceive('getAccountsByType') - ->withArgs([[AccountType::ASSET, AccountType::DEFAULT]])->andReturn(new Collection([$account]))->twice(); - $journalRepos->shouldReceive('firstNull')->once()->andReturn($deposit); $journalRepos->shouldReceive('getJournalSourceAccounts')->andReturn(new Collection([$account])); @@ -270,9 +263,6 @@ class SplitControllerTest extends TestCase $currencyRepository->shouldReceive('get')->once()->andReturn(new Collection); $budgetRepository->shouldReceive('getActiveBudgets')->andReturn(new Collection); - // mock for new account list and for account array - $accountRepository->shouldReceive('getAccountsByType') - ->withArgs([[AccountType::ASSET, AccountType::DEFAULT]])->andReturn(new Collection([$account]))->twice(); $this->be($this->user()); $response = $this->get(route('transactions.split.edit', [$deposit->id])); From 3654e75b8c82214b5ea4336fe335077d110c37a2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 06:40:24 +0200 Subject: [PATCH 154/182] Improve test coverage. --- tests/Unit/Import/Routine/BunqRoutineTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Import/Routine/BunqRoutineTest.php b/tests/Unit/Import/Routine/BunqRoutineTest.php index b07615ba29..eb53266989 100644 --- a/tests/Unit/Import/Routine/BunqRoutineTest.php +++ b/tests/Unit/Import/Routine/BunqRoutineTest.php @@ -45,7 +45,7 @@ class BunqRoutineTest extends TestCase { $job = new ImportJob; $job->user_id = $this->user()->id; - $job->key = 'br_' . random_int(1, 1000); + $job->key = 'brY_' . random_int(1, 1000); $job->status = 'ready_to_run'; $job->stage = 'go-for-import'; $job->provider = 'bunq'; @@ -84,7 +84,7 @@ class BunqRoutineTest extends TestCase { $job = new ImportJob; $job->user_id = $this->user()->id; - $job->key = 'br_' . random_int(1, 1000); + $job->key = 'brX_' . random_int(1, 1000); $job->status = 'ready_to_run'; $job->stage = 'new'; $job->provider = 'bunq'; From 4c04415e80a69f23c9eb77fca2ea6e546a929474 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 13:11:10 +0200 Subject: [PATCH 155/182] Fix #1452. --- app/Http/Controllers/TagController.php | 23 ++++++------------- app/Models/Tag.php | 2 ++ app/Repositories/Tag/TagRepository.php | 12 ++++++++-- .../Tag/TagRepositoryInterface.php | 6 +++++ .../Feature/Controllers/TagControllerTest.php | 2 ++ 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index 96dbe6d289..8857bd8ea0 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -145,27 +145,18 @@ class TagController extends Controller public function index(TagRepositoryInterface $repository) { // start with oldest tag - $oldestTag = $repository->oldestTag(); - /** @var Carbon $start */ - $start = new Carbon; - if (null !== $oldestTag) { - /** @var Carbon $start */ - $start = $oldestTag->date; // @codeCoverageIgnore - } - if (null === $oldestTag) { - /** @var Carbon $start */ - $start = clone session('first'); - } - - $now = new Carbon; + $oldestTagDate = null === $repository->oldestTag() ? clone session('first') : $repository->oldestTag()->date; + $newestTagDate = null === $repository->newestTag() ? new Carbon : $repository->newestTag()->date; + $oldestTagDate->startOfYear(); + $newestTagDate->endOfYear(); $clouds = []; $clouds['no-date'] = $repository->tagCloud(null); - while ($now > $start) { - $year = $now->year; + while ($newestTagDate > $oldestTagDate) { + $year = $newestTagDate->year; $clouds[$year] = $repository->tagCloud($year); - $now->subYear(); + $newestTagDate->subYear(); } $count = $repository->count(); diff --git a/app/Models/Tag.php b/app/Models/Tag.php index cb7861d845..9a085b0325 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -32,9 +32,11 @@ use FireflyIII\Models\TransactionJournal; /** * Class Tag. + * * @property Collection $transactionJournals * @property string $tag * @property int $id + * @property \Carbon\Carbon $date */ class Tag extends Model { diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index 0bd3980bf9..90c545cb58 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -336,7 +336,6 @@ class TagRepository implements TagRepositoryInterface } ) ->groupBy(['tags.id', 'tags.tag']); - // add date range (or not): if (null === $year) { Log::debug('Get tags without a date.'); @@ -464,4 +463,13 @@ class TagRepository implements TagRepositoryInterface { return $this->user->tags()->find($tagId); } -} + + /** + * Will return the newest tag (if known) or NULL. + * + * @return Tag|null + */ + public function newestTag(): ?Tag + { + return $this->user->tags()->whereNotNull('date')->orderBy('date', 'DESC')->first(); +}} diff --git a/app/Repositories/Tag/TagRepositoryInterface.php b/app/Repositories/Tag/TagRepositoryInterface.php index ab5c537575..e4e8b44ba5 100644 --- a/app/Repositories/Tag/TagRepositoryInterface.php +++ b/app/Repositories/Tag/TagRepositoryInterface.php @@ -116,10 +116,16 @@ interface TagRepositoryInterface public function lastUseDate(Tag $tag): Carbon; /** + * Will return the newest tag (if known) or NULL. * @return Tag|null */ public function oldestTag(): ?Tag; + /** + * Will return the newest tag (if known) or NULL. + * @return Tag|null + */ + public function newestTag(): ?Tag; /** * @param User $user */ diff --git a/tests/Feature/Controllers/TagControllerTest.php b/tests/Feature/Controllers/TagControllerTest.php index 29fbf5046f..7efd4a623c 100644 --- a/tests/Feature/Controllers/TagControllerTest.php +++ b/tests/Feature/Controllers/TagControllerTest.php @@ -130,6 +130,8 @@ class TagControllerTest extends TestCase $repository->shouldReceive('count')->andReturn(0); $repository->shouldReceive('tagCloud')->andReturn([]); $repository->shouldReceive('oldestTag')->andReturn(null)->once(); + $repository->shouldReceive('newestTag')->andReturn(null)->once(); + $this->be($this->user()); $response = $this->get(route('tags.index')); From e47e6b1958423d322c5adf245160f168a95f48f3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 13:11:23 +0200 Subject: [PATCH 156/182] Fix null pointer. #1443 --- app/Import/JobConfiguration/BunqJobConfiguration.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Import/JobConfiguration/BunqJobConfiguration.php b/app/Import/JobConfiguration/BunqJobConfiguration.php index e2f29cea69..9ea6382b4d 100644 --- a/app/Import/JobConfiguration/BunqJobConfiguration.php +++ b/app/Import/JobConfiguration/BunqJobConfiguration.php @@ -111,6 +111,7 @@ class BunqJobConfiguration implements JobConfigurationInterface switch ($this->importJob->stage) { case 'new'; $handler = app(NewBunqJobHandler::class); + $handler->setImportJob($this->importJob); break; case 'choose-accounts': /** @var ChooseAccountsHandler $handler */ From 0064d060ea70e8d4db6c16908090f354e376d133 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 1 Jun 2018 13:14:51 +0200 Subject: [PATCH 157/182] Show bunq payment ID. #1443 --- resources/lang/en_US/list.php | 1 + resources/views/transactions/show.twig | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index 4416196368..bd11777b11 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -122,4 +122,5 @@ return [ 'spectre_bank' => 'Bank', 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', + 'bunq_payment_id' => 'bunq payment ID', ]; diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index e66b3ff28d..439c29f036 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -210,7 +210,7 @@ {% endif %} {% endfor %} {# all other meta values #} - {% for metaField in ['external_id','internal_reference','sepa-ct-id','sepa-ct-op','sepa-db','sepa-country','sepa-cc','sepa-ep','sepa-ci'] %} + {% for metaField in ['external_id','bunq_payment_id','internal_reference','sepa-ct-id','sepa-ct-op','sepa-db','sepa-country','sepa-cc','sepa-ep','sepa-ci'] %} {% if journalHasMeta(journal, metaField) %}
  • {{ trans('list.'~metaField) }}