You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
408 lines
15 KiB
408 lines
15 KiB
<?php
|
|
/*
|
|
* ConfigurationController.php
|
|
* Copyright (c) 2021 james@firefly-iii.org
|
|
*
|
|
* This file is part of the Firefly III Data Importer
|
|
* (https://github.com/firefly-iii/data-importer).
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers\Import;
|
|
|
|
|
|
use App\Exceptions\ImporterErrorException;
|
|
use App\Exceptions\ImporterHttpException;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Middleware\ConfigurationControllerMiddleware;
|
|
use App\Http\Request\ConfigurationPostRequest;
|
|
use App\Services\CSV\Configuration\Configuration;
|
|
use App\Services\CSV\Converter\Date;
|
|
use App\Services\Nordigen\Model\Account as NordigenAccount;
|
|
use App\Services\Nordigen\Request\ListAccountsRequest;
|
|
use App\Services\Nordigen\Response\ListAccountsResponse;
|
|
use App\Services\Nordigen\Services\AccountInformationCollector;
|
|
use App\Services\Nordigen\TokenManager;
|
|
use App\Services\Session\Constants;
|
|
use App\Services\Spectre\Model\Account as SpectreAccount;
|
|
use App\Services\Spectre\Request\GetAccountsRequest as SpectreGetAccountsRequest;
|
|
use App\Services\Spectre\Response\GetAccountsResponse as SpectreGetAccountsResponse;
|
|
use App\Services\Storage\StorageService;
|
|
use App\Support\Token;
|
|
use Cache;
|
|
use Carbon\Carbon;
|
|
use GrumpyDictator\FFIIIApiSupport\Model\Account;
|
|
use GrumpyDictator\FFIIIApiSupport\Request\GetAccountsRequest;
|
|
use Illuminate\Contracts\View\Factory;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
use JsonException;
|
|
use Log;
|
|
|
|
/**
|
|
* Class ConfigurationController
|
|
*/
|
|
class ConfigurationController extends Controller
|
|
{
|
|
/**
|
|
* StartController constructor.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
app('view')->share('pageTitle', 'Configuration');
|
|
$this->middleware(ConfigurationControllerMiddleware::class);
|
|
}
|
|
|
|
/**
|
|
* @return Factory|RedirectResponse|View
|
|
* @throws ImporterErrorException
|
|
*/
|
|
public function index(Request $request)
|
|
{
|
|
Log::debug(sprintf('Now at %s', __METHOD__));
|
|
$mainTitle = 'Configuration';
|
|
$subTitle = 'Configure your import';
|
|
$accounts = [];
|
|
$flow = $request->cookie(Constants::FLOW_COOKIE);
|
|
|
|
$configuration = null;
|
|
if (session()->has(Constants::CONFIGURATION)) {
|
|
$configuration = Configuration::fromArray(session()->get(Constants::CONFIGURATION));
|
|
}
|
|
// if config says to skip it, skip it:
|
|
$overruleSkip = 'true' === $request->get('overruleskip');
|
|
if (null !== $configuration && true === $configuration->isSkipForm() && false === $overruleSkip) {
|
|
// skipForm
|
|
return redirect()->route('005-roles.index');
|
|
}
|
|
|
|
// get list of asset accounts:
|
|
$url = Token::getURL();
|
|
$token = Token::getAccessToken();
|
|
$request = new GetAccountsRequest($url, $token);
|
|
$request->setType(GetAccountsRequest::ASSET);
|
|
$request->setVerify(config('importer.connection.verify'));
|
|
$request->setTimeOut(config('importer.connection.timeout'));
|
|
$response = $request->get();
|
|
|
|
/** @var Account $account */
|
|
foreach ($response as $account) {
|
|
$accounts['Asset accounts'][$account->id] = $account;
|
|
}
|
|
|
|
// also get liabilities
|
|
$url = Token::getURL();
|
|
$token = Token::getAccessToken();
|
|
$request = new GetAccountsRequest($url, $token);
|
|
$request->setVerify(config('importer.connection.verify'));
|
|
$request->setTimeOut(config('importer.connection.timeout'));
|
|
$request->setType(GetAccountsRequest::LIABILITIES);
|
|
$response = $request->get();
|
|
/** @var Account $account */
|
|
foreach ($response as $account) {
|
|
$accounts['Liabilities'][$account->id] = $account;
|
|
}
|
|
|
|
// created default configuration object for sensible defaults:
|
|
if (null === $configuration) {
|
|
$configuration = Configuration::make();
|
|
}
|
|
|
|
// also get the nordigen / spectre accounts
|
|
$importerAccounts = [];
|
|
if ('nordigen' === $flow) {
|
|
// list all accounts in Nordigen:
|
|
$reference = $configuration->getRequisition(session()->get(Constants::REQUISITION_REFERENCE));
|
|
$importerAccounts = $this->getNordigenAccounts($reference);
|
|
$importerAccounts = $this->mergeNordigenAccountLists($importerAccounts, $accounts);
|
|
}
|
|
|
|
if ('spectre' === $flow) {
|
|
// get the accounts over at Spectre.
|
|
$url = config('spectre.url');
|
|
$appId = config('spectre.app_id');
|
|
$secret = config('spectre.secret');
|
|
$spectreList = new SpectreGetAccountsRequest($url, $appId, $secret);
|
|
$spectreList->connection = $configuration->getConnection();
|
|
$spectreAccounts = $spectreList->get();
|
|
$importerAccounts = $this->mergeSpectreAccountLists($spectreAccounts, $accounts);
|
|
}
|
|
|
|
return view(
|
|
'import.004-configure.index',
|
|
compact('mainTitle', 'subTitle', 'accounts', 'configuration', 'flow', 'importerAccounts')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param Request $request
|
|
*
|
|
* @return JsonResponse
|
|
*/
|
|
public function phpDate(Request $request): JsonResponse
|
|
{
|
|
Log::debug(sprintf('Method %s', __METHOD__));
|
|
|
|
$dateObj = new Date;
|
|
[$locale, $format] = $dateObj->splitLocaleFormat((string) $request->get('format'));
|
|
$date = Carbon::make('1984-09-17')->locale($locale);
|
|
|
|
return response()->json(['result' => $date->translatedFormat($format)]);
|
|
}
|
|
|
|
/**
|
|
* @param ConfigurationPostRequest $request
|
|
*
|
|
* @return RedirectResponse
|
|
* @throws ImporterErrorException
|
|
*/
|
|
public function postIndex(ConfigurationPostRequest $request): RedirectResponse
|
|
{
|
|
Log::debug(sprintf('Now running %s', __METHOD__));
|
|
// store config on drive.
|
|
$fromRequest = $request->getAll();
|
|
$configuration = Configuration::fromRequest($fromRequest);
|
|
$configuration->setFlow($request->cookie(Constants::FLOW_COOKIE));
|
|
|
|
// TODO are all fields actually in the config?
|
|
|
|
// loop accounts:
|
|
$accounts = [];
|
|
foreach (array_keys($fromRequest['do_import']) as $identifier) {
|
|
if (isset($fromRequest['accounts'][$identifier])) {
|
|
$accounts[$identifier] = (int) $fromRequest['accounts'][$identifier];
|
|
}
|
|
}
|
|
$configuration->setAccounts($accounts);
|
|
$configuration->updateDateRange();
|
|
|
|
|
|
$json = '{}';
|
|
try {
|
|
$json = json_encode($configuration->toArray(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT);
|
|
} catch (JsonException $e) {
|
|
Log::error($e->getMessage());
|
|
throw new ImporterErrorException($e->getMessage(), 0, $e);
|
|
}
|
|
StorageService::storeContent($json);
|
|
|
|
session()->put(Constants::CONFIGURATION, $configuration->toSessionArray());
|
|
|
|
|
|
Log::debug(sprintf('Configuration debug: Connection ID is "%s"', $configuration->getConnection()));
|
|
// set config as complete.
|
|
session()->put(Constants::CONFIG_COMPLETE_INDICATOR, true);
|
|
if ('nordigen' === $configuration->getFlow() || 'spectre' === $configuration->getFlow()) {
|
|
// at this point, nordigen is ready for data conversion.
|
|
session()->put(Constants::READY_FOR_CONVERSION, true);
|
|
}
|
|
// always redirect to roles, even if this isn't the step yet
|
|
// for nordigen and spectre, roles will be skipped right away.
|
|
return redirect(route('005-roles.index'));
|
|
}
|
|
|
|
/**
|
|
* List Nordigen accounts with account details, balances, and 2 transactions (if present)
|
|
* @return array
|
|
* @throws ImporterErrorException
|
|
*/
|
|
private function getNordigenAccounts(string $identifier): array
|
|
{
|
|
if (Cache::has($identifier)) {
|
|
$result = Cache::get($identifier);
|
|
$return = [];
|
|
foreach ($result as $arr) {
|
|
$return[] = NordigenAccount::fromLocalArray($arr);
|
|
}
|
|
Log::debug('Grab accounts from cache', $result);
|
|
return $return;
|
|
}
|
|
Log::debug(sprintf('Now in %s', __METHOD__));
|
|
// get banks and countries
|
|
$accessToken = TokenManager::getAccessToken();
|
|
$url = config('nordigen.url');
|
|
$request = new ListAccountsRequest($url, $identifier, $accessToken);
|
|
/** @var ListAccountsResponse $response */
|
|
try {
|
|
$response = $request->get();
|
|
} catch (ImporterErrorException $e) {
|
|
} catch (ImporterHttpException $e) {
|
|
throw new ImporterErrorException($e->getMessage(), 0, $e);
|
|
}
|
|
$total = count($response);
|
|
$return = [];
|
|
$cache = [];
|
|
Log::debug(sprintf('Found %d accounts.', $total));
|
|
|
|
/** @var Account $account */
|
|
foreach ($response as $index => $account) {
|
|
Log::debug(sprintf('[%d/%d] Now collecting information for account %s', ($index + 1), $total, $account->getIdentifier()), $account->toLocalArray());
|
|
$account = AccountInformationCollector::collectInformation($account);
|
|
$return[] = $account;
|
|
$cache[] = $account->toLocalArray();
|
|
}
|
|
Cache::put($identifier, $cache, 1800); // half an hour
|
|
return $return;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param SpectreGetAccountsResponse $spectre
|
|
* @param array $firefly
|
|
*
|
|
* TODO should be a helper
|
|
*/
|
|
private function mergeSpectreAccountLists(SpectreGetAccountsResponse $spectre, array $firefly): array
|
|
{
|
|
$return = [];
|
|
Log::debug('Now creating Spectre account lists.');
|
|
|
|
/** @var SpectreAccount $spectreAccount */
|
|
foreach ($spectre as $spectreAccount) {
|
|
Log::debug(sprintf('Now working on Spectre account "%s": "%s"', $spectreAccount->name, $spectreAccount->id));
|
|
$iban = $spectreAccount->iban;
|
|
$currency = $spectreAccount->currencyCode;
|
|
$entry = [
|
|
'import_service' => $spectreAccount,
|
|
'firefly' => [],
|
|
];
|
|
|
|
// only iban?
|
|
$filteredByIban = $this->filterByIban($firefly, $iban);
|
|
|
|
if (1 === count($filteredByIban)) {
|
|
Log::debug(sprintf('This account (%s) has a single Firefly III counter part (#%d, "%s", same IBAN), so will use that one.', $iban, $filteredByIban[0]->id, $filteredByIban[0]->name));
|
|
$entry['firefly'] = $filteredByIban;
|
|
$return[] = $entry;
|
|
continue;
|
|
}
|
|
Log::debug(sprintf('Found %d accounts with the same IBAN ("%s")', count($filteredByIban), $iban));
|
|
|
|
// only currency?
|
|
$filteredByCurrency = $this->filterByCurrency($firefly, $currency);
|
|
|
|
if (count($filteredByCurrency) > 0) {
|
|
Log::debug(sprintf('This account (%s) has some Firefly III counter parts with the same currency so will only use those.', $currency));
|
|
$entry['firefly'] = $filteredByCurrency;
|
|
$return[] = $entry;
|
|
continue;
|
|
}
|
|
Log::debug('No special filtering on the Firefly III account list.');
|
|
$entry['firefly'] = $firefly;
|
|
$return[] = $entry;
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param array $nordigen
|
|
* @param array $firefly
|
|
* @return array
|
|
*
|
|
* TODO move to some helper.
|
|
*/
|
|
private function mergeNordigenAccountLists(array $nordigen, array $firefly): array
|
|
{
|
|
Log::debug('Now creating Nordigen account lists.');
|
|
$return = [];
|
|
/** @var NordigenAccount $nordigenAccount */
|
|
foreach ($nordigen as $nordigenAccount) {
|
|
Log::debug(sprintf('Now working on account "%s": "%s"', $nordigenAccount->getName(), $nordigenAccount->getIdentifier()));
|
|
$iban = $nordigenAccount->getIban();
|
|
$currency = $nordigenAccount->getCurrency();
|
|
$entry = [
|
|
'import_service' => $nordigenAccount,
|
|
'firefly' => [],
|
|
];
|
|
|
|
// only iban?
|
|
$filteredByIban = $this->filterByIban($firefly, $iban);
|
|
|
|
if (1 === count($filteredByIban)) {
|
|
Log::debug(sprintf('This account (%s) has a single Firefly III counter part (#%d, "%s", same IBAN), so will use that one.', $iban, $filteredByIban[0]->id, $filteredByIban[0]->name));
|
|
$entry['firefly'] = $filteredByIban;
|
|
$return[] = $entry;
|
|
continue;
|
|
}
|
|
Log::debug(sprintf('Found %d accounts with the same IBAN ("%s")', count($filteredByIban), $iban));
|
|
|
|
// only currency?
|
|
$filteredByCurrency = $this->filterByCurrency($firefly, $currency);
|
|
|
|
if (count($filteredByCurrency) > 0) {
|
|
Log::debug(sprintf('This account (%s) has some Firefly III counter parts with the same currency so will only use those.', $currency));
|
|
$entry['firefly'] = $filteredByCurrency;
|
|
$return[] = $entry;
|
|
continue;
|
|
}
|
|
Log::debug('No special filtering on the Firefly III account list.');
|
|
$entry['firefly'] = $firefly;
|
|
$return[] = $entry;
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* TODO move to some helper.
|
|
*
|
|
* @param array $firefly
|
|
* @param string $iban
|
|
* @return array
|
|
*/
|
|
private function filterByIban(array $firefly, string $iban): array
|
|
{
|
|
if ('' === $iban) {
|
|
return [];
|
|
}
|
|
$result = [];
|
|
$all = array_merge($firefly['Asset accounts'] ?? [], $firefly['Liabilities'] ?? []);
|
|
/** @var Account $account */
|
|
foreach ($all as $account) {
|
|
if ($iban === $account->iban) {
|
|
$result[] = $account;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param array $firefly
|
|
* @param string $currency
|
|
* @return array
|
|
*/
|
|
private function filterByCurrency(array $firefly, string $currency): array
|
|
{
|
|
if ('' === $currency) {
|
|
return [];
|
|
}
|
|
$result = [];
|
|
$all = array_merge($firefly['Asset accounts'] ?? [], $firefly['Liabilities'] ?? []);
|
|
/** @var Account $account */
|
|
foreach ($all as $account) {
|
|
if ($currency === $account->currencyCode) {
|
|
$result[] = $account;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
|
|
}
|