From 2c206bba64be485d2bb5b257fce0943cf6fd4ed8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 19 May 2018 21:13:00 +0200 Subject: [PATCH] First working version of a working Spectre import. --- .../SpectreJobConfiguration.php | 2 + app/Import/Routine/SpectreRoutine.php | 37 ++- app/Import/Storage/ImportArrayStorage.php | 4 +- app/Models/Account.php | 1 + app/Models/ImportJob.php | 34 +-- app/Services/Spectre/Object/Transaction.php | 36 ++- .../Spectre/AuthenticateConfig.php | 90 ++++++- .../Spectre/AuthenticatedConfigHandler.php | 10 +- .../Spectre/ChooseAccount.php | 11 +- .../Spectre/ChooseLoginHandler.php | 23 +- .../JobConfiguration/Spectre/NewConfig.php | 1 - .../Spectre/SpectreJobConfig.php | 5 + .../Routine/Spectre/ImportDataHandler.php | 220 +++++++++++++++++- .../Routine/Spectre/ManageLoginsHandler.php | 98 +------- .../Spectre/StageAuthenticatedHandler.php | 96 +++++++- .../Routine/Spectre/StageNewHandler.php | 52 +++-- resources/lang/en_US/import.php | 4 +- 17 files changed, 552 insertions(+), 172 deletions(-) diff --git a/app/Import/JobConfiguration/SpectreJobConfiguration.php b/app/Import/JobConfiguration/SpectreJobConfiguration.php index 5e2b961edf..521ae2a32d 100644 --- a/app/Import/JobConfiguration/SpectreJobConfiguration.php +++ b/app/Import/JobConfiguration/SpectreJobConfiguration.php @@ -33,6 +33,7 @@ use FireflyIII\Support\Import\JobConfiguration\Spectre\ChooseLoginHandler; use FireflyIII\Support\Import\JobConfiguration\Spectre\NewConfig; use FireflyIII\Support\Import\JobConfiguration\Spectre\SpectreJobConfig; use Illuminate\Support\MessageBag; +use Log; /** * Class SpectreJobConfiguration @@ -117,6 +118,7 @@ class SpectreJobConfiguration implements JobConfigurationInterface */ private function getHandler(): SpectreJobConfig { + Log::debug(sprintf('Now in SpectreJobConfiguration::getHandler() with stage "%s"', $this->importJob->stage)); $handler = null; switch ($this->importJob->stage) { case 'new': diff --git a/app/Import/Routine/SpectreRoutine.php b/app/Import/Routine/SpectreRoutine.php index 145eb96b7c..db939b11cb 100644 --- a/app/Import/Routine/SpectreRoutine.php +++ b/app/Import/Routine/SpectreRoutine.php @@ -29,10 +29,10 @@ use FireflyIII\Support\Import\Routine\Spectre\ImportDataHandler; use FireflyIII\Support\Import\Routine\Spectre\ManageLoginsHandler; use FireflyIII\Support\Import\Routine\Spectre\StageAuthenticatedHandler; use FireflyIII\Support\Import\Routine\Spectre\StageNewHandler; +use Log; /** - * @codeCoverageIgnore - * Class FileRoutine + * Class SpectreRoutine */ class SpectreRoutine implements RoutineInterface { @@ -57,28 +57,19 @@ class SpectreRoutine implements RoutineInterface */ public function run(): void { + Log::debug(sprintf('Now in SpectreRoutine::run() with status "%s" and stage "%s".', $this->importJob->status, $this->importJob->stage)); $valid = ['ready_to_run']; // should be only ready_to_run - if (in_array($this->importJob->status, $valid)) { + if (\in_array($this->importJob->status, $valid, true)) { switch ($this->importJob->stage) { default: throw new FireflyException(sprintf('SpectreRoutine cannot handle stage "%s".', $this->importJob->stage)); case 'new': + // list all of the users logins. + $this->repository->setStatus($this->importJob, 'running'); /** @var StageNewHandler $handler */ $handler = app(StageNewHandler::class); $handler->setImportJob($this->importJob); $handler->run(); - $this->repository->setStage($this->importJob, 'manage-logins'); - break; - case 'authenticate': - // set job to require config. - $this->repository->setStatus($this->importJob, 'need_job_config'); - - return; - case 'manage-logins': - // list all of the users logins. - $handler = new ManageLoginsHandler; - $handler->setImportJob($this->importJob); - $handler->run(); // if count logins is zero, go to authenticate stage if ($handler->countLogins === 0) { @@ -91,9 +82,16 @@ class SpectreRoutine implements RoutineInterface $this->repository->setStage($this->importJob, 'choose-login'); $this->repository->setStatus($this->importJob, 'need_job_config'); break; + case 'authenticate': + // set job to require config. + $this->repository->setStatus($this->importJob, 'need_job_config'); + + return; case 'authenticated': + $this->repository->setStatus($this->importJob, 'running'); // get accounts from login, store in job. - $handler = new StageAuthenticatedHandler; + /** @var StageAuthenticatedHandler $handler */ + $handler = app(StageAuthenticatedHandler::class); $handler->setImportJob($this->importJob); $handler->run(); @@ -103,9 +101,10 @@ class SpectreRoutine implements RoutineInterface break; case 'go-for-import': // user has chosen account mapping. Should now be ready to import data. - //$this->repository->setStatus($this->importJob, 'running'); - //$this->repository->setStage($this->importJob, 'do_import'); - $handler = new ImportDataHandler; + $this->repository->setStatus($this->importJob, 'running'); + $this->repository->setStage($this->importJob, 'do_import'); + /** @var ImportDataHandler $handler */ + $handler = app(ImportDataHandler::class); $handler->setImportJob($this->importJob); $handler->run(); $this->repository->setStatus($this->importJob, 'provider_finished'); diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 3500ddbe6b..bf95a6e296 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -382,7 +382,9 @@ class ImportArrayStorage $store['date'] = Carbon::createFromFormat('Y-m-d', $store['date']); $store['description'] = $store['description'] === '' ? '(empty description)' : $store['description']; // store the journal. - $collection->push($this->journalRepos->store($store)); + $journal = $this->journalRepos->store($store); + Log::debug(sprintf('Stored as journal #%d', $journal->id)); + $collection->push($journal); } Log::debug('DONE storing!'); diff --git a/app/Models/Account.php b/app/Models/Account.php index 22efa81b4e..b4c3487c8e 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -38,6 +38,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Account. * + * @property int $id * @property string $name * @property string $iban */ diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php index 3a06d8f179..91ad6871cc 100644 --- a/app/Models/ImportJob.php +++ b/app/Models/ImportJob.php @@ -22,7 +22,6 @@ declare(strict_types=1); namespace FireflyIII\Models; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -30,8 +29,12 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class ImportJob. * - * @property array $configuration - * @property User $user + * @property array $transactions + * @property array $configuration + * @property User $user + * @property int $user_id + * @property string $status + * @property string $stage */ class ImportJob extends Model { @@ -53,22 +56,12 @@ class ImportJob extends Model /** @var array */ protected $fillable = ['key', 'user_id', 'file_type', 'provider', 'status', 'stage', 'configuration', 'extended_status', 'transactions', 'errors']; - /** - * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\MorphMany - */ - public function attachments() - { - return $this->morphMany(Attachment::class, 'attachable'); - } - /** * @param $value * * @return mixed * * @throws NotFoundHttpException - * @throws FireflyException */ public static function routeBinder(string $value): ImportJob { @@ -84,11 +77,11 @@ class ImportJob extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\MorphMany */ - public function user() + public function attachments() { - return $this->belongsTo(User::class); + return $this->morphMany(Attachment::class, 'attachable'); } /** @@ -99,4 +92,13 @@ class ImportJob extends Model { return $this->belongsTo(Tag::class); } + + /** + * @codeCoverageIgnore + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo(User::class); + } } diff --git a/app/Services/Spectre/Object/Transaction.php b/app/Services/Spectre/Object/Transaction.php index 4109f95c68..48a8a4283e 100644 --- a/app/Services/Spectre/Object/Transaction.php +++ b/app/Services/Spectre/Object/Transaction.php @@ -112,9 +112,9 @@ class Transaction extends SpectreObject } /** - * @return TransactionExtra + * @return TransactionExtra|null */ - public function getExtra(): TransactionExtra + public function getExtra(): ?TransactionExtra { return $this->extra; } @@ -167,6 +167,38 @@ class Transaction extends SpectreObject return $this->mode; } + /** + * @return array + */ + public function getOpposingAccountData(): array + { + $data = [ + 'name' => null, + 'iban' => null, + 'number' => null, + 'bic' => null, + ]; + $extra = $this->getExtra(); + if (null !== $extra) { + $arr = $extra->toArray(); + foreach ($arr as $key => $value) { + switch ($key) { + case 'account_number': + $data['number'] = $value; + $data['name'] = $data['name'] ?? trans('import.spectre_account_with_number', ['number' => $value]); + break; + case 'payee': + $data['name'] = $value; + break; + default: + break; + } + } + } + + return $data; + } + /** * @return string */ diff --git a/app/Support/Import/JobConfiguration/Spectre/AuthenticateConfig.php b/app/Support/Import/JobConfiguration/Spectre/AuthenticateConfig.php index dcb4db42bb..468d791453 100644 --- a/app/Support/Import/JobConfiguration/Spectre/AuthenticateConfig.php +++ b/app/Support/Import/JobConfiguration/Spectre/AuthenticateConfig.php @@ -26,8 +26,13 @@ namespace FireflyIII\Support\Import\JobConfiguration\Spectre; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Services\Spectre\Object\Customer; use FireflyIII\Services\Spectre\Object\Token; +use FireflyIII\Services\Spectre\Request\CreateTokenRequest; +use FireflyIII\Services\Spectre\Request\ListCustomersRequest; +use FireflyIII\Services\Spectre\Request\NewCustomerRequest; use Illuminate\Support\MessageBag; +use Log; /** * Class AuthenticateConfig @@ -50,6 +55,8 @@ class AuthenticateConfig implements SpectreJobConfig */ public function configurationComplete(): bool { + Log::debug('AuthenticateConfig::configurationComplete() will always return false'); + return false; } @@ -62,7 +69,8 @@ class AuthenticateConfig implements SpectreJobConfig */ public function configureJob(array $data): MessageBag { - // does nothing + Log::debug('AuthenticateConfig::configureJob() will do nothing.'); + return new MessageBag; } @@ -74,15 +82,22 @@ class AuthenticateConfig implements SpectreJobConfig */ public function getNextData(): array { + Log::debug('Now in AuthenticateConfig::getNextData()'); // next data only makes sure the job is ready for the next stage. $this->repository->setStatus($this->importJob, 'ready_to_run'); $this->repository->setStage($this->importJob, 'authenticated'); $config = $this->importJob->configuration; $token = isset($config['token']) ? new Token($config['token']) : null; if (null !== $token) { + Log::debug(sprintf('Return "%s" from token in config.', $token->getConnectUrl())); + return ['token-url' => $token->getConnectUrl()]; } - throw new FireflyException('The import routine cannot continue without a Spectre token. Apologies.'); + Log::debug('No existing token, get a new one.'); + // get a new token from Spectre. + $customer = $this->getCustomer(); + $token = $this->getToken($customer); + return ['token-url' => $token->getConnectUrl()]; } /** @@ -106,4 +121,75 @@ class AuthenticateConfig implements SpectreJobConfig $this->repository = app(ImportJobRepositoryInterface::class); $this->repository->setUser($importJob->user); } + + /** + * @return Customer + * @throws FireflyException + */ + private function getCustomer(): Customer + { + Log::debug('Now in AuthenticateConfig::getCustomer()'); + $customer = $this->getExistingCustomer(); + if (null === $customer) { + Log::debug('The customer is NULL, will fire a newCustomerRequest.'); + $newCustomerRequest = new NewCustomerRequest($this->importJob->user); + $customer = $newCustomerRequest->getCustomer(); + + } + Log::debug('The customer is not null.'); + + return $customer; + } + + /** + * @return Customer|null + * @throws FireflyException + */ + private function getExistingCustomer(): ?Customer + { + Log::debug('Now in AuthenticateConfig::getExistingCustomer()'); + $preference = app('preferences')->getForUser($this->importJob->user, 'spectre_customer'); + if (null !== $preference) { + Log::debug('Customer is in user configuration'); + $customer = new Customer($preference->data); + + return $customer; + } + Log::debug('Customer is not in user config'); + $customer = null; + $getCustomerRequest = new ListCustomersRequest($this->importJob->user); + $getCustomerRequest->call(); + $customers = $getCustomerRequest->getCustomers(); + + Log::debug(sprintf('Found %d customer(s)', \count($customers))); + /** @var Customer $current */ + foreach ($customers as $current) { + if ('default_ff3_customer' === $current->getIdentifier()) { + $customer = $current; + Log::debug('Found the correct customer.'); + app('preferences')->setForUser($this->importJob->user, 'spectre_customer', $customer->toArray()); + break; + } + } + + return $customer; + } + + /** + * @param Customer $customer + * + * @throws FireflyException + * @return Token + */ + private function getToken(Customer $customer): Token + { + Log::debug('Now in AuthenticateConfig::getToken()'); + $request = new CreateTokenRequest($this->importJob->user); + $request->setUri(route('import.job.status.index', [$this->importJob->key])); + $request->setCustomer($customer); + $request->call(); + Log::debug('Call to get token is finished'); + + return $request->getToken(); + } } \ No newline at end of file diff --git a/app/Support/Import/JobConfiguration/Spectre/AuthenticatedConfigHandler.php b/app/Support/Import/JobConfiguration/Spectre/AuthenticatedConfigHandler.php index 6e2bfca329..32b9da53a1 100644 --- a/app/Support/Import/JobConfiguration/Spectre/AuthenticatedConfigHandler.php +++ b/app/Support/Import/JobConfiguration/Spectre/AuthenticatedConfigHandler.php @@ -23,10 +23,10 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\JobConfiguration\Spectre; - use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use Illuminate\Support\MessageBag; +use Log; class AuthenticatedConfigHandler implements SpectreJobConfig { @@ -42,6 +42,8 @@ class AuthenticatedConfigHandler implements SpectreJobConfig */ public function configurationComplete(): bool { + Log::debug('AuthenticatedConfigHandler::configurationComplete() always returns true'); + return true; } @@ -54,6 +56,8 @@ class AuthenticatedConfigHandler implements SpectreJobConfig */ public function configureJob(array $data): MessageBag { + Log::debug('AuthenticatedConfigHandler::configurationComplete() always returns empty message bag'); + return new MessageBag(); } @@ -64,6 +68,8 @@ class AuthenticatedConfigHandler implements SpectreJobConfig */ public function getNextData(): array { + Log::debug('AuthenticatedConfigHandler::getNextData() always returns []'); + return []; } @@ -74,6 +80,8 @@ class AuthenticatedConfigHandler implements SpectreJobConfig */ public function getNextView(): string { + Log::debug('AuthenticatedConfigHandler::getNextView() always returns ""'); + return ''; } diff --git a/app/Support/Import/JobConfiguration/Spectre/ChooseAccount.php b/app/Support/Import/JobConfiguration/Spectre/ChooseAccount.php index b0b42c527d..09a2b8af02 100644 --- a/app/Support/Import/JobConfiguration/Spectre/ChooseAccount.php +++ b/app/Support/Import/JobConfiguration/Spectre/ChooseAccount.php @@ -35,6 +35,7 @@ use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Services\Spectre\Object\Account as SpectreAccount; use FireflyIII\Services\Spectre\Object\Login; use Illuminate\Support\MessageBag; +use Log; /** * Class ChooseAccount @@ -60,10 +61,12 @@ class ChooseAccount implements SpectreJobConfig */ public function configurationComplete(): bool { + Log::debug('Now in ChooseAccount::configurationComplete()'); $config = $this->importJob->configuration; $importAccounts = $config['account_mapping'] ?? []; $complete = \count($importAccounts) > 0 && $importAccounts !== [0 => 0]; if ($complete) { + Log::debug('Looks like user has mapped import accounts to Firefly III accounts', $importAccounts); $this->repository->setStage($this->importJob, 'go-for-import'); } @@ -79,6 +82,7 @@ class ChooseAccount implements SpectreJobConfig */ public function configureJob(array $data): MessageBag { + Log::debug('Now in ChooseAccount::configureJob()', $data); $config = $this->importJob->configuration; $mapping = $data['account_mapping'] ?? []; $final = []; @@ -89,11 +93,12 @@ class ChooseAccount implements SpectreJobConfig $final[$spectreId] = $accountId; } + Log::debug('Final mapping is:', $final); $messages = new MessageBag; $config['account_mapping'] = $final; $this->repository->setConfiguration($this->importJob, $config); - if (\count($final) === 0 || $final === [0 => 0]) { + if ($final === [0 => 0] || \count($final) === 0) { $messages->add('count', trans('import.spectre_no_mapping')); } @@ -108,6 +113,7 @@ class ChooseAccount implements SpectreJobConfig */ public function getNextData(): array { + Log::debug('Now in ChooseAccount::getnextData()'); $config = $this->importJob->configuration; $accounts = $config['accounts'] ?? []; if (\count($accounts) === 0) { @@ -125,14 +131,17 @@ class ChooseAccount implements SpectreJobConfig if (\count($logins) === 0) { throw new FireflyException('It seems you have no configured logins in this import job. The import cannot continue.'); } + Log::debug(sprintf('Selected login to use is %d', $selected)); if ($selected === 0) { $login = new Login($logins[0]); + Log::debug(sprintf('Will use login %d (%s %s)', $login->getId(), $login->getProviderName(), $login->getCountryCode())); } if ($selected !== 0) { foreach ($logins as $loginArray) { $loginId = $loginArray['id'] ?? -1; if ($loginId === $selected) { $login = new Login($loginArray); + Log::debug(sprintf('Will use login %d (%s %s)', $login->getId(), $login->getProviderName(), $login->getCountryCode())); } } } diff --git a/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php b/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php index 5948d75bbb..a947cf31d2 100644 --- a/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php +++ b/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php @@ -55,10 +55,14 @@ class ChooseLoginHandler implements SpectreJobConfig */ public function configurationComplete(): bool { + Log::debug('Now in ChooseLoginHandler::configurationComplete()'); $config = $this->importJob->configuration; - if(isset($config['selected-login'])) { + if (isset($config['selected-login'])) { + Log::debug('config[selected-login] is set, return true.'); + return true; } + Log::debug('config[selected-login] is not set, return false.'); return false; } @@ -73,24 +77,30 @@ class ChooseLoginHandler implements SpectreJobConfig */ public function configureJob(array $data): MessageBag { + Log::debug('Now in ChooseLoginHandler::configureJob()'); $selectedLogin = (int)$data['spectre_login_id']; $config = $this->importJob->configuration; $config['selected-login'] = $selectedLogin; $this->repository->setConfiguration($this->importJob, $config); + Log::debug(sprintf('The selected login by the user is #%d', $selectedLogin)); // if selected login is zero, create a new one. if ($selectedLogin === 0) { + Log::debug('Login is zero, get a new customer + token and store it in config.'); $customer = $this->getCustomer(); // get a token for the user and redirect to next stage - $token = $this->getToken($customer); - $config['token'] = $token->toArray(); + $token = $this->getToken($customer); + $config['customer'] = $customer->toArray(); + $config['token'] = $token->toArray(); $this->repository->setConfiguration($this->importJob, $config); // move job to correct stage to redirect to Spectre: $this->repository->setStage($this->importJob, 'authenticate'); + return new MessageBag; } $this->repository->setStage($this->importJob, 'authenticated'); + return new MessageBag; } @@ -101,9 +111,11 @@ class ChooseLoginHandler implements SpectreJobConfig */ public function getNextData(): array { + Log::debug('Now in ChooseLoginHandler::getNextData()'); $config = $this->importJob->configuration; $data = ['logins' => []]; $logins = $config['all-logins'] ?? []; + Log::debug(sprintf('Count of logins in configuration is %d.', \count($logins))); foreach ($logins as $login) { $data['logins'][] = new Login($login); } @@ -158,7 +170,7 @@ class ChooseLoginHandler implements SpectreJobConfig */ private function getExistingCustomer(): ?Customer { - Log::debug('Now in getExistingCustomer()'); + Log::debug('Now in ChooseLoginHandler::getExistingCustomer()'); $preference = app('preferences')->getForUser($this->importJob->user, 'spectre_customer'); if (null !== $preference) { Log::debug('Customer is in user configuration'); @@ -178,6 +190,7 @@ class ChooseLoginHandler implements SpectreJobConfig if ('default_ff3_customer' === $current->getIdentifier()) { $customer = $current; Log::debug('Found the correct customer.'); + app('preferences')->setForUser($this->importJob->user, 'spectre_customer', $customer->toArray()); break; } } @@ -193,7 +206,7 @@ class ChooseLoginHandler implements SpectreJobConfig */ private function getToken(Customer $customer): Token { - Log::debug('Now in ChooseLoginsHandler::getToken()'); + Log::debug('Now in ChooseLoginHandler::ChooseLoginsHandler::getToken()'); $request = new CreateTokenRequest($this->importJob->user); $request->setUri(route('import.job.status.index', [$this->importJob->key])); $request->setCustomer($customer); diff --git a/app/Support/Import/JobConfiguration/Spectre/NewConfig.php b/app/Support/Import/JobConfiguration/Spectre/NewConfig.php index a34684e049..9a5c03f600 100644 --- a/app/Support/Import/JobConfiguration/Spectre/NewConfig.php +++ b/app/Support/Import/JobConfiguration/Spectre/NewConfig.php @@ -84,6 +84,5 @@ class NewConfig implements SpectreJobConfig */ public function setImportJob(ImportJob $importJob): void { - return; } } \ No newline at end of file diff --git a/app/Support/Import/JobConfiguration/Spectre/SpectreJobConfig.php b/app/Support/Import/JobConfiguration/Spectre/SpectreJobConfig.php index 3672c704ec..12a6bad1f7 100644 --- a/app/Support/Import/JobConfiguration/Spectre/SpectreJobConfig.php +++ b/app/Support/Import/JobConfiguration/Spectre/SpectreJobConfig.php @@ -27,6 +27,11 @@ namespace FireflyIII\Support\Import\JobConfiguration\Spectre; use FireflyIII\Models\ImportJob; use Illuminate\Support\MessageBag; +/** + * Interface SpectreJobConfig + * + * @package FireflyIII\Support\Import\JobConfiguration\Spectre + */ interface SpectreJobConfig { /** diff --git a/app/Support/Import/Routine/Spectre/ImportDataHandler.php b/app/Support/Import/Routine/Spectre/ImportDataHandler.php index 13235ab8b4..e24b8b558a 100644 --- a/app/Support/Import/Routine/Spectre/ImportDataHandler.php +++ b/app/Support/Import/Routine/Spectre/ImportDataHandler.php @@ -24,8 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\Routine\Spectre; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Account as LocalAccount; +use FireflyIII\Models\AccountType; use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Services\Spectre\Object\Account as SpectreAccount; +use FireflyIII\Services\Spectre\Object\Transaction as SpectreTransaction; +use FireflyIII\Services\Spectre\Request\ListTransactionsRequest; +use FireflyIII\Support\Import\Routine\File\OpposingAccountMapper; +use Log; /** * Class ImportDataHandler @@ -34,18 +43,37 @@ use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; */ class ImportDataHandler { + /** @var AccountRepositoryInterface */ + private $accountRepository; /** @var ImportJob */ private $importJob; + /** @var OpposingAccountMapper */ + private $mapper; /** @var ImportJobRepositoryInterface */ private $repository; /** - * + * @throws FireflyException */ - public function run() + public function run(): void { - die('here we are'); - + Log::debug('Now in ImportDataHandler::run()'); + $config = $this->importJob->configuration; + $accounts = $config['accounts'] ?? []; + Log::debug(sprintf('Count of accounts in array is %d', \count($accounts))); + if (\count($accounts) === 0) { + throw new FireflyException('There are no accounts in this import job. Cannot continue.'); + } + $toImport = $config['account_mapping'] ?? []; + foreach ($toImport as $spectreId => $localId) { + if ((int)$localId > 0) { + Log::debug(sprintf('Will get transactions from Spectre account #%d and save them in Firefly III account #%d', $spectreId, $localId)); + $spectreAccount = $this->getSpectreAccount((int)$spectreId); + $localAccount = $this->getLocalAccount((int)$localId); + $set = $this->getTransactions($spectreAccount, $localAccount); + $this->repository->setTransactions($this->importJob, $set); + } + } } /** @@ -55,8 +83,188 @@ class ImportDataHandler */ public function setImportJob(ImportJob $importJob): void { - $this->importJob = $importJob; - $this->repository = app(ImportJobRepositoryInterface::class); + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->mapper = app(OpposingAccountMapper::class); + $this->accountRepository->setUser($importJob->user); $this->repository->setUser($importJob->user); + $this->mapper->setUser($importJob->user); } + + /** + * @param array $transactions + * @param SpectreAccount $spectreAccount + * @param LocalAccount $originalSource + * + * @return array + */ + private function convertToArray(array $transactions, SpectreAccount $spectreAccount, LocalAccount $originalSource): array + { + $array = []; + $total = \count($transactions); + Log::debug(sprintf('Now in ImportDataHandler::convertToArray() with count %d', \count($transactions))); + /** @var SpectreTransaction $transaction */ + foreach ($transactions as $index => $transaction) { + Log::debug(sprintf('Now creating array for transaction %d of %d', $index + 1, $total)); + $extra = []; + if (null !== $transaction->getExtra()) { + $extra = $transaction->getExtra()->toArray(); + } + $destinationData = $transaction->getOpposingAccountData(); + $amount = $transaction->getAmount(); + $source = $originalSource; + $destination = $this->mapper->map(null, $amount, $destinationData); + $notes = (string)trans('import.imported_from_account', ['account' => $spectreAccount->getName()]) . ' ' . "\n"; + $foreignAmount = null; + $foreignCurrencyCode = null; + + $currencyCode = $transaction->getCurrencyCode(); + $type = 'withdrawal'; + // switch source and destination if amount is greater than zero. + if (bccomp($amount, '0') === 1) { + [$source, $destination] = [$destination, $source]; + $type = 'deposit'; + } + + Log::debug(sprintf('Mapped destination to #%d ("%s")', $destination->id, $destination->name)); + Log::debug(sprintf('Set source to #%d ("%s")', $source->id, $source->name)); + + // put some data in tags: + $tags = []; + $tags[] = $transaction->getMode(); + $tags[] = $transaction->getStatus(); + if ($transaction->isDuplicated()) { + $tags[] = 'possibly-duplicated'; + } + + // get extra fields: + foreach ($extra as $key => $value) { + if ('' === (string)$value) { + continue; + } + switch ($key) { + case 'original_category': + case 'original_subcategory': + case 'customer_category_code': + case 'customer_category_name': + $tags[] = $value; + break; + case 'original_amount': + $foreignAmount = $value; + break; + case 'original_currency_code': + $foreignCurrencyCode = $value; + break; + default: + $notes .= $key . ': ' . $value . ' ' . "\n"; // for newline in Markdown. + } + } + + $entry = [ + 'type' => $type, + 'date' => $transaction->getMadeOn()->format('Y-m-d'), + 'tags' => $tags, + 'user' => $this->importJob->user_id, + 'notes' => $notes, + + // all custom fields: + 'external_id' => (string)$transaction->getId(), + + // journal data: + 'description' => $transaction->getDescription(), + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'bill_id' => null, + 'bill_name' => null, + + // transaction data: + 'transactions' => [ + [ + 'currency_id' => null, + 'currency_code' => $currencyCode, + 'description' => null, + 'amount' => $amount, + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => $transaction->getCategory(), + 'source_id' => $source->id, + 'source_name' => null, + 'destination_id' => $destination->id, + 'destination_name' => null, + 'foreign_currency_id' => null, + 'foreign_currency_code' => $foreignCurrencyCode, + 'foreign_amount' => $foreignAmount, + 'reconciled' => false, + 'identifier' => 0, + ], + ], + ]; + $array[] = $entry; + } + Log::debug(sprintf('Return %d entries', \count($array))); + + return $array; + } + + /** + * @param int $accountId + * + * @return LocalAccount + * @throws FireflyException + */ + private function getLocalAccount(int $accountId): LocalAccount + { + $account = $this->accountRepository->findNull($accountId); + if (null === $account) { + throw new FireflyException(sprintf('Cannot find Firefly III asset account with ID #%d. Job must stop now.', $accountId)); + } + if ($account->accountType->type !== AccountType::ASSET) { + throw new FireflyException(sprintf('Account with ID #%d is not an asset account. Job must stop now.', $accountId)); + } + + return $account; + } + + /** + * @param int $accountId + * + * @return SpectreAccount + * @throws FireflyException + */ + private function getSpectreAccount(int $accountId): SpectreAccount + { + $config = $this->importJob->configuration; + $accounts = $config['accounts'] ?? []; + foreach ($accounts as $account) { + $spectreId = (int)($account['id'] ?? 0.0); + if ($spectreId === $accountId) { + return new SpectreAccount($account); + } + } + throw new FireflyException(sprintf('Cannot find Spectre account with ID #%d in configuration. Job will exit.', $accountId)); + } + + /** + * @param SpectreAccount $spectreAccount + * @param LocalAccount $localAccount + * + * @return array + * @throws FireflyException + */ + private function getTransactions(SpectreAccount $spectreAccount, LocalAccount $localAccount): array + { + // grab all transactions + $request = new ListTransactionsRequest($this->importJob->user); + + $request->setAccount($spectreAccount); + $request->call(); + + $transactions = $request->getTransactions(); + + return $this->convertToArray($transactions, $spectreAccount, $localAccount); + } + + } \ No newline at end of file diff --git a/app/Support/Import/Routine/Spectre/ManageLoginsHandler.php b/app/Support/Import/Routine/Spectre/ManageLoginsHandler.php index c7a9f6417d..d879ab91ae 100644 --- a/app/Support/Import/Routine/Spectre/ManageLoginsHandler.php +++ b/app/Support/Import/Routine/Spectre/ManageLoginsHandler.php @@ -33,101 +33,11 @@ use FireflyIII\Services\Spectre\Request\ListLoginsRequest; use FireflyIII\Services\Spectre\Request\NewCustomerRequest; use Log; +/** + * Class ManageLoginsHandler + */ class ManageLoginsHandler { - - - public $countLogins = 0; - /** @var ImportJob */ - private $importJob; - /** @var ImportJobRepositoryInterface */ - private $repository; - - /** - * Tasks for this stage: - * - * - List all of the users logins. - * - If zero, return to "get-token" stage and make user make a login. That stage redirects here. - * - If one or more, list and let user select. - * - * @throws FireflyException - */ - public function run(): void - { - $customer = $this->getCustomer(); - - $request = new ListLoginsRequest($this->importJob->user); - $request->setCustomer($customer); - $request->call(); - - $list = $request->getLogins(); - - // count is zero? - $this->countLogins = \count($list); - if ($this->countLogins > 0) { - $store = []; - /** @var Login $login */ - foreach ($list as $login) { - $store[] = $login->toArray(); - } - $config = $this->repository->getConfiguration($this->importJob); - $config['all-logins'] = $store; - $this->repository->setConfiguration($this->importJob, $config); - } - } - - /** - * @param ImportJob $importJob - */ - public function setImportJob(ImportJob $importJob): void - { - $this->importJob = $importJob; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($importJob->user); - } - - /** - * @return Customer - * @throws FireflyException - */ - private function getCustomer(): Customer - { - Log::debug('Now in manageLoginsHandler::getCustomer()'); - $customer = $this->getExistingCustomer(); - if (null === $customer) { - Log::debug('The customer is NULL, will fire a newCustomerRequest.'); - $newCustomerRequest = new NewCustomerRequest($this->importJob->user); - $customer = $newCustomerRequest->getCustomer(); - - } - Log::debug('The customer is not null.'); - - return $customer; - } - - /** - * @return Customer|null - * @throws FireflyException - */ - private function getExistingCustomer(): ?Customer - { - Log::debug('Now in getExistingCustomer()'); - $customer = null; - $getCustomerRequest = new ListCustomersRequest($this->importJob->user); - $getCustomerRequest->call(); - $customers = $getCustomerRequest->getCustomers(); - - Log::debug(sprintf('Found %d customer(s)', \count($customers))); - /** @var Customer $current */ - foreach ($customers as $current) { - if ('default_ff3_customer' === $current->getIdentifier()) { - $customer = $current; - Log::debug('Found the correct customer.'); - break; - } - } - - return $customer; - } + } \ No newline at end of file diff --git a/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php b/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php index c1abadd7fb..611ce33f8e 100644 --- a/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php +++ b/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php @@ -27,9 +27,17 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Services\Spectre\Object\Account; +use FireflyIII\Services\Spectre\Object\Customer; use FireflyIII\Services\Spectre\Object\Login; use FireflyIII\Services\Spectre\Request\ListAccountsRequest; +use FireflyIII\Services\Spectre\Request\ListCustomersRequest; +use FireflyIII\Services\Spectre\Request\ListLoginsRequest; +use FireflyIII\Services\Spectre\Request\NewCustomerRequest; +use Log; +/** + * Class StageAuthenticatedHandler + */ class StageAuthenticatedHandler { /** @var ImportJob */ @@ -43,24 +51,31 @@ class StageAuthenticatedHandler * * @throws FireflyException */ - public function run() + public function run(): void { + Log::debug('Now in StageAuthenticatedHandler::run()'); // grab a list of logins. $config = $this->importJob->configuration; $logins = $config['all-logins'] ?? []; + Log::debug(sprintf('%d logins in config', \count($logins))); if (\count($logins) === 0) { - throw new FireflyException('StageAuthenticatedHandler expects more than 0 logins. Apologies, the import has stopped.'); + // get logins from Spectre. + $logins = $this->getLogins(); + $config['all-logins'] = $logins; } - $selectedLogin = $config['selected-login']; + $selectedLogin = $config['selected-login'] ?? 0; $login = null; + Log::debug(sprintf('$selectedLogin is %d', $selectedLogin)); foreach ($logins as $loginArray) { $loginId = $loginArray['id'] ?? -1; if ($loginId === $selectedLogin) { + Log::debug('Selected login is in the array with logins.'); $login = new Login($loginArray); } } if (null === $login) { + Log::debug('Login is null, simply use the first one from the array.'); $login = new Login($logins[0]); } @@ -93,13 +108,88 @@ class StageAuthenticatedHandler */ private function getAccounts(Login $login): array { + Log::debug(sprintf('Now in StageAuthenticatedHandler::getAccounts() for login #%d', $login->getId())); $request = new ListAccountsRequest($this->importJob->user); $request->setLogin($login); $request->call(); $accounts = $request->getAccounts(); + Log::debug(sprintf('Found %d accounts using login', \count($accounts))); return $accounts; } + /** + * @return Customer + * @throws FireflyException + */ + private function getCustomer(): Customer + { + Log::debug('Now in stageNewHandler::getCustomer()'); + $customer = $this->getExistingCustomer(); + if (null === $customer) { + Log::debug('The customer is NULL, will fire a newCustomerRequest.'); + $newCustomerRequest = new NewCustomerRequest($this->importJob->user); + $customer = $newCustomerRequest->getCustomer(); + + } + Log::debug('The customer is not null.'); + + return $customer; + } + + /** + * @return Customer|null + * @throws FireflyException + */ + private function getExistingCustomer(): ?Customer + { + Log::debug('Now in ChooseLoginHandler::getExistingCustomer()'); + $preference = app('preferences')->getForUser($this->importJob->user, 'spectre_customer'); + if (null !== $preference) { + Log::debug('Customer is in user configuration'); + $customer = new Customer($preference->data); + + return $customer; + } + Log::debug('Customer is not in user config'); + $customer = null; + $getCustomerRequest = new ListCustomersRequest($this->importJob->user); + $getCustomerRequest->call(); + $customers = $getCustomerRequest->getCustomers(); + + Log::debug(sprintf('Found %d customer(s)', \count($customers))); + /** @var Customer $current */ + foreach ($customers as $current) { + if ('default_ff3_customer' === $current->getIdentifier()) { + $customer = $current; + Log::debug('Found the correct customer.'); + app('preferences')->setForUser($this->importJob->user, 'spectre_customer', $customer->toArray()); + break; + } + } + + return $customer; + } + + /** + * @return array + * @throws FireflyException + */ + private function getLogins(): array + { + $customer = $this->getCustomer(); + $request = new ListLoginsRequest($this->importJob->user); + $request->setCustomer($customer); + $request->call(); + $logins = $request->getLogins(); + $return = []; + /** @var Login $login */ + foreach ($logins as $login) { + $return[] = $login->toArray(); + } + + return $return; + } + } \ No newline at end of file diff --git a/app/Support/Import/Routine/Spectre/StageNewHandler.php b/app/Support/Import/Routine/Spectre/StageNewHandler.php index 86ec6c6269..f81b7f8ff9 100644 --- a/app/Support/Import/Routine/Spectre/StageNewHandler.php +++ b/app/Support/Import/Routine/Spectre/StageNewHandler.php @@ -27,9 +27,9 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Services\Spectre\Object\Customer; -use FireflyIII\Services\Spectre\Object\Token; -use FireflyIII\Services\Spectre\Request\CreateTokenRequest; +use FireflyIII\Services\Spectre\Object\Login; use FireflyIII\Services\Spectre\Request\ListCustomersRequest; +use FireflyIII\Services\Spectre\Request\ListLoginsRequest; use FireflyIII\Services\Spectre\Request\NewCustomerRequest; use Log; @@ -40,35 +40,49 @@ use Log; */ class StageNewHandler { + /** @var int */ + public $countLogins = 0; /** @var ImportJob */ private $importJob; - /** @var ImportJobRepositoryInterface */ private $repository; /** * Tasks for this stage: * - * - Get the user's customer from Spectre. - * - Create a new customer if it does not exist. - * - Store it in the job either way. - * - Use it to grab a token. - * - Store the token in the job. + * - List all of the users logins. + * - If zero, return to "get-token" stage and make user make a login. That stage redirects here. + * - If one or more, list and let user select. * * @throws FireflyException */ public function run(): void { - Log::debug('Now in stageNewHandler::run()'); - $customer = $this->getCustomer(); + Log::debug('Now in ManageLoginsHandler::run()'); + $customer = $this->getCustomer(); + $config = $this->repository->getConfiguration($this->importJob); - // get token using customer. - app('preferences')->setForUser($this->importJob->user, 'spectre_customer', $customer->toArray()); + Log::debug('Going to get a list of logins.'); + $request = new ListLoginsRequest($this->importJob->user); + $request->setCustomer($customer); + $request->call(); - // store token in the job. - $config = $this->repository->getConfiguration($this->importJob); + $list = $request->getLogins(); - $this->repository->setConfiguration($this->importJob, $config); + // count is zero? + $this->countLogins = \count($list); + Log::debug(sprintf('Number of logins is %d', $this->countLogins)); + if ($this->countLogins > 0) { + $store = []; + /** @var Login $login */ + foreach ($list as $login) { + $store[] = $login->toArray(); + } + + $config['all-logins'] = $store; + $this->repository->setConfiguration($this->importJob, $config); + Log::debug('Stored all logins in configuration.'); + } } /** @@ -87,7 +101,7 @@ class StageNewHandler */ private function getCustomer(): Customer { - Log::debug('Now in stageNewHandler::getCustomer()'); + Log::debug('Now in manageLoginsHandler::getCustomer()'); $customer = $this->getExistingCustomer(); if (null === $customer) { Log::debug('The customer is NULL, will fire a newCustomerRequest.'); @@ -106,7 +120,7 @@ class StageNewHandler */ private function getExistingCustomer(): ?Customer { - Log::debug('Now in getExistingCustomer()'); + Log::debug('Now in manageLoginsHandler::getExistingCustomer()'); $customer = null; $getCustomerRequest = new ListCustomersRequest($this->importJob->user); $getCustomerRequest->call(); @@ -120,11 +134,9 @@ class StageNewHandler Log::debug('Found the correct customer.'); break; } + Log::debug(sprintf('Skip customer with name "%s"', $current->getIdentifier())); } return $customer; } - - - } \ No newline at end of file diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 515823a21e..7b71a29fdd 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -120,6 +120,8 @@ return [ 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', 'spectre_do_not_import' => '(do not import)', 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'imported_from_account' => 'Imported from ":account"', + 'spectre_account_with_number' => 'Account :number', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', @@ -460,5 +462,5 @@ return [ // 'spectre_extra_key_transactions_count' => 'Transaction count', // // // various other strings: - // 'imported_from_account' => 'Imported from ":account"', + // ];