Lots of new code for Spectre import.

This commit is contained in:
James Cole 2018-01-03 19:17:30 +01:00
parent 4e0319bacc
commit 5177619301
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
17 changed files with 911 additions and 114 deletions

View File

@ -172,6 +172,7 @@ class FileConfigurator implements ConfiguratorInterface
'column-mapping-complete' => false, // so mapping is not complete.
'apply-rules' => true,
'match-bills' => false,
'auto-start' => false,
];
$config = $this->job->configuration ?? [];
$finalConfig = array_merge($defaultConfig, $config);

View File

@ -22,7 +22,9 @@ declare(strict_types=1);
namespace FireflyIII\Import\Configuration;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob;
use FireflyIII\Support\Import\Configuration\Spectre\HaveAccounts;
use Log;
/**
@ -52,7 +54,29 @@ class SpectreConfigurator implements ConfiguratorInterface
*/
public function configureJob(array $data): bool
{
die('cannot store config');
$config = $this->job->configuration;
$stage = $config['stage'];
$status = $this->job->status;
Log::debug(sprintf('in getNextData(), for stage "%s".', $stage));
switch ($stage) {
case 'have-accounts':
/** @var HaveAccounts $class */
$class = app(HaveAccounts::class);
$class->setJob($this->job);
$class->storeConfiguration($data);
// update job for next step and set to "configured".
$config = $this->job->configuration;
$config['stage'] = 'have-account-mapping';
$this->job->configuration = $config;
$this->job->status = 'configured';
$this->job->save();
return true;
break;
default:
throw new FireflyException(sprintf('Cannot store configuration when job is in state "%s"', $stage));
break;
}
}
/**
@ -62,13 +86,34 @@ class SpectreConfigurator implements ConfiguratorInterface
*/
public function getNextData(): array
{
Log::debug('in getNextData(), user will be redirected next.');
// update config to tell Firefly the user is redirected.
$config = $this->job->configuration;
$config['is-redirected'] = true;
$config['stage'] = 'redirected';
$config = $this->job->configuration;
$stage = $config['stage'];
$status = $this->job->status;
Log::debug(sprintf('in getNextData(), for stage "%s".', $stage));
switch ($stage) {
case 'has-token':
// simply redirect to Spectre.
$config['is-redirected'] = true;
$config['stage'] = 'user-logged-in';
$status = 'configured';
break;
case 'have-accounts':
// use special class:
/** @var HaveAccounts $class */
$class = app(HaveAccounts::class);
$class->setJob($this->job);
$data = $class->getData();
return $data;
default:
return [];
break;
}
// update config and status:
$this->job->configuration = $config;
$this->job->status = 'configured';
$this->job->status = $status;
$this->job->save();
return $this->job->configuration;
@ -79,10 +124,22 @@ class SpectreConfigurator implements ConfiguratorInterface
*/
public function getNextView(): string
{
Log::debug('Send user redirect view');
$config = $this->job->configuration;
$stage = $config['stage'];
Log::debug(sprintf('in getNextView(), for stage "%s".', $stage));
switch ($stage) {
case 'has-token':
// redirect to Spectre.
return 'import.spectre.redirect';
break;
case 'have-accounts':
return 'import.spectre.accounts';
break;
default:
return '';
break;
// sends the user to spectre.
return 'import.spectre.redirect';
}
}
/**
@ -100,17 +157,20 @@ class SpectreConfigurator implements ConfiguratorInterface
*/
public function isJobConfigured(): bool
{
Log::debug('in isJobConfigured');
// job is configured (and can start) when token is empty:
$config = $this->job->configuration;
if ($config['has-token'] === false && $config['is-redirected'] === true) {
Log::debug('has-token is false, is-redirected is true, return true');
$stage = $config['stage'];
Log::debug(sprintf('in isJobConfigured(), for stage "%s".', $stage));
switch ($stage) {
case 'has-token':
case 'have-accounts':
Log::debug('isJobConfigured returns false');
return true;
return false;
default:
Log::debug('isJobConfigured returns true');
return true;
}
Log::debug('return false');
return false;
}
/**
@ -119,15 +179,17 @@ class SpectreConfigurator implements ConfiguratorInterface
public function setJob(ImportJob $job)
{
$defaultConfig = [
'has-token' => false,
'token' => '',
'token-expires' => 0,
'token-url' => '',
'is-redirected' => false,
'customer' => null,
'login' => null,
'stage' => 'initial',
'accounts' => [],
'has-token' => false,
'token' => '',
'token-expires' => 0,
'token-url' => '',
'is-redirected' => false,
'customer' => null,
'login' => null,
'stage' => 'initial',
'accounts' => '',
'accounts-mapped' => '',
'auto-start' => true,
];
$extendedStatus = $job->extended_status;
$extendedStatus['steps'] = 100;

View File

@ -26,12 +26,16 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Services\Spectre\Exception\DuplicatedCustomerException;
use FireflyIII\Services\Spectre\Exception\SpectreException;
use FireflyIII\Services\Spectre\Object\Account;
use FireflyIII\Services\Spectre\Object\Customer;
use FireflyIII\Services\Spectre\Object\Login;
use FireflyIII\Services\Spectre\Object\Token;
use FireflyIII\Services\Spectre\Request\CreateTokenRequest;
use FireflyIII\Services\Spectre\Request\ListAccountsRequest;
use FireflyIII\Services\Spectre\Request\ListCustomersRequest;
use FireflyIII\Services\Spectre\Request\ListLoginsRequest;
use FireflyIII\Services\Spectre\Request\ListTransactionsRequest;
use FireflyIII\Services\Spectre\Request\NewCustomerRequest;
use Illuminate\Support\Collection;
use Log;
@ -99,6 +103,8 @@ class SpectreRoutine implements RoutineInterface
* 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".
*
@ -111,90 +117,35 @@ class SpectreRoutine implements RoutineInterface
public function run(): bool
{
if ('configured' === $this->job->status) {
$this->repository->updateStatus($this->job,'running');
$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!
$config = $this->job->configuration;
$hasToken = $config['has-token'] ?? false;
if ($hasToken === false) {
Log::debug('Job has no token');
// 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()));
$config = $this->job->configuration;
$stage = $config['stage'];
// update job, give it the token:
$config = $this->job->configuration;
$config['has-token'] = true;
$config['token'] = $token->getToken();
$config['token-expires'] = $token->getExpiresAt()->format('U');
$config['token-url'] = $token->getConnectUrl();
$this->job->configuration = $config;
Log::debug('Job config is now', $config);
// update job, set status to "configuring".
$this->job->status = 'configuring';
$this->job->save();
Log::debug(sprintf('Job status is now %s', $this->job->status));
return true;
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));
}
$isRedirected = $config['is-redirected'] ?? false;
if ($isRedirected === true) {
// update job to say it's running
$extended = $this->job->extended_status;
$this->job->status = 'running';
$extended['steps'] = 100;
$extended['done'] = 1;
$this->job->extended_status = $extended;
$this->job->save();
}
// is job running?
if ($this->job->status === 'running') {
// 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 = intval($attempt->getCreatedAt()->format('U'));
if($attemptTime > $time && is_null($attempt->getFailErrorClass())) {
$time = $attemptTime;
$final = $login;
}
}
if(is_null($final)) {
throw new FireflyException('No valid login attempt found.');
}
var_dump($final);
//var_dump($logins);
exit;
// assume user has "used" the token.
// ...
// now what?
Log::debug('Token has been used. User was redirected. Now check status with Spectre and respond?');
throw new FireflyException('Application cannot handle this.');
}
var_dump($config);
exit;
throw new FireflyException('Application cannot handle this.');
}
@ -204,7 +155,7 @@ class SpectreRoutine implements RoutineInterface
*/
public function setJob(ImportJob $job)
{
$this->job = $job;
$this->job = $job;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
}
@ -289,4 +240,124 @@ class SpectreRoutine implements RoutineInterface
return $request->getToken();
}
/**
* @throws FireflyException
* @throws SpectreException
*/
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->job->configuration;
$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->job->configuration = $config;
Log::debug('Job config is now', $config);
// update job, set status to "configuring".
$this->job->status = 'configuring';
$this->job->save();
Log::debug(sprintf('Job status is now %s', $this->job->status));
}
/**
* @throws FireflyException
* @throws SpectreException
*/
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 = intval($attempt->getCreatedAt()->format('U'));
if ($attemptTime > $time && is_null($attempt->getFailErrorClass())) {
$time = $attemptTime;
$final = $login;
}
}
if (is_null($final)) {
throw new FireflyException('No valid login attempt found.');
}
// 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->job->configuration;
$config['accounts'] = $all;
$config['login'] = $login->toArray();
$config['stage'] = 'have-accounts';
$this->job->configuration = $config;
$this->job->status = 'configuring';
$this->job->save();
return;
}
/**
*
*/
private function runStageHaveMapping()
{
// for each spectre account id in 'account-mappings'.
// find FF account
// get transactions.
// import?!
$config = $this->job->configuration;
$accounts = $config['accounts'] ?? [];
/** @var array $accountArray */
foreach ($accounts as $accountArray) {
$account = new Account($accountArray);
$importId = intval($config['accounts-mapped'][$account->getid()] ?? 0);
$doImport = $importId !== 0 ? true : false;
if (!$doImport) {
continue;
}
// import into account
$listTransactionsRequest = new ListTransactionsRequest($this->job->user);
$listTransactionsRequest->setAccount($account);
$listTransactionsRequest->call();
$transactions = $listTransactionsRequest->getTransactions();
var_dump($transactions);exit;
}
var_dump($config);
exit;
}
}

View File

@ -0,0 +1,101 @@
<?php
/**
* Account.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\Spectre\Object;
use Carbon\Carbon;
/**
* Class Account
*/
class Account extends SpectreObject
{
/** @var float */
private $balance;
/** @var Carbon */
private $createdAt;
/** @var string */
private $currencyCode;
/** @var array */
private $extra = [];
/** @var int */
private $id;
/** @var int */
private $loginId;
/** @var string */
private $name;
/** @var string */
private $nature;
/** @var Carbon */
private $updatedAt;
/**
* Account constructor.
*
* @param array $data
*/
public function __construct(array $data)
{
$this->id = $data['id'];
$this->loginId = $data['login_id'];
$this->currencyCode = $data['currency_code'];
$this->balance = $data['balance'];
$this->name = $data['name'];
$this->nature = $data['nature'];
$this->createdAt = new Carbon($data['created_at']);
$this->updatedAt = new Carbon($data['updated_at']);
foreach ($data['extra'] as $key => $value) {
$this->extra[$key] = $value;
}
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @return array
*/
public function toArray(): array
{
$array = [
'balance' => $this->balance,
'created_at' => $this->createdAt->toIso8601String(),
'currency_code' => $this->currencyCode,
'extra' => $this->extra,
'id' => $this->id,
'login_id' => $this->loginId,
'name' => $this->name,
'nature' => $this->nature,
'updated_at' => $this->updatedAt->toIso8601String(),
];
return $array;
}
}

View File

@ -163,6 +163,46 @@ class Attempt extends SpectreObject
return $this->failMessage;
}
/**
* @return array
*/
public function toArray(): array
{
$array = [
'api_mode' => $this->apiMode,
'api_version' => $this->apiVersion,
'automatic_fetch' => $this->automaticFetch,
'categorize' => $this->categorize,
'created_at' => $this->createdAt->toIso8601String(),
'consent_given_at' => $this->consentGivenAt->toIso8601String(),
'consent_types' => $this->consentTypes,
'custom_fields' => $this->customFields,
'daily_refresh' => $this->dailyRefresh,
'device_type' => $this->deviceType,
'user_agent' => $this->userAgent,
'remote_ip' => $this->remoteIp,
'exclude_accounts' => $this->excludeAccounts,
'fail_at' => $this->failAt->toIso8601String(),
'fail_error_class' => $this->failErrorClass,
'fail_message' => $this->failMessage,
'fetch_type' => $this->fetchType,
'finished' => $this->finished,
'finished_recent' => $this->finishedRecent,
'from_date' => $this->fromDate->toIso8601String(),
'id' => $this->id,
'interactive' => $this->interactive,
'locale' => $this->locale,
'partial' => $this->partial,
'show_consent_confirmation' => $this->showConsentInformation,
'stages' => $this->stages,
'store_credentials' => $this->storeCredentials,
'success_at' => $this->successAt->toIso8601String(),
'to_date' => $this->toDate->toIso8601String(),
'updated_at' => $this->updatedAt->toIso8601String(),
];
return $array;
}
}

View File

@ -37,4 +37,12 @@ class Holder extends SpectreObject
{
}
/**
* @return array
*/
public function toArray(): array
{
return [];
}
}

View File

@ -96,6 +96,14 @@ class Login extends SpectreObject
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @return Attempt
*/
@ -104,5 +112,35 @@ class Login extends SpectreObject
return $this->lastAttempt;
}
/**
* @return array
*/
public function toArray(): array
{
$array = [
'consent_given_at' => $this->consentGivenAt->toIso8601String(),
'consent_types' => $this->consentTypes,
'country_code' => $this->countryCode,
'created_at' => $this->createdAt->toIso8601String(),
'updated_at' => $this->updatedAt->toIso8601String(),
'customer_id' => $this->customerId,
'daily_refresh' => $this->dailyRefresh,
'holder_info' => $this->holderInfo->toArray(),
'id' => $this->id,
'last_attempt' => $this->lastAttempt->toArray(),
'last_success_at' => $this->lastSuccessAt->toIso8601String(),
'next_refresh_possible_at' => $this->nextRefreshPossibleAt,
'provider_code' => $this->providerCode,
'provider_id' => $this->providerId,
'provider_name' => $this->providerName,
'show_consent_confirmation' => $this->showConsentConfirmation,
'status' => $this->status,
'store_credentials' => $this->storeCredentials,
];
return $array;
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* Transaction.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\Spectre\Object;
/**
* Class Transaction
*/
class Transaction extends SpectreObject
{
/**
* Transaction constructor.
*
* @param array $data
*/
public function __construct(array $data) {
var_dump($data);
exit;
}
}

View File

@ -0,0 +1,93 @@
<?php
/**
* ListAccountsRequest.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\Spectre\Request;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Services\Spectre\Exception\SpectreException;
use FireflyIII\Services\Spectre\Object\Account;
use FireflyIII\Services\Spectre\Object\Login;
use Log;
/**
* Class ListAccountsRequest
*/
class ListAccountsRequest extends SpectreRequest
{
/** @var array */
private $accounts = [];
/** @var Login */
private $login;
/**
* @throws SpectreException
* @throws FireflyException
*/
public function call(): void
{
$hasNextPage = true;
$nextId = 0;
while ($hasNextPage) {
Log::debug(sprintf('Now calling ListAccountsRequest for next_id %d', $nextId));
$parameters = ['from_id' => $nextId, 'login_id' => $this->login->getId()];
$uri = '/api/v3/accounts?' . http_build_query($parameters);
$response = $this->sendSignedSpectreGet($uri, []);
// count entries:
Log::debug(sprintf('Found %d entries in data-array', count($response['data'])));
// extract next ID
$hasNextPage = false;
if (isset($response['meta']['next_id']) && intval($response['meta']['next_id']) > $nextId) {
$hasNextPage = true;
$nextId = $response['meta']['next_id'];
Log::debug(sprintf('Next ID is now %d.', $nextId));
} else {
Log::debug('No next page.');
}
// store customers:
foreach ($response['data'] as $accountArray) {
$this->accounts[] = new Account($accountArray);
}
}
}
/**
* @return array
*/
public function getAccounts(): array
{
return $this->accounts;
}
/**
* @param Login $login
*/
public function setLogin(Login $login): void
{
$this->login = $login;
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* ListTransactionsRequest.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\Spectre\Request;
use FireflyIII\Services\Spectre\Object\Account;
use FireflyIII\Services\Spectre\Object\Transaction;
use Log;
/**
* Class ListTransactionsRequest
*/
class ListTransactionsRequest extends SpectreRequest
{
/** @var Account */
private $account;
/** @var array */
private $transactions = [];
/**
*
*/
public function call(): void
{
$hasNextPage = true;
$nextId = 0;
while ($hasNextPage) {
Log::debug(sprintf('Now calling ListTransactionsRequest for next_id %d', $nextId));
$parameters = ['from_id' => $nextId,'account_id' => $this->account->getId()];
$uri = '/api/v3/transactions?' . http_build_query($parameters);
$response = $this->sendSignedSpectreGet($uri, []);
// count entries:
Log::debug(sprintf('Found %d entries in data-array', count($response['data'])));
// extract next ID
$hasNextPage = false;
if (isset($response['meta']['next_id']) && intval($response['meta']['next_id']) > $nextId) {
$hasNextPage = true;
$nextId = $response['meta']['next_id'];
Log::debug(sprintf('Next ID is now %d.', $nextId));
} else {
Log::debug('No next page.');
}
// store customers:
foreach ($response['data'] as $transactionArray) {
$this->transactions[] = new Transaction($transactionArray);
}
}
}
/**
* @return array
*/
public function getTransactions(): array
{
return $this->transactions;
}
/**
* @param Account $account
*/
public function setAccount(Account $account): void
{
$this->account = $account;
}
}

View File

@ -0,0 +1,157 @@
<?php
/**
* HaveAccounts.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\Configuration\Spectre;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
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
*/
class HaveAccounts implements ConfigurationInterface
{
/** @var ImportJob */
private $job;
/**
* Get the data necessary to show the configuration screen.
*
* @return array
*/
public function getData(): array
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
/** @var CurrencyRepositoryInterface $currencyRepository */
$currencyRepository = app(CurrencyRepositoryInterface::class);
$data = [];
$config = $this->job->configuration;
$collection = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$defaultCurrency = app('amount')->getDefaultCurrency();
$dbAccounts = [];
/** @var Account $dbAccount */
foreach ($collection as $dbAccount) {
$id = $dbAccount->id;
$currencyId = intval($dbAccount->getMeta('currency_id'));
$currency = $currencyRepository->find($currencyId);
$dbAccounts[$id] = [
'account' => $dbAccount,
'currency' => is_null($currency->id) ? $defaultCurrency : $currency,
];
}
// loop Spectre accounts:
/**
* @var int $index
* @var array $spectreAccount
*/
foreach ($config['accounts'] as $index => $spectreAccount) {
// find accounts with currency code
$code = $spectreAccount['currency_code'];
$selection = $this->filterAccounts($dbAccounts, $code);
$config['accounts'][$index]['options'] = app('expandedform')->makeSelectList($selection);
}
$data = [
'config' => $config,
];
return $data;
}
/**
* Return possible warning to user.
*
* @return string
*/
public function getWarningMessage(): string
{
return '';
}
/**
* @param ImportJob $job
*
* @return ConfigurationInterface
*/
public function setJob(ImportJob $job)
{
$this->job = $job;
return $this;
}
/**
* Store the result.
*
* @param array $data
*
* @return bool
*/
public function storeConfiguration(array $data): bool
{
$accounts = $data['spectre_account_id'] ?? [];
$mapping = [];
foreach ($accounts as $spectreId) {
$spectreId = intval($spectreId);
$doImport = intval($data['do_import'][$spectreId] ?? 0) === 1;
$account = intval($data['import'][$spectreId] ?? 0);
if ($doImport) {
$mapping[$spectreId] = $account;
}
}
$config = $this->job->configuration;
$config['accounts-mapped'] = $mapping;
$this->job->configuration = $config;
$this->job->save();
return true;
}
/**
* @param array $dbAccounts
* @param string $code
*
* @return Collection
*/
private function filterAccounts(array $dbAccounts, string $code): Collection
{
$collection = new Collection;
foreach ($dbAccounts as $accountId => $data) {
if ($data['currency']->code === $code) {
$collection->push($data['account']);
}
}
return $collection;
}
}

View File

@ -174,14 +174,20 @@ function jobIsStalled(data) {
/**
* 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() {
// disable the button, add loading thing.
$('.start-job').prop('disabled', true).text('...');
$.post(jobStartUri, {_token: token}).fail(reportOnSubmitError);
if (job.status === "configured") {
console.log("Job auto started!");
// 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);
// check status, every 500 ms.
timeOutId = setTimeout(checkJobStatus, startInterval);
return;
}
console.log("Job not auto started because state is " + job.status);
}
/**

View File

@ -33,7 +33,7 @@
</div>
</div>
{% endif %}
{% if accounts.count == 0 and page == 0 %}
{% if accounts.count == 0 and page == 1 %}
{% include 'partials.empty' with {what: what, type: 'accounts',route: route('accounts.create', [what])} %}
{% endif %}
{% endblock %}

View File

@ -5,7 +5,7 @@
</label>
<div class="col-sm-8">
<div class="radio">
<div class="checkbox">
<label>
{{ Form.checkbox('create_another', '1', false, {'id': name ~ '_return_to_form'}) }}
{{ trans('form.returnHereExplanation') }}

View File

@ -47,7 +47,7 @@
</label>
<div class="col-sm-8">
<div class="radio"><label>
<div class="checkbox"><label>
{{ Form.checkbox('apply_rules', '1',
job.configuration.apply_rules == '1', {'id': 'apply_rules_label'}) }}
{{ trans('import.file_apply_rules_description') }}
@ -61,7 +61,7 @@
</label>
<div class="col-sm-8">
<div class="radio"><label>
<div class="checkbox"><label>
{{ Form.checkbox('match_bills', '1',
job.configuration.match_bills == '1', {'id': 'match_bills_label'}) }}
{{ trans('import.file_match_bills_description') }}
@ -77,7 +77,7 @@
</label>
<div class="col-sm-8">
<div class="radio"><label>
<div class="checkbox"><label>
{{ Form.checkbox('specifics['~type~']', '1',
job.configuration.specifics[type] == '1', {'id': type ~ '_label'}) }}
{{ specific.description }}

View File

@ -0,0 +1,88 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.render }}
{% endblock %}
{% block content %}
<div class="row">
<form class="form-horizontal" action="{{ route('import.configure.post',[job.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.spectre_accounts_title') }}</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-8">
<p>
{{ trans('import.spectre_accounts_text')|raw }}
</p>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>{{ trans('list.account_on_spectre') }}</th>
<th>{{ trans('list.account') }}</th>
<th>{{ trans('list.do_import') }}</th>
</tr>
</thead>
<tbody>
{% for account in data.config.accounts %}
<tr>
<td>
<input type="hidden" name="spectre_account_id[]" value="{{ account.id }}" />
{{ account.nature|capitalize }} "<strong>{{ account.name }}</strong>" ({{ formatAmountBySymbol(account.balance, account.currency_code~' ') }})<br />
{% for name, value in account.extra %}
{% if not value is iterable and name != 'sort_code' and name !='current_date' and name != 'available_amount' and name !='current_time' and name != 'last_posted_transaction_id' %}
{{ name|capitalize }}: {{ value }}<br />
{% endif %}
{% if name == 'available_amount' %}
Available amount: {{ formatAmountBySymbol(value, account.currency_code~' ') }}
{% endif %}
{% endfor %}
</td>
<td>
<select class="form-control" name="import[{{ account.id }}]">
{% for id,name in account.options %}
<option value="{{ id }}" label="{{ name }}">{{ name }}</option>
{% endfor %}
</select>
</td>
<td>
<div class="checkbox">
<label>
<input type="checkbox" value="1" name="do_import[{{ account.id }}]" checked>
{{ 'spectre_do_import'|_ }}
</label>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn pull-right btn-success">
{{ ('submit')|_ }}
</button>
</div>
</div>
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}
{% block styles %}
{% endblock %}

View File

@ -145,6 +145,7 @@
<script type="text/javascript">
// some useful translations.
var langImportTimeOutError = '(time out thing)';
var langImportSingleError = '{{ trans('import.status_errors_single')|escape('js') }}';
var langImportMultiError = '{{ trans('import.status_errors_multi')|escape('js') }}';
var jobStatusUri = '{{ route('import.status.json', [job.key]) }}';