New code for YNAB import.

This commit is contained in:
James Cole
2018-07-29 21:02:03 +02:00
parent 7ad09da4e9
commit dfd9cf0874
19 changed files with 1341 additions and 264 deletions

View File

@@ -132,11 +132,12 @@ class JobStatusController extends Controller
*/ */
public function start(ImportJob $importJob): JsonResponse public function start(ImportJob $importJob): JsonResponse
{ {
Log::debug('Now in JobStatusController::start');
// catch impossible status: // catch impossible status:
$allowed = ['ready_to_run', 'need_job_config']; $allowed = ['ready_to_run', 'need_job_config'];
if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) {
Log::error('Job is not ready.'); Log::error(sprintf('Job is not ready. Status should be in array, but is %s', $importJob->status), $allowed);
$this->repository->setStatus($importJob, 'error'); $this->repository->setStatus($importJob, 'error');
return response()->json( return response()->json(
@@ -157,7 +158,11 @@ class JobStatusController extends Controller
/** @var RoutineInterface $routine */ /** @var RoutineInterface $routine */
$routine = app($className); $routine = app($className);
$routine->setImportJob($importJob); $routine->setImportJob($importJob);
Log::debug(sprintf('Created class of type %s', $className));
try { try {
Log::debug(sprintf('Try to call %s:run()', $className));
$routine->run(); $routine->run();
} catch (FireflyException|Exception $e) { } catch (FireflyException|Exception $e) {
$message = 'The import routine crashed: ' . $e->getMessage(); $message = 'The import routine crashed: ' . $e->getMessage();
@@ -189,7 +194,7 @@ class JobStatusController extends Controller
// catch impossible status: // catch impossible status:
$allowed = ['provider_finished', 'storing_data']; $allowed = ['provider_finished', 'storing_data'];
if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) {
Log::error('Job is not ready.'); Log::error(sprintf('Job is not ready. Status should be in array, but is %s', $importJob->status), $allowed);
return response()->json( return response()->json(
['status' => 'NOK', 'message' => sprintf('JobStatusController::start expects status "provider_finished" instead of "%s".', $importJob->status)] ['status' => 'NOK', 'message' => sprintf('JobStatusController::start expects status "provider_finished" instead of "%s".', $importJob->status)]

View File

@@ -23,8 +23,12 @@ declare(strict_types=1);
namespace FireflyIII\Import\JobConfiguration; namespace FireflyIII\Import\JobConfiguration;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob; use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Support\Import\JobConfiguration\Ynab\NewYnabJobHandler;
use FireflyIII\Support\Import\JobConfiguration\Ynab\SelectBudgetsHandler;
use FireflyIII\Support\Import\JobConfiguration\Ynab\YnabJobConfigurationInterface;
use Illuminate\Support\MessageBag; use Illuminate\Support\MessageBag;
use Log; use Log;
@@ -33,6 +37,8 @@ use Log;
*/ */
class YnabJobConfiguration implements JobConfigurationInterface class YnabJobConfiguration implements JobConfigurationInterface
{ {
/** @var YnabJobConfigurationInterface The job handler. */
private $handler;
/** @var ImportJob The import job */ /** @var ImportJob The import job */
private $importJob; private $importJob;
/** @var ImportJobRepositoryInterface Import job repository */ /** @var ImportJobRepositoryInterface Import job repository */
@@ -45,16 +51,7 @@ class YnabJobConfiguration implements JobConfigurationInterface
*/ */
public function configurationComplete(): bool public function configurationComplete(): bool
{ {
// config is only needed when the job is in stage "new". return $this->handler->configurationComplete();
if ($this->importJob->stage === 'new') {
Log::debug('YNAB configurationComplete: stage is new, return false');
return false;
}
Log::debug('YNAB configurationComplete: stage is not new, return true');
return true;
} }
/** /**
@@ -67,10 +64,7 @@ class YnabJobConfiguration implements JobConfigurationInterface
*/ */
public function configureJob(array $data): MessageBag public function configureJob(array $data): MessageBag
{ {
Log::debug('YNAB configureJob: nothing to do.'); return $this->handler->configureJob($data);
// there is never anything to store from this job.
return new MessageBag;
} }
/** /**
@@ -80,24 +74,7 @@ class YnabJobConfiguration implements JobConfigurationInterface
*/ */
public function getNextData(): array public function getNextData(): array
{ {
$data = []; return $this->handler->getNextData();
// here we update the job so it can redirect properly to YNAB
if ($this->importJob->stage === 'new') {
// update stage to make sure we catch the token.
$this->repository->setStage($this->importJob, 'catch-auth-code');
$clientId = (string)config('import.options.ynab.client_id');
$callBackUri = route('import.callback.ynab');
$uri = sprintf(
'https://app.youneedabudget.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=%s', $clientId, $callBackUri,
$this->importJob->key
);
$data['token-url'] = $uri;
Log::debug(sprintf('YNAB getNextData: URI to redirect to is %s', $uri));
}
return $data;
} }
/** /**
@@ -107,19 +84,53 @@ class YnabJobConfiguration implements JobConfigurationInterface
*/ */
public function getNextView(): string public function getNextView(): string
{ {
Log::debug('Return YNAB redirect view.'); return $this->handler->getNextView();
return 'import.ynab.redirect';
} }
/** /**
* Set import job. * Set import job.
* *
* @param ImportJob $importJob * @param ImportJob $importJob
*
* @throws FireflyException
*/ */
public function setImportJob(ImportJob $importJob): void public function setImportJob(ImportJob $importJob): void
{ {
$this->importJob = $importJob; $this->importJob = $importJob;
$this->repository = app(ImportJobRepositoryInterface::class); $this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($importJob->user); $this->repository->setUser($importJob->user);
$this->handler = $this->getHandler();
}
/**
* Get correct handler.
*
* @return YnabJobConfigurationInterface
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
private function getHandler(): YnabJobConfigurationInterface
{
Log::debug(sprintf('Now in YnabJobConfiguration::getHandler() with stage "%s"', $this->importJob->stage));
$handler = null;
switch ($this->importJob->stage) {
case 'new':
/** @var NewYnabJobHandler $handler */
$handler = app(NewYnabJobHandler::class);
$handler->setImportJob($this->importJob);
break;
case 'select_budgets':
/** @var SelectBudgetsHandler $handler */
$handler = app(SelectBudgetsHandler::class);
$handler->setImportJob($this->importJob);
break;
default:
// @codeCoverageIgnoreStart
throw new FireflyException(sprintf('Firefly III cannot create a YNAB configuration handler for stage "%s"', $this->importJob->stage));
// @codeCoverageIgnoreEnd
}
return $handler;
} }
} }

View File

@@ -26,7 +26,9 @@ namespace FireflyIII\Import\Routine;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob; use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Support\Import\Routine\Ynab\GetAccountsHandler;
use FireflyIII\Support\Import\Routine\Ynab\StageGetAccessHandler; use FireflyIII\Support\Import\Routine\Ynab\StageGetAccessHandler;
use FireflyIII\Support\Import\Routine\Ynab\StageGetBudgetsHandler;
use Log; use Log;
/** /**
@@ -61,12 +63,69 @@ class YnabRoutine implements RoutineInterface
$handler = app(StageGetAccessHandler::class); $handler = app(StageGetAccessHandler::class);
$handler->setImportJob($this->importJob); $handler->setImportJob($this->importJob);
$handler->run(); $handler->run();
$this->repository->setStage($this->importJob, 'get_transactions');
// back to correct stage:
$this->repository->setStatus($this->importJob, 'ready_to_run');
$this->repository->setStage($this->importJob, 'get_budgets');
return; return;
} }
if ('get_budgets' === $this->importJob->stage) {
$this->repository->setStatus($this->importJob, 'running');
/** @var StageGetBudgetsHandler $handler */
$handler = app(StageGetBudgetsHandler::class);
$handler->setImportJob($this->importJob);
$handler->run();
// count budgets in job, to determine next step.
$configuration = $this->repository->getConfiguration($this->importJob);
$budgets = $configuration['budgets'] ?? [];
// if more than 1 budget, select budget first.
if (\count($budgets) > 0) { // TODO should be 1
$this->repository->setStage($this->importJob, 'select_budgets');
$this->repository->setStatus($this->importJob, 'need_job_config');
return;
}
if (\count($budgets) === 1) {
$this->repository->setStage($this->importJob, 'match_accounts');
}
return;
}
if('get_accounts' === $this->importJob->stage) {
$this->repository->setStatus($this->importJob, 'running');
/** @var GetAccountsHandler $handler */
$handler = app(GetAccountsHandler::class);
$handler->setImportJob($this->importJob);
$handler->run();
$this->repository->setStage($this->importJob, 'select_accounts');
$this->repository->setStatus($this->importJob, 'need_job_config');
}
// if ('match_accounts' === $this->importJob->stage) {
// // $this->repository->setStatus($this->importJob, 'running');
// /** @var StageGetBudgetsHandler $handler */
// $handler = app(StageGetBudgetsHandler::class);
// $handler->setImportJob($this->importJob);
// $handler->run();
// $this->repository->setStage($this->importJob, 'get_transactions');
// }
//
// if ('get_transactions' === $this->importJob->stage) {
// // $this->repository->setStatus($this->importJob, 'running');
// /** @var StageGetBudgetsHandler $handler */
// $handler = app(StageGetBudgetsHandler::class);
// $handler->setImportJob($this->importJob);
// $handler->run();
// $this->repository->setStage($this->importJob, 'get_transactions');
// }
throw new FireflyException(sprintf('YNAB import routine cannot handle stage "%s"', $this->importJob->stage)); throw new FireflyException(sprintf('YNAB import routine cannot handle stage "%s"', $this->importJob->stage));
} }
throw new FireflyException(sprintf('YNAB import routine cannot handle status "%s"', $this->importJob->status));
} }
/** /**

View File

@@ -184,7 +184,6 @@ class ImportJobRepository implements ImportJobRepositoryInterface
$newConfig = array_merge($currentConfig, $configuration); $newConfig = array_merge($currentConfig, $configuration);
$job->configuration = $newConfig; $job->configuration = $newConfig;
$job->save(); $job->save();
//Log::debug(sprintf('Set config of job "%s" to: ', $job->key), $newConfig); //Log::debug(sprintf('Set config of job "%s" to: ', $job->key), $newConfig);
return $job; return $job;

View File

@@ -0,0 +1,53 @@
<?php
/**
* GetAccountsRequest.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Services\Ynab\Request;
use Log;
/**
* Class GetAccountsRequest
*/
class GetAccountsRequest extends YnabRequest
{
/** @var array */
public $accounts;
/** @var string */
public $budgetId;
/**
*
*/
public function call(): void
{
Log::debug('Now in GetAccountsRequest::call()');
$uri = $this->api . sprintf('/budgets/%s/accounts', $this->budgetId);
Log::debug(sprintf('URI is %s', $uri));
$result = $this->authenticatedGetRequest($uri, []);
// expect data in [data][accounts]
$this->accounts = $result['data']['accounts'] ?? [];
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* GetBudgetsRequest.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Services\Ynab\Request;
use Log;
/**
* Class GetBudgetsRequest
*/
class GetBudgetsRequest extends YnabRequest
{
/** @var array */
public $budgets;
public function __construct()
{
parent::__construct();
$this->budgets = [];
}
/**
*
*/
public function call(): void
{
Log::debug('Now in GetBudgetsRequest::call()');
$uri = $this->api . '/budgets';
Log::debug(sprintf('URI is %s', $uri));
$result = $this->authenticatedGetRequest($uri, []);
// expect data in [data][budgets]
$rawBudgets = $result['data']['budgets'] ?? [];
$freshBudgets = [];
foreach ($rawBudgets as $rawBudget) {
$freshBudgets[] = [
'id' => $rawBudget['id'],
'name' => $rawBudget['name'],
'currency_code' => $rawBudget['currency_format']['iso_code'],
];
}
$this->budgets = $freshBudgets;
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* YnabRequest.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Services\Ynab\Request;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Log;
use RuntimeException;
/**
* Class YnabRequest
*/
abstract class YnabRequest
{
/** @var string */
protected $api;
/** @var string */
protected $token;
public function __construct()
{
$this->api = 'https://' . config('import.options.ynab.live') . '/' . config('import.options.ynab.version');
}
/**
* @param string $uri
* @param array|null $params
*
* @return array
*/
public function authenticatedGetRequest(string $uri, array $params = null): array
{
Log::debug(sprintf('Now in YnabRequest::authenticatedGetRequest(%s)', $uri), $params);
$client = new Client;
$params = $params ?? [];
$options = [
'headers' => [
'Authorization' => 'Bearer ' . $this->token,
],
];
if (\count($params) > 0) {
$uri = $uri . '?' . http_build_query($params);
}
Log::debug(sprintf('Going to call YNAB on URI: %s', $uri), $options);
try {
$res = $client->request('get', $uri, $options);
} catch (GuzzleException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
return [];
}
try {
$content = trim($res->getBody()->getContents());
} catch (RuntimeException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
return [];
}
return json_decode($content, true) ?? [];
}
/**
*
*/
abstract public function call(): void;
/**
* @param string $token
*/
public function setAccessToken(string $token): void
{
$this->token = $token;
}
}

View File

@@ -0,0 +1,252 @@
<?php
/**
* NewYnabJobHandler.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\JobConfiguration\Ynab;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\MessageBag;
use Log;
use RuntimeException;
/**
* Class NewYnabJobHandler
*/
class NewYnabJobHandler implements YnabJobConfigurationInterface
{
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $repository;
/**
* Return true when this stage is complete.
*
* @return bool
* @throws FireflyException
*/
public function configurationComplete(): bool
{
if (!$this->hasRefreshToken()) {
Log::debug('YNAB NewYnabJobHandler configurationComplete: stage is new, no refresh token, return false');
return false;
}
if ($this->hasRefreshToken() && $this->hasClientId() && $this->hasClientSecret()) {
Log::debug('YNAB NewYnabJobHandler configurationComplete: stage is new, has a refresh token, return true');
// need to grab access token using refresh token
$this->getAccessToken();
$this->repository->setStatus($this->importJob, 'ready_to_run');
$this->repository->setStage($this->importJob, 'get_budgets');
return true;
}
Log::error('YNAB NewYnabJobHandler configurationComplete: something broke, return true');
return true;
}
/**
* Store the job configuration. There is never anything to store for this stage.
*
* @param array $data
*
* @return MessageBag
*/
public function configureJob(array $data): MessageBag
{
Log::debug('YNAB NewYnabJobHandler configureJob: nothing to do.');
return new MessageBag;
}
/**
* Get data for config view.
*
* @return array
*/
public function getNextData(): array
{
$data = [];
// here we update the job so it can redirect properly to YNAB
if (!$this->hasRefreshToken() && $this->hasClientSecret() && $this->hasClientId()) {
// update stage to make sure we catch the token.
$this->repository->setStage($this->importJob, 'catch-auth-code');
$clientId = app('preferences')->get('ynab_client_id')->data;
$callBackUri = route('import.callback.ynab');
$uri = sprintf(
'https://app.youneedabudget.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=%s', $clientId, $callBackUri,
$this->importJob->key
);
$data['token-url'] = $uri;
Log::debug(sprintf('YNAB getNextData: URI to redirect to is %s', $uri));
}
return $data;
}
/**
* Get the view for this stage.
*
* @return string
*/
public function getNextView(): string
{
Log::debug('Return YNAB redirect view.');
return 'import.ynab.redirect';
}
/**
* Set the import job.
*
* @param ImportJob $importJob
*/
public function setImportJob(ImportJob $importJob): void
{
$this->importJob = $importJob;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($importJob->user);
}
/**
* @throws FireflyException
*/
private function getAccessToken(): void
{
$clientId = app('preferences')->get('ynab_client_id')->data;
$clientSecret = app('preferences')->get('ynab_client_secret')->data;
$refreshToken = app('preferences')->get('ynab_refresh_token')->data;
$uri = sprintf(
'https://app.youneedabudget.com/oauth/token?client_id=%s&client_secret=%s&grant_type=refresh_token&refresh_token=%s', $clientId, $clientSecret,
$refreshToken
);
$client = new Client();
try {
$res = $client->request('post', $uri, []);
} catch (GuzzleException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage());
}
$statusCode = $res->getStatusCode();
try {
$content = trim($res->getBody()->getContents());
} catch (RuntimeException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage());
}
$json = json_decode($content, true) ?? [];
Log::debug(sprintf('Status code from YNAB is %d', $statusCode));
Log::debug(sprintf('Body of result is %s', $content), $json);
// store refresh token (if present?) as preference
// store token in job:
$configuration = $this->repository->getConfiguration($this->importJob);
$configuration['access_token'] = $json['access_token'];
$configuration['access_token_expires'] = (int)$json['created_at'] + (int)$json['expires_in'];
$this->repository->setConfiguration($this->importJob, $configuration);
// also store new refresh token:
$refreshToken = (string)($json['refresh_token'] ?? '');
if ('' !== $refreshToken) {
app('preferences')->set('ynab_refresh_token', $refreshToken);
}
Log::debug('end of NewYnabJobHandler::getAccessToken()');
}
/**
* Check if we have the client ID.
*
* @return bool
*/
private function hasClientId(): bool
{
$clientId = app('preferences')->getForUser($this->importJob->user, 'ynab_client_id', null);
if (null === $clientId) {
Log::debug('user has no YNAB client ID');
return false;
}
if ('' === (string)$clientId->data) {
Log::debug('user has no YNAB client ID (empty)');
return false;
}
Log::debug('user has a YNAB client ID');
return true;
}
/**
* Check if we have the client secret
*
* @return bool
*/
private function hasClientSecret(): bool
{
$clientSecret = app('preferences')->getForUser($this->importJob->user, 'ynab_client_secret', null);
if (null === $clientSecret) {
Log::debug('user has no YNAB client secret');
return false;
}
if ('' === (string)$clientSecret->data) {
Log::debug('user has no YNAB client secret (empty)');
return false;
}
Log::debug('user has a YNAB client secret');
return true;
}
/**
* @return bool
*/
private function hasRefreshToken(): bool
{
$preference = app('preferences')->get('ynab_refresh_token');
if (null === $preference) {
Log::debug('user has no YNAB refresh token.');
return false;
}
if ('' === (string)$preference->data) {
Log::debug('user has no YNAB refresh token (empty).');
return false;
}
Log::debug(sprintf('user has YNAB refresh token: %s', $preference->data));
return true;
}
}

View File

@@ -0,0 +1,126 @@
<?php
/**
* SelectBudgetsHandler.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\JobConfiguration\Ynab;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use Illuminate\Support\MessageBag;
use Log;
/**
* Class SelectBudgetsHandler
*/
class SelectBudgetsHandler implements YnabJobConfigurationInterface
{
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $repository;
/**
* Return true when this stage is complete.
*
* @return bool
*/
public function configurationComplete(): bool
{
Log::debug('Now in SelectBudgetsHandler::configComplete');
$configuration = $this->repository->getConfiguration($this->importJob);
$selectedBudget = $configuration['selected_budget'] ?? '';
if ($selectedBudget !== '') {
Log::debug(sprintf('Selected budget is %s, config is complete. Return true.', $selectedBudget));
$this->repository->setStage($this->importJob, 'get_accounts');
return true;
}
Log::debug('User has not selected a budget yet, config is not yet complete.');
return false;
}
/**
* Store the job configuration.
*
* @param array $data
*
* @return MessageBag
*/
public function configureJob(array $data): MessageBag
{
Log::debug('Now in SelectBudgetsHandler::configureJob');
$configuration = $this->repository->getConfiguration($this->importJob);
$configuration['selected_budget'] = $data['budget_id'];
Log::debug(sprintf('Set selected budget to %s', $data['budget_id']));
Log::debug('Mark job as ready for next stage.');
$this->repository->setConfiguration($this->importJob, $configuration);
return new MessageBag;
}
/**
* Get data for config view.
*
* @return array
*/
public function getNextData(): array
{
Log::debug('Now in SelectBudgetsHandler::getNextData');
$configuration = $this->repository->getConfiguration($this->importJob);
$budgets = $configuration['budgets'] ?? [];
$return = [];
foreach ($budgets as $budget) {
$return[$budget['id']] = $budget['name'] . ' (' . $budget['currency_code'] . ')';
}
return [
'budgets' => $return,
];
}
/**
* Get the view for this stage.
*
* @return string
*/
public function getNextView(): string
{
Log::debug('Now in SelectBudgetsHandler::getNextView');
return 'import.ynab.select-budgets';
}
/**
* Set the import job.
*
* @param ImportJob $importJob
*/
public function setImportJob(ImportJob $importJob): void
{
$this->importJob = $importJob;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($importJob->user);
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* YnabJobConfigurationInterface.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\JobConfiguration\Ynab;
use FireflyIII\Models\ImportJob;
use Illuminate\Support\MessageBag;
/**
* Interface YnabJobConfigurationInterface
*
* @package FireflyIII\Support\Import\JobConfiguration\Ynab
*/
interface YnabJobConfigurationInterface
{
/**
* Return true when this stage is complete.
*
* @return bool
*/
public function configurationComplete(): bool;
/**
* Store the job configuration.
*
* @param array $data
*
* @return MessageBag
*/
public function configureJob(array $data): MessageBag;
/**
* Get data for config view.
*
* @return array
*/
public function getNextData(): array;
/**
* Get the view for this stage.
*
* @return string
*/
public function getNextView(): string;
/**
* Set the import job.
*
* @param ImportJob $importJob
*/
public function setImportJob(ImportJob $importJob): void;
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* GetAccountsHandler.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\Routine\Ynab;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Services\Ynab\Request\GetAccountsRequest;
/**
* Class GetAccountsHandler
*/
class GetAccountsHandler
{
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $repository;
/**
* Get list of accounts for the selected budget.
*
* @throws FireflyException
*/
public function run(): void
{
$config = $this->repository->getConfiguration($this->importJob);
$selectedBudget = $config['selected_budget'] ?? '';
if ('' === $selectedBudget) {
$firstBudget = $config['budgets'][0] ?? false;
if (false === $firstBudget) {
throw new FireflyException('The configuration contains no budget. Erroring out.');
}
$selectedBudget = $firstBudget['id'];
$config['selected_budget'] = $selectedBudget;
}
$token = $config['access_token'];
$request = new GetAccountsRequest;
$request->budgetId = $selectedBudget;
$request->setAccessToken($token);
$request->call();
$config['accounts'] = $request->accounts;
$this->repository->setConfiguration($this->importJob, $config);
if (0 === \count($config['accounts'])) {
throw new FireflyException('This budget contains zero accounts.');
}
}
/**
* @param ImportJob $importJob
*/
public function setImportJob(ImportJob $importJob): void
{
$this->importJob = $importJob;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($importJob->user);
}
}

View File

@@ -30,6 +30,7 @@ use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use Log; use Log;
use RuntimeException;
/** /**
* Class StageGetAccessHandler * Class StageGetAccessHandler
@@ -66,12 +67,31 @@ class StageGetAccessHandler
throw new FireflyException($e->getMessage()); throw new FireflyException($e->getMessage());
} }
$statusCode = $res->getStatusCode(); $statusCode = $res->getStatusCode();
$content = trim($res->getBody()->getContents()); try {
$content = trim($res->getBody()->getContents());
} catch(RuntimeException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage());
}
$json = json_decode($content, true) ?? []; $json = json_decode($content, true) ?? [];
Log::debug(sprintf('Status code from YNAB is %d', $statusCode)); Log::debug(sprintf('Status code from YNAB is %d', $statusCode));
Log::debug(sprintf('Body of result is %s', $content), $json); Log::debug(sprintf('Body of result is %s', $content), $json);
Log::error('Hard exit');
exit; // store refresh token (if present?) as preference
// store token in job:
$configuration = $this->repository->getConfiguration($this->importJob);
$configuration['access_token'] = $json['access_token'];
$configuration['access_token_expires'] = (int)$json['created_at'] + (int)$json['expires_in'];
$this->repository->setConfiguration($this->importJob, $configuration);
Log::debug('end of StageGetAccessHandler::run()');
$refreshToken = (string)($json['refresh_token'] ?? '');
if ('' !== $refreshToken) {
app('preferences')->set('ynab_refresh_token', $refreshToken);
}
} }
/** /**

View File

@@ -0,0 +1,75 @@
<?php
/**
* StageGetBudgetsHandler.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\Routine\Ynab;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Services\Ynab\Request\GetBudgetsRequest;
use Log;
/**
* Class StageGetBudgetsHandler
*/
class StageGetBudgetsHandler
{
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $repository;
/**
*
* @throws FireflyException
*/
public function run(): void
{
Log::debug('Now in StageGetBudgetsHandler::run()');
// grab access token from job:
$configuration = $this->repository->getConfiguration($this->importJob);
$token = $configuration['access_token'];
$request = new GetBudgetsRequest;
$request->setAccessToken($token);
$request->call();
// store budgets in users preferences.
$configuration['budgets'] = $request->budgets;
$this->repository->setConfiguration($this->importJob, $configuration);
Log::debug(sprintf('Found %d budgets', \count($request->budgets)));
if (\count($request->budgets) === 0) {
throw new FireflyException('It seems this user has zero budgets or an error prevented Firefly III from reading them.');
}
}
/**
* @param ImportJob $importJob
*/
public function setImportJob(ImportJob $importJob): void
{
$this->importJob = $importJob;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($importJob->user);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* StageGetTransactionsHandler.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\Routine\Ynab;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
/**
* Class StageGetTransactionsHandler
*/
class StageGetTransactionsHandler
{
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $repository;
/**
*
*/
public function run(): void
{
}
/**
* @param ImportJob $importJob
*/
public function setImportJob(ImportJob $importJob): void
{
$this->importJob = $importJob;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($importJob->user);
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* StageMatchAccountsHandler.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Import\Routine\Ynab;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
/**
* Class StageMatchAccountsHandler
*/
class StageMatchAccountsHandler
{
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $repository;
/**
*
*/
public function run(): void
{
}
/**
* @param ImportJob $importJob
*/
public function setImportJob(ImportJob $importJob): void
{
$this->importJob = $importJob;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($importJob->user);
}
}

View File

@@ -172,7 +172,7 @@ class Preferences
$lastActivity = implode(',', $lastActivity); $lastActivity = implode(',', $lastActivity);
} }
$hash = md5($lastActivity); $hash = md5($lastActivity);
Log::debug(sprintf('Value of last activity is %s, hash is %s', $lastActivity, $hash)); //Log::debug(sprintf('Value of last activity is %s, hash is %s', $lastActivity, $hash));
return $hash; return $hash;
} }

View File

@@ -151,8 +151,9 @@ return [
'spectre' => [ 'spectre' => [
'server' => 'www.saltedge.com', 'server' => 'www.saltedge.com',
], ],
'ynab' => [ 'ynab' => [
'client_id' => '666db19f6c5a2299bf44999a6ea802e96a5f488c3a5c8a5cbb417b59dcf18b72', 'live' => 'api.youneedabudget.com',
'version' => 'v1',
], ],
'plaid' => [], 'plaid' => [],
'quovo' => [], 'quovo' => [],

View File

@@ -24,252 +24,259 @@ declare(strict_types=1);
return [ return [
// ALL breadcrumbs and subtitles: // ALL breadcrumbs and subtitles:
'index_breadcrumb' => 'Import data into Firefly III', 'index_breadcrumb' => 'Import data into Firefly III',
'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider',
'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre',
'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq',
'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB',
'job_configuration_breadcrumb' => 'Configuration for ":key"', 'job_configuration_breadcrumb' => 'Configuration for ":key"',
'job_status_breadcrumb' => 'Import status for ":key"', 'job_status_breadcrumb' => 'Import status for ":key"',
'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.',
'disabled_for_demo_user' => 'disabled in demo', 'disabled_for_demo_user' => 'disabled in demo',
// index page: // index page:
'general_index_title' => 'Import a file', '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_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): // import provider strings (index):
'button_fake' => 'Fake an import', 'button_fake' => 'Fake an import',
'button_file' => 'Import a file', 'button_file' => 'Import a file',
'button_bunq' => 'Import from bunq', 'button_bunq' => 'Import from bunq',
'button_spectre' => 'Import using Spectre', 'button_spectre' => 'Import using Spectre',
'button_plaid' => 'Import using Plaid', 'button_plaid' => 'Import using Plaid',
'button_yodlee' => 'Import using Yodlee', 'button_yodlee' => 'Import using Yodlee',
'button_quovo' => 'Import using Quovo', 'button_quovo' => 'Import using Quovo',
'button_ynab' => 'Import from You Need A Budget', 'button_ynab' => 'Import from You Need A Budget',
// global config box (index) // global config box (index)
'global_config_title' => 'Global import configuration', '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_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.',
// prerequisites box (index) // prerequisites box (index)
'need_prereq_title' => 'Import prerequisites', '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.', '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_fake' => 'Prerequisites for the fake provider',
'do_prereq_file' => 'Prerequisites for file imports', 'do_prereq_file' => 'Prerequisites for file imports',
'do_prereq_bunq' => 'Prerequisites for imports from bunq', 'do_prereq_bunq' => 'Prerequisites for imports from bunq',
'do_prereq_spectre' => 'Prerequisites for imports using Spectre', 'do_prereq_spectre' => 'Prerequisites for imports using Spectre',
'do_prereq_plaid' => 'Prerequisites for imports using Plaid', 'do_prereq_plaid' => 'Prerequisites for imports using Plaid',
'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee',
'do_prereq_quovo' => 'Prerequisites for imports using Quovo', 'do_prereq_quovo' => 'Prerequisites for imports using Quovo',
'do_prereq_ynab' => 'Prerequisites for imports from YNAB',
// provider config box (index) // provider config box (index)
'can_config_title' => 'Import configuration', 'can_config_title' => 'Import configuration',
'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', '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_fake' => 'Configuration for the fake provider',
'do_config_file' => 'Configuration for file imports', 'do_config_file' => 'Configuration for file imports',
'do_config_bunq' => 'Configuration for bunq imports', 'do_config_bunq' => 'Configuration for bunq imports',
'do_config_spectre' => 'Configuration for imports from Spectre', 'do_config_spectre' => 'Configuration for imports from Spectre',
'do_config_plaid' => 'Configuration for imports from Plaid', 'do_config_plaid' => 'Configuration for imports from Plaid',
'do_config_yodlee' => 'Configuration for imports from Yodlee', 'do_config_yodlee' => 'Configuration for imports from Yodlee',
'do_config_quovo' => 'Configuration for imports from Quovo', 'do_config_quovo' => 'Configuration for imports from Quovo',
// prerequisites: // prerequisites:
'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', '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_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_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 <a href="https://www.saltedge.com/clients/profile/secrets">secrets page</a>.', '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 <a href="https://www.saltedge.com/clients/profile/secrets">secrets page</a>.',
'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 <a href="https://www.saltedge.com/clients/profile/secrets">secrets page</a>.', '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 <a href="https://www.saltedge.com/clients/profile/secrets">secrets page</a>.',
'prereq_bunq_title' => 'Prerequisites for an import from bunq', '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_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 <a href="https://www.ipify.org/">the ipify service</a>. Make sure this IP address is correct, or the import will fail.', 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using <a href="https://www.ipify.org/">the ipify service</a>. Make sure this IP address is correct, or the import will fail.',
'prereq_ynab_title' => 'Prerequisites for an import from YNAB', 'prereq_ynab_title' => 'Prerequisites for an import from YNAB',
'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your <a href="https://app.youneedabudget.com/settings/developer">Developer Settings Page</a> and enter the client ID and secret on this page.', 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your <a href="https://app.youneedabudget.com/settings/developer">Developer Settings Page</a> and enter the client ID and secret on this page.',
'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the <a href="https://app.youneedabudget.com/settings/developer">Developer Settings Page</a> under the "Redirect URI(s)".', 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the <a href="https://app.youneedabudget.com/settings/developer">Developer Settings Page</a> under the "Redirect URI(s)".',
// prerequisites success messages: // prerequisites success messages:
'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!',
'prerequisites_saved_for_spectre' => 'App ID and secret stored!', 'prerequisites_saved_for_spectre' => 'App ID and secret stored!',
'prerequisites_saved_for_bunq' => 'API key and IP stored!', 'prerequisites_saved_for_bunq' => 'API key and IP stored!',
'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!',
// job configuration: // job configuration:
'job_config_apply_rules_title' => 'Job configuration - apply your rules?', '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_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_config_input' => 'Your input',
// job configuration for the fake provider: // job configuration for the fake provider:
'job_config_fake_artist_title' => 'Enter album name', '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_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_title' => 'Enter song name',
'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', '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_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_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 configuration form the file provider
'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', '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_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_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 <a href="https://github.com/firefly-iii/import-configurations/wiki">configuration file</a>', '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 <a href="https://github.com/firefly-iii/import-configurations/wiki">configuration file</a>',
'job_config_file_upload_type_help' => 'Select the type of file you will upload', 'job_config_file_upload_type_help' => 'Select the type of file you will upload',
'job_config_file_upload_submit' => 'Upload files', 'job_config_file_upload_submit' => 'Upload files',
'import_file_type_csv' => 'CSV (comma separated values)', 'import_file_type_csv' => 'CSV (comma separated values)',
'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.', '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_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_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_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 <a href="https://secure.php.net/manual/en/datetime.createfromformat.php#refsect1-datetime.createfromformat-parameters">this page</a> indicates. The default value will parse dates that look like this: :dateExample.', 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as <a href="https://secure.php.net/manual/en/datetime.createfromformat.php#refsect1-datetime.createfromformat-parameters">this page</a> 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_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_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_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_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_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_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', 'job_config_uc_submit' => 'Continue',
'invalid_import_account' => 'You have selected an invalid account to import into.', 'invalid_import_account' => 'You have selected an invalid account to import into.',
// job configuration for Spectre: // job configuration for Spectre:
'job_config_spectre_login_title' => 'Choose your login', '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?', '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_active' => 'Active',
'spectre_login_status_inactive' => 'Inactive', 'spectre_login_status_inactive' => 'Inactive',
'spectre_login_status_disabled' => 'Disabled', 'spectre_login_status_disabled' => 'Disabled',
'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', '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_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.', '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_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.',
'spectre_do_not_import' => '(do not import)', 'spectre_do_not_import' => '(do not import)',
'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.',
'imported_from_account' => 'Imported from ":account"', 'imported_from_account' => 'Imported from ":account"',
'spectre_account_with_number' => 'Account :number', 'spectre_account_with_number' => 'Account :number',
'job_config_spectre_apply_rules' => 'Apply rules', 'job_config_spectre_apply_rules' => 'Apply rules',
'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.',
// job configuration for bunq: // job configuration for bunq:
'job_config_bunq_accounts_title' => 'bunq accounts', '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.', '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.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.',
'should_download_config' => 'You should download <a href=":route">the configuration file</a> for this job. This will make future imports way easier.', 'should_download_config' => 'You should download <a href=":route">the configuration file</a> for this job. This will make future imports way easier.',
'share_config_file' => 'If you have imported data from a public bank, you should <a href="https://github.com/firefly-iii/import-configurations/wiki">share your configuration file</a> so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', 'share_config_file' => 'If you have imported data from a public bank, you should <a href="https://github.com/firefly-iii/import-configurations/wiki">share your configuration file</a> so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.',
'job_config_bunq_apply_rules' => 'Apply rules', 'job_config_bunq_apply_rules' => 'Apply rules',
'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.',
// job configuration for YNAB:
'job_config_select_budgets' => 'Select your budget',
'job_config_spectre_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.',
// keys from "extra" array: // keys from "extra" array:
'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_iban' => 'IBAN',
'spectre_extra_key_swift' => 'SWIFT', 'spectre_extra_key_swift' => 'SWIFT',
'spectre_extra_key_status' => 'Status', 'spectre_extra_key_status' => 'Status',
'spectre_extra_key_card_type' => 'Card type', 'spectre_extra_key_card_type' => 'Card type',
'spectre_extra_key_account_name' => 'Account name', 'spectre_extra_key_account_name' => 'Account name',
'spectre_extra_key_client_name' => 'Client name', 'spectre_extra_key_client_name' => 'Client name',
'spectre_extra_key_account_number' => 'Account number', 'spectre_extra_key_account_number' => 'Account number',
'spectre_extra_key_blocked_amount' => 'Blocked amount', 'spectre_extra_key_blocked_amount' => 'Blocked amount',
'spectre_extra_key_available_amount' => 'Available amount', 'spectre_extra_key_available_amount' => 'Available amount',
'spectre_extra_key_credit_limit' => 'Credit limit', 'spectre_extra_key_credit_limit' => 'Credit limit',
'spectre_extra_key_interest_rate' => 'Interest rate', 'spectre_extra_key_interest_rate' => 'Interest rate',
'spectre_extra_key_expiry_date' => 'Expiry date', 'spectre_extra_key_expiry_date' => 'Expiry date',
'spectre_extra_key_open_date' => 'Open date', 'spectre_extra_key_open_date' => 'Open date',
'spectre_extra_key_current_time' => 'Current time', 'spectre_extra_key_current_time' => 'Current time',
'spectre_extra_key_current_date' => 'Current date', 'spectre_extra_key_current_date' => 'Current date',
'spectre_extra_key_cards' => 'Cards', 'spectre_extra_key_cards' => 'Cards',
'spectre_extra_key_units' => 'Units', 'spectre_extra_key_units' => 'Units',
'spectre_extra_key_unit_price' => 'Unit price', 'spectre_extra_key_unit_price' => 'Unit price',
'spectre_extra_key_transactions_count' => 'Transaction count', 'spectre_extra_key_transactions_count' => 'Transaction count',
// specifics: // specifics:
'specific_ing_name' => 'ING NL', 'specific_ing_name' => 'ING NL',
'specific_ing_descr' => 'Create better descriptions in ING exports', 'specific_ing_descr' => 'Create better descriptions in ING exports',
'specific_sns_name' => 'SNS / Volksbank NL', 'specific_sns_name' => 'SNS / Volksbank NL',
'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files',
'specific_abn_name' => 'ABN AMRO NL', 'specific_abn_name' => 'ABN AMRO NL',
'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files',
'specific_rabo_name' => 'Rabobank NL', 'specific_rabo_name' => 'Rabobank NL',
'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files',
'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_name' => 'President\'s Choice Financial CA',
'specific_pres_descr' => 'Fixes potential problems with PC files', 'specific_pres_descr' => 'Fixes potential problems with PC files',
// job configuration for file provider (stage: roles) // job configuration for file provider (stage: roles)
'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', '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_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_submit' => 'Continue',
'job_config_roles_column_name' => 'Name of column', 'job_config_roles_column_name' => 'Name of column',
'job_config_roles_column_example' => 'Column example data', 'job_config_roles_column_example' => 'Column example data',
'job_config_roles_column_role' => 'Column data meaning', 'job_config_roles_column_role' => 'Column data meaning',
'job_config_roles_do_map_value' => 'Map these values', 'job_config_roles_do_map_value' => 'Map these values',
'job_config_roles_no_example' => 'No example data available', '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_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_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_roles_colum_count' => 'Column',
// job config for the file provider (stage: mapping): // 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_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_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_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_value' => 'Field value',
'job_config_field_mapped' => 'Mapped to', 'job_config_field_mapped' => 'Mapped to',
'map_do_not_map' => '(do not map)', 'map_do_not_map' => '(do not map)',
'job_config_map_submit' => 'Start the import', 'job_config_map_submit' => 'Start the import',
// import status page: // import status page:
'import_with_key' => 'Import with key \':key\'', 'import_with_key' => 'Import with key \':key\'',
'status_wait_title' => 'Please hold...', 'status_wait_title' => 'Please hold...',
'status_wait_text' => 'This box will disappear in a moment.', 'status_wait_text' => 'This box will disappear in a moment.',
'status_running_title' => 'The import is running', 'status_running_title' => 'The import is running',
'status_job_running' => 'Please wait, running the import...', 'status_job_running' => 'Please wait, running the import...',
'status_job_storing' => 'Please wait, storing data...', 'status_job_storing' => 'Please wait, storing data...',
'status_job_rules' => 'Please wait, running rules...', 'status_job_rules' => 'Please wait, running rules...',
'status_fatal_title' => 'Fatal error', 'status_fatal_title' => 'Fatal error',
'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', '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_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_title' => 'Import finished',
'status_finished_text' => 'The import has finished.', 'status_finished_text' => 'The import has finished.',
'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.',
'unknown_import_result' => 'Unknown import result', '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_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 <a href=":route" class="label label-success" style="font-size:100%;font-weight:normal;">:tag</a> where you can inspect it further.', 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag <a href=":route" class="label label-success" style="font-size:100%;font-weight:normal;">:tag</a> where you can inspect it further.',
'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag <a href=":route" class="label label-success" style="font-size:100%;font-weight:normal;">:tag</a> where you can inspect them further.', 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag <a href=":route" class="label label-success" style="font-size:100%;font-weight:normal;">:tag</a> where you can inspect them further.',
// general errors and warnings: // general errors and warnings:
'bad_job_status' => 'To access this page, your import job cannot have status ":status".', 'bad_job_status' => 'To access this page, your import job cannot have status ":status".',
// column roles for CSV import: // column roles for CSV import:
'column__ignore' => '(ignore this column)', 'column__ignore' => '(ignore this column)',
'column_account-iban' => 'Asset account (IBAN)', 'column_account-iban' => 'Asset account (IBAN)',
'column_account-id' => 'Asset account ID (matching FF3)', 'column_account-id' => 'Asset account ID (matching FF3)',
'column_account-name' => 'Asset account (name)', 'column_account-name' => 'Asset account (name)',
'column_account-bic' => 'Asset account (BIC)', 'column_account-bic' => 'Asset account (BIC)',
'column_amount' => 'Amount', 'column_amount' => 'Amount',
'column_amount_foreign' => 'Amount (in foreign currency)', 'column_amount_foreign' => 'Amount (in foreign currency)',
'column_amount_debit' => 'Amount (debit column)', 'column_amount_debit' => 'Amount (debit column)',
'column_amount_credit' => 'Amount (credit column)', 'column_amount_credit' => 'Amount (credit column)',
'column_amount-comma-separated' => 'Amount (comma as decimal separator)', 'column_amount-comma-separated' => 'Amount (comma as decimal separator)',
'column_bill-id' => 'Bill ID (matching FF3)', 'column_bill-id' => 'Bill ID (matching FF3)',
'column_bill-name' => 'Bill name', 'column_bill-name' => 'Bill name',
'column_budget-id' => 'Budget ID (matching FF3)', 'column_budget-id' => 'Budget ID (matching FF3)',
'column_budget-name' => 'Budget name', 'column_budget-name' => 'Budget name',
'column_category-id' => 'Category ID (matching FF3)', 'column_category-id' => 'Category ID (matching FF3)',
'column_category-name' => 'Category name', 'column_category-name' => 'Category name',
'column_currency-code' => 'Currency code (ISO 4217)', 'column_currency-code' => 'Currency code (ISO 4217)',
'column_foreign-currency-code' => 'Foreign currency code (ISO 4217)', 'column_foreign-currency-code' => 'Foreign currency code (ISO 4217)',
'column_currency-id' => 'Currency ID (matching FF3)', 'column_currency-id' => 'Currency ID (matching FF3)',
'column_currency-name' => 'Currency name (matching FF3)', 'column_currency-name' => 'Currency name (matching FF3)',
'column_currency-symbol' => 'Currency symbol (matching FF3)', 'column_currency-symbol' => 'Currency symbol (matching FF3)',
'column_date-interest' => 'Interest calculation date', 'column_date-interest' => 'Interest calculation date',
'column_date-book' => 'Transaction booking date', 'column_date-book' => 'Transaction booking date',
'column_date-process' => 'Transaction process date', 'column_date-process' => 'Transaction process date',
'column_date-transaction' => 'Date', 'column_date-transaction' => 'Date',
'column_date-due' => 'Transaction due date', 'column_date-due' => 'Transaction due date',
'column_date-payment' => 'Transaction payment date', 'column_date-payment' => 'Transaction payment date',
'column_date-invoice' => 'Transaction invoice date', 'column_date-invoice' => 'Transaction invoice date',
'column_description' => 'Description', 'column_description' => 'Description',
'column_opposing-iban' => 'Opposing account (IBAN)', 'column_opposing-iban' => 'Opposing account (IBAN)',
'column_opposing-bic' => 'Opposing account (BIC)', 'column_opposing-bic' => 'Opposing account (BIC)',
'column_opposing-id' => 'Opposing account ID (matching FF3)', 'column_opposing-id' => 'Opposing account ID (matching FF3)',
'column_external-id' => 'External ID', 'column_external-id' => 'External ID',
'column_opposing-name' => 'Opposing account (name)', 'column_opposing-name' => 'Opposing account (name)',
'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator',
'column_ing-debit-credit' => 'ING 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-id' => 'SEPA end-to-end Identifier',
'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier',
'column_sepa-db' => 'SEPA Mandate Identifier', 'column_sepa-db' => 'SEPA Mandate Identifier',
'column_sepa-cc' => 'SEPA Clearing Code', 'column_sepa-cc' => 'SEPA Clearing Code',
'column_sepa-ci' => 'SEPA Creditor Identifier', 'column_sepa-ci' => 'SEPA Creditor Identifier',
'column_sepa-ep' => 'SEPA External Purpose', 'column_sepa-ep' => 'SEPA External Purpose',
'column_sepa-country' => 'SEPA Country Code', 'column_sepa-country' => 'SEPA Country Code',
'column_tags-comma' => 'Tags (comma separated)', 'column_tags-comma' => 'Tags (comma separated)',
'column_tags-space' => 'Tags (space separated)', 'column_tags-space' => 'Tags (space separated)',
'column_account-number' => 'Asset account (account number)', 'column_account-number' => 'Asset account (account number)',
'column_opposing-number' => 'Opposing account (account number)', 'column_opposing-number' => 'Opposing account (account number)',
'column_note' => 'Note(s)', 'column_note' => 'Note(s)',
'column_internal-reference' => 'Internal reference', 'column_internal-reference' => 'Internal reference',
]; ];

View File

@@ -0,0 +1,39 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.render }}
{% endblock %}
{% block content %}
<div class="row">
<form class="form-horizontal" action="{{ route('import.job.configuration.post',[importJob.key]) }}" method="post">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('import.job_config_select_budgets') }}</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-6">
<p>
{{ trans('import.job_config_spectre_select_budgets_text', {count: data.budgets|length}) }}
</p>
{{ ExpandedForm.select('budget_id', data.budgets) }}
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn pull-right btn-success">
{{ ('submit')|_ }}
</button>
</div>
</div>
</div>
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}
{% block styles %}
{% endblock %}