Browse Source

Can now submit Nordigen

pull/1/head
James Cole 4 years ago
parent
commit
deddee3b30
No known key found for this signature in database GPG Key ID: BDE6667570EADBD5
  1. 5
      .env.example
  2. 4
      app/Http/Controllers/Import/SubmitController.php
  3. 67
      app/Services/Nordigen/Conversion/Routine/FilterTransactions.php
  4. 428
      app/Services/Nordigen/Conversion/Routine/GenerateTransactions.php
  5. 9
      app/Services/Nordigen/Conversion/Routine/TransactionProcessor.php
  6. 28
      app/Services/Nordigen/Conversion/RoutineManager.php
  7. 8
      app/Services/Nordigen/Model/Transaction.php
  8. 6
      app/Services/Shared/Import/Routine/ApiSubmitter.php
  9. 355
      changelog.md
  10. 5
      config/importer.php
  11. 2
      public/js/convert.js
  12. 2
      public/js/submit.js
  13. 14
      resources/js/components/import/ConversionStatus.vue
  14. 15
      resources/js/components/submit/SubmissionStatus.vue

5
.env.example

@ -52,6 +52,11 @@ NORDIGEN_SANDBOX=false
SPECTRE_APP_ID=
SPECTRE_SECRET=
#
# Use cache
#
USE_CACHE=false
#
# When you're running Firefly III under a (self-signed) certificate,
# the CSV importer may have trouble verifying the TLS connection.

4
app/Http/Controllers/Import/SubmitController.php

@ -69,8 +69,8 @@ class SubmitController extends Controller
$configuration = Configuration::fromArray(session()->get(Constants::CONFIGURATION));
// append info from the file on disk:
$configFileName = Constants::UPLOAD_CONFIG_FILE;
if (null !== $configFileName) {
$diskArray = json_decode(StorageService::getContent(session()->get($configFileName)), true, JSON_THROW_ON_ERROR);
if (null !== session()->get($configFileName)) {
$diskArray = json_decode(StorageService::getContent($configFileName), true, JSON_THROW_ON_ERROR);
$diskConfig = Configuration::fromArray($diskArray);
$configuration->setDoMapping($diskConfig->getDoMapping());
$configuration->setMapping($diskConfig->getMapping());

67
app/Services/Nordigen/Conversion/Routine/FilterTransactions.php

@ -0,0 +1,67 @@
<?php
/*
* FilterTransactions.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of the Firefly III Nordigen importer
* (https://github.com/firefly-iii/nordigen-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\Services\Nordigen\Conversion\Routine;
use App\Services\Shared\Conversion\ProgressInformation;
/**
* Class FilterTransactions
*/
class FilterTransactions
{
use ProgressInformation;
/**
* FilterTransactions constructor.
*/
public function __construct()
{
}
/**
* @param array $transactions
*
* @return array
*/
public function filter(array $transactions): array
{
$start = count($transactions);
$return = [];
/** @var array $transaction */
foreach ($transactions as $transaction) {
// TODO no real filtering takes place yet.
unset($transaction['transactions'][0]['datetime']);
$return[] = $transaction;
}
$end = count($return);
$this->addMessage(0, sprintf('Filtered down from %d (possibly duplicate) entries to %d unique transactions.', $start, $end));
return $return;
}
}

428
app/Services/Nordigen/Conversion/Routine/GenerateTransactions.php

@ -0,0 +1,428 @@
<?php
/*
* GenerateTransactions.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\Services\Nordigen\Conversion\Routine;
use App\Exceptions\ImporterErrorException;
use App\Exceptions\ImporterHttpException;
use App\Services\CSV\Configuration\Configuration;
use App\Services\Nordigen\Model\Transaction;
use App\Services\Nordigen\Request\GetAccountInformationRequest;
use App\Services\Nordigen\Response\ArrayResponse;
use App\Services\Nordigen\TokenManager;
use App\Services\Shared\Conversion\ProgressInformation;
use Cache;
use GrumpyDictator\FFIIIApiSupport\Exceptions\ApiHttpException;
use GrumpyDictator\FFIIIApiSupport\Model\Account;
use GrumpyDictator\FFIIIApiSupport\Request\GetAccountRequest;
use GrumpyDictator\FFIIIApiSupport\Request\GetAccountsRequest;
use GrumpyDictator\FFIIIApiSupport\Response\GetAccountResponse;
use GrumpyDictator\FFIIIApiSupport\Response\GetAccountsResponse;
use Log;
/**
* Class GenerateTransactions.
*/
class GenerateTransactions
{
use ProgressInformation;
private array $accounts;
private Configuration $configuration;
private array $targetAccounts;
private array $targetTypes;
private array $nordigenAccountInfo;
/**
* GenerateTransactions constructor.
*/
public function __construct()
{
$this->targetAccounts = [];
$this->targetTypes = [];
$this->nordigenAccountInfo = [];
bcscale(16);
}
/**
*
*/
public function collectTargetAccounts(): void
{
if (config('importer.use_cache') && Cache::has('collect_target_accounts')) {
Log::debug('Grab target accounts from cache.');
$info = Cache::get('collect_target_accounts');
$this->targetAccounts = $info['accounts'];
$this->targetTypes = $info['types'];
return;
}
Log::debug('Going to collect all target accounts from Firefly III.');
// send account list request to Firefly III.
$token = (string) config('importer.access_token');
$url = (string) config('importer.url');
$request = new GetAccountsRequest($url, $token);
/** @var GetAccountsResponse $result */
$result = $request->get();
$return = [];
$types = [];
/** @var Account $entry */
foreach ($result as $entry) {
$type = $entry->type;
if (in_array($type, ['reconciliation', 'initial-balance', 'expense', 'revenue'], true)) {
continue;
}
$iban = $entry->iban;
if ('' === (string) $iban) {
continue;
}
Log::debug(sprintf('Collected %s (%s) under ID #%d', $iban, $entry->type, $entry->id));
$return[$iban] = $entry->id;
$types[$iban] = $entry->type;
Log::debug(sprintf('Added account #%d (%s) with IBAN "%s"', $entry->id, $entry->type, $iban));
}
$this->targetAccounts = $return;
$this->targetTypes = $types;
Log::debug(sprintf('Collected %d accounts.', count($this->targetAccounts)));
if (config('importer.use_cache')) {
$array = [
'accounts' => $return,
'types' => $types,
];
Cache::put('collect_target_accounts', $array, 86400); // 24h
Log::info('Stored collected accounts in cache.');
}
}
/**
* TODO the result of this method is currently not used.
*
* @throws ImporterErrorException
*/
public function collectNordigenAccounts(): void
{
if (config('importer.use_cache') && Cache::has('collect_nordigen_accounts')) {
Log::debug('Grab Nordigen accounts from cache.');
$this->nordigenAccountInfo = Cache::get('collect_nordigen_accounts');
return;
}
$url = config('nordigen.url');
$accessToken = TokenManager::getAccessToken();
$info = [];
Log::debug('Going to collect account information from Nordigen.');
/**
* @var string $nordigenIdentifier
* @var int $account
*/
foreach ($this->accounts as $nordigenIdentifier => $account) {
Log::debug(sprintf('Now at #%d => %s', $account, $nordigenIdentifier));
$set = [];
// get account details
$request = new GetAccountInformationRequest($url, $accessToken, $nordigenIdentifier);
/** @var ArrayResponse $response */
try {
$response = $request->get();
} catch (ImporterHttpException $e) {
throw new ImporterErrorException($e->getMessage(), 0, $e);
}
$accountInfo = $response->data['account'];
$set['iban'] = $accountInfo['iban'] ?? '';
$info[$nordigenIdentifier] = $set;
Log::debug(sprintf('Collected IBAN "%s" for Nordigen account "%s"', $set['iban'], $nordigenIdentifier));
}
$this->nordigenAccountInfo = $info;
if (config('importer.use_cache')) {
Cache::put('collect_nordigen_accounts', $info, 86400); // 24h
Log::info('Stored collected Nordigen accounts in cache.');
}
}
/**
* @param array $transactions
*
* @return array
*/
public function getTransactions(array $transactions): array
{
Log::debug('Now generate transactions.');
$return = [];
/**
* @var string $accountId
* @var array $entries
*/
foreach ($transactions as $accountId => $entries) {
$total = count($entries);
app('log')->debug(sprintf('Going to parse account %s with %d transaction(s).', $accountId, $total));
/**
* @var int $index
* @var Transaction $entry
*/
foreach ($entries as $index => $entry) {
Log::debug(sprintf('[%d/%d] Parsing transaction.', ($index + 1), $total));
$return[] = $this->generateTransaction($accountId, $entry);
Log::debug(sprintf('[%d/%d] Done parsing transaction.', ($index + 1), $total));
}
}
$this->addMessage(0, sprintf('Parsed %d Spectre transactions for further processing.', count($return)));
Log::debug('Done parsing transactions.');
return $return;
}
/**
* TODO function is way too complex.
*
* @param string $accountId
* @param Transaction $entry
* @return array
*/
private function generateTransaction(string $accountId, Transaction $entry): array
{
Log::debug(sprintf('Nordigen transaction: "%s" with amount %s %s', $entry->getDescription(), $entry->currencyCode, $entry->transactionAmount));
Log::debug('Original Nordigen transaction', $entry->toLocalArray());
$return = [
'apply_rules' => $this->configuration->isRules(),
'error_if_duplicate_hash' => true,
'transactions' => [
[
'type' => 'withdrawal', // reverse
'date' => $entry->valueDate->format('Y-m-d'),
'datetime' => $entry->valueDate->toW3cString(),
'amount' => $entry->transactionAmount,
'description' => $entry->getDescription(),
'order' => 0,
'currency_code' => $entry->currencyCode,
'tags' => [],
'category_name' => null,
'category_id' => null,
],
],
];
// save meta:
$return['transactions'][0]['external_id'] = $entry->transactionId;
$return['transactions'][0]['internal_reference'] = $entry->accountIdentifier;
if (1 === bccomp($entry->transactionAmount, '0')) {
Log::debug('Amount is positive: perhaps transfer or deposit.');
// amount is positive: deposit or transfer. Spectre account is destination
$return['transactions'][0]['type'] = 'deposit';
$return['transactions'][0]['amount'] = $entry->transactionAmount;
// destination is a Nordigen account
$return['transactions'][0]['destination_id'] = (int) $this->accounts[$accountId];
// source iban valid?
$sourceIban = $entry->getSourceIban() ?? '';
if ('' !== $sourceIban && array_key_exists($sourceIban, $this->targetAccounts)) {
// source is also an ID:
Log::debug(sprintf('Recognized %s as a Firefly III asset account so this is a transfer.', $sourceIban));
$return['transactions'][0]['source_id'] = $this->targetAccounts[$sourceIban];
$return['transactions'][0]['type'] = 'transfer';
}
if ('' === $sourceIban || !array_key_exists($sourceIban, $this->targetAccounts)) {
Log::debug(sprintf('"%s" is not a valid IBAN OR not recognized as Firefly III asset account so submitted as-is.', $sourceIban));
// source is the other side:
$return['transactions'][0]['source_name'] = $entry->getSourceName() ?? '(unknown source account)';
$return['transactions'][0]['source_iban'] = $entry->getSourceIban() ?? null;
}
$mappedId = null;
if (isset($return['transactions'][0]['source_name'])) {
Log::debug(sprintf('Check if "%s" is mapped to an account by the user.', $return['transactions'][0]['source_name']));
$mappedId = $this->getMappedAccountId($return['transactions'][0]['source_name']);
}
if (null !== $mappedId && 0 !== $mappedId) {
Log::debug(sprintf('Account name "%s" is mapped to Firefly III account ID "%d"', $return['transactions'][0]['source_name'], $mappedId));
$mappedType = $this->getMappedAccountType($mappedId);
$originalSourceName = $return['transactions'][0]['source_name'];
$return['transactions'][0]['source_id'] = $mappedId;
// catch error here:
try {
$return['transactions'][0]['type'] = $this->getTransactionType($mappedType, 'asset');
Log::debug(sprintf('Transaction type seems to be %s', $return['transactions'][0]['type']));
} catch (ImporterErrorException $e) {
Log::error($e->getMessage());
Log::info('Will not use mapped ID, Firefly III account is of the wrong type.');
unset($return['transactions'][0]['source_id']);
$return['transactions'][0]['source_name'] = $originalSourceName;
}
}
}
if (-1 === bccomp($entry->transactionAmount, '0')) {
// amount is negative: withdrawal or transfer.
Log::debug('Amount is negative: assume transfer or withdrawal.');
$return['transactions'][0]['amount'] = bcmul($entry->transactionAmount, '-1');
// source is a Nordigen account
// TODO entry may not exist, then what?
$return['transactions'][0]['source_id'] = (int) $this->accounts[$accountId];
// destination iban valid?
$destinationIban = $entry->getDestinationIban() ?? '';
if ('' !== $destinationIban && array_key_exists($destinationIban, $this->targetAccounts)) {
// source is also an ID:
Log::debug(sprintf('Recognized %s as a Firefly III asset account so this is a transfer.', $destinationIban));
$return['transactions'][0]['destination_id'] = $this->targetAccounts[$destinationIban];
$return['transactions'][0]['type'] = 'transfer';
}
// destination iban valid or doesn't exist:
if ('' === $destinationIban || !array_key_exists($destinationIban, $this->targetAccounts)) {
Log::debug(sprintf('"%s" is not a valid IBAN OR not recognized as Firefly III asset account so submitted as-is.', $destinationIban));
// destination is the other side:
$return['transactions'][0]['destination_name'] = $entry->getDestinationName() ?? '(unknown destination account)';
$return['transactions'][0]['destination_iban'] = $entry->getDestinationIban() ?? null;
}
$mappedId = null;
if (isset($return['transactions'][0]['destination_name'])) {
Log::debug(sprintf('Check if "%s" is mapped to an account by the user.', $return['transactions'][0]['destination_name']));
$mappedId = $this->getMappedAccountId($return['transactions'][0]['destination_name']);
}
if (null !== $mappedId && 0 !== $mappedId) {
Log::debug(sprintf('Account name "%s" is mapped to Firefly III account ID "%d"', $return['transactions'][0]['destination_name'], $mappedId));
$mappedType = $this->getMappedAccountType($mappedId);
$originalDestName = $return['transactions'][0]['destination_name'];
$return['transactions'][0]['destination_id'] = $mappedId;
// catch error here:
try {
$return['transactions'][0]['type'] = $this->getTransactionType('asset', $mappedType);
Log::debug(sprintf('Transaction type seems to be %s', $return['transactions'][0]['type']));
} catch (ImporterErrorException $e) {
Log::error($e->getMessage());
Log::info('Will not use mapped ID, Firefly III account is of the wrong type.');
unset($return['transactions'][0]['destination_id']);
$return['transactions'][0]['destination_name'] = $originalDestName;
}
app('log')->debug(sprintf('Parsed Nordigen transaction "%s".', $entry->transactionId), $return);
}
}
app('log')->debug(sprintf('Parsed Nordigen transaction "%s".', $entry->transactionId));
return $return;
}
/**
* @param string $name
*
* @return int|null
*/
private function getMappedAccountId(string $name): ?int
{
if (isset($this->configuration->getMapping()['accounts'][$name])) {
return (int) $this->configuration->getMapping()['accounts'][$name];
}
return null;
}
/**
* @param int $mappedId
*
* @return string
*/
private function getMappedAccountType(int $mappedId): string
{
if (!isset($this->configuration->getAccountTypes()[$mappedId])) {
app('log')->warning(sprintf('Cannot find account type for Firefly III account #%d.', $mappedId));
$accountType = $this->getAccountType($mappedId);
$accountTypes = $this->configuration->getAccountTypes();
$accountTypes[$mappedId] = $accountType;
$this->configuration->setAccountTypes($accountTypes);
Log::debug(sprintf('Account type for Firefly III account #%d is "%s"', $mappedId, $accountType));
return $accountType;
}
$type = $this->configuration->getAccountTypes()[$mappedId] ?? 'expense';
Log::debug(sprintf('Account type for Firefly III account #%d is "%s"', $mappedId, $type));
return $type;
}
/**
* @param int $accountId
*
* @return string
* @throws ImporterHttpException
*/
private function getAccountType(int $accountId): string
{
$url = (string) config('importer.url');
$token = (string) config('importer.access_token');
app('log')->debug(sprintf('Going to download account #%d', $accountId));
$request = new GetAccountRequest($url, $token);
$request->setId($accountId);
/** @var GetAccountResponse $result */
try {
$result = $request->get();
} catch (ApiHttpException $e) {
throw new ImporterHttpException($e->getMessage(), 0, $e);
}
$type = $result->getAccount()->type;
app('log')->debug(sprintf('Discovered that account #%d is of type "%s"', $accountId, $type));
return $type;
}
/**
* @param string $source
* @param string $destination
*
* @return string
* @throws ImporterErrorException
*/
private function getTransactionType(string $source, string $destination): string
{
$combination = sprintf('%s-%s', $source, $destination);
switch ($combination) {
default:
throw new ImporterErrorException(sprintf('Unknown combination: %s and %s', $source, $destination));
case 'asset-liabilities':
case 'asset-expense':
return 'withdrawal';
case 'asset-asset':
return 'transfer';
case 'liabilities-asset':
case 'revenue-asset':
return 'deposit';
}
}
/**
* @param Configuration $configuration
*/
public function setConfiguration(Configuration $configuration): void
{
$this->configuration = $configuration;
$this->accounts = $configuration->getAccounts();
}
}

9
app/Services/Nordigen/Conversion/TransactionProcessor.php → app/Services/Nordigen/Conversion/Routine/TransactionProcessor.php

@ -3,8 +3,8 @@
* TransactionProcessor.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of the Firefly III Nordigen importer
* (https://github.com/firefly-iii/nordigen-importer).
* 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
@ -22,11 +22,12 @@
declare(strict_types=1);
namespace App\Services\Nordigen\Conversion;
namespace App\Services\Nordigen\Conversion\Routine;
use App\Exceptions\ImporterErrorException;
use App\Exceptions\ImporterHttpException;
use App\Services\CSV\Configuration\Configuration;
use App\Services\Nordigen\Model\Transaction;
use App\Services\Nordigen\Request\GetTransactionsRequest;
use App\Services\Nordigen\Response\GetTransactionsResponse;
use App\Services\Nordigen\TokenManager;
@ -123,7 +124,7 @@ class TransactionProcessor
continue;
}
app('log')->info(sprintf('Include transaction because date is "%s".', $madeOn->format(self::DATE_TIME_FORMAT),));
$return[] = $transaction->toLocalArray();
$return[] = $transaction;
}
Log::debug(sprintf('After filtering, set is %d transaction(s)', count($return)));

28
app/Services/Nordigen/Conversion/RoutineManager.php

@ -22,7 +22,11 @@
namespace App\Services\Nordigen\Conversion;
use App\Exceptions\ImporterErrorException;
use App\Services\CSV\Configuration\Configuration;
use App\Services\Nordigen\Conversion\Routine\FilterTransactions;
use App\Services\Nordigen\Conversion\Routine\GenerateTransactions;
use App\Services\Nordigen\Conversion\Routine\TransactionProcessor;
use App\Services\Shared\Authentication\IsRunningCli;
use App\Services\Shared\Conversion\GeneratesIdentifier;
use App\Services\Shared\Conversion\RoutineManagerInterface;
@ -37,6 +41,8 @@ class RoutineManager implements RoutineManagerInterface
private Configuration $configuration;
private TransactionProcessor $transactionProcessor;
private GenerateTransactions $transactionGenerator;
private FilterTransactions $transactionFilter;
/**
*
@ -50,6 +56,8 @@ class RoutineManager implements RoutineManagerInterface
$this->identifier = $identifier;
}
$this->transactionProcessor = new TransactionProcessor;
$this->transactionGenerator = new GenerateTransactions;
$this->transactionFilter = new FilterTransactions;
}
/**
@ -62,14 +70,19 @@ class RoutineManager implements RoutineManagerInterface
// share config
$this->transactionProcessor->setConfiguration($configuration);
$this->transactionGenerator->setConfiguration($configuration);
//$this->transactionFilter->setConfiguration($configuration);
// set identifier
$this->transactionProcessor->setIdentifier($this->identifier);
$this->transactionGenerator->setIdentifier($this->identifier);
$this->transactionFilter->setIdentifier($this->identifier);
}
/**
* @inheritDoc
* @throws ImporterErrorException
*/
public function start(): array
{
@ -79,11 +92,18 @@ class RoutineManager implements RoutineManagerInterface
Log::debug('Call transaction processor download.');
$nordigen = $this->transactionProcessor->download();
echo '<pre>';
var_dump($nordigen);
exit;
die('need to convert to Firefly III things, then return.');
// generate Firefly III ready transactions:
app('log')->debug('Generating Firefly III transactions.');
$this->transactionGenerator->collectTargetAccounts();
$this->transactionGenerator->collectNordigenAccounts();
$transactions = $this->transactionGenerator->getTransactions($nordigen);
app('log')->debug(sprintf('Generated %d Firefly III transactions.', count($transactions)));
$filtered = $this->transactionFilter->filter($transactions);
app('log')->debug(sprintf('Filtered down to %d Firefly III transactions.', count($filtered)));
return $filtered;
}
/**

8
app/Services/Nordigen/Model/Transaction.php

@ -40,6 +40,7 @@ class Transaction
public string $creditorId;
public string $creditorName;
public array $currencyExchange; // is an array (see https://github.com/firefly-iii/firefly-iii/issues/5286)
// TODO use currency exchange info in notes
public string $debtorAgent;
public string $debtorName;
public string $entryReference;
@ -155,8 +156,15 @@ class Transaction
if ('' !== $this->remittanceInformationUnstructured) {
$description = $this->remittanceInformationUnstructured;
}
// try other values as well (Revolut)
if('' === $description && count($this->remittanceInformationUnstructuredArray) > 0) {
$description = implode(' ', $this->remittanceInformationUnstructuredArray);
}
if ('' === $description) {
Log::warning(sprintf('Transaction "%s" has no description.', $this->transactionId));
$description = '(no description)';
}
return $description;
}

6
app/Services/Shared/Import/Routine/ApiSubmitter.php

@ -326,7 +326,8 @@ class ApiSubmitter
/** @var Transaction $transaction */
foreach ($group->transactions as $index => $transaction) {
// compare currency ID
if (null !== $line['transactions'][$index]['currency_id']
if (array_key_exists('currency_id', $line['transactions'][$index]) &&
null !== $line['transactions'][$index]['currency_id']
&& (int) $line['transactions'][$index]['currency_id'] !== (int) $transaction->currencyId
) {
$this->addWarning(
@ -338,7 +339,8 @@ class ApiSubmitter
);
}
// compare currency code:
if (null !== $line['transactions'][$index]['currency_code']
if (array_key_exists('currency_code', $line['transactions'][$index]) &&
null !== $line['transactions'][$index]['currency_code']
&& $line['transactions'][$index]['currency_code'] !== $transaction->currencyCode
) {
$this->addWarning(

355
changelog.md

@ -2,360 +2,7 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 2.7.0 - 2021-10-10
### Deprecated
- The CSV importer requires Firefly III 5.6.0 or higher.
### Fixed
- [Issue 5047](https://github.com/firefly-iii/firefly-iii/issues/5047) When importing using large configuration files, the importer would only save the top ~400 mapping entries.
## 2.6.1 - 2020-09-04
### Changed
- The mapper now sorts all dropdowns by alphabet.
### Security
- Updated JS and PHP packages.
## 2.6.0 - 2020-07-23
### Changed
- The CSV importer now requires PHP 8.0
## 2.5.4 - 2020-07-21
### Security
- Updated JS and PHP packages.
## 2.5.3 - 2020-06-07
### Fixed
- [Issue 4840](https://github.com/firefly-iii/firefly-iii/issues/4840) Corrected error catching in IBAN check
## 2.5.2 - 2020-05-23
### Fixed
- [Issue 4714](https://github.com/firefly-iii/firefly-iii/issues/4714) Bad count in line numbers.
- [PR 147](https://github.com/firefly-iii/csv-importer/pull/147) Vanity URL in messages.
### Changed
- Some textual changes.
- [PR 146](https://github.com/firefly-iii/csv-importer/pull/146) iDeal transactions in ING specific.
### Security
- Lots of updated packages.
## 2.5.1 - 2021-04-11
### Added
- Expand the readme with instructions and new logo. Thanks, @ColinMaudry!
### Fixed
- Several cases where URL's could be empty.
- CSV importer will start counting from 1, not from 0 when showing messages. Thanks, @lastlink!
## 2.5.0 - 2021-02-06
### Added
- [Issue 3788](https://github.com/firefly-iii/firefly-iii/issues/3788) It's now possible to change the way the CSV importer detects duplicates. Please read [the documentation](https://docs.firefly-iii.org/csv/usage/configure/).
### Fixed
- [Issue 4333](https://github.com/firefly-iii/firefly-iii/issues/4333) Bad coding on my part mixed up the vanity URL and the normal URL.
## 2.4.0 - 2021-01-29
### Added
- [Issue 4297](https://github.com/firefly-iii/firefly-iii/issues/4297) You can now POST upload new imports. See [the documentation](https://docs.firefly-iii.org/csv/usage/post/) for more info.
### Fixed
- [Issue 4298](https://github.com/firefly-iii/firefly-iii/issues/4298) Tags would not append, only overwrite when using multiple tag columns.
- [Issue 4293](https://github.com/firefly-iii/firefly-iii/issues/4293) In some cases, importing transactions with new IBAN's twice would create duplicate transactions.
## 2.3.4 - 2021-01-20
### Fixed
- Fix issue with uninitialized variables.
- Fix issue found in https://github.com/YunoHost-Apps/firefly-iii/issues/29
### Changed
- Update Laravel and other libraries.
- New Docker build!
## 2.3.3 - 2021-01-03
### Fixed
- [Issue 4215](https://github.com/firefly-iii/firefly-iii/issues/4215) Sloppy programming on my side broke the CSV importer, my apologies.
## 2.3.2 - 2021-01-02
### Added
- [Issue 4134](https://github.com/firefly-iii/firefly-iii/issues/4134) CSV importer will also send emails when running on command line.
### Fixed
- [Issue 4106](https://github.com/firefly-iii/firefly-iii/issues/4106) Make sure amounts like "EUR 1.234,56" can be parsed.
- [Issue 4183](https://github.com/firefly-iii/firefly-iii/issues/4183) Make sure all calls use the verify settings, thx @zjean.
### Security
- Lots of library updates as usual.
## 2.3.1 - 2020-11-20
### Fixed
- [Issue 4100](https://github.com/firefly-iii/firefly-iii/issues/4100) Bad parameters in token call.
## 2.3.0 - 2020-11-29
⚠️ Several changes in this release may break Firefly III's duplication detection or are backwards incompatible.
### Changed
- ⚠️ All environment variables that used to be called "URI" are now called "URL" because I finally learned the difference between a URL and a URI.
### Fixed
- [Issue 4094](https://github.com/firefly-iii/firefly-iii/issues/4094) CSV importer would only files with a lowercase `.csv` extension.
## 2.2.4 - 2020-11-20
### Fixed
- [Issue 3975](https://github.com/firefly-iii/firefly-iii/issues/3975) Better error handling when Firefly III responds with invalid JSON.
- [Issue 4069](https://github.com/firefly-iii/firefly-iii/issues/4069) CSV importer would contact the wrong URL to get an access token if you configure a vanity URL.
## 2.2.3 - 2020-11-05
### Fixed
- [Issue 4209](https://github.com/firefly-iii/firefly-iii/issues/4209) Bad error parsing wasn't really user friendly.
## 2.2.2 - 2020-10-23
### Fixed
- An informational message was shown twice.
- Bad JSON wasn't intercepted, leading to weird errors.
## 2.2.1 - 2020-10-04
### Added
- New `/autoimport` route you can POST to.
### Fixed
- A bug where JSON errors would lead to errors in the CSV importer.
## 2.2.0 - 2020-09-21
⚠️ Several changes in this release may break Firefly III's duplication detection or are backwards incompatible.
### Added
- Support for public clients. Check out the docs.
- Created a [public installation](https://docs.firefly-iii.org/csv/help/public/).
### Changed
- ⚠️ This version of the CSV importer requires PHP 7.4.
- ⚠️ The import commands now start with `importer:` instead of `csv:`
### Fixed
- ABN AMRO specific was broken.
## 2.1.1 - 2020-09-05
### Added
- Can now parse locale dates, see the [documentation](https://docs.firefly-iii.org/csv/install/configure/)
### Fixed
- [Issue 3706](https://github.com/firefly-iii/firefly-iii/issues/3706) Bug in amount parsing.
- [Issue 3767](https://github.com/firefly-iii/firefly-iii/issues/3767) Date parsing was broken.
## 2.1.0 - 2020-09-05
### Added
- Can now parse locale dates, see the [documentation](https://docs.firefly-iii.org/csv/install/configure/)
### Fixed
- [Issue 3706](https://github.com/firefly-iii/firefly-iii/issues/3706) Bug in amount parsing.
## 2.0.5 - 2020-08-10
### Fixed
- Longer standard timeout for slow installations.
## 2.0.4 - 2020-08-10
### Fixed
- Nullpointer in support class.
## 2.0.3 - 2020-08-09
### Fixed
- Bad exception call in support class.
## 2.0.2 - 2020-08-09
### Added
- Reset button
### Fixed
- [Issue 3644](https://github.com/firefly-iii/firefly-iii/issues/3644) Bank specific options were ignored.
- [Issue 3676](https://github.com/firefly-iii/firefly-iii/issues/3676) Better error handling.
## 2.0.1 - 2020-08-01
### Changed
- Now supports the "S", when using the generic bank debit/credit selector (German banks use it)
## 2.0.0 - 2020-07-10
### Changed
- Now requires PHP 7.4. Make sure you update!
- Can now use a vanity URL. See the example environment variables file, `.env.example` for instructions.
- This version requires Firefly III v5.3.0
## 1.0.15 - 2020-07-05
⚠️ Several changes in this release may break Firefly III's duplication detection. Be careful importing large batches.
### Fixed
- ⚠️ The importer will no longer match account names like `online` to accounts like `online account`. If you were relying on this behavior, please use the
"mapping" function instead.
- The "mapping" page would always show you all mappable fields, even when you only selected one field to map.
## 1.0.14 - 2020-06-30
Fixes [issue 3501](https://github.com/firefly-iii/firefly-iii/issues/3501).
## 1.0.12 - 2020-06-19
Now liabilities can be selected as the default account.
## 1.0.11 - 2020-06-16
Some changes in the ING (Belgium) parser.
## 1.0.10 - 2020-06-04
⚠️ Several changes in this release may break Firefly III's duplication detection. Be careful importing large batches.
### Added
You can now set the timezone using the `TZ` environment variable.
### Changed
- Improved the error message when you forget to upload stuff.
- All documentation will point to the `latest` branch for more consistency.
- Some date values were not imported properly.
### Fixed
- ⚠️ Several edge cases exist where the CSV importer and Firefly III disagree on which account to use. This can result in errors like "*Could not find a
valid source account when searching for ...*." I have introduced several fixes to mitigate this issue. These fixes will most definitively change the
way transactions are handled, so be careful importing large batches.
- IBAN in lower case or spaces works now.
## 1.0.9 - 2020-05-19
### Fixed
- Fixed error message about "root directory" because the CSV importer submitted an empty string.
### Changed
- CSV importer requires the latest version of Firefly III.
## 1.0.8 - 2020-05-14
⚠️ Several changes in this release may break Firefly III's duplication detection. Be careful importing large batches.
### Added
- The import tag now has a date as well.
- ⚠️ [issue 3346](https://github.com/firefly-iii/firefly-iii/issues/3346) If your file has them, you can import the timestamp with the transaction.
- You can store your import configurations under `storage/configurations` for easy access during the import.
- The UI would not respect specifics in your JSON config.
### Fixed
- If the API response was bad, the importer would crash. No longer.
- [Issue 3345](https://github.com/firefly-iii/firefly-iii/issues/3345) Would ignore the delimiter in some cases.
## 1.0.7 - 2020-05-04
⚠️ Several changes in this release may break Firefly III's duplication detection. Be careful importing large batches.
### Added
- ⚠️ Reimplement the search for IBANs and names. This makes it easier to import using incomplete data. This changes the importer's behavior.
- CSV import can add a tag to your import.
### Fixed
- [Issue 3290](https://github.com/firefly-iii/firefly-iii/issues/3290) Issues with refunds from credit cards.
- [Issue 3299](https://github.com/firefly-iii/firefly-iii/issues/3299) Issue with bcmod.
- Merge [fix](https://github.com/firefly-iii/csv-importer/pull/5) for mail config.
- Catch JSON errors, so the importer handles invalid UTF8 data properly.
## 1.0.6 - 2020-04-26
⚠️ Several changes in this release may break Firefly III's duplication detection. Be careful importing large batches.
### Added
- You can now navigate back and forth between steps.
- You can configure the importer to send email reports. Checkout `.env.example`.
### Changed
- ⚠️ When the destination of a withdrawal is empty, *or* the source of a deposit is empty, the CSV importer will substitute these values with `(no name)` as
it used to do when the CSV importer was part of Firefly III itself.
## 1.0.5 - 2020-04-22
### Fixed
- [Issue 3268](https://github.com/firefly-iii/firefly-iii/issues/3268) Issue with asset management.
- [Issue 3271](https://github.com/firefly-iii/firefly-iii/issues/3271) Bad handing of debit/credit columns.
- [Issue 3279](https://github.com/firefly-iii/firefly-iii/issues/3279) Issue handling JSON.
## 1.0.4 - 2020-04-16
- [Issue 3266](https://github.com/firefly-iii/firefly-iii/issues/3266) Import loop due to bad bccomp call.
- Some code cleanup.
## 1.0.3 - 2020-04-13
- Fix issue with account selection.
- Fix issue with amounts.
## 1.0.2 - 2020-04-12
### Added
- Add ability to handle `TRUSTED_PROXIES` environment variable.
### Fixed
- [Issue 3253](https://github.com/firefly-iii/firefly-iii/issues/3253) Could not map values if the delimiter wasn't a comma.
- [Issue 3254](https://github.com/firefly-iii/firefly-iii/issues/3254) Better handling of strings.
- [Issue 3258](https://github.com/firefly-iii/firefly-iii/issues/3258) Better handling of existing accounts.
- Better error handling (500 errors will not make the importer loop).
- Fixed handling of specifics, thanks to @FelikZ
## 1.0.1 - 2020-04-10
### Fixed
- Call to `convertBoolean` with bad parameters.
- Catch exception where Firefly III returns the wrong account.
- Update minimum version for Firefly III to 5.2.0.
## 1.0.0 - 2020-04-10
This release was preceded by several alpha and beta versions:
- 1.0.0-alpha.1 on 2019-10-31
- 1.0.0-alpha.2 on 2020-01-03
- 1.0.0-alpha.3 on 2020-01-11
- 1.0.0-beta.1 on 2020-02-23
- 1.0.0-beta.2 on 2020-03-13
- 1.0.0-beta.3 on 2020-04-08
## 0.0.0 - 2021-xx-xx
### Added
- Initial release.

5
config/importer.php

@ -31,9 +31,10 @@ return [
'upload_path' => storage_path('uploads'),
'expect_secure_url' => env('EXPECT_SECURE_URL', false),
'is_external' => env('IS_EXTERNAL', false),
'minimum_version' => '5.6.0',
'use_cache' => env('USE_CACHE', false),
'minimum_version' => '5.6.4',
'cache_api_calls' => false,
'ignored_files' => ['.gitignore'],
'ignored_files' => ['.gitignore'],
'tracker_site_id' => env('TRACKER_SITE_ID', ''),
'tracker_url' => env('TRACKER_URL', ''),
'vanity_url' => envNonEmpty('VANITY_URL'),

2
public/js/convert.js
File diff suppressed because it is too large
View File

2
public/js/submit.js
File diff suppressed because it is too large
View File

14
resources/js/components/import/ConversionStatus.vue

@ -54,7 +54,8 @@
</div>
<div class="card-body" v-if="'conv_done' === this.status ">
<p>
The TODO routine has finished 🎉. TODO Next step: importing. Please wait to be redirected! <span class="fas fa-sync fa-spin"></span>
The TODO routine has finished 🎉. TODO Next step: importing. Please wait to be redirected! <span
class="fas fa-sync fa-spin"></span>
</p>
<conversion-messages
:messages="this.messages"
@ -64,7 +65,8 @@
</div>
<div class="card-body" v-if="'conv_errored' === this.status">
<p class="text-danger">
The conversion could not be started, or failed due to an error. Please check the log files. Sorry about
The conversion could not be started, or failed due to an error. Please check the log files.
Sorry about
this :(. TODO
</p>
<conversion-messages
@ -116,7 +118,7 @@ export default {
this.errors = response.data.errors;
this.warnings = response.data.warnings;
this.messages = response.data.messages;
console.log(`Job status is ${this.status}.`);
console.log(`Job status is ${response.data.status}.`);
if (false === this.triedToStart && 'waiting_to_start' === response.data.status) {
// call to job start.
console.log('Job hasn\'t started yet. Show user some info');
@ -131,6 +133,10 @@ export default {
this.status = response.data.status;
return;
}
if ('conv_running' === response.data.status) {
console.log('Conversion is running...')
this.status = response.data.status;
}
if ('conv_done' === response.data.status) {
console.log('Job is done!');
this.status = response.data.status;
@ -152,7 +158,7 @@ export default {
}.bind(this), 1000);
});
},
redirectToImport: function() {
redirectToImport: function () {
window.location = importStartUrl;
},
callStart: function () {

15
resources/js/components/submit/SubmissionStatus.vue

@ -64,7 +64,8 @@
</div>
<div class="card-body" v-if="'submission_errored' === this.status">
<p class="text-danger">
The submission could not be started, or failed due to an error. Please check the log files. Sorry about
The submission could not be started, or failed due to an error. Please check the log files.
Sorry about
this :(. TODO
</p>
<submission-messages
@ -73,6 +74,7 @@
:errors="this.errors"
></submission-messages>
</div>
</div>
</div>
</div>
@ -103,12 +105,12 @@ export default {
},
methods: {
getJobStatus: function () {
console.log('getJobStatus');
console.log('get submission status');
axios.get(jobStatusUrl).then((response) => {
// first try post result:
if (true === this.triedToStart && 'submission_errored' === this.status) {
console.error('Job failed!!!');
console.error('Job failed! :(');
return;
}
@ -116,8 +118,7 @@ export default {
this.errors = response.data.errors;
this.warnings = response.data.warnings;
this.messages = response.data.messages;
console.log(response.data);
console.log(`Job status returned is "${response.data.status}".`);
console.log(`Submission status returned is "${response.data.status}".`);
if (false === this.triedToStart && 'waiting_to_start' === response.data.status) {
// call to job start.
console.log('Job hasn\'t started yet. Show user some info');
@ -132,6 +133,10 @@ export default {
this.status = response.data.status;
return;
}
if ('submission_running' === response.data.status) {
console.log('Job is running...');
this.status = response.data.status;
}
if ('submission_done' === response.data.status) {
console.log('Job is done!');
this.status = response.data.status;

Loading…
Cancel
Save