Browse Source

Code cleanup part #4

pull/373/head
James Cole 2 years ago
parent
commit
f97b611a09
  1. 246
      app/Console/AutoImports.php
  2. 70
      app/Console/Commands/UpgradeImportConfigurations.php
  3. 18
      app/Console/HaveAccess.php
  4. 8
      app/Console/Kernel.php
  5. 6
      app/Console/ManageMessages.php
  6. 2
      app/Console/VerifyJSON.php
  7. 8
      app/Events/ImportedTransactions.php
  8. 4
      app/Exceptions/Handler.php
  9. 2
      app/Handlers/Events/ImportedTransactionsEventHandler.php
  10. 22
      app/Http/Controllers/AutoImportController.php
  11. 26
      app/Http/Controllers/AutoUploadController.php
  12. 6
      app/Http/Controllers/DebugController.php
  13. 1
      app/Http/Controllers/HealthcheckController.php
  14. 4
      app/Http/Controllers/Import/AuthenticateController.php
  15. 6
      app/Http/Controllers/Import/ConfigurationController.php
  16. 3
      app/Http/Controllers/Import/ConversionController.php
  17. 2
      app/Http/Controllers/Import/DownloadController.php
  18. 228
      app/Http/Controllers/Import/File/RoleController.php
  19. 252
      app/Http/Controllers/Import/MapController.php
  20. 2
      app/Http/Controllers/Import/Nordigen/LinkController.php
  21. 4
      app/Http/Controllers/Import/Spectre/ConnectionController.php
  22. 2
      app/Http/Controllers/Import/SubmitController.php
  23. 159
      app/Http/Controllers/Import/UploadController.php
  24. 38
      app/Http/Controllers/IndexController.php
  25. 2
      app/Http/Controllers/NavController.php
  26. 2
      app/Http/Controllers/ServiceController.php
  27. 94
      app/Http/Controllers/TokenController.php
  28. 100
      app/Http/Middleware/IsReadyForStep.php
  29. 6
      app/Http/Middleware/RedirectIfAuthenticated.php
  30. 2
      app/Http/Middleware/TrustProxies.php
  31. 3
      app/Http/Middleware/VerifyCsrfToken.php
  32. 4
      app/Http/Request/ConfigurationPostRequest.php
  33. 16
      app/Http/Request/Request.php
  34. 4
      app/Http/Request/RolesPostRequest.php
  35. 2
      app/Http/Request/SelectionRequest.php
  36. 1
      app/Providers/AuthServiceProvider.php
  37. 1
      app/Providers/RouteServiceProvider.php
  38. 5
      app/Rules/Iban.php
  39. 2
      app/Services/CSV/Configuration/ConfigFileProcessor.php
  40. 68
      app/Services/CSV/Conversion/Routine/CSVFileProcessor.php
  41. 12
      app/Services/CSV/Conversion/Routine/ColumnValue.php
  42. 8
      app/Services/CSV/Conversion/Routine/ColumnValueConverter.php
  43. 142
      app/Services/CSV/Conversion/Routine/LineProcessor.php
  44. 50
      app/Services/CSV/Conversion/Routine/PseudoTransactionProcessor.php
  45. 76
      app/Services/CSV/Conversion/RoutineManager.php
  46. 4
      app/Services/CSV/Conversion/Support/DeterminesTransactionType.php
  47. 4
      app/Services/CSV/Conversion/Task/AbstractTask.php
  48. 716
      app/Services/CSV/Conversion/Task/Accounts.php
  49. 6
      app/Services/CSV/Conversion/Task/Amount.php
  50. 4
      app/Services/CSV/Conversion/Task/Currency.php
  51. 4
      app/Services/CSV/Conversion/Task/EmptyAccounts.php
  52. 4
      app/Services/CSV/Conversion/Task/EmptyDescription.php
  53. 4
      app/Services/CSV/Conversion/Task/Tags.php
  54. 6
      app/Services/CSV/Conversion/Task/TaskInterface.php
  55. 131
      app/Services/CSV/Converter/Amount.php
  56. 3
      app/Services/CSV/Converter/AmountCredit.php
  57. 3
      app/Services/CSV/Converter/AmountDebit.php
  58. 3
      app/Services/CSV/Converter/AmountNegated.php
  59. 2
      app/Services/CSV/Converter/BankDebitCredit.php
  60. 2
      app/Services/CSV/Converter/CleanId.php
  61. 2
      app/Services/CSV/Converter/CleanInteger.php
  62. 2
      app/Services/CSV/Converter/CleanNlString.php
  63. 2
      app/Services/CSV/Converter/CleanString.php
  64. 2
      app/Services/CSV/Converter/CleanUrl.php
  65. 3
      app/Services/CSV/Converter/ConverterInterface.php
  66. 10
      app/Services/CSV/Converter/ConverterService.php
  67. 6
      app/Services/CSV/Converter/Date.php
  68. 2
      app/Services/CSV/Converter/Description.php
  69. 87
      app/Services/CSV/Converter/Iban.php
  70. 2
      app/Services/CSV/Converter/TagsComma.php
  71. 2
      app/Services/CSV/Converter/TagsSpace.php
  72. 6
      app/Services/CSV/File/FileReader.php
  73. 34
      app/Services/CSV/Mapper/GetAccounts.php
  74. 18
      app/Services/CSV/Mapper/MapperService.php
  75. 27
      app/Services/CSV/Roles/RoleService.php
  76. 32
      app/Services/Camt/Conversion/RoutineManager.php
  77. 13
      app/Services/Camt/Conversion/TransactionConverter.php
  78. 4
      app/Services/Camt/Conversion/TransactionExtractor.php
  79. 286
      app/Services/Camt/Conversion/TransactionMapper.php
  80. 48
      app/Services/Camt/Transaction.php
  81. 46
      app/Services/Nordigen/Authentication/SecretManager.php
  82. 2
      app/Services/Nordigen/Conversion/Routine/FilterTransactions.php
  83. 490
      app/Services/Nordigen/Conversion/Routine/GenerateTransactions.php
  84. 20
      app/Services/Nordigen/Conversion/Routine/TransactionProcessor.php
  85. 36
      app/Services/Nordigen/Conversion/RoutineManager.php
  86. 126
      app/Services/Nordigen/Model/Account.php
  87. 2
      app/Services/Nordigen/Model/Balance.php
  88. 2
      app/Services/Nordigen/Model/Bank.php
  89. 6
      app/Services/Nordigen/Model/Country.php
  90. 2
      app/Services/Nordigen/Model/Transaction.php
  91. 30
      app/Services/Nordigen/Request/GetAccountBalanceRequest.php
  92. 30
      app/Services/Nordigen/Request/GetAccountBasicRequest.php
  93. 30
      app/Services/Nordigen/Request/GetAccountInformationRequest.php
  94. 6
      app/Services/Nordigen/Request/GetRequisitionRequest.php
  95. 22
      app/Services/Nordigen/Request/GetTransactionsRequest.php
  96. 22
      app/Services/Nordigen/Request/ListAccountsRequest.php
  97. 4
      app/Services/Nordigen/Request/ListBanksRequest.php
  98. 6
      app/Services/Nordigen/Request/PostNewRequisitionRequest.php
  99. 6
      app/Services/Nordigen/Request/PostNewUserAgreement.php
  100. 132
      app/Services/Nordigen/Request/Request.php

246
app/Console/AutoImports.php

@ -56,7 +56,7 @@ trait AutoImports
protected array $importWarnings = [];
/**
* @param string $directory
* @param string $directory
*
* @return array
*/
@ -94,45 +94,39 @@ trait AutoImports
}
/**
* @param string $file
* @param string $directory
* @param array $files
*
* @return string
* @throws ImporterErrorException
*/
private function getExtension(string $file): string
protected function importFiles(string $directory, array $files): void
{
$parts = explode('.', $file);
if (1 === count($parts)) {
return '';
/** @var string $file */
foreach ($files as $file) {
$this->importFile($directory, $file);
}
return strtolower($parts[count($parts) - 1]);
}
/**
* @param string $directory
* @param string $file
* @param string $file
*
* @return bool
* @return string
*/
private function hasJsonConfiguration(string $directory, string $file): bool
private function getExtension(string $file): string
{
$short = substr($file, 0, -4);
$jsonFile = sprintf('%s.json', $short);
$fullJson = sprintf('%s/%s', $directory, $jsonFile);
if (!file_exists($fullJson)) {
$this->warn(sprintf('Can\'t find JSON file "%s" expected to go with CSV file "%s". CSV file will be ignored.', $fullJson, $file));
return false;
$parts = explode('.', $file);
if (1 === count($parts)) {
return '';
}
return true;
return strtolower($parts[count($parts) - 1]);
}
/**
* TODO this function must be more universal.
*
* @param string $directory
* @param string $file
* @param string $directory
* @param string $file
*
* @return bool
*/
@ -149,22 +143,28 @@ trait AutoImports
}
/**
* @param string $directory
* @param array $files
* @param string $directory
* @param string $file
*
* @throws ImporterErrorException
* @return bool
*/
protected function importFiles(string $directory, array $files): void
private function hasJsonConfiguration(string $directory, string $file): bool
{
/** @var string $file */
foreach ($files as $file) {
$this->importFile($directory, $file);
$short = substr($file, 0, -4);
$jsonFile = sprintf('%s.json', $short);
$fullJson = sprintf('%s/%s', $directory, $jsonFile);
if (!file_exists($fullJson)) {
$this->warn(sprintf('Can\'t find JSON file "%s" expected to go with CSV file "%s". CSV file will be ignored.', $fullJson, $file));
return false;
}
return true;
}
/**
* @param string $file
* @param string $directory
* @param string $file
* @param string $directory
*
* @throws ImporterErrorException
*/
@ -226,11 +226,99 @@ trait AutoImports
);
}
/**
* @param string $jsonFile
* @param null|string $importableFile
*
* @throws ImporterErrorException
*/
private function importUpload(string $jsonFile, ?string $importableFile): void
{
// do JSON check
$jsonResult = $this->verifyJSON($jsonFile);
if (false === $jsonResult) {
$message = sprintf('The importer can\'t import %s: could not decode the JSON in config file %s.', $importableFile, $jsonFile);
$this->error($message);
return;
}
$configuration = Configuration::fromArray(json_decode(file_get_contents($jsonFile), true));
$configuration->updateDateRange();
$this->line(sprintf('Going to convert from file "%s" using configuration "%s" and flow "%s".', $importableFile, $jsonFile, $configuration->getFlow()));
// this is it!
$this->startConversion($configuration, $importableFile);
$this->reportConversion();
$this->line(sprintf('Done converting from file %s using configuration %s.', $importableFile, $jsonFile));
$this->startImport($configuration);
$this->reportImport();
$this->line('Done!');
event(
new ImportedTransactions(
array_merge($this->conversionMessages, $this->importMessages),
array_merge($this->conversionWarnings, $this->importWarnings),
array_merge($this->conversionErrors, $this->importErrors)
)
);
}
/**
* @param Configuration $configuration
*
* @param string|null $importableFile
*/
private function reportConversion(): void
{
$list = [
'info' => $this->conversionMessages,
'warn' => $this->conversionWarnings,
'error' => $this->conversionErrors,
];
foreach ($list as $func => $set) {
/**
* @var int $index
* @var array $messages
*/
foreach ($set as $index => $messages) {
if (count($messages) > 0) {
foreach ($messages as $message) {
$this->$func(sprintf('Conversion index %d: %s', $index, $message));
}
}
}
}
}
/**
*
*/
private function reportImport(): void
{
$list = [
'info' => $this->importMessages,
'warn' => $this->importWarnings,
'error' => $this->importErrors,
];
foreach ($list as $func => $set) {
/**
* @var int $index
* @var array $messages
*/
foreach ($set as $index => $messages) {
if (count($messages) > 0) {
foreach ($messages as $message) {
$this->$func(sprintf('Import index %d: %s', $index, $message));
}
}
}
}
}
/**
* @param Configuration $configuration
*
* @param string|null $importableFile
*
* @throws ImporterErrorException
*/
@ -324,31 +412,6 @@ trait AutoImports
}
}
/**
*
*/
private function reportConversion(): void
{
$list = [
'info' => $this->conversionMessages,
'warn' => $this->conversionWarnings,
'error' => $this->conversionErrors,
];
foreach ($list as $func => $set) {
/**
* @var int $index
* @var array $messages
*/
foreach ($set as $index => $messages) {
if (count($messages) > 0) {
foreach ($messages as $message) {
$this->$func(sprintf('Conversion index %d: %s', $index, $message));
}
}
}
}
}
/**
* @param Configuration $configuration
* @throws FilesystemException
@ -425,69 +488,4 @@ trait AutoImports
$this->importWarnings = $routine->getAllWarnings();
$this->importErrors = $routine->getAllErrors();
}
/**
*
*/
private function reportImport(): void
{
$list = [
'info' => $this->importMessages,
'warn' => $this->importWarnings,
'error' => $this->importErrors,
];
foreach ($list as $func => $set) {
/**
* @var int $index
* @var array $messages
*/
foreach ($set as $index => $messages) {
if (count($messages) > 0) {
foreach ($messages as $message) {
$this->$func(sprintf('Import index %d: %s', $index, $message));
}
}
}
}
}
/**
* @param string $jsonFile
* @param null|string $importableFile
*
* @throws ImporterErrorException
*/
private function importUpload(string $jsonFile, ?string $importableFile): void
{
// do JSON check
$jsonResult = $this->verifyJSON($jsonFile);
if (false === $jsonResult) {
$message = sprintf('The importer can\'t import %s: could not decode the JSON in config file %s.', $importableFile, $jsonFile);
$this->error($message);
return;
}
$configuration = Configuration::fromArray(json_decode(file_get_contents($jsonFile), true));
$configuration->updateDateRange();
$this->line(sprintf('Going to convert from file "%s" using configuration "%s" and flow "%s".', $importableFile, $jsonFile, $configuration->getFlow()));
// this is it!
$this->startConversion($configuration, $importableFile);
$this->reportConversion();
$this->line(sprintf('Done converting from file %s using configuration %s.', $importableFile, $jsonFile));
$this->startImport($configuration);
$this->reportImport();
$this->line('Done!');
event(
new ImportedTransactions(
array_merge($this->conversionMessages, $this->importMessages),
array_merge($this->conversionWarnings, $this->importWarnings),
array_merge($this->conversionErrors, $this->importErrors)
)
);
}
}

70
app/Console/Commands/UpgradeImportConfigurations.php

@ -83,23 +83,37 @@ class UpgradeImportConfigurations extends Command
}
/**
* @param string $directory
* @param string $name
*
* @return string
*/
private function processRoot(string $directory): void
private function getExtension(string $name): string
{
$dir = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);
/**
* @var string $name
* @var SplFileInfo $object
*/
foreach ($files as $name => $object) {
$this->processFile($name);
$parts = explode('.', $name);
return $parts[count($parts) - 1];
}
/**
* @param string $content
*
* @return bool
*/
private function isValidJson(string $content): bool
{
if ('' === $content) {
return false;
}
$json = json_decode($content, true);
if (false === $json) {
return false;
}
return true;
}
/**
* @param string $name
* @param string $name
*/
private function processFile(string $name): void
{
@ -121,32 +135,18 @@ class UpgradeImportConfigurations extends Command
}
/**
* @param string $name
*
* @return string
* @param string $directory
*/
private function getExtension(string $name): string
{
$parts = explode('.', $name);
return $parts[count($parts) - 1];
}
/**
* @param string $content
*
* @return bool
*/
private function isValidJson(string $content): bool
private function processRoot(string $directory): void
{
if ('' === $content) {
return false;
}
$json = json_decode($content, true);
if (false === $json) {
return false;
$dir = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);
/**
* @var string $name
* @var SplFileInfo $object
*/
foreach ($files as $name => $object) {
$this->processFile($name);
}
return true;
}
}

18
app/Console/HaveAccess.php

@ -32,6 +32,14 @@ use GrumpyDictator\FFIIIApiSupport\Request\SystemInformationRequest;
*/
trait HaveAccess
{
/**
* @param $string
* @param null $verbosity
*
* @return void
*/
abstract public function error($string, $verbosity = null);
/**
* @return bool
*/
@ -56,15 +64,7 @@ trait HaveAccess
}
/**
* @param $string
* @param null $verbosity
*
* @return void
*/
abstract public function error($string, $verbosity = null);
/**
* @param string $path
* @param string $path
*
* @return bool
*/

8
app/Console/Kernel.php

@ -55,16 +55,16 @@ class Kernel extends ConsoleKernel
$vanityUrl = (string)env('VANITY_URL', '');
// access token AND client ID cannot be set together
if ('' !== $accessToken && $clientId !== '') {
echo 'You can\'t set FIREFLY_III_ACCESS_TOKEN together with FIREFLY_III_CLIENT_ID. One must remain empty.' . PHP_EOL;
echo 'You can\'t set FIREFLY_III_ACCESS_TOKEN together with FIREFLY_III_CLIENT_ID. One must remain empty.'.PHP_EOL;
exit;
}
// if vanity URL is not empty, Firefly III url must also be set.
if ('' !== $vanityUrl && '' === $baseUrl) {
echo 'If you set VANITY_URL you must also set FIREFLY_III_URL' . PHP_EOL;
echo 'If you set VANITY_URL you must also set FIREFLY_III_URL'.PHP_EOL;
exit;
}
$this->load(__DIR__ . '/Commands');
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
@ -72,7 +72,7 @@ class Kernel extends ConsoleKernel
/**
* Define the application's command schedule.
*
* @param Schedule $schedule
* @param Schedule $schedule
*
* @return void
*/

6
app/Console/ManageMessages.php

@ -30,8 +30,8 @@ namespace App\Console;
trait ManageMessages
{
/**
* @param string $key
* @param array $messages
* @param string $key
* @param array $messages
*/
protected function listMessages(string $key, array $messages): void
{
@ -45,7 +45,7 @@ trait ManageMessages
if (0 !== count($messages)) {
/**
* @var int $index
* @var int $index
* @var array $error
*/
foreach ($messages as $index => $list) {

2
app/Console/VerifyJSON.php

@ -33,7 +33,7 @@ use JsonException;
trait VerifyJSON
{
/**
* @param string $file
* @param string $file
*
* @return bool
*/

8
app/Events/ImportedTransactions.php

@ -39,9 +39,9 @@ class ImportedTransactions
public array $warnings;
/**
* @param array $messages
* @param array $warnings
* @param array $errors
* @param array $messages
* @param array $warnings
* @param array $errors
*/
public function __construct(array $messages, array $warnings, array $errors)
{
@ -54,7 +54,7 @@ class ImportedTransactions
}
/**
* @param array $collection
* @param array $collection
*
* @return array
*/

4
app/Exceptions/Handler.php

@ -56,8 +56,8 @@ class Handler extends ExceptionHandler
];
/**
* @param Request $request
* @param Throwable $e
* @param Request $request
* @param Throwable $e
*
* @return JsonResponse|\Illuminate\Http\Response|Response
* @throws Throwable

2
app/Handlers/Events/ImportedTransactionsEventHandler.php

@ -33,7 +33,7 @@ use Symfony\Component\Mailer\Exception\TransportException;
class ImportedTransactionsEventHandler
{
/**
* @param ImportedTransactions $event
* @param ImportedTransactions $event
*/
public function sendReportOverMail(ImportedTransactions $event): void
{

22
app/Http/Controllers/AutoImportController.php

@ -42,6 +42,15 @@ class AutoImportController extends Controller
private string $directory;
/**
* @inheritDoc
*/
public function error($string, $verbosity = null)
{
app('log')->error($string);
$this->line($string);
}
/**
*
* @throws ImporterErrorException
@ -93,7 +102,7 @@ class AutoImportController extends Controller
/**
* @param $string
* @param null $verbosity
* @param null $verbosity
*/
public function info($string, $verbosity = null)
{
@ -105,18 +114,9 @@ class AutoImportController extends Controller
echo sprintf("%s: %s\n", date('Y-m-d H:i:s'), $string);
}
/**
* @inheritDoc
*/
public function error($string, $verbosity = null)
{
app('log')->error($string);
$this->line($string);
}
/**
* @param $string
* @param null $verbosity
* @param null $verbosity
*/
public function warn($string, $verbosity = null)
{

26
app/Http/Controllers/AutoUploadController.php

@ -39,6 +39,15 @@ class AutoUploadController extends Controller
use AutoImports;
use VerifyJSON;
/**
* @inheritDoc
*/
public function error($string, $verbosity = null)
{
app('log')->error($string);
$this->line($string);
}
/**
*
* @throws ImporterErrorException
@ -76,11 +85,11 @@ class AutoUploadController extends Controller
}
/**
* @inheritDoc
* @param $string
* @param null $verbosity
*/
public function error($string, $verbosity = null)
public function info($string, $verbosity = null)
{
app('log')->error($string);
$this->line($string);
}
@ -91,16 +100,7 @@ class AutoUploadController extends Controller
/**
* @param $string
* @param null $verbosity
*/
public function info($string, $verbosity = null)
{
$this->line($string);
}
/**
* @param $string
* @param null $verbosity
* @param null $verbosity
*/
public function warn($string, $verbosity = null)
{

6
app/Http/Controllers/DebugController.php

@ -37,7 +37,7 @@ class DebugController extends Controller
/**
* Show debug info.
*
* @param Request $request
* @param Request $request
*
* @return Factory|View
*/
@ -100,7 +100,7 @@ class DebugController extends Controller
}
if ('' !== $logContent) {
// last few lines
$logContent = 'Truncated from this point <----|' . substr($logContent, -8192);
$logContent = 'Truncated from this point <----|'.substr($logContent, -8192);
}
if (true === config('importer.is_external')) {
$logContent = 'No logs, external installation.';
@ -135,7 +135,7 @@ class DebugController extends Controller
/**
* Some common combinations.
*
* @param int $value
* @param int $value
*
* @return string
*/

1
app/Http/Controllers/HealthcheckController.php

@ -1,4 +1,5 @@
<?php
/*
* HealthcheckController.php
* Copyright (c) 2021 https://github.com/ajgon and github.com/davidschlachter

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

@ -56,7 +56,7 @@ class AuthenticateController extends Controller
}
/**
* @param Request $request
* @param Request $request
*
* @return Application|Factory|View|RedirectResponse|Redirector
* @throws ImporterErrorException
@ -100,7 +100,7 @@ class AuthenticateController extends Controller
}
/**
* @param Request $request
* @param Request $request
*
* @return Application|RedirectResponse|Redirector
* @throws ImporterErrorException

6
app/Http/Controllers/Import/ConfigurationController.php

@ -71,7 +71,7 @@ class ConfigurationController extends Controller
}
/**
* @param Request $request
* @param Request $request
*
* @return Factory|RedirectResponse|View
* @throws ImporterErrorException
@ -140,7 +140,7 @@ class ConfigurationController extends Controller
/**
* @param Request $request
* @param Request $request
*
* @return JsonResponse
*/
@ -156,7 +156,7 @@ class ConfigurationController extends Controller
}
/**
* @param ConfigurationPostRequest $request
* @param ConfigurationPostRequest $request
*
* @return RedirectResponse
* @throws ImporterErrorException

3
app/Http/Controllers/Import/ConversionController.php

@ -107,7 +107,6 @@ class ConversionController extends Controller
}
/** @var RoutineManagerInterface $routine */
if ('file' === $flow) {
$contentType = $configuration->getContentType();
switch ($contentType) {
default:
@ -234,7 +233,7 @@ class ConversionController extends Controller
}
/**
* @param Request $request
* @param Request $request
*
* @return JsonResponse
*/

2
app/Http/Controllers/Import/DownloadController.php

@ -53,7 +53,7 @@ class DownloadController extends Controller
$response = response($result);
$name = sprintf('import_config_%s.json', date('Y-m-d'));
$response->header('Content-disposition', 'attachment; filename=' . $name)
$response->header('Content-disposition', 'attachment; filename='.$name)
->header('Content-Type', 'application/json')
->header('Content-Description', 'File Transfer')
->header('Connection', 'Keep-Alive')

228
app/Http/Controllers/Import/File/RoleController.php

@ -33,7 +33,6 @@ use App\Services\Session\Constants;
use App\Services\Shared\Configuration\Configuration;
use App\Services\Storage\StorageService;
use App\Support\Http\RestoresConfiguration;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
@ -63,7 +62,7 @@ class RoleController extends Controller
}
/**
* @param Request $request
* @param Request $request
*
* @return View|void
* @throws JsonException
@ -92,47 +91,35 @@ class RoleController extends Controller
return $this->csvIndex($request, $configuration);
case 'camt':
return $this->camtIndex($request, $configuration);
}
}
/**
* @param Request $request
* @param Configuration $configuration
* @param RolesPostRequest $request
*
* @return View
* @return RedirectResponse
* @throws ContainerExceptionInterface
* @throws Exception
* @throws InvalidArgument
* @throws FilesystemException
* @throws ImporterErrorException
* @throws JsonException
* @throws NotFoundExceptionInterface
* @throws UnableToProcessCsv
*/
private function csvIndex(Request $request, Configuration $configuration): View
public function postIndex(RolesPostRequest $request): RedirectResponse
{
$mainTitle = 'Role definition';
$subTitle = 'Configure the role of each column in your file';
// get columns from file
$content = StorageService::getContent(session()->get(Constants::UPLOAD_DATA_FILE), $configuration->isConversion());
$columns = RoleService::getColumns($content, $configuration);
$examples = RoleService::getExampleData($content, $configuration);
// submit mapping from config.
$mapping = base64_encode(json_encode($configuration->getMapping(), JSON_THROW_ON_ERROR));
// roles
$roles = config('csv.import_roles');
ksort($roles);
// the request object must be able to handle all file types.
$configuration = $this->restoreConfiguration();
$contentType = $configuration->getContentType();
// configuration (if it is set)
$configuredRoles = $configuration->getRoles();
$configuredDoMapping = $configuration->getDoMapping();
switch ($contentType) {
default:
throw new ImporterErrorException(sprintf('Cannot handle file type "%s" in POST.', $contentType));
case 'csv':
return $this->csvPostIndex($request, $configuration);
case 'camt':
return $this->camtPostIndex($request, $configuration);
//return $this->camtPostIndex($request, $configuration);
return view(
'import.005-roles.index-csv',
compact('mainTitle', 'configuration', 'subTitle', 'columns', 'examples', 'roles', 'configuredRoles', 'configuredDoMapping', 'mapping')
);
}
}
/**
@ -156,51 +143,53 @@ class RoleController extends Controller
$doMapping = $configuration->getDoMapping();
// four levels in a CAMT file, level A B C D. Each level has a pre-defined set of
// available fields and information.
$levels = [];
$levels['A'] = [
$levels = [];
$levels['A'] = [
'title' => trans('camt.level_A'),
'explanation' => trans('camt.explain_A'),
'fields' => $this->getFieldsForLevel('A'),
];
$levels['B'] = [
$levels['B'] = [
'title' => trans('camt.level_B'),
'explanation' => trans('camt.explain_B'),
'fields' => $this->getFieldsForLevel('B'),
];
$levels['C'] = [
$levels['C'] = [
'title' => trans('camt.level_C'),
'explanation' => trans('camt.explain_C'),
'fields' => [
// have to collect C by hand because of intermediate sections
'entryAccountServicerReference' => config('camt.fields.entryAccountServicerReference'),
'entryReference' => config('camt.fields.entryReference'),
'entryAdditionalInfo' => config('camt.fields.entryAdditionalInfo'),
'section_transaction' => ['section' => true, 'title' => 'transaction',],
'entryAmount' => config('camt.fields.entryAmount'),
'entryAmountCurrency' => config('camt.fields.entryAmountCurrency'),
'entryValueDate' => config('camt.fields.entryValueDate'),
'entryBookingDate' => config('camt.fields.entryBookingDate'),
'section_btc' => ['section' => true, 'title' => 'Btc',],
'entryBtcDomainCode' => config('camt.fields.entryBtcDomainCode'),
'entryBtcFamilyCode' => config('camt.fields.entryBtcFamilyCode'),
'entryBtcSubFamilyCode' => config('camt.fields.entryBtcSubFamilyCode'),
'section_opposing' => ['section' => true, 'title' => 'opposingPart',],
'entryAccountServicerReference' => config('camt.fields.entryAccountServicerReference'),
'entryReference' => config('camt.fields.entryReference'),
'entryAdditionalInfo' => config('camt.fields.entryAdditionalInfo'),
'section_transaction' => ['section' => true, 'title' => 'transaction',],
'entryAmount' => config('camt.fields.entryAmount'),
'entryAmountCurrency' => config('camt.fields.entryAmountCurrency'),
'entryValueDate' => config('camt.fields.entryValueDate'),
'entryBookingDate' => config('camt.fields.entryBookingDate'),
'section_btc' => ['section' => true, 'title' => 'Btc',],
'entryBtcDomainCode' => config('camt.fields.entryBtcDomainCode'),
'entryBtcFamilyCode' => config('camt.fields.entryBtcFamilyCode'),
'entryBtcSubFamilyCode' => config('camt.fields.entryBtcSubFamilyCode'),
'section_opposing' => ['section' => true, 'title' => 'opposingPart',],
],
];
$group_handling = $configuration->getGroupedTransactionHandling();
if('group' === $group_handling) {
if ('group' === $group_handling) {
$levels['D'] = [
'title' => trans('camt.level_D'),
'explanation' => trans('camt.explain_D_dropped')
'explanation' => trans('camt.explain_D_dropped'),
];
}
if('group' !== $group_handling) {
if ('group' !== $group_handling) {
$levels['D'] = [
'title' => trans('camt.level_D'),
'explanation' => trans('camt.explain_D'),
'fields' => [
// have to collect D by hand because of intermediate sections
'entryDetailAccountServicerReference' => config('camt.fields.entryDetailAccountServicerReference'),
'entryDetailAccountServicerReference' => config(
'camt.fields.entryDetailAccountServicerReference'
),
'entryDetailRemittanceInformationUnstructuredBlockMessage' => config(
'camt.fields.entryDetailRemittanceInformationUnstructuredBlockMessage'
),
@ -229,61 +218,18 @@ class RoleController extends Controller
}
/**
* @param string $level
* TODO is basically the same as the CSV processor.
*
* @return array
*/
private function getFieldsForLevel(string $level): array
{
$allFields = config('camt.fields');
$return = [];
foreach ($allFields as $title => $field) {
if ($level === $field['level']) {
$return[$title] = $field;
}
}
return $return;
}
/**
* @param RolesPostRequest $request
* @param Configuration $configuration
*
* @return RedirectResponse
* @throws ContainerExceptionInterface
* @throws FilesystemException
* @throws ImporterErrorException
* @throws JsonException
* @throws NotFoundExceptionInterface
*/
public function postIndex(RolesPostRequest $request): RedirectResponse
{
// the request object must be able to handle all file types.
$configuration = $this->restoreConfiguration();
$contentType = $configuration->getContentType();
switch ($contentType) {
default:
throw new ImporterErrorException(sprintf('Cannot handle file type "%s" in POST.', $contentType));
case 'csv':
return $this->csvPostIndex($request, $configuration);
case 'camt':
return $this->camtPostIndex($request, $configuration);
//return $this->camtPostIndex($request, $configuration);
}
}
/**
* @param RolesPostRequest $request
* @param Configuration $configuration
*
* @return RedirectResponse
* @throws ContainerExceptionInterface
* @throws JsonException
* @throws NotFoundExceptionInterface
*/
private function csvPostIndex(RolesPostRequest $request, Configuration $configuration): RedirectResponse
private function camtPostIndex(RolesPostRequest $request, Configuration $configuration): RedirectResponse
{
$data = $request->getAllForFile();
$needsMapping = $this->needMapping($data['do_mapping']);
@ -319,37 +265,54 @@ class RoleController extends Controller
}
/**
* Will tell you if any role needs mapping.
*
* @param array $array
* @param Request $request
* @param Configuration $configuration
*
* @return bool
* @return View
* @throws ContainerExceptionInterface
* @throws Exception
* @throws InvalidArgument
* @throws JsonException
* @throws NotFoundExceptionInterface
* @throws UnableToProcessCsv
*/
private function needMapping(array $array): bool
private function csvIndex(Request $request, Configuration $configuration): View
{
$need = false;
foreach ($array as $value) {
if (true === $value) {
$need = true;
}
}
$mainTitle = 'Role definition';
$subTitle = 'Configure the role of each column in your file';
return $need;
// get columns from file
$content = StorageService::getContent(session()->get(Constants::UPLOAD_DATA_FILE), $configuration->isConversion());
$columns = RoleService::getColumns($content, $configuration);
$examples = RoleService::getExampleData($content, $configuration);
// submit mapping from config.
$mapping = base64_encode(json_encode($configuration->getMapping(), JSON_THROW_ON_ERROR));
// roles
$roles = config('csv.import_roles');
ksort($roles);
// configuration (if it is set)
$configuredRoles = $configuration->getRoles();
$configuredDoMapping = $configuration->getDoMapping();
return view(
'import.005-roles.index-csv',
compact('mainTitle', 'configuration', 'subTitle', 'columns', 'examples', 'roles', 'configuredRoles', 'configuredDoMapping', 'mapping')
);
}
/**
* TODO is basically the same as the CSV processor.
*
* @param RolesPostRequest $request
* @param Configuration $configuration
*
* @return RedirectResponse
* @throws ContainerExceptionInterface
* @throws FilesystemException
* @throws JsonException
* @throws NotFoundExceptionInterface
*/
private function camtPostIndex(RolesPostRequest $request, Configuration $configuration): RedirectResponse
private function csvPostIndex(RolesPostRequest $request, Configuration $configuration): RedirectResponse
{
$data = $request->getAllForFile();
$needsMapping = $this->needMapping($data['do_mapping']);
@ -383,4 +346,41 @@ class RoleController extends Controller
return redirect()->route('007-convert.index');
}
/**
* @param string $level
*
* @return array
*/
private function getFieldsForLevel(string $level): array
{
$allFields = config('camt.fields');
$return = [];
foreach ($allFields as $title => $field) {
if ($level === $field['level']) {
$return[$title] = $field;
}
}
return $return;
}
/**
* Will tell you if any role needs mapping.
*
* @param array $array
*
* @return bool
*/
private function needMapping(array $array): bool
{
$need = false;
foreach ($array as $value) {
if (true === $value) {
$need = true;
}
}
return $need;
}
}

252
app/Http/Controllers/Import/MapController.php

@ -117,6 +117,92 @@ class MapController extends Controller
return view('import.006-mapping.index', compact('mainTitle', 'subTitle', 'roles', 'data'));
}
/**
* @param Request $request
*
* @return RedirectResponse
* @throws ContainerExceptionInterface
* @throws JsonException
* @throws NotFoundExceptionInterface
*/
public function postIndex(Request $request): RedirectResponse
{
$values = $request->get('values') ?? [];
$mapping = $request->get('mapping') ?? [];
$values = !is_array($values) ? [] : $values;
$mapping = !is_array($mapping) ? [] : $mapping;
$data = [];
$configuration = $this->restoreConfiguration();
/**
* Loop array with available columns.
*
* @var int $index
* @var array $row
*/
foreach ($values as $columnIndex => $column) {
/**
* Loop all values for this column
*
* @var int $valueIndex
* @var string $value
*/
foreach ($column as $valueIndex => $value) {
$mappedValue = $mapping[$columnIndex][$valueIndex] ?? null;
if (null !== $mappedValue && 0 !== $mappedValue && '0' !== $mappedValue) {
$data[$columnIndex][$value] = (int)$mappedValue;
}
}
}
// at this point the $data array must be merged with the mapping as it is on the disk,
// and then saved to disk once again in a new config file.
$configFileName = session()->get(Constants::UPLOAD_CONFIG_FILE);
$originalMapping = [];
$diskConfig = null;
if (null !== $configFileName) {
$diskArray = json_decode(StorageService::getContent($configFileName), true, JSON_THROW_ON_ERROR);
$diskConfig = Configuration::fromArray($diskArray);
$originalMapping = $diskConfig->getMapping();
}
// loop $data and save values:
$mergedMapping = $this->mergeMapping($originalMapping, $data);
$configuration->setMapping($mergedMapping);
// store mapping in config object ( + session)
session()->put(Constants::CONFIGURATION, $configuration->toSessionArray());
// since the configuration saved in the session will omit 'mapping', 'do_mapping' and 'roles'
// these must be set to the configuration file
// no need to do this sooner because toSessionArray would have dropped them anyway.
if (null !== $diskConfig) {
$configuration->setRoles($diskConfig->getRoles());
$configuration->setDoMapping($diskConfig->getDoMapping());
}
// then save entire thing to a new disk file:
// TODO write config needs helper too
$configFileName = StorageService::storeArray($configuration->toArray());
app('log')->debug(sprintf('Old configuration was stored under key "%s".', session()->get(Constants::UPLOAD_CONFIG_FILE)));
session()->put(Constants::UPLOAD_CONFIG_FILE, $configFileName);
app('log')->debug(sprintf('New configuration is stored under key "%s".', session()->get(Constants::UPLOAD_CONFIG_FILE)));
// set map config as complete.
session()->put(Constants::MAPPING_COMPLETE_INDICATOR, true);
session()->put(Constants::READY_FOR_CONVERSION, true);
if ('nordigen' === $configuration->getFlow() || 'spectre' === $configuration->getFlow()) {
// if nordigen, now ready for submission!
session()->put(Constants::READY_FOR_SUBMISSION, true);
}
return redirect()->route('007-convert.index');
}
/**
* Return the map data necessary for the importable file mapping based on some weird helpers.
* TODO needs refactoring and proper splitting into helpers.
@ -242,6 +328,44 @@ class MapController extends Controller
return MapperService::getMapDataForCamt($configuration, $content, $data);
}
/**
* @return array
* @throws ContainerExceptionInterface
* @throws FileNotFoundException
* @throws ImporterErrorException
* @throws NotFoundExceptionInterface
*/
private function getCategories(): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
$downloadIdentifier = session()->get(Constants::CONVERSION_JOB_IDENTIFIER);
$disk = Storage::disk(self::DISK_NAME);
$json = $disk->get(sprintf('%s.json', $downloadIdentifier));
try {
$array = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new ImporterErrorException(sprintf('Could not decode download: %s', $e->getMessage()), 0, $e);
}
$categories = [];
$total = count($array);
/** @var array $transaction */
foreach ($array as $index => $transaction) {
app('log')->debug(sprintf('[%s/%s] Parsing transaction (2)', ($index + 1), $total));
/** @var array $row */
foreach ($transaction['transactions'] as $row) {
$categories[] = (string)array_key_exists('category_name', $row) ? $row['category_name'] : '';
}
}
$filtered = array_filter(
$categories,
static function (?string $value) {
return '' !== (string)$value;
}
);
return array_unique($filtered);
}
/**
* Weird bunch of code to return info on Spectre and Nordigen.
*
@ -349,132 +473,8 @@ class MapController extends Controller
}
/**
* @return array
* @throws ContainerExceptionInterface
* @throws FileNotFoundException
* @throws ImporterErrorException
* @throws NotFoundExceptionInterface
*/
private function getCategories(): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
$downloadIdentifier = session()->get(Constants::CONVERSION_JOB_IDENTIFIER);
$disk = Storage::disk(self::DISK_NAME);
$json = $disk->get(sprintf('%s.json', $downloadIdentifier));
try {
$array = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new ImporterErrorException(sprintf('Could not decode download: %s', $e->getMessage()), 0, $e);
}
$categories = [];
$total = count($array);
/** @var array $transaction */
foreach ($array as $index => $transaction) {
app('log')->debug(sprintf('[%s/%s] Parsing transaction (2)', ($index + 1), $total));
/** @var array $row */
foreach ($transaction['transactions'] as $row) {
$categories[] = (string)array_key_exists('category_name', $row) ? $row['category_name'] : '';
}
}
$filtered = array_filter(
$categories,
static function (?string $value) {
return '' !== (string)$value;
}
);
return array_unique($filtered);
}
/**
* @param Request $request
*
* @return RedirectResponse
* @throws ContainerExceptionInterface
* @throws JsonException
* @throws NotFoundExceptionInterface
*/
public function postIndex(Request $request): RedirectResponse
{
$values = $request->get('values') ?? [];
$mapping = $request->get('mapping') ?? [];
$values = !is_array($values) ? [] : $values;
$mapping = !is_array($mapping) ? [] : $mapping;
$data = [];
$configuration = $this->restoreConfiguration();
/**
* Loop array with available columns.
*
* @var int $index
* @var array $row
*/
foreach ($values as $columnIndex => $column) {
/**
* Loop all values for this column
*
* @var int $valueIndex
* @var string $value
*/
foreach ($column as $valueIndex => $value) {
$mappedValue = $mapping[$columnIndex][$valueIndex] ?? null;
if (null !== $mappedValue && 0 !== $mappedValue && '0' !== $mappedValue) {
$data[$columnIndex][$value] = (int)$mappedValue;
}
}
}
// at this point the $data array must be merged with the mapping as it is on the disk,
// and then saved to disk once again in a new config file.
$configFileName = session()->get(Constants::UPLOAD_CONFIG_FILE);
$originalMapping = [];
$diskConfig = null;
if (null !== $configFileName) {
$diskArray = json_decode(StorageService::getContent($configFileName), true, JSON_THROW_ON_ERROR);
$diskConfig = Configuration::fromArray($diskArray);
$originalMapping = $diskConfig->getMapping();
}
// loop $data and save values:
$mergedMapping = $this->mergeMapping($originalMapping, $data);
$configuration->setMapping($mergedMapping);
// store mapping in config object ( + session)
session()->put(Constants::CONFIGURATION, $configuration->toSessionArray());
// since the configuration saved in the session will omit 'mapping', 'do_mapping' and 'roles'
// these must be set to the configuration file
// no need to do this sooner because toSessionArray would have dropped them anyway.
if (null !== $diskConfig) {
$configuration->setRoles($diskConfig->getRoles());
$configuration->setDoMapping($diskConfig->getDoMapping());
}
// then save entire thing to a new disk file:
// TODO write config needs helper too
$configFileName = StorageService::storeArray($configuration->toArray());
app('log')->debug(sprintf('Old configuration was stored under key "%s".', session()->get(Constants::UPLOAD_CONFIG_FILE)));
session()->put(Constants::UPLOAD_CONFIG_FILE, $configFileName);
app('log')->debug(sprintf('New configuration is stored under key "%s".', session()->get(Constants::UPLOAD_CONFIG_FILE)));
// set map config as complete.
session()->put(Constants::MAPPING_COMPLETE_INDICATOR, true);
session()->put(Constants::READY_FOR_CONVERSION, true);
if ('nordigen' === $configuration->getFlow() || 'spectre' === $configuration->getFlow()) {
// if nordigen, now ready for submission!
session()->put(Constants::READY_FOR_SUBMISSION, true);
}
return redirect()->route('007-convert.index');
}
/**
* @param array $original
* @param array $new
* @param array $original
* @param array $new
*
* @return array
*/

2
app/Http/Controllers/Import/Nordigen/LinkController.php

@ -129,7 +129,7 @@ class LinkController extends Controller
}
/**
* @param Request $request
* @param Request $request
*
* @return Application|RedirectResponse|Redirector
* @throws ImporterErrorException

4
app/Http/Controllers/Import/Spectre/ConnectionController.php

@ -141,7 +141,7 @@ class ConnectionController extends Controller
}
/**
* @param Request $request
* @param Request $request
*
* @return Application|RedirectResponse|Redirector
* @throws ImporterErrorException
@ -151,7 +151,7 @@ class ConnectionController extends Controller
*/
public function post(Request $request)
{
$connectionId = $request->get('spectre_connection_id');
$connectionId = $request->get('spectre_connection_id');
$configuration = $this->restoreConfiguration();
if ('00' === $connectionId) {

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

@ -185,7 +185,7 @@ class SubmitController extends Controller
}
/**
* @param Request $request
* @param Request $request
*
* @return JsonResponse
*/

159
app/Http/Controllers/Import/UploadController.php

@ -139,61 +139,35 @@ class UploadController extends Controller
}
/**
* @param string $string
*
* @param string $flow
* @param MessageBag $errors
* @param UploadedFile|null $file
* @return MessageBag
* @throws FilesystemException
* @throws ImporterErrorException
* @return string
*/
private function processUploadedFile(string $flow, MessageBag $errors, UploadedFile|null $file): MessageBag
private function detectEOL(string $string): string
{
if (null === $file && 'file' === $flow) {
$errors->add('importable_file', 'No file was uploaded.');
return $errors;
}
if ('file' === $flow) {
$errorNumber = $file->getError();
if (0 !== $errorNumber) {
$errors->add('importable_file', $this->getError($errorNumber));
}
// upload the file to a temp directory and use it from there.
if (0 === $errorNumber) {
$detector = new FileContentSherlock();
$fileType = $detector->detectContentType($file->getPathname());
$content = '';
if ('csv' === $fileType) {
$content = file_get_contents($file->getPathname());
// https://stackoverflow.com/questions/11066857/detect-eol-type-using-php
// because apparently there are banks that use "\r" as newline. Looking at the morons of KBC Bank, Belgium.
// This one is for you: 🤦‍♀️
$eol = $this->detectEOL($content);
if ("\r" === $eol) {
app('log')->error('You bank is dumb. Tell them to fix their CSV files.');
$content = str_replace("\r", "\n", $content);
}
}
if ('camt' === $fileType) {
$content = file_get_contents($file->getPathname());
}
$fileName = StorageService::storeContent($content);
session()->put(Constants::UPLOAD_DATA_FILE, $fileName);
session()->put(Constants::HAS_UPLOAD, true);
$eols = [
'\n\r' => "\n\r", // 0x0A - 0x0D - acorn BBC
'\r\n' => "\r\n", // 0x0D - 0x0A - Windows, DOS OS/2
'\n' => "\n", // 0x0A - - Unix, OSX
'\r' => "\r", // 0x0D - - Apple ][, TRS80
];
$curCount = 0;
$curEol = '';
foreach ($eols as $eolKey => $eol) {
$count = substr_count($string, $eol);
app('log')->debug(sprintf('Counted %dx "%s" EOL in upload.', $count, $eolKey));
if ($count > $curCount) {
$curCount = $count;
$curEol = $eol;
app('log')->debug(sprintf('Conclusion: "%s" is the EOL in this file.', $eolKey));
}
}
return $errors;
return $curEol;
}
/**
* @param int $error
* @param int $error
*
* @return string
*/
@ -215,36 +189,8 @@ class UploadController extends Controller
}
/**
* @param string $string
*
* @return string
*/
private function detectEOL(string $string): string
{
$eols = [
'\n\r' => "\n\r", // 0x0A - 0x0D - acorn BBC
'\r\n' => "\r\n", // 0x0D - 0x0A - Windows, DOS OS/2
'\n' => "\n", // 0x0A - - Unix, OSX
'\r' => "\r", // 0x0D - - Apple ][, TRS80
];
$curCount = 0;
$curEol = '';
foreach ($eols as $eolKey => $eol) {
$count = substr_count($string, $eol);
app('log')->debug(sprintf('Counted %dx "%s" EOL in upload.', $count, $eolKey));
if ($count > $curCount) {
$curCount = $count;
$curEol = $eol;
app('log')->debug(sprintf('Conclusion: "%s" is the EOL in this file.', $eolKey));
}
}
return $curEol;
}
/**
* @param MessageBag $errors
* @param UploadedFile|null $file
* @param MessageBag $errors
* @param UploadedFile|null $file
*
* @return MessageBag
* @throws ImporterErrorException
@ -267,7 +213,7 @@ class UploadController extends Controller
session()->put(Constants::UPLOAD_CONFIG_FILE, $configFileName);
// process the config file
$success = false;
$success = false;
$configuration = null;
try {
$configuration = ConfigFileProcessor::convertConfigFile($configFileName);
@ -288,9 +234,9 @@ class UploadController extends Controller
}
/**
* @param MessageBag $errors
* @param string $selection
* @param UploadedFile|null $file
* @param MessageBag $errors
* @param string $selection
* @param UploadedFile|null $file
*
* @return MessageBag
* @throws ImporterErrorException
@ -316,4 +262,57 @@ class UploadController extends Controller
return $errors;
}
/**
*
* @param string $flow
* @param MessageBag $errors
* @param UploadedFile|null $file
* @return MessageBag
* @throws FilesystemException
* @throws ImporterErrorException
*/
private function processUploadedFile(string $flow, MessageBag $errors, UploadedFile|null $file): MessageBag
{
if (null === $file && 'file' === $flow) {
$errors->add('importable_file', 'No file was uploaded.');
return $errors;
}
if ('file' === $flow) {
$errorNumber = $file->getError();
if (0 !== $errorNumber) {
$errors->add('importable_file', $this->getError($errorNumber));
}
// upload the file to a temp directory and use it from there.
if (0 === $errorNumber) {
$detector = new FileContentSherlock();
$fileType = $detector->detectContentType($file->getPathname());
$content = '';
if ('csv' === $fileType) {
$content = file_get_contents($file->getPathname());
// https://stackoverflow.com/questions/11066857/detect-eol-type-using-php
// because apparently there are banks that use "\r" as newline. Looking at the morons of KBC Bank, Belgium.
// This one is for you: 🤦‍♀️
$eol = $this->detectEOL($content);
if ("\r" === $eol) {
app('log')->error('You bank is dumb. Tell them to fix their CSV files.');
$content = str_replace("\r", "\n", $content);
}
}
if ('camt' === $fileType) {
$content = file_get_contents($file->getPathname());
}
$fileName = StorageService::storeContent($content);
session()->put(Constants::UPLOAD_DATA_FILE, $fileName);
session()->put(Constants::HAS_UPLOAD, true);
}
}
return $errors;
}
}

38
app/Http/Controllers/IndexController.php

@ -45,7 +45,24 @@ class IndexController extends Controller
}
/**
* @param Request $request
* @return mixed
*/
public function flush(): mixed
{
app('log')->debug(sprintf('Now at %s', __METHOD__));
session()->forget([Constants::UPLOAD_DATA_FILE, Constants::UPLOAD_CONFIG_FILE, Constants::IMPORT_JOB_IDENTIFIER]);
session()->flush();
$cookies = [
cookie(Constants::FLOW_COOKIE, ''),
];
Artisan::call('cache:clear');
Artisan::call('config:clear');
return redirect(route('index'))->withCookies($cookies);
}
/**
* @param Request $request
*
* @return mixed
*/
@ -88,7 +105,7 @@ class IndexController extends Controller
}
/**
* @param Request $request
* @param Request $request
*
* @return mixed
*/
@ -129,21 +146,4 @@ class IndexController extends Controller
return redirect(route('index'))->withCookies($cookies);
}
/**
* @return mixed
*/
public function flush(): mixed
{
app('log')->debug(sprintf('Now at %s', __METHOD__));
session()->forget([Constants::UPLOAD_DATA_FILE, Constants::UPLOAD_CONFIG_FILE, Constants::IMPORT_JOB_IDENTIFIER]);
session()->flush();
$cookies = [
cookie(Constants::FLOW_COOKIE, ''),
];
Artisan::call('cache:clear');
Artisan::call('config:clear');
return redirect(route('index'))->withCookies($cookies);
}
}

2
app/Http/Controllers/NavController.php

@ -42,7 +42,7 @@ class NavController extends Controller
app('log')->debug(__METHOD__);
session()->forget(Constants::CONFIG_COMPLETE_INDICATOR);
return redirect(route('004-configure.index') . '?overruleskip=true');
return redirect(route('004-configure.index').'?overruleskip=true');
}
/**

2
app/Http/Controllers/ServiceController.php

@ -67,7 +67,7 @@ class ServiceController extends Controller
}
/**
* @param Request $request
* @param Request $request
*
* @return JsonResponse
*/

94
app/Http/Controllers/TokenController.php

@ -51,7 +51,7 @@ class TokenController extends Controller
/**
* The user ends up here when they come back from Firefly III.
*
* @param Request $request
* @param Request $request
*
* @return Application|Factory|\Illuminate\Contracts\View\View|RedirectResponse|Redirector
* @throws ImporterErrorException
@ -181,7 +181,7 @@ class TokenController extends Controller
* 2. Has client ID + URL. Will send user to Firefly III for permission.
* 3. Has either 1 of those. Will show user some input form.
*
* @param Request $request
* @param Request $request
*
* @return Application|Factory|RedirectResponse|Redirector|View
*/
@ -228,55 +228,11 @@ class TokenController extends Controller
return view('token.client_id', compact('baseUrl', 'clientId', 'pageTitle'));
}
/**
* This method forwards the user to Firefly III. Some parameters are stored in the user's session.
*
* @param Request $request
* @param string $baseURL
* @param string $vanityURL
* @param int $clientId
*
* @return RedirectResponse
*/
private function redirectForPermission(Request $request, string $baseURL, string $vanityURL, int $clientId): RedirectResponse
{
$baseURL = rtrim($baseURL, '/');
$vanityURL = rtrim($vanityURL, '/');
app('log')->debug(sprintf('Now in %s(request, "%s", "%s", %d)', __METHOD__, $baseURL, $vanityURL, $clientId));
$state = Str::random(40);
$codeVerifier = Str::random(128);
$request->session()->put('state', $state);
$request->session()->put('code_verifier', $codeVerifier);
$request->session()->put('form_client_id', $clientId);
$request->session()->put('form_base_url', $baseURL);
$request->session()->put('form_vanity_url', $vanityURL);
$codeChallenge = strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
$params = [
'client_id' => $clientId,
'redirect_uri' => route('token.callback'),
'response_type' => 'code',
'scope' => '',
'state' => $state,
'code_challenge' => $codeChallenge,
'code_challenge_method' => 'S256',
];
$query = http_build_query($params);
// we redirect the user to the vanity URL, which is the same as the base_url, unless the user actually set a vanity URL.
$finalURL = sprintf('%s/oauth/authorize?', $vanityURL);
app('log')->debug('Query parameters are', $params);
app('log')->debug(sprintf('Now redirecting to "%s" (params omitted)', $finalURL));
return redirect($finalURL . $query);
}
/**
* User submits the client ID + optionally the base URL.
* Whatever happens, we redirect the user to Firefly III and beg for permission.
*
* @param Request $request
* @param Request $request
*
* @return Application|RedirectResponse|Redirector
*/
@ -325,4 +281,48 @@ class TokenController extends Controller
// return request for permission:
return $this->redirectForPermission($request, $baseURL, $vanityURL, $data['client_id']);
}
/**
* This method forwards the user to Firefly III. Some parameters are stored in the user's session.
*
* @param Request $request
* @param string $baseURL
* @param string $vanityURL
* @param int $clientId
*
* @return RedirectResponse
*/
private function redirectForPermission(Request $request, string $baseURL, string $vanityURL, int $clientId): RedirectResponse
{
$baseURL = rtrim($baseURL, '/');
$vanityURL = rtrim($vanityURL, '/');
app('log')->debug(sprintf('Now in %s(request, "%s", "%s", %d)', __METHOD__, $baseURL, $vanityURL, $clientId));
$state = Str::random(40);
$codeVerifier = Str::random(128);
$request->session()->put('state', $state);
$request->session()->put('code_verifier', $codeVerifier);
$request->session()->put('form_client_id', $clientId);
$request->session()->put('form_base_url', $baseURL);
$request->session()->put('form_vanity_url', $vanityURL);
$codeChallenge = strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
$params = [
'client_id' => $clientId,
'redirect_uri' => route('token.callback'),
'response_type' => 'code',
'scope' => '',
'state' => $state,
'code_challenge' => $codeChallenge,
'code_challenge_method' => 'S256',
];
$query = http_build_query($params);
// we redirect the user to the vanity URL, which is the same as the base_url, unless the user actually set a vanity URL.
$finalURL = sprintf('%s/oauth/authorize?', $vanityURL);
app('log')->debug('Query parameters are', $params);
app('log')->debug(sprintf('Now redirecting to "%s" (params omitted)', $finalURL));
return redirect($finalURL.$query);
}
}

100
app/Http/Middleware/IsReadyForStep.php

@ -41,8 +41,8 @@ trait IsReadyForStep
public const TEST = 'test';
/**
* @param Request $request
* @param Closure $next
* @param Request $request
* @param Closure $next
*
* @return mixed
* @throws ImporterErrorException
@ -101,6 +101,50 @@ trait IsReadyForStep
return $this->isReadyForBasicStep();
}
/**
* @param Request $request
*
* @return RedirectResponse|null
* @throws ContainerExceptionInterface
* @throws ImporterErrorException
* @throws NotFoundExceptionInterface
*/
protected function redirectToCorrectStep(Request $request): ?RedirectResponse
{
$flow = $request->cookie(Constants::FLOW_COOKIE);
if (null === $flow) {
app('log')->debug('redirectToCorrectStep returns true because $flow is null');
return null;
}
if ('file' === $flow) {
return $this->redirectToCorrectFileStep();
}
if ('nordigen' === $flow) {
return $this->redirectToCorrectNordigenStep();
}
if ('spectre' === $flow) {
return $this->redirectToCorrectSpectreStep();
}
return $this->redirectToBasicStep();
}
/**
* @return bool
* @throws ImporterErrorException
*/
private function isReadyForBasicStep(): bool
{
app('log')->debug(sprintf('isReadyForBasicStep("%s")', self::STEP));
switch (self::STEP) {
default:
throw new ImporterErrorException(sprintf('isReadyForBasicStep: Cannot handle basic step "%s"', self::STEP));
case 'service-validation':
return true;
}
}
/**
* @return bool
* @throws ImporterErrorException
@ -335,47 +379,16 @@ trait IsReadyForStep
}
/**
* @return bool
* @return RedirectResponse
* @throws ImporterErrorException
*/
private function isReadyForBasicStep(): bool
private function redirectToBasicStep(): RedirectResponse
{
app('log')->debug(sprintf('isReadyForBasicStep("%s")', self::STEP));
app('log')->debug(sprintf('redirectToBasicStep("%s")', self::STEP));
switch (self::STEP) {
default:
throw new ImporterErrorException(sprintf('isReadyForBasicStep: Cannot handle basic step "%s"', self::STEP));
case 'service-validation':
return true;
}
}
/**
* @param Request $request
*
* @return RedirectResponse|null
* @throws ContainerExceptionInterface
* @throws ImporterErrorException
* @throws NotFoundExceptionInterface
*/
protected function redirectToCorrectStep(Request $request): ?RedirectResponse
{
$flow = $request->cookie(Constants::FLOW_COOKIE);
if (null === $flow) {
app('log')->debug('redirectToCorrectStep returns true because $flow is null');
return null;
}
if ('file' === $flow) {
return $this->redirectToCorrectFileStep();
}
if ('nordigen' === $flow) {
return $this->redirectToCorrectNordigenStep();
}
if ('spectre' === $flow) {
return $this->redirectToCorrectSpectreStep();
throw new ImporterErrorException(sprintf('redirectToBasicStep: Cannot handle basic step "%s"', self::STEP));
}
return $this->redirectToBasicStep();
}
/**
@ -544,17 +557,4 @@ trait IsReadyForStep
}
}
}
/**
* @return RedirectResponse
* @throws ImporterErrorException
*/
private function redirectToBasicStep(): RedirectResponse
{
app('log')->debug(sprintf('redirectToBasicStep("%s")', self::STEP));
switch (self::STEP) {
default:
throw new ImporterErrorException(sprintf('redirectToBasicStep: Cannot handle basic step "%s"', self::STEP));
}
}
}

6
app/Http/Middleware/RedirectIfAuthenticated.php

@ -37,9 +37,9 @@ class RedirectIfAuthenticated
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @param string|null $guard
* @param Request $request
* @param Closure $next
* @param string|null $guard
*
* @return mixed
*/

2
app/Http/Middleware/TrustProxies.php

@ -55,7 +55,7 @@ class TrustProxies extends Middleware
/**
* TrustProxies constructor.
*
* @param Repository $config
* @param Repository $config
*/
public function __construct(Repository $config)
{

3
app/Http/Middleware/VerifyCsrfToken.php

@ -38,6 +38,7 @@ class VerifyCsrfToken extends Middleware
*/
protected $except
= [
'autoimport', 'autoupload',
'autoimport',
'autoupload',
];
}

4
app/Http/Request/ConfigurationPostRequest.php

@ -95,7 +95,7 @@ class ConfigurationPostRequest extends Request
// camt
'grouped_transaction_handling' => $this->convertToString('grouped_transaction_handling'),
'use_entire_opposing_address' => $this->convertBoolean($this->get('use_entire_opposing_address')),
'use_entire_opposing_address' => $this->convertBoolean($this->get('use_entire_opposing_address')),
];
@ -139,7 +139,7 @@ class ConfigurationPostRequest extends Request
/**
* Configure the validator instance with special rules for after the basic validation rules.
*
* @param Validator $validator
* @param Validator $validator
*
* @return void
*/

16
app/Http/Request/Request.php

@ -37,7 +37,7 @@ use Illuminate\Foundation\Http\FormRequest;
class Request extends FormRequest
{
/**
* @param string|null $value
* @param string|null $value
*
* @return bool
*/
@ -56,7 +56,7 @@ class Request extends FormRequest
/**
* Return integer value.
*
* @param string $field
* @param string $field
*
* @return int
*/
@ -68,7 +68,7 @@ class Request extends FormRequest
/**
* Return string value.
*
* @param string $field
* @param string $field
*
* @return string
*/
@ -80,7 +80,7 @@ class Request extends FormRequest
/**
* Parse to integer
*
* @param string|null $string
* @param string|null $string
*
* @return int|null
*/
@ -99,7 +99,7 @@ class Request extends FormRequest
/**
* Return integer value, or NULL when it's not set.
*
* @param string $field
* @param string $field
*
* @return int|null
*/
@ -120,7 +120,7 @@ class Request extends FormRequest
/**
* Return string value, or NULL if empty.
*
* @param string $field
* @param string $field
*
* @return string|null
*/
@ -136,7 +136,7 @@ class Request extends FormRequest
/**
* Parse and clean a string.
*
* @param string|null $string
* @param string|null $string
*
* @return string|null
*/
@ -153,7 +153,7 @@ class Request extends FormRequest
/**
* Return date or NULL.
*
* @param string $field
* @param string $field
*
* @return Carbon|null
*/

4
app/Http/Request/RolesPostRequest.php

@ -74,7 +74,7 @@ class RolesPostRequest extends Request
/**
* Configure the validator instance with special rules for after the basic validation rules.
*
* @param Validator $validator
* @param Validator $validator
*
* @return void
*/
@ -89,7 +89,7 @@ class RolesPostRequest extends Request
}
/**
* @param Validator $validator
* @param Validator $validator
*/
protected function validateAmountRole(Validator $validator): void
{

2
app/Http/Request/SelectionRequest.php

@ -62,7 +62,7 @@ class SelectionRequest extends Request
/**
* Configure the validator instance with special rules for after the basic validation rules.
*
* @param Validator $validator
* @param Validator $validator
* See reference nr. 74
*
* @return void

1
app/Providers/AuthServiceProvider.php

@ -49,7 +49,6 @@ class AuthServiceProvider extends ServiceProvider
public function boot(): void
{
$this->registerPolicies();
//
}
}

1
app/Providers/RouteServiceProvider.php

@ -57,7 +57,6 @@ class RouteServiceProvider extends ServiceProvider
$this->mapApiRoutes();
$this->mapWebRoutes();
//
}

5
app/Rules/Iban.php

@ -1,4 +1,5 @@
<?php
/*
* Iban.php
* Copyright (c) 2021 james@firefly-iii.org
@ -44,8 +45,8 @@ class Iban implements Rule
/**
* Determine if the given value is a valid IBAN.
*
* @param string $attribute
* @param mixed $value
* @param string $attribute
* @param mixed $value
*
* @return bool
*/

2
app/Services/CSV/Configuration/ConfigFileProcessor.php

@ -38,7 +38,7 @@ class ConfigFileProcessor
/**
* Input (the content of) a configuration file and this little script will convert it to a compatible array.
*
* @param string $fileName
* @param string $fileName
*
* @return Configuration
* @throws ImporterErrorException

68
app/Services/CSV/Conversion/Routine/CSVFileProcessor.php

@ -48,7 +48,7 @@ class CSVFileProcessor
/**
* CSVFileProcessor constructor.
*
* @param Configuration $configuration
* @param Configuration $configuration
*/
public function __construct(Configuration $configuration)
{
@ -102,7 +102,7 @@ class CSVFileProcessor
}
/**
* @param string $delimiter
* @param string $delimiter
*/
public function setDelimiter(string $delimiter): void
{
@ -115,10 +115,26 @@ class CSVFileProcessor
$this->delimiter = $map[$delimiter] ?? ',';
}
/**
* @param bool $hasHeaders
*/
public function setHasHeaders(bool $hasHeaders): void
{
$this->hasHeaders = $hasHeaders;
}
/**
* @param Reader $reader
*/
public function setReader(Reader $reader): void
{
$this->reader = $reader;
}
/**
* Loop all records from CSV file.
*
* @param ResultSet $records
* @param ResultSet $records
*
* @return array
* @throws ImporterErrorException
@ -147,27 +163,7 @@ class CSVFileProcessor
}
/**
* Do a first sanity check on whatever comes out of the CSV file.
*
* @param array $line
*
* @return array
*/
private function sanitize(array $line): array
{
$lineValues = array_values($line);
array_walk(
$lineValues,
static function ($element) {
return trim(str_replace('&nbsp;', ' ', (string)$element));
}
);
return $lineValues;
}
/**
* @param array $array
* @param array $array
*
* @return array
* @throws ImporterErrorException
@ -200,18 +196,22 @@ class CSVFileProcessor
}
/**
* @param bool $hasHeaders
* Do a first sanity check on whatever comes out of the CSV file.
*
* @param array $line
*
* @return array
*/
public function setHasHeaders(bool $hasHeaders): void
private function sanitize(array $line): array
{
$this->hasHeaders = $hasHeaders;
}
$lineValues = array_values($line);
array_walk(
$lineValues,
static function ($element) {
return trim(str_replace('&nbsp;', ' ', (string)$element));
}
);
/**
* @param Reader $reader
*/
public function setReader(Reader $reader): void
{
$this->reader = $reader;
return $lineValues;
}
}

12
app/Services/CSV/Conversion/Routine/ColumnValue.php

@ -56,7 +56,7 @@ class ColumnValue
}
/**
* @param int $mappedValue
* @param int $mappedValue
*/
public function setMappedValue(int $mappedValue): void
{
@ -72,7 +72,7 @@ class ColumnValue
}
/**
* @param string $originalRole
* @param string $originalRole
*/
public function setOriginalRole(string $originalRole): void
{
@ -105,7 +105,7 @@ class ColumnValue
}
/**
* @param string $role
* @param string $role
*/
public function setRole(string $role): void
{
@ -121,7 +121,7 @@ class ColumnValue
}
/**
* @param string $value
* @param string $value
*/
public function setValue(string $value): void
{
@ -137,7 +137,7 @@ class ColumnValue
}
/**
* @param bool $appendValue
* @param bool $appendValue
*/
public function setAppendValue(bool $appendValue): void
{
@ -145,7 +145,7 @@ class ColumnValue
}
/**
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

8
app/Services/CSV/Conversion/Routine/ColumnValueConverter.php

@ -46,7 +46,7 @@ class ColumnValueConverter
/**
* ColumnValueConverter constructor.
*
* @param Configuration $configuration
* @param Configuration $configuration
*/
public function __construct(Configuration $configuration)
{
@ -55,7 +55,7 @@ class ColumnValueConverter
}
/**
* @param array $lines
* @param array $lines
*
* @return array
* @throws ImporterErrorException
@ -77,7 +77,7 @@ class ColumnValueConverter
}
/**
* @param array $line
* @param array $line
*
* @return array
* @throws ImporterErrorException
@ -115,7 +115,7 @@ class ColumnValueConverter
],
];
/**
* @var int $columnIndex
* @var int $columnIndex
* @var ColumnValue $value
*/
foreach ($line as $columnIndex => $value) {

142
app/Services/CSV/Conversion/Routine/LineProcessor.php

@ -48,7 +48,7 @@ class LineProcessor
/**
* LineProcessor constructor.
*
* @param Configuration $configuration
* @param Configuration $configuration
*/
public function __construct(Configuration $configuration)
{
@ -62,7 +62,7 @@ class LineProcessor
}
/**
* @param array $lines
* @param array $lines
*
* @return array
*/
@ -89,11 +89,79 @@ class LineProcessor
return $processed;
}
/**
* If the value in the column is mapped to a certain ID,
* the column where this ID must be placed will change.
*
* For example, if you map role "budget-name" with value "groceries" to 1,
* then that should become the budget-id. Not the name.
*
* @param int $column
* @param int $mapped
*
* @return string
* @throws ImporterErrorException
*/
private function getRoleForColumn(int $column, int $mapped): string
{
$role = $this->roles[$column] ?? '_ignore';
if (0 === $mapped) {
app('log')->debug(sprintf('Column #%d with role "%s" is not mapped.', $column + 1, $role));
return $role;
}
if (!(isset($this->doMapping[$column]) && true === $this->doMapping[$column])) {
// if the mapping has been filled in already by a role with a higher priority,
// ignore the mapping.
app('log')->debug(sprintf('Column #%d ("%s") has something already.', $column, $role));
return $role;
}
$roleMapping = [
'account-id' => 'account-id',
'account-name' => 'account-id',
'account-iban' => 'account-id',
'account-number' => 'account-id',
'bill-id' => 'bill-id',
'bill-name' => 'bill-id',
'budget-id' => 'budget-id',
'budget-name' => 'budget-id',
'currency-id' => 'currency-id',
'currency-name' => 'currency-id',
'currency-code' => 'currency-id',
'currency-symbol' => 'currency-id',
'category-id' => 'category-id',
'category-name' => 'category-id',
'foreign-currency-id' => 'foreign-currency-id',
'foreign-currency-code' => 'foreign-currency-id',
'opposing-id' => 'opposing-id',
'opposing-name' => 'opposing-id',
'opposing-iban' => 'opposing-id',
'opposing-number' => 'opposing-id',
];
if (!isset($roleMapping[$role])) {
throw new ImporterErrorException(sprintf('Cannot indicate new role for mapped role "%s"', $role)); // @codeCoverageIgnore
}
$newRole = $roleMapping[$role];
if ($newRole !== $role) {
app('log')->debug(sprintf('Role was "%s", but because of mapping (mapped to #%d), role becomes "%s"', $role, $mapped, $newRole));
}
// also store the $mapped values in a "mappedValues" array.
// used to validate whatever has been set as mapping
$this->mappedValues[$newRole][] = $mapped;
$this->mappedValues[$newRole] = array_unique($this->mappedValues[$newRole]);
app('log')->debug(sprintf('Values mapped to role "%s" are: ', $newRole), $this->mappedValues[$newRole]);
return $newRole;
}
/**
* Convert each raw CSV to a set of ColumnValue objects, which hold as much info
* as we can cram into it. These new lines can be imported later on.
*
* @param array $line
* @param array $line
*
* @return array
* @throws ImporterErrorException
@ -158,72 +226,4 @@ class LineProcessor
return $return;
}
/**
* If the value in the column is mapped to a certain ID,
* the column where this ID must be placed will change.
*
* For example, if you map role "budget-name" with value "groceries" to 1,
* then that should become the budget-id. Not the name.
*
* @param int $column
* @param int $mapped
*
* @return string
* @throws ImporterErrorException
*/
private function getRoleForColumn(int $column, int $mapped): string
{
$role = $this->roles[$column] ?? '_ignore';
if (0 === $mapped) {
app('log')->debug(sprintf('Column #%d with role "%s" is not mapped.', $column + 1, $role));
return $role;
}
if (!(isset($this->doMapping[$column]) && true === $this->doMapping[$column])) {
// if the mapping has been filled in already by a role with a higher priority,
// ignore the mapping.
app('log')->debug(sprintf('Column #%d ("%s") has something already.', $column, $role));
return $role;
}
$roleMapping = [
'account-id' => 'account-id',
'account-name' => 'account-id',
'account-iban' => 'account-id',
'account-number' => 'account-id',
'bill-id' => 'bill-id',
'bill-name' => 'bill-id',
'budget-id' => 'budget-id',
'budget-name' => 'budget-id',
'currency-id' => 'currency-id',
'currency-name' => 'currency-id',
'currency-code' => 'currency-id',
'currency-symbol' => 'currency-id',
'category-id' => 'category-id',
'category-name' => 'category-id',
'foreign-currency-id' => 'foreign-currency-id',
'foreign-currency-code' => 'foreign-currency-id',
'opposing-id' => 'opposing-id',
'opposing-name' => 'opposing-id',
'opposing-iban' => 'opposing-id',
'opposing-number' => 'opposing-id',
];
if (!isset($roleMapping[$role])) {
throw new ImporterErrorException(sprintf('Cannot indicate new role for mapped role "%s"', $role)); // @codeCoverageIgnore
}
$newRole = $roleMapping[$role];
if ($newRole !== $role) {
app('log')->debug(sprintf('Role was "%s", but because of mapping (mapped to #%d), role becomes "%s"', $role, $mapped, $newRole));
}
// also store the $mapped values in a "mappedValues" array.
// used to validate whatever has been set as mapping
$this->mappedValues[$newRole][] = $mapped;
$this->mappedValues[$newRole] = array_unique($this->mappedValues[$newRole]);
app('log')->debug(sprintf('Values mapped to role "%s" are: ', $newRole), $this->mappedValues[$newRole]);
return $newRole;
}
}

50
app/Services/CSV/Conversion/Routine/PseudoTransactionProcessor.php

@ -52,7 +52,7 @@ class PseudoTransactionProcessor
/**
* PseudoTransactionProcessor constructor.
*
* @param int|null $defaultAccountId
* @param int|null $defaultAccountId
*
* @throws ImporterErrorException
*/
@ -64,7 +64,29 @@ class PseudoTransactionProcessor
}
/**
* @param int|null $accountId
* @param array $lines
*
* @return array
*/
public function processPseudo(array $lines): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
$count = count($lines);
$processed = [];
app('log')->info(sprintf('Converting %d lines into transactions.', $count));
/** @var array $line */
foreach ($lines as $index => $line) {
app('log')->info(sprintf('Now processing line %d/%d.', ($index + 1), $count));
$processed[] = $this->processPseudoLine($line);
// $this->addMessage($index, sprintf('Converted CSV line %d into a transaction.', $index + 1));
}
app('log')->info(sprintf('Done converting %d lines into transactions.', $count));
return $processed;
}
/**
* @param int|null $accountId
*
* @throws ImporterErrorException
*/
@ -125,29 +147,7 @@ class PseudoTransactionProcessor
}
/**
* @param array $lines
*
* @return array
*/
public function processPseudo(array $lines): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
$count = count($lines);
$processed = [];
app('log')->info(sprintf('Converting %d lines into transactions.', $count));
/** @var array $line */
foreach ($lines as $index => $line) {
app('log')->info(sprintf('Now processing line %d/%d.', ($index + 1), $count));
$processed[] = $this->processPseudoLine($line);
// $this->addMessage($index, sprintf('Converted CSV line %d into a transaction.', $index + 1));
}
app('log')->info(sprintf('Done converting %d lines into transactions.', $count));
return $processed;
}
/**
* @param array $line
* @param array $line
*
* @return array
*/

76
app/Services/CSV/Conversion/RoutineManager.php

@ -120,6 +120,22 @@ class RoutineManager implements RoutineManagerInterface
$this->pseudoTransactionProcessor->setIdentifier($this->identifier);
}
/**
* @param string $content
*/
public function setContent(string $content): void
{
$this->content = $content;
}
/**
* @param bool $forceCli
*/
public function setForceCli(bool $forceCli): void
{
$this->forceCli = $forceCli;
}
/**
* @inheritDoc
* @throws ImporterErrorException
@ -164,14 +180,14 @@ class RoutineManager implements RoutineManagerInterface
}
/**
* @param int $count
* @param int $count
*/
private function mergeMessages(int $count): void
private function mergeErrors(int $count): void
{
$one = $this->csvFileProcessor->getMessages();
$two = $this->lineProcessor->getMessages();
$three = $this->columnValueConverter->getMessages();
$four = $this->pseudoTransactionProcessor->getMessages();
$one = $this->csvFileProcessor->getErrors();
$two = $this->lineProcessor->getErrors();
$three = $this->columnValueConverter->getErrors();
$four = $this->pseudoTransactionProcessor->getErrors();
$total = [];
for ($i = 0; $i < $count; $i++) {
$total[$i] = array_merge(
@ -182,18 +198,18 @@ class RoutineManager implements RoutineManagerInterface
);
}
$this->allMessages = $total;
$this->allErrors = $total;
}
/**
* @param int $count
* @param int $count
*/
private function mergeWarnings(int $count): void
private function mergeMessages(int $count): void
{
$one = $this->csvFileProcessor->getWarnings();
$two = $this->lineProcessor->getWarnings();
$three = $this->columnValueConverter->getWarnings();
$four = $this->pseudoTransactionProcessor->getWarnings();
$one = $this->csvFileProcessor->getMessages();
$two = $this->lineProcessor->getMessages();
$three = $this->columnValueConverter->getMessages();
$four = $this->pseudoTransactionProcessor->getMessages();
$total = [];
for ($i = 0; $i < $count; $i++) {
$total[$i] = array_merge(
@ -203,18 +219,19 @@ class RoutineManager implements RoutineManagerInterface
$four[$i] ?? [],
);
}
$this->allWarnings = $total;
$this->allMessages = $total;
}
/**
* @param int $count
* @param int $count
*/
private function mergeErrors(int $count): void
private function mergeWarnings(int $count): void
{
$one = $this->csvFileProcessor->getErrors();
$two = $this->lineProcessor->getErrors();
$three = $this->columnValueConverter->getErrors();
$four = $this->pseudoTransactionProcessor->getErrors();
$one = $this->csvFileProcessor->getWarnings();
$two = $this->lineProcessor->getWarnings();
$three = $this->columnValueConverter->getWarnings();
$four = $this->pseudoTransactionProcessor->getWarnings();
$total = [];
for ($i = 0; $i < $count; $i++) {
$total[$i] = array_merge(
@ -224,23 +241,6 @@ class RoutineManager implements RoutineManagerInterface
$four[$i] ?? [],
);
}
$this->allErrors = $total;
}
/**
* @param string $content
*/
public function setContent(string $content): void
{
$this->content = $content;
}
/**
* @param bool $forceCli
*/
public function setForceCli(bool $forceCli): void
{
$this->forceCli = $forceCli;
$this->allWarnings = $total;
}
}

4
app/Services/CSV/Conversion/Support/DeterminesTransactionType.php

@ -30,8 +30,8 @@ namespace App\Services\CSV\Conversion\Support;
trait DeterminesTransactionType
{
/**
* @param string|null $sourceType
* @param string|null $destinationType
* @param string|null $sourceType
* @param string|null $destinationType
*
* @return string
*/

4
app/Services/CSV/Conversion/Task/AbstractTask.php

@ -36,7 +36,7 @@ abstract class AbstractTask implements TaskInterface
protected TransactionCurrency $transactionCurrency;
/**
* @param Account $account
* @param Account $account
*/
public function setAccount(Account $account): void
{
@ -44,7 +44,7 @@ abstract class AbstractTask implements TaskInterface
}
/**
* @param TransactionCurrency $transactionCurrency
* @param TransactionCurrency $transactionCurrency
*/
public function setTransactionCurrency(TransactionCurrency $transactionCurrency): void
{

716
app/Services/CSV/Conversion/Task/Accounts.php

@ -80,240 +80,9 @@ class Accounts extends AbstractTask
}
/**
* @param array $transaction
* @param array $array
*
* @return array
* @throws ImporterErrorException
*/
private function processTransaction(array $transaction): array
{
app('log')->debug('Now in Accounts::processTransaction()');
/*
* Try to find the source and destination accounts in the transaction.
*
* The source account will default back to the user's submitted default account.
* So when everything fails, the transaction will be an expense for amount X.
*/
$sourceArray = $this->getSourceArray($transaction);
$destArray = $this->getDestinationArray($transaction);
$source = $this->findAccount($sourceArray, $this->account);
$destination = $this->findAccount($destArray, null);
/*
* First, set source and destination in the transaction array:
*/
$transaction = $this->setSource($transaction, $source);
$transaction = $this->setDestination($transaction, $destination);
$transaction['type'] = $this->determineType($source['type'], $destination['type']);
app('log')->debug(sprintf('Transaction type is set to "%s"', $transaction['type']));
app('log')->debug('Source is now:', $source);
app('log')->debug('Destination is now:', $destination);
$amount = (string)$transaction['amount'];
$amount = '' === $amount ? '0' : $amount;
if ('0' === $amount) {
app('log')->error('Amount is ZERO. This will give trouble further down the line.');
}
/*
* If the amount is positive, the transaction is a deposit. We switch Source
* and Destination and see if we can still handle the transaction, but only if the transaction
* isn't already a deposit (it has to be a withdrawal).
*
*/
if ('withdrawal' === $transaction['type'] && 1 === bccomp($amount, '0')) {
// amount is positive
app('log')->debug(sprintf('%s is positive and type is "%s", switch source/destination', $amount, $transaction['type']));
$transaction = $this->setSource($transaction, $destination);
$transaction = $this->setDestination($transaction, $source);
$transaction['type'] = $this->determineType($destination['type'], $source['type']);
app('log')->debug('Source is now:', $destination); // yes this is correct.
app('log')->debug('Destination is now:', $source); // yes this is correct.
// switch variables because processing further ahead will otherwise be messed up:
[$source, $destination] = [$destination, $source];
}
/*
* If the amount is positive and the type is a transfer, switch accounts around.
*/
if ('transfer' === $transaction['type'] && 1 === bccomp($amount, '0')) {
app('log')->debug('Transaction is a transfer, and amount is positive, will switch accounts.');
$transaction = $this->setSource($transaction, $destination);
$transaction = $this->setDestination($transaction, $source);
app('log')->debug('Source is now:', $destination); // yes this is correct!
app('log')->debug('Destination is now:', $source); // yes this is correct!
// also switch amount and foreign currency amount, if both are present.
// if this data is missing, Firefly III will break later either way.
if ($this->hasAllAmountInformation($transaction)) {
app('log')->debug('This transfer has all necessary (foreign) currency + amount information, so swap these too.');
$transaction = $this->swapCurrencyInformation($transaction);
}
}
/*
* If deposit and amount is positive, do nothing.
*/
if ('deposit' === $transaction['type'] && 1 === bccomp($amount, '0')) {
app('log')->debug('Transaction is a deposit, and amount is positive. Will not change account types.');
}
/*
* If deposit and amount is positive, but the source is not a revenue, fall back to
* some "original-field-name" values (if they exist) and hope for the best.
*/
if (
'deposit' === $transaction['type'] && 1 === bccomp($amount, '0') && 'revenue' !== $source['type'] && '' !== (string)$source['type']
) {
app('log')->warning(
sprintf(
'Transaction is a deposit, and amount is positive, but source is not a revenue ("%s"). Will fall back to original field names.',
$source['type']
)
);
$newSource = [
'id' => null,
'name' => $transaction['original-opposing-name'] ?? '(no name)',
'iban' => $transaction['original-opposing-iban'] ?? null,
'number' => $transaction['original-opposing-number'] ?? null,
'bic' => null,
];
$transaction = $this->setSource($transaction, $newSource);
}
/*
* If amount is negative and type is transfer, make sure accounts are "original".
*/
if ('transfer' === $transaction['type'] && -1 === bccomp($amount, '0')) {
app('log')->debug('Transaction is a transfer, and amount is negative, must not change accounts.');
$transaction = $this->setSource($transaction, $source);
$transaction = $this->setDestination($transaction, $destination);
app('log')->debug('Source is now:', $source);
app('log')->debug('Destination is now:', $destination);
}
/*
* Final check. If the type is "withdrawal" but the destination account found is "revenue"
* we found the wrong one. Just submit the name and hope for the best.
*/
if ('revenue' === $destination['type'] && 'withdrawal' === $transaction['type']) {
app('log')->warning('The found destination account is of type revenue but this is a withdrawal. Out of cheese error.');
app('log')->debug(
sprintf('Data importer will submit name "%s" and IBAN "%s" and let Firefly III sort it out.', $destination['name'], $destination['iban'])
);
$transaction['destination_id'] = null;
$transaction['destination_name'] = $destination['name'];
$transaction['destination_iban'] = $destination['iban'];
}
/*
* Same but for the other way around.
* If type is "deposit" but the source account is an expense account.
* Submit just the name.
*/
if ('expense' === $source['type'] && 'deposit' === $transaction['type']) {
app('log')->warning('The found source account is of type expense but this is a deposit. Out of cheese error.');
app('log')->debug(sprintf('Data importer will submit name "%s" and IBAN "%s" and let Firefly III sort it out.', $source['name'], $source['iban']));
$transaction['source_id'] = null;
$transaction['source_name'] = $source['name'];
$transaction['source_iban'] = $source['iban'];
}
/*
* if new source or destination ID is filled in, drop the other fields:
*/
if (0 !== $transaction['source_id'] && null !== $transaction['source_id']) {
$transaction['source_name'] = null;
$transaction['source_iban'] = null;
$transaction['source_number'] = null;
}
if (0 !== $transaction['destination_id'] && null !== $transaction['destination_id']) {
$transaction['destination_name'] = null;
$transaction['destination_iban'] = null;
$transaction['destination_number'] = null;
}
if ($this->hasAllCurrencies($transaction)) {
app('log')->debug('Final validation of foreign amount and or normal transaction amount');
// withdrawal
if ('withdrawal' === $transaction['type']) {
// currency info must match $source
// so if we can switch them around we will.
if ($transaction['currency_code'] !== $source['currency_code']
&& $transaction['foreign_currency_code'] === $source['currency_code']) {
app('log')->debug('Source account accepts %s, so foreign / native numbers are switched now.');
$amount = $transaction['amount'] ?? '0';
$currency = $transaction['currency_code'] ?? '';
$transaction['amount'] = $transaction['foreign_amount'] ?? '0';
$transaction['currency_code'] = $transaction['foreign_currency_code'] ?? '';
$transaction['foreign_amount'] = $amount;
$transaction['foreign_currency_code'] = $currency;
}
}
// deposit
if ('deposit' === $transaction['type']) {
// currency info must match $destination,
// so if we can switch them around we will.
if ($transaction['currency_code'] !== $destination['currency_code']
&& $transaction['foreign_currency_code'] === $destination['currency_code']) {
app('log')->debug('Destination account accepts %s, so foreign / native numbers are switched now.');
$amount = $transaction['amount'] ?? '0';
$currency = $transaction['currency_code'] ?? '';
$transaction['amount'] = $transaction['foreign_amount'] ?? '0';
$transaction['currency_code'] = $transaction['foreign_currency_code'] ?? '';
$transaction['foreign_amount'] = $amount;
$transaction['foreign_currency_code'] = $currency;
}
}
app('log')->debug('Final validation of foreign amount and or normal transaction amount finished.');
}
return $transaction;
}
/**
* @param array $transaction
*
* @return array
*/
private function getSourceArray(array $transaction): array
{
return [
'transaction_type' => $transaction['type'],
'id' => $transaction['source_id'],
'name' => $transaction['source_name'],
'iban' => $transaction['source_iban'] ?? null,
'number' => $transaction['source_number'] ?? null,
'bic' => $transaction['source_bic'] ?? null,
'direction' => 'source',
];
}
/**
* @param array $transaction
*
* @return array
*/
private function getDestinationArray(array $transaction): array
{
return [
'transaction_type' => $transaction['type'],
'id' => $transaction['destination_id'],
'name' => $transaction['destination_name'],
'iban' => $transaction['destination_iban'] ?? null,
'number' => $transaction['destination_number'] ?? null,
'bic' => $transaction['destination_bic'] ?? null,
'direction' => 'destination',
];
}
/**
* @param array $array
*
* @param Account|null $defaultAccount
* @param Account|null $defaultAccount
*
* @return array
* @throws ImporterErrorException
@ -415,27 +184,34 @@ class Accounts extends AbstractTask
}
/**
* @param string $value
* @param string $iban
* @param string $transactionType
*
* @return Account|null
* @throws ImporterErrorException
*/
private function findById(string $value): ?Account
private function findByIban(string $iban, string $transactionType): ?Account
{
app('log')->debug(sprintf('Going to search account with ID "%s"', $value));
app('log')->debug(sprintf('Going to search account with IBAN "%s"', $iban));
$url = SecretManager::getBaseUrl();
$token = SecretManager::getAccessToken();
$request = new GetSearchAccountRequest($url, $token);
$request->setVerify(config('importer.connection.verify'));
$request->setTimeOut(config('importer.connection.timeout'));
$request->setField('id');
$request->setQuery($value);
$request->setField('iban');
$request->setQuery($iban);
/** @var GetAccountsResponse $response */
try {
$response = $request->get();
} catch (GrumpyApiHttpException $e) {
throw new ImporterErrorException($e->getMessage());
}
if (0 === count($response)) {
app('log')->debug('Found NOTHING in findbyiban.');
return null;
}
if (1 === count($response)) {
/** @var Account $account */
try {
@ -443,46 +219,65 @@ class Accounts extends AbstractTask
} catch (ApiException $e) {
throw new ImporterErrorException($e->getMessage());
}
// catch impossible combination "expense" with "deposit"
if ('expense' === $account->type && 'deposit' === $transactionType) {
app('log')->debug(
sprintf(
'Out of cheese error (IBAN). Found Found %s account #%d based on IBAN "%s". But not going to use expense/deposit combi.',
$account->type,
$account->id,
$iban
)
);
app('log')->debug('Firefly III will have to make the correct decision.');
app('log')->debug(sprintf('[a] Found %s account #%d based on ID "%s"', $account->type, $account->id, $value));
return $account;
return null;
}
app('log')->debug(sprintf('[a] Found %s account #%d based on IBAN "%s"', $account->type, $account->id, $iban));
// to fix issue #4293, Firefly III will ignore this account if it's an expense or a revenue account.
if (in_array($account->type, ['expense', 'revenue'], true)) {
app('log')->debug('[a] Data importer will pretend not to have found anything. Firefly III must handle the IBAN.');
return null;
}
return $account;
}
app('log')->debug('Found NOTHING in findById.');
if (2 === count($response)) {
app('log')->debug('Found 2 results, Firefly III will have to make the correct decision.');
return null;
}
app('log')->debug(sprintf('Found %d result(s), Firefly III will have to make the correct decision.', count($response)));
return null;
}
/**
* @param string $iban
* @param string $transactionType
* @param string $value
*
* @return Account|null
* @throws ImporterErrorException
*/
private function findByIban(string $iban, string $transactionType): ?Account
private function findById(string $value): ?Account
{
app('log')->debug(sprintf('Going to search account with IBAN "%s"', $iban));
app('log')->debug(sprintf('Going to search account with ID "%s"', $value));
$url = SecretManager::getBaseUrl();
$token = SecretManager::getAccessToken();
$request = new GetSearchAccountRequest($url, $token);
$request->setVerify(config('importer.connection.verify'));
$request->setTimeOut(config('importer.connection.timeout'));
$request->setField('iban');
$request->setQuery($iban);
$request->setField('id');
$request->setQuery($value);
/** @var GetAccountsResponse $response */
try {
$response = $request->get();
} catch (GrumpyApiHttpException $e) {
throw new ImporterErrorException($e->getMessage());
}
if (0 === count($response)) {
app('log')->debug('Found NOTHING in findbyiban.');
return null;
}
if (1 === count($response)) {
/** @var Account $account */
try {
@ -490,46 +285,63 @@ class Accounts extends AbstractTask
} catch (ApiException $e) {
throw new ImporterErrorException($e->getMessage());
}
// catch impossible combination "expense" with "deposit"
if ('expense' === $account->type && 'deposit' === $transactionType) {
app('log')->debug(
sprintf(
'Out of cheese error (IBAN). Found Found %s account #%d based on IBAN "%s". But not going to use expense/deposit combi.',
$account->type,
$account->id,
$iban
)
);
app('log')->debug('Firefly III will have to make the correct decision.');
return null;
}
app('log')->debug(sprintf('[a] Found %s account #%d based on IBAN "%s"', $account->type, $account->id, $iban));
app('log')->debug(sprintf('[a] Found %s account #%d based on ID "%s"', $account->type, $account->id, $value));
// to fix issue #4293, Firefly III will ignore this account if it's an expense or a revenue account.
if (in_array($account->type, ['expense', 'revenue'], true)) {
app('log')->debug('[a] Data importer will pretend not to have found anything. Firefly III must handle the IBAN.');
return $account;
}
return null;
}
app('log')->debug('Found NOTHING in findById.');
return null;
}
return $account;
/**
* @param string $name
*
* @return Account|null
* @throws ImporterErrorException
*/
private function findByName(string $name): ?Account
{
app('log')->debug(sprintf('Going to search account with name "%s"', $name));
$url = SecretManager::getBaseUrl();
$token = SecretManager::getAccessToken();
$request = new GetSearchAccountRequest($url, $token);
$request->setVerify(config('importer.connection.verify'));
$request->setTimeOut(config('importer.connection.timeout'));
$request->setField('name');
$request->setQuery($name);
/** @var GetAccountsResponse $response */
try {
$response = $request->get();
} catch (GrumpyApiHttpException $e) {
throw new ImporterErrorException($e->getMessage());
}
if (2 === count($response)) {
app('log')->debug('Found 2 results, Firefly III will have to make the correct decision.');
if (0 === count($response)) {
app('log')->debug('Found NOTHING in findbyname.');
return null;
}
app('log')->debug(sprintf('Found %d result(s), Firefly III will have to make the correct decision.', count($response)));
/** @var Account $account */
foreach ($response as $account) {
if (in_array($account->type, [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true)
&& strtolower($account->name) === strtolower($name)) {
app('log')->debug(sprintf('[b] Found "%s" account #%d based on name "%s"', $account->type, $account->id, $name));
return $account;
}
}
app('log')->debug(
sprintf('Found %d account(s) searching for "%s" but not going to use them. Firefly III must handle the values.', count($response), $name)
);
return null;
}
/**
* @param string $accountNumber
* @param string $transactionType
* @param string $accountNumber
* @param string $transactionType
*
* @return Account|null
* @throws ImporterErrorException
@ -601,80 +413,272 @@ class Accounts extends AbstractTask
}
/**
* @param string $name
* @param array $transaction
*
* @return Account|null
* @throws ImporterErrorException
* @return array
*/
private function findByName(string $name): ?Account
private function getDestinationArray(array $transaction): array
{
app('log')->debug(sprintf('Going to search account with name "%s"', $name));
$url = SecretManager::getBaseUrl();
$token = SecretManager::getAccessToken();
$request = new GetSearchAccountRequest($url, $token);
$request->setVerify(config('importer.connection.verify'));
$request->setTimeOut(config('importer.connection.timeout'));
$request->setField('name');
$request->setQuery($name);
/** @var GetAccountsResponse $response */
try {
$response = $request->get();
} catch (GrumpyApiHttpException $e) {
throw new ImporterErrorException($e->getMessage());
}
if (0 === count($response)) {
app('log')->debug('Found NOTHING in findbyname.');
return null;
}
/** @var Account $account */
foreach ($response as $account) {
if (in_array($account->type, [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true)
&& strtolower($account->name) === strtolower($name)) {
app('log')->debug(sprintf('[b] Found "%s" account #%d based on name "%s"', $account->type, $account->id, $name));
return [
'transaction_type' => $transaction['type'],
'id' => $transaction['destination_id'],
'name' => $transaction['destination_name'],
'iban' => $transaction['destination_iban'] ?? null,
'number' => $transaction['destination_number'] ?? null,
'bic' => $transaction['destination_bic'] ?? null,
'direction' => 'destination',
];
}
return $account;
}
}
app('log')->debug(
sprintf('Found %d account(s) searching for "%s" but not going to use them. Firefly III must handle the values.', count($response), $name)
);
/**
* @param array $transaction
*
* @return array
*/
private function getSourceArray(array $transaction): array
{
return [
'transaction_type' => $transaction['type'],
'id' => $transaction['source_id'],
'name' => $transaction['source_name'],
'iban' => $transaction['source_iban'] ?? null,
'number' => $transaction['source_number'] ?? null,
'bic' => $transaction['source_bic'] ?? null,
'direction' => 'source',
];
}
return null;
/**
* Basic check for currency info.
*
* @param array $transaction
*
* @return bool
*/
private function hasAllAmountInformation(array $transaction): bool
{
return
array_key_exists('amount', $transaction)
&& array_key_exists('foreign_amount', $transaction)
&& (array_key_exists('foreign_currency_code', $transaction) || array_key_exists('foreign_currency_id', $transaction))
&& (array_key_exists('currency_code', $transaction) || array_key_exists('currency_id', $transaction));
}
/**
* @param array $transaction
* @param array $source
* @param array $transaction
*
* @return array
* @return bool
*/
private function setSource(array $transaction, array $source): array
private function hasAllCurrencies(array $transaction): bool
{
return $this->setTransactionAccount('source', $transaction, $source);
$transaction['foreign_currency_code'] = $transaction['foreign_currency_code'] ?? '';
$transaction['currency_code'] = $transaction['currency_code'] ?? '';
$transaction['amount'] = $transaction['amount'] ?? '';
$transaction['foreign_amount'] = $transaction['foreign_amount'] ?? '';
return '' !== (string)$transaction['currency_code'] && '' !== (string)$transaction['foreign_currency_code']
&& '' !== (string)$transaction['amount']
&& '' !== (string)$transaction['foreign_amount'];
}
/**
* @param string $direction
* @param array $transaction
* @param array $account
* @param array $transaction
*
* @return array
* @throws ImporterErrorException
*/
private function setTransactionAccount(string $direction, array $transaction, array $account): array
private function processTransaction(array $transaction): array
{
$transaction[sprintf('%s_id', $direction)] = $account['id'];
$transaction[sprintf('%s_name', $direction)] = $account['name'];
$transaction[sprintf('%s_iban', $direction)] = $account['iban'];
$transaction[sprintf('%s_number', $direction)] = $account['number'];
$transaction[sprintf('%s_bic', $direction)] = $account['bic'];
app('log')->debug('Now in Accounts::processTransaction()');
/*
* Try to find the source and destination accounts in the transaction.
*
* The source account will default back to the user's submitted default account.
* So when everything fails, the transaction will be an expense for amount X.
*/
$sourceArray = $this->getSourceArray($transaction);
$destArray = $this->getDestinationArray($transaction);
$source = $this->findAccount($sourceArray, $this->account);
$destination = $this->findAccount($destArray, null);
/*
* First, set source and destination in the transaction array:
*/
$transaction = $this->setSource($transaction, $source);
$transaction = $this->setDestination($transaction, $destination);
$transaction['type'] = $this->determineType($source['type'], $destination['type']);
app('log')->debug(sprintf('Transaction type is set to "%s"', $transaction['type']));
app('log')->debug('Source is now:', $source);
app('log')->debug('Destination is now:', $destination);
$amount = (string)$transaction['amount'];
$amount = '' === $amount ? '0' : $amount;
if ('0' === $amount) {
app('log')->error('Amount is ZERO. This will give trouble further down the line.');
}
/*
* If the amount is positive, the transaction is a deposit. We switch Source
* and Destination and see if we can still handle the transaction, but only if the transaction
* isn't already a deposit (it has to be a withdrawal).
*
*/
if ('withdrawal' === $transaction['type'] && 1 === bccomp($amount, '0')) {
// amount is positive
app('log')->debug(sprintf('%s is positive and type is "%s", switch source/destination', $amount, $transaction['type']));
$transaction = $this->setSource($transaction, $destination);
$transaction = $this->setDestination($transaction, $source);
$transaction['type'] = $this->determineType($destination['type'], $source['type']);
app('log')->debug('Source is now:', $destination); // yes this is correct.
app('log')->debug('Destination is now:', $source); // yes this is correct.
// switch variables because processing further ahead will otherwise be messed up:
[$source, $destination] = [$destination, $source];
}
/*
* If the amount is positive and the type is a transfer, switch accounts around.
*/
if ('transfer' === $transaction['type'] && 1 === bccomp($amount, '0')) {
app('log')->debug('Transaction is a transfer, and amount is positive, will switch accounts.');
$transaction = $this->setSource($transaction, $destination);
$transaction = $this->setDestination($transaction, $source);
app('log')->debug('Source is now:', $destination); // yes this is correct!
app('log')->debug('Destination is now:', $source); // yes this is correct!
// also switch amount and foreign currency amount, if both are present.
// if this data is missing, Firefly III will break later either way.
if ($this->hasAllAmountInformation($transaction)) {
app('log')->debug('This transfer has all necessary (foreign) currency + amount information, so swap these too.');
$transaction = $this->swapCurrencyInformation($transaction);
}
}
/*
* If deposit and amount is positive, do nothing.
*/
if ('deposit' === $transaction['type'] && 1 === bccomp($amount, '0')) {
app('log')->debug('Transaction is a deposit, and amount is positive. Will not change account types.');
}
/*
* If deposit and amount is positive, but the source is not a revenue, fall back to
* some "original-field-name" values (if they exist) and hope for the best.
*/
if (
'deposit' === $transaction['type'] && 1 === bccomp($amount, '0') && 'revenue' !== $source['type'] && '' !== (string)$source['type']
) {
app('log')->warning(
sprintf(
'Transaction is a deposit, and amount is positive, but source is not a revenue ("%s"). Will fall back to original field names.',
$source['type']
)
);
$newSource = [
'id' => null,
'name' => $transaction['original-opposing-name'] ?? '(no name)',
'iban' => $transaction['original-opposing-iban'] ?? null,
'number' => $transaction['original-opposing-number'] ?? null,
'bic' => null,
];
$transaction = $this->setSource($transaction, $newSource);
}
/*
* If amount is negative and type is transfer, make sure accounts are "original".
*/
if ('transfer' === $transaction['type'] && -1 === bccomp($amount, '0')) {
app('log')->debug('Transaction is a transfer, and amount is negative, must not change accounts.');
$transaction = $this->setSource($transaction, $source);
$transaction = $this->setDestination($transaction, $destination);
app('log')->debug('Source is now:', $source);
app('log')->debug('Destination is now:', $destination);
}
/*
* Final check. If the type is "withdrawal" but the destination account found is "revenue"
* we found the wrong one. Just submit the name and hope for the best.
*/
if ('revenue' === $destination['type'] && 'withdrawal' === $transaction['type']) {
app('log')->warning('The found destination account is of type revenue but this is a withdrawal. Out of cheese error.');
app('log')->debug(
sprintf('Data importer will submit name "%s" and IBAN "%s" and let Firefly III sort it out.', $destination['name'], $destination['iban'])
);
$transaction['destination_id'] = null;
$transaction['destination_name'] = $destination['name'];
$transaction['destination_iban'] = $destination['iban'];
}
/*
* Same but for the other way around.
* If type is "deposit" but the source account is an expense account.
* Submit just the name.
*/
if ('expense' === $source['type'] && 'deposit' === $transaction['type']) {
app('log')->warning('The found source account is of type expense but this is a deposit. Out of cheese error.');
app('log')->debug(sprintf('Data importer will submit name "%s" and IBAN "%s" and let Firefly III sort it out.', $source['name'], $source['iban']));
$transaction['source_id'] = null;
$transaction['source_name'] = $source['name'];
$transaction['source_iban'] = $source['iban'];
}
/*
* if new source or destination ID is filled in, drop the other fields:
*/
if (0 !== $transaction['source_id'] && null !== $transaction['source_id']) {
$transaction['source_name'] = null;
$transaction['source_iban'] = null;
$transaction['source_number'] = null;
}
if (0 !== $transaction['destination_id'] && null !== $transaction['destination_id']) {
$transaction['destination_name'] = null;
$transaction['destination_iban'] = null;
$transaction['destination_number'] = null;
}
if ($this->hasAllCurrencies($transaction)) {
app('log')->debug('Final validation of foreign amount and or normal transaction amount');
// withdrawal
if ('withdrawal' === $transaction['type']) {
// currency info must match $source
// so if we can switch them around we will.
if ($transaction['currency_code'] !== $source['currency_code']
&& $transaction['foreign_currency_code'] === $source['currency_code']) {
app('log')->debug('Source account accepts %s, so foreign / native numbers are switched now.');
$amount = $transaction['amount'] ?? '0';
$currency = $transaction['currency_code'] ?? '';
$transaction['amount'] = $transaction['foreign_amount'] ?? '0';
$transaction['currency_code'] = $transaction['foreign_currency_code'] ?? '';
$transaction['foreign_amount'] = $amount;
$transaction['foreign_currency_code'] = $currency;
}
}
// deposit
if ('deposit' === $transaction['type']) {
// currency info must match $destination,
// so if we can switch them around we will.
if ($transaction['currency_code'] !== $destination['currency_code']
&& $transaction['foreign_currency_code'] === $destination['currency_code']) {
app('log')->debug('Destination account accepts %s, so foreign / native numbers are switched now.');
$amount = $transaction['amount'] ?? '0';
$currency = $transaction['currency_code'] ?? '';
$transaction['amount'] = $transaction['foreign_amount'] ?? '0';
$transaction['currency_code'] = $transaction['foreign_currency_code'] ?? '';
$transaction['foreign_amount'] = $amount;
$transaction['foreign_currency_code'] = $currency;
}
}
app('log')->debug('Final validation of foreign amount and or normal transaction amount finished.');
}
return $transaction;
}
/**
* @param array $transaction
* @param array $source
* @param array $transaction
* @param array $source
*
* @return array
*/
@ -684,23 +688,36 @@ class Accounts extends AbstractTask
}
/**
* Basic check for currency info.
* @param array $transaction
* @param array $source
*
* @param array $transaction
* @return array
*/
private function setSource(array $transaction, array $source): array
{
return $this->setTransactionAccount('source', $transaction, $source);
}
/**
* @param string $direction
* @param array $transaction
* @param array $account
*
* @return bool
* @return array
*/
private function hasAllAmountInformation(array $transaction): bool
private function setTransactionAccount(string $direction, array $transaction, array $account): array
{
return
array_key_exists('amount', $transaction)
&& array_key_exists('foreign_amount', $transaction)
&& (array_key_exists('foreign_currency_code', $transaction) || array_key_exists('foreign_currency_id', $transaction))
&& (array_key_exists('currency_code', $transaction) || array_key_exists('currency_id', $transaction));
$transaction[sprintf('%s_id', $direction)] = $account['id'];
$transaction[sprintf('%s_name', $direction)] = $account['name'];
$transaction[sprintf('%s_iban', $direction)] = $account['iban'];
$transaction[sprintf('%s_number', $direction)] = $account['number'];
$transaction[sprintf('%s_bic', $direction)] = $account['bic'];
return $transaction;
}
/**
* @param array $transaction
* @param array $transaction
*
* @return array
*/
@ -732,21 +749,4 @@ class Accounts extends AbstractTask
return $transaction;
}
/**
* @param array $transaction
*
* @return bool
*/
private function hasAllCurrencies(array $transaction): bool
{
$transaction['foreign_currency_code'] = $transaction['foreign_currency_code'] ?? '';
$transaction['currency_code'] = $transaction['currency_code'] ?? '';
$transaction['amount'] = $transaction['amount'] ?? '';
$transaction['foreign_amount'] = $transaction['foreign_amount'] ?? '';
return '' !== (string)$transaction['currency_code'] && '' !== (string)$transaction['foreign_currency_code']
&& '' !== (string)$transaction['amount']
&& '' !== (string)$transaction['foreign_amount'];
}
}

6
app/Services/CSV/Conversion/Task/Amount.php

@ -30,7 +30,7 @@ namespace App\Services\CSV\Conversion\Task;
class Amount extends AbstractTask
{
/**
* @param array $group
* @param array $group
*
* @return array
*/
@ -64,7 +64,7 @@ class Amount extends AbstractTask
}
/**
* @param array $transaction
* @param array $transaction
*
* @return array
*/
@ -139,7 +139,7 @@ class Amount extends AbstractTask
}
/**
* @param string $amount
* @param string $amount
*
* @return bool
*/

4
app/Services/CSV/Conversion/Task/Currency.php

@ -30,7 +30,7 @@ namespace App\Services\CSV\Conversion\Task;
class Currency extends AbstractTask
{
/**
* @param array $group
* @param array $group
*
* @return array
*/
@ -64,7 +64,7 @@ class Currency extends AbstractTask
}
/**
* @param array $transaction
* @param array $transaction
*
* @return array
*/

4
app/Services/CSV/Conversion/Task/EmptyAccounts.php

@ -33,7 +33,7 @@ namespace App\Services\CSV\Conversion\Task;
class EmptyAccounts extends AbstractTask
{
/**
* @param array $group
* @param array $group
*
* @return array
*/
@ -70,7 +70,7 @@ class EmptyAccounts extends AbstractTask
}
/**
* @param array $transaction
* @param array $transaction
*
* @return array
*/

4
app/Services/CSV/Conversion/Task/EmptyDescription.php

@ -30,7 +30,7 @@ namespace App\Services\CSV\Conversion\Task;
class EmptyDescription extends AbstractTask
{
/**
* @param array $group
* @param array $group
*
* @return array
*/
@ -64,7 +64,7 @@ class EmptyDescription extends AbstractTask
}
/**
* @param array $transaction
* @param array $transaction
*
* @return array
*/

4
app/Services/CSV/Conversion/Task/Tags.php

@ -30,7 +30,7 @@ namespace App\Services\CSV\Conversion\Task;
class Tags extends AbstractTask
{
/**
* @param array $group
* @param array $group
*
* @return array
*/
@ -66,7 +66,7 @@ class Tags extends AbstractTask
/**
* Do something with the collected tags.
*
* @param array $transaction
* @param array $transaction
*
* @return array
*/

6
app/Services/CSV/Conversion/Task/TaskInterface.php

@ -33,7 +33,7 @@ use GrumpyDictator\FFIIIApiSupport\Model\TransactionCurrency;
interface TaskInterface
{
/**
* @param array $group
* @param array $group
*
* @return array
*/
@ -54,12 +54,12 @@ interface TaskInterface
public function requiresTransactionCurrency(): bool;
/**
* @param Account $account
* @param Account $account
*/
public function setAccount(Account $account): void;
/**
* @param TransactionCurrency $transactionCurrency
* @param TransactionCurrency $transactionCurrency
*/
public function setTransactionCurrency(TransactionCurrency $transactionCurrency): void;
}

131
app/Services/CSV/Converter/Amount.php

@ -1,4 +1,5 @@
<?php
/*
* Amount.php
* Copyright (c) 2021 james@firefly-iii.org
@ -29,7 +30,7 @@ namespace App\Services\CSV\Converter;
class Amount implements ConverterInterface
{
/**
* @param string $amount
* @param string $amount
*
* @return string
*/
@ -43,7 +44,7 @@ class Amount implements ConverterInterface
}
/**
* @param string $amount
* @param string $amount
*
* @return string
*/
@ -108,7 +109,7 @@ class Amount implements ConverterInterface
app('log')->debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $original, $value));
}
if (str_starts_with($value, '.')) {
$value = '0' . $value;
$value = '0'.$value;
}
if (is_numeric($value)) {
@ -128,59 +129,32 @@ class Amount implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{
}
/**
* Strip amount from weird characters.
*
* @param string $value
*
* @return string
*/
private function stripAmount(string $value): string
{
if (str_starts_with($value, '--')) {
$value = substr($value, 2);
}
// have to strip the € because apparently the Postbank (DE) thinks "1.000,00 €" is a normal way to format a number.
// 2020-12-01 added "EUR" because another German bank doesn't know what a data format is.
// This way of stripping exceptions is unsustainable.
$value = trim((string)str_replace(['€', 'EUR'], '', $value));
$str = preg_replace('/[^\-().,0-9 ]/', '', $value);
$len = strlen($str);
if (str_starts_with($str, '(') && ')' === $str[$len - 1]) {
$str = '-' . substr($str, 1, $len - 2);
}
$str = trim($str);
app('log')->debug(sprintf('Stripped "%s" to "%s"', $value, $str));
return $str;
}
/**
* Helper function to see if the decimal separator is a dot.
* Check if the value has a dot or comma on an alternative place,
* catching strings like ",1" or ".5".
*
* @param string $value
* @param string $value
*
* @return bool
*/
private function decimalIsDot(string $value): bool
private function alternativeDecimalSign(string $value): bool
{
$length = strlen($value);
$decimalPosition = $length - 3;
$length = strlen($value);
$altPosition = $length - 2;
return ($length > 2 && '.' === $value[$decimalPosition]) || ($length > 2 && strpos($value, '.') > $decimalPosition);
return $length > 1 && ('.' === $value[$altPosition] || ',' === $value[$altPosition]);
}
/**
* Helper function to see if the decimal separator is a comma.
*
* @param string $value
* @param string $value
*
* @return bool
*/
@ -202,41 +176,24 @@ class Amount implements ConverterInterface
}
/**
* Check if the value has a dot or comma on an alternative place,
* catching strings like ",1" or ".5".
* Helper function to see if the decimal separator is a dot.
*
* @param string $value
* @param string $value
*
* @return bool
*/
private function alternativeDecimalSign(string $value): bool
{
$length = strlen($value);
$altPosition = $length - 2;
return $length > 1 && ('.' === $value[$altPosition] || ',' === $value[$altPosition]);
}
/**
* Returns the alternative decimal point used, such as a dot or a comma,
* from strings like ",1" or "0.5".
*
* @param string $value
*
* @return string
*/
private function getAlternativeDecimalSign(string $value): string
private function decimalIsDot(string $value): bool
{
$length = strlen($value);
$altPosition = $length - 2;
$length = strlen($value);
$decimalPosition = $length - 3;
return $value[$altPosition];
return ($length > 2 && '.' === $value[$decimalPosition]) || ($length > 2 && strpos($value, '.') > $decimalPosition);
}
/**
* Search from the left for decimal sign.
*
* @param string $value
* @param string $value
*
* @return string|null
*/
@ -254,12 +211,28 @@ class Amount implements ConverterInterface
return $decimal;
}
/**
* Returns the alternative decimal point used, such as a dot or a comma,
* from strings like ",1" or "0.5".
*
* @param string $value
*
* @return string
*/
private function getAlternativeDecimalSign(string $value): string
{
$length = strlen($value);
$altPosition = $length - 2;
return $value[$altPosition];
}
/**
* Replaces other characters like thousand separators with nothing to make the decimal separator the only special
* character in the string.
*
* @param string $decimal
* @param string $value
* @param string $decimal
* @param string $value
*
* @return string
*/
@ -274,4 +247,32 @@ class Amount implements ConverterInterface
/** @noinspection CascadeStringReplacementInspection */
return str_replace(',', '.', $value);
}
/**
* Strip amount from weird characters.
*
* @param string $value
*
* @return string
*/
private function stripAmount(string $value): string
{
if (str_starts_with($value, '--')) {
$value = substr($value, 2);
}
// have to strip the € because apparently the Postbank (DE) thinks "1.000,00 €" is a normal way to format a number.
// 2020-12-01 added "EUR" because another German bank doesn't know what a data format is.
// This way of stripping exceptions is unsustainable.
$value = trim((string)str_replace(['€', 'EUR'], '', $value));
$str = preg_replace('/[^\-().,0-9 ]/', '', $value);
$len = strlen($str);
if (str_starts_with($str, '(') && ')' === $str[$len - 1]) {
$str = '-'.substr($str, 1, $len - 2);
}
$str = trim($str);
app('log')->debug(sprintf('Stripped "%s" to "%s"', $value, $str));
return $str;
}
}

3
app/Services/CSV/Converter/AmountCredit.php

@ -1,4 +1,5 @@
<?php
/*
* AmountCredit.php
* Copyright (c) 2021 james@firefly-iii.org
@ -50,7 +51,7 @@ class AmountCredit implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

3
app/Services/CSV/Converter/AmountDebit.php

@ -1,4 +1,5 @@
<?php
/*
* AmountDebit.php
* Copyright (c) 2021 james@firefly-iii.org
@ -52,7 +53,7 @@ class AmountDebit implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

3
app/Services/CSV/Converter/AmountNegated.php

@ -1,4 +1,5 @@
<?php
/*
* AmountNegated.php
* Copyright (c) 2021 james@firefly-iii.org
@ -47,7 +48,7 @@ class AmountNegated implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

2
app/Services/CSV/Converter/BankDebitCredit.php

@ -62,7 +62,7 @@ class BankDebitCredit implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

2
app/Services/CSV/Converter/CleanId.php

@ -48,7 +48,7 @@ class CleanId implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

2
app/Services/CSV/Converter/CleanInteger.php

@ -45,7 +45,7 @@ class CleanInteger implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

2
app/Services/CSV/Converter/CleanNlString.php

@ -45,7 +45,7 @@ class CleanNlString implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

2
app/Services/CSV/Converter/CleanString.php

@ -48,7 +48,7 @@ class CleanString implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

2
app/Services/CSV/Converter/CleanUrl.php

@ -53,7 +53,7 @@ class CleanUrl implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

3
app/Services/CSV/Converter/ConverterInterface.php

@ -1,4 +1,5 @@
<?php
/*
* ConverterInterface.php
* Copyright (c) 2021 james@firefly-iii.org
@ -41,7 +42,7 @@ interface ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void;
}

10
app/Services/CSV/Converter/ConverterService.php

@ -32,9 +32,9 @@ use UnexpectedValueException;
class ConverterService
{
/**
* @param string $class
* @param mixed $value
* @param string|null $configuration
* @param string $class
* @param mixed $value
* @param string|null $configuration
*
* @return mixed
*/
@ -57,7 +57,7 @@ class ConverterService
}
/**
* @param string $class
* @param string $class
*
* @return bool
*/
@ -69,7 +69,7 @@ class ConverterService
}
/**
* @param string $class
* @param string $class
*
* @return string
*/

6
app/Services/CSV/Converter/Date.php

@ -45,7 +45,7 @@ class Date implements ConverterInterface
{
$this->dateFormat = 'Y-m-d';
$this->dateLocale = 'en';
$this->dateFormatPattern = '/(?:(' . join("|", array_keys(Language::all())) . ')\:)?(.+)/';
$this->dateFormatPattern = '/(?:('.join("|", array_keys(Language::all())).')\:)?(.+)/';
}
/**
@ -87,7 +87,7 @@ class Date implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{
@ -95,7 +95,7 @@ class Date implements ConverterInterface
}
/**
* @param string $format
* @param string $format
*
* @return array
*/

2
app/Services/CSV/Converter/Description.php

@ -45,7 +45,7 @@ class Description implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

87
app/Services/CSV/Converter/Iban.php

@ -31,6 +31,62 @@ use ValueError;
*/
class Iban implements ConverterInterface
{
/**
* @param string $value
*
* @return bool
*/
public static function isValidIban(string $value): bool
{
app('log')->debug(sprintf('isValidIBAN("%s")', $value));
$value = strtoupper(trim(app('steam')->cleanStringAndNewlines($value)));
$value = str_replace("\x20", '', $value);
app('log')->debug(sprintf('Trim: isValidIBAN("%s")', $value));
$search = [' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
$replace = [
'',
'10',
'11',
'12',
'13',
'14',
'15',
'16',
'17',
'18',
'19',
'20',
'21',
'22',
'23',
'24',
'25',
'26',
'27',
'28',
'29',
'30',
'31',
'32',
'33',
'34',
'35',
];
// take
$first = substr($value, 0, 4);
$last = substr($value, 4);
$iban = $last.$first;
$iban = str_replace($search, $replace, $iban);
try {
$checksum = bcmod($iban, '97');
} catch (ValueError $e) {
app('log')->error(sprintf('Bad IBAN: %s', $e->getMessage()));
$checksum = 2;
}
return 1 === (int)$checksum;
}
/**
* Convert a value.
*
@ -56,38 +112,9 @@ class Iban implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{
}
/**
* @param string $value
*
* @return bool
*/
public static function isValidIban(string $value): bool
{
app('log')->debug(sprintf('isValidIBAN("%s")', $value));
$value = strtoupper(trim(app('steam')->cleanStringAndNewlines($value)));
$value = str_replace("\x20", '', $value);
app('log')->debug(sprintf('Trim: isValidIBAN("%s")', $value));
$search = [' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
$replace = ['', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31',
'32', '33', '34', '35',];
// take
$first = substr($value, 0, 4);
$last = substr($value, 4);
$iban = $last . $first;
$iban = str_replace($search, $replace, $iban);
try {
$checksum = bcmod($iban, '97');
} catch (ValueError $e) {
app('log')->error(sprintf('Bad IBAN: %s', $e->getMessage()));
$checksum = 2;
}
return 1 === (int)$checksum;
}
}

2
app/Services/CSV/Converter/TagsComma.php

@ -48,7 +48,7 @@ class TagsComma implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

2
app/Services/CSV/Converter/TagsSpace.php

@ -48,7 +48,7 @@ class TagsSpace implements ConverterInterface
/**
* Add extra configuration parameters.
*
* @param string $configuration
* @param string $configuration
*/
public function setConfiguration(string $configuration): void
{

6
app/Services/CSV/File/FileReader.php

@ -36,8 +36,8 @@ use Psr\Container\NotFoundExceptionInterface;
class FileReader
{
/**
* @param string $content
* @param bool $convert
* @param string $content
* @param bool $convert
*
* @return Reader
*/
@ -57,7 +57,7 @@ class FileReader
/**
* Get a CSV file reader and fill it with data from CSV file.
*
* @param bool $convert
* @param bool $convert
*
* @return Reader
* @throws ContainerExceptionInterface

34
app/Services/CSV/Mapper/GetAccounts.php

@ -74,21 +74,6 @@ trait GetAccounts
return array_merge($accounts);
}
/**
* @param GetAccountsResponse $list
*
* @return array
*/
private function toArray(GetAccountsResponse $list): array
{
$return = [];
foreach ($list as $account) {
$return[] = $account;
}
return $return;
}
/**
* Returns a combined list of asset accounts and all liability accounts.
*
@ -154,7 +139,7 @@ trait GetAccounts
/**
* Merge all arrays into <select> ready list.
*
* @param array $accounts
* @param array $accounts
*
* @return array
*/
@ -191,7 +176,7 @@ trait GetAccounts
/**
* Merge all arrays into <select> ready list.
*
* @param array $accounts
* @param array $accounts
*
* @return array
*/
@ -223,4 +208,19 @@ trait GetAccounts
return $result;
}
/**
* @param GetAccountsResponse $list
*
* @return array
*/
private function toArray(GetAccountsResponse $list): array
{
$return = [];
foreach ($list as $account) {
$return[] = $account;
}
return $return;
}
}

18
app/Services/CSV/Mapper/MapperService.php

@ -44,11 +44,11 @@ class MapperService
* Appends the given array with data from the CSV file in the config.
* TODO remove reference to specifics.
*
* @param string $content
* @param string $delimiter
* @param bool $hasHeaders
* @param array $specifics
* @param array $data
* @param string $content
* @param string $delimiter
* @param bool $hasHeaders
* @param array $specifics
* @param array $data
*
* @return array
* @throws ImporterErrorException
@ -149,7 +149,7 @@ class MapperService
foreach ($statements as $statement) { // -> Level B
$entries = $statement->getEntries();
/** @var Entry $entry */
foreach ($entries as $entry) { // -> Level C
foreach ($entries as $entry) { // -> Level C
$count = count($entry->getTransactionDetails()); // count level D entries.
if (0 === $count) {
// TODO Create a single transaction, I guess?
@ -172,10 +172,10 @@ class MapperService
foreach (array_keys($mappableFields) as $title) {
if (array_key_exists($title, $data)) {
if(0 !== $splits) {
for($index = 0; $index < $splits; $index++) {
if (0 !== $splits) {
for ($index = 0; $index < $splits; $index++) {
$value = $transaction->getFieldByIndex($title, $index);
if('' !== $value) {
if ('' !== $value) {
$data[$title]['values'][] = $value;
}
}

27
app/Services/CSV/Roles/RoleService.php

@ -50,8 +50,8 @@ class RoleService
public const EXAMPLE_LENGTH = 26;
/**
* @param string $content
* @param Configuration $configuration
* @param string $content
* @param Configuration $configuration
*
* @return array
* @throws InvalidArgument
@ -100,7 +100,6 @@ class RoleService
for ($i = 0; $i < $count; $i++) {
$headers[] = sprintf('Column #%d', $i + 1);
}
// @codeCoverageIgnoreStart
} catch (Exception $e) {
app('log')->error($e->getMessage());
@ -112,8 +111,8 @@ class RoleService
}
/**
* @param string $content
* @param Configuration $configuration
* @param string $content
* @param Configuration $configuration
*
* @return array
* @throws Exception
@ -185,7 +184,7 @@ class RoleService
$camtReader = new CamtReader(Config::getDefault());
$camtMessage = $camtReader->readString(StorageService::getContent(session()->get(Constants::UPLOAD_DATA_FILE))); // -> Level A
$transactions = [];
$examples = [];
$examples = [];
$fieldNames = array_keys(config('camt.fields'));
foreach ($fieldNames as $name) {
$examples[$name] = [];
@ -199,7 +198,7 @@ class RoleService
/** @var CamtStatement $statement */
foreach ($statements as $statement) { // -> Level B
$entries = $statement->getEntries();
foreach ($entries as $entry) { // -> Level C
foreach ($entries as $entry) { // -> Level C
$count = count($entry->getTransactionDetails()); // count level D entries.
if (0 === $count) {
// TODO Create a single transaction, I guess?
@ -219,22 +218,22 @@ class RoleService
break;
}
foreach ($fieldNames as $name) {
if(array_key_exists($name, $examples)) { // there is at least one example, so we can check how many
if(count($examples[$name]) > 5) { // there are already five examples, so jump to next field
if (array_key_exists($name, $examples)) { // there is at least one example, so we can check how many
if (count($examples[$name]) > 5) { // there are already five examples, so jump to next field
continue;
}
} // otherwise, try to fetch data
$splits = $transaction->countSplits();
if(0 === $splits) {
if (0 === $splits) {
$value = $transaction->getFieldByIndex($name, 0);
if('' !== $value) {
if ('' !== $value) {
$examples[$name][] = $value;
}
}
if($splits > 0) {
for($index = 0; $index < $splits; $index++) {
if ($splits > 0) {
for ($index = 0; $index < $splits; $index++) {
$value = $transaction->getFieldByIndex($name, $index);
if('' !== $value) {
if ('' !== $value) {
$examples[$name][] = $value;
}
}

32
app/Services/Camt/Conversion/RoutineManager.php

@ -112,6 +112,22 @@ class RoutineManager implements RoutineManagerInterface
$this->transactionMapper = new TransactionMapper($this->configuration);
}
/**
* @param string $content
*/
public function setContent(string $content): void
{
$this->content = $content;
}
/**
* @param bool $forceCli
*/
public function setForceCli(bool $forceCli): void
{
$this->forceCli = $forceCli;
}
/**
* @inheritDoc
* @return array
@ -160,20 +176,4 @@ class RoutineManager implements RoutineManagerInterface
return $camtMessage;
}
/**
* @param string $content
*/
public function setContent(string $content): void
{
$this->content = $content;
}
/**
* @param bool $forceCli
*/
public function setForceCli(bool $forceCli): void
{
$this->forceCli = $forceCli;
}
}

13
app/Services/Camt/Conversion/TransactionConverter.php

@ -11,7 +11,7 @@ class TransactionConverter
private Configuration $configuration;
/**
* @param Configuration $configuration
* @param Configuration $configuration
*/
public function __construct(Configuration $configuration)
{
@ -19,7 +19,7 @@ class TransactionConverter
}
/**
* @param array $transactions
* @param array $transactions
*
* @return array
* @throws ImporterErrorException
@ -38,7 +38,7 @@ class TransactionConverter
}
/**
* @param Transaction $transaction
* @param Transaction $transaction
*
* @return array
* @throws ImporterErrorException
@ -74,15 +74,14 @@ class TransactionConverter
$value = trim($transaction->getFieldByIndex($field, $i));
if ('' !== $value) {
$current[$role] = $current[$role] ?? [
'data' => [],
'mapping' => []
'data' => [],
'mapping' => [],
];
if(array_key_exists($field, $mapping)) {
if (array_key_exists($field, $mapping)) {
$current[$role]['mapping'] = array_merge($mapping[$field], $current[$role]['mapping']);
}
$current[$role]['data'][] = $value;
$current[$role]['data'] = array_unique($current[$role]['data']);
}
}
$result['transactions'][] = $current;

4
app/Services/Camt/Conversion/TransactionExtractor.php

@ -12,7 +12,7 @@ class TransactionExtractor
private Configuration $configuration;
/**
* @param Configuration $configuration
* @param Configuration $configuration
*/
public function __construct(Configuration $configuration)
{
@ -27,7 +27,7 @@ class TransactionExtractor
/** @var CamtStatement $statement */
foreach ($statements as $statement) { // -> Level B
$entries = $statement->getEntries();
foreach ($entries as $entry) { // -> Level C
foreach ($entries as $entry) { // -> Level C
$count = count($entry->getTransactionDetails()); // count level D entries.
if (0 === $count) {
// TODO Create a single transaction, I guess?

286
app/Services/Camt/Conversion/TransactionMapper.php

@ -14,9 +14,9 @@ class TransactionMapper
{
use GetAccounts;
private Configuration $configuration;
private array $allAccounts;
private array $accountIdentificationSuffixes;
private array $allAccounts;
private Configuration $configuration;
/**
* @param Configuration $configuration
@ -48,6 +48,146 @@ class TransactionMapper
return $result;
}
/**
* @param array $current
*
* @return string
*/
private function determineTransactionType(array $current)
{
$directions = ['source', 'destination'];
foreach ($directions as $direction) {
$accountType[$direction] = null;
foreach ($this->accountIdentificationSuffixes as $accountIdentificationSuffix) {
// try to find destination account
if (array_key_exists($direction.'_'.$accountIdentificationSuffix, $current)) {
$fieldName = $direction.'_'.$accountIdentificationSuffix;
$accountType[$direction] = $this->getAccountType($accountIdentificationSuffix, $current[$fieldName]);
}
}
}
// TODO catch all cases according lines 281 - 285 and https://docs.firefly-iii.org/firefly-iii/financial-concepts/transactions/#:~:text=In%20Firefly%20III%2C%20a%20transaction,slightly%20different%20from%20one%20another.
switch (true) {
case $accountType['source'] === 'asset' && $accountType['destination'] === 'expense':
case $accountType['source'] === 'asset' && $accountType['destination'] === null:
return "withdrawal"; // line 281
case $accountType['source'] === 'revenue' && $accountType['destination'] === 'asset':
case $accountType['source'] === null && $accountType['destination'] === 'asset':
return "deposit"; // line 282
case $accountType['source'] === 'transfer' && $accountType['destination'] === 'transfer':
return "transfer"; // line 283 / 284
default:
app('log')->error(
sprintf(
'Invalid transaction: source = "%s", destination = "%s"',
$accountType['source'] ?: '<null>',
$accountType['destination'] ?: '<null>'
)
); // 285
}
}
private function getAccountId($direction, $current)
{
foreach ($this->accountIdentificationSuffixes as $accountIdentificationSuffix) {
$field = $direction.'_'.$accountIdentificationSuffix;
if (array_key_exists($field, $current)) {
// there is a value...
foreach ($this->allAccounts as $account) {
// so we check all accounts for a match
if ($current[$field] == $account->$accountIdentificationSuffix) {
// we have a match
app('log')->warning(sprintf('Just mapped account "%s"', $account->id));
return $account->id;
}
}
//app('log')->warning(sprintf('Unable to map an account for "%s"',$current[$field]));
}
//app('log')->warning(sprintf('There is no field for "%s" in the transaction',$direction));
}
}
private function getAccountType($fieldName, $fieldValue)
{
$accountType = null;
foreach ($this->allAccounts as $account) {
if ($account->$fieldName == $fieldValue) {
$accountType = $account->type;
}
}
return $accountType;
}
/**
* This function takes the value in $data['data'], which is for example the account
* name or the account IBAN. It will check if there is a mapping for this value, mapping for example
* the value "ShrtBankName" to "Short Bank Name".
*
* If there is a mapping the value will be replaced. If there is not, no replacement will take place.
* Either way, the new value will be placed in the correct place in $current.
*
* Example results:
* source_iban = something
* destination_number = 12345
* source_id = 5
*
* @param array $current
* @param string $fieldName
* @param string $direction
* @param array $data
*
* @return array
*/
private function mapAccount(array $current, string $fieldName, string $direction, array $data): array
{
// bravely assume there's just one value in the array:
$fieldValue = join('', $data['data']);
// replace with mapping
if (array_key_exists($fieldValue, $data['mapping'])) {
$key = sprintf('%s_id', $direction);
$current[$key] = $data['mapping'][$fieldValue];
}
// leave original value
if (!array_key_exists($fieldValue, $data['mapping'])) {
// $direction is either 'source' or 'destination'
// $fieldName is 'id', 'iban','name' or 'number'
$key = sprintf('%s_%s', $direction, $fieldName);
$current[$key] = $fieldValue;
}
return $current;
}
/**
* @param mixed $current
* @param string $type
* @param array $data
*
* @return array
*/
private function mapCurrency(mixed $current, string $type, array $data): array
{
$code = join('', $data['data']);
// replace with mapping
if (array_key_exists($code, $data['mapping'])) {
$key = sprintf('%s_id', $type);
$current[$key] = $data['mapping'][$code];
}
// leave original IBAN
if (!array_key_exists($code, $data['mapping'])) {
$key = sprintf('%s_code', $type);
$current[$key] = $code;
}
return $current;
}
/**
* @param array $transaction
*
@ -132,7 +272,7 @@ class TransactionMapper
break;
case 'description': // TODO think about a config value to use both values from level C and D
$current['description'] = $current['description'] ?? '';
$addition = '';
$addition = '';
if ('group' === $group_handling || 'split' === $group_handling) {
// use first description
$addition = $data['data'][0];
@ -178,73 +318,6 @@ class TransactionMapper
return $result;
}
/**
* This function takes the value in $data['data'], which is for example the account
* name or the account IBAN. It will check if there is a mapping for this value, mapping for example
* the value "ShrtBankName" to "Short Bank Name".
*
* If there is a mapping the value will be replaced. If there is not, no replacement will take place.
* Either way, the new value will be placed in the correct place in $current.
*
* Example results:
* source_iban = something
* destination_number = 12345
* source_id = 5
*
* @param array $current
* @param string $fieldName
* @param string $direction
* @param array $data
*
* @return array
*/
private function mapAccount(array $current, string $fieldName, string $direction, array $data): array
{
// bravely assume there's just one value in the array:
$fieldValue = join('', $data['data']);
// replace with mapping
if (array_key_exists($fieldValue, $data['mapping'])) {
$key = sprintf('%s_id', $direction);
$current[$key] = $data['mapping'][$fieldValue];
}
// leave original value
if (!array_key_exists($fieldValue, $data['mapping'])) {
// $direction is either 'source' or 'destination'
// $fieldName is 'id', 'iban','name' or 'number'
$key = sprintf('%s_%s', $direction, $fieldName);
$current[$key] = $fieldValue;
}
return $current;
}
/**
* @param mixed $current
* @param string $type
* @param array $data
*
* @return array
*/
private function mapCurrency(mixed $current, string $type, array $data): array
{
$code = join('', $data['data']);
// replace with mapping
if (array_key_exists($code, $data['mapping'])) {
$key = sprintf('%s_id', $type);
$current[$key] = $data['mapping'][$code];
}
// leave original IBAN
if (!array_key_exists($code, $data['mapping'])) {
$key = sprintf('%s_code', $type);
$current[$key] = $code;
}
return $current;
}
/**
* A transaction has a bunch of minimal requirements. This method checks if they are met.
*
@ -392,77 +465,4 @@ class TransactionMapper
return false;
}
/**
* @param array $current
*
* @return string
*/
private function determineTransactionType(array $current)
{
$directions = ['source', 'destination'];
foreach ($directions as $direction) {
$accountType[$direction] = null;
foreach ($this->accountIdentificationSuffixes as $accountIdentificationSuffix) {
// try to find destination account
if (array_key_exists($direction.'_'.$accountIdentificationSuffix, $current)) {
$fieldName = $direction.'_'.$accountIdentificationSuffix;
$accountType[$direction] = $this->getAccountType($accountIdentificationSuffix, $current[$fieldName]);
}
}
}
// TODO catch all cases according lines 281 - 285 and https://docs.firefly-iii.org/firefly-iii/financial-concepts/transactions/#:~:text=In%20Firefly%20III%2C%20a%20transaction,slightly%20different%20from%20one%20another.
switch (true) {
case $accountType['source'] === 'asset' && $accountType['destination'] === 'expense':
case $accountType['source'] === 'asset' && $accountType['destination'] === null:
return "withdrawal"; // line 281
case $accountType['source'] === 'revenue' && $accountType['destination'] === 'asset':
case $accountType['source'] === null && $accountType['destination'] === 'asset':
return "deposit"; // line 282
case $accountType['source'] === 'transfer' && $accountType['destination'] === 'transfer':
return "transfer"; // line 283 / 284
default:
app('log')->error(
sprintf(
'Invalid transaction: source = "%s", destination = "%s"',
$accountType['source'] ?: '<null>',
$accountType['destination'] ?: '<null>'
)
); // 285
}
}
private function getAccountType($fieldName, $fieldValue)
{
$accountType = null;
foreach ($this->allAccounts as $account) {
if ($account->$fieldName == $fieldValue) {
$accountType = $account->type;
}
}
return $accountType;
}
private function getAccountId($direction, $current)
{
foreach ($this->accountIdentificationSuffixes as $accountIdentificationSuffix) {
$field = $direction.'_'.$accountIdentificationSuffix;
if (array_key_exists($field, $current)) {
// there is a value...
foreach ($this->allAccounts as $account) {
// so we check all accounts for a match
if ($current[$field] == $account->$accountIdentificationSuffix) {
// we have a match
app('log')->warning(sprintf('Just mapped account "%s"', $account->id));
return $account->id;
}
}
//app('log')->warning(sprintf('Unable to map an account for "%s"',$current[$field]));
}
//app('log')->warning(sprintf('There is no field for "%s" in the transaction',$direction));
}
}
}

48
app/Services/Camt/Transaction.php

@ -62,6 +62,17 @@ class Transaction
return count($this->levelD);
}
/**
* @param int $index
*
* @return string
*/
public function getAmount(int $index): string
{
// TODO loop level D for the date that belongs to the index
return (string)$this->getDecimalAmount($this->levelC->getAmount());
}
/**
* @param int $index
*
@ -96,18 +107,18 @@ class Transaction
switch ($field) {
default:
// temporary debug message:
// echo sprintf('Unknown field "%s" in getFieldByIndex(%d)', $field, $index);
// echo PHP_EOL;
// exit;
// echo sprintf('Unknown field "%s" in getFieldByIndex(%d)', $field, $index);
// echo PHP_EOL;
// exit;
// end temporary debug message
throw new ImporterErrorException(sprintf('Unknown field "%s" in getFieldByIndex(%d)', $field, $index));
// LEVEL A
// LEVEL A
case 'messageId':
// always the same, since its level A.
return (string)$this->levelA->getGroupHeader()->getMessageId();
// LEVEL B
// LEVEL B
case 'statementId':
// always the same, since its level B.
return (string)$this->levelB->getId();
@ -164,7 +175,7 @@ class Transaction
// always the same, since its level C.
return (string)$this->levelC->getBankTransactionCode()->getDomain()->getFamily()->getSubFamilyCode();
// LEVEL D
// LEVEL D
case 'entryDetailAccountServicerReference':
if (0 === count($this->levelD) || !array_key_exists($index, $this->levelD)) {
return '';
@ -295,6 +306,13 @@ class Transaction
}
}
private function generateAddressLine(Address $address = null)
{
$addressLines = implode(", ", $address->getAddressLines());
return $addressLines;
}
private function getDecimalAmount(?Money $money): string
{
if (null === $money) {
@ -306,18 +324,8 @@ class Transaction
return $moneyDecimalFormatter->format($money);
}
/**
* @param int $index
*
* @return string
*/
public function getAmount(int $index): string
private function getOpposingName(RelatedParty $relatedParty, bool $useEntireAddress = false): string
{
// TODO loop level D for the date that belongs to the index
return (string)$this->getDecimalAmount($this->levelC->getAmount());
}
private function getOpposingName(RelatedParty $relatedParty, bool $useEntireAddress = false): string {
$opposingName = '';
// TODO make depend on configuration
if ('' === (string)$relatedParty->getRelatedPartyType()->getName()) {
@ -336,12 +344,6 @@ class Transaction
return $opposingName;
}
private function generateAddressLine(Address $address = null) {
$addressLines = implode(", ", $address->getAddressLines());
return $addressLines;
}
/**
* @param EntryTransactionDetail $transactionDetail
*/

46
app/Services/Nordigen/Authentication/SecretManager.php

@ -52,17 +52,6 @@ class SecretManager
return request()->cookie(self::NORDIGEN_ID);
}
/**
* Will verify if the user has a Nordigen ID (in a cookie)
* TODO is a cookie the best place?
*
* @return bool
*/
private static function hasId(): bool
{
return '' !== (string)request()->cookie(self::NORDIGEN_ID);
}
/**
* Will return the Nordigen ID. From a cookie if its there, otherwise from configuration.
*
@ -80,39 +69,50 @@ class SecretManager
}
/**
* Will verify if the user has a Nordigen Key (in a cookie)
* Store access token in a cookie.
* TODO is a cookie the best place?
*
* @return bool
* @param string $identifier
*
* @return Cookie
*/
private static function hasKey(): bool
public static function saveId(string $identifier): Cookie
{
return '' !== (string)request()->cookie(self::NORDIGEN_KEY);
return cookie(self::NORDIGEN_ID, $identifier);
}
/**
* Store access token in a cookie.
* TODO is a cookie the best place?
*
* @param string $identifier
* @param string $key
*
* @return Cookie
*/
public static function saveId(string $identifier): Cookie
public static function saveKey(string $key): Cookie
{
return cookie(self::NORDIGEN_ID, $identifier);
return cookie(self::NORDIGEN_KEY, $key);
}
/**
* Store access token in a cookie.
* Will verify if the user has a Nordigen ID (in a cookie)
* TODO is a cookie the best place?
*
* @param string $key
* @return bool
*/
private static function hasId(): bool
{
return '' !== (string)request()->cookie(self::NORDIGEN_ID);
}
/**
* Will verify if the user has a Nordigen Key (in a cookie)
* TODO is a cookie the best place?
*
* @return Cookie
* @return bool
*/
public static function saveKey(string $key): Cookie
private static function hasKey(): bool
{
return cookie(self::NORDIGEN_KEY, $key);
return '' !== (string)request()->cookie(self::NORDIGEN_KEY);
}
}

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

@ -41,7 +41,7 @@ class FilterTransactions
}
/**
* @param array $transactions
* @param array $transactions
*
* @return array
*/

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

@ -86,7 +86,7 @@ class GenerateTransactions
app('log')->debug('Going to collect account information from Nordigen.');
/**
* @var string $nordigenIdentifier
* @var int $account
* @var int $account
*/
foreach ($this->accounts as $nordigenIdentifier => $account) {
app('log')->debug(sprintf('Now at #%d => %s', $account, $nordigenIdentifier));
@ -173,66 +173,7 @@ class GenerateTransactions
}
/**
* @param string $iban
*
* @return string
*/
private function filterSpaces(string $iban): string
{
$search = [
"\u{0001}", // start of heading
"\u{0002}", // start of text
"\u{0003}", // end of text
"\u{0004}", // end of transmission
"\u{0005}", // enquiry
"\u{0006}", // ACK
"\u{0007}", // BEL
"\u{0008}", // backspace
"\u{000E}", // shift out
"\u{000F}", // shift in
"\u{0010}", // data link escape
"\u{0011}", // DC1
"\u{0012}", // DC2
"\u{0013}", // DC3
"\u{0014}", // DC4
"\u{0015}", // NAK
"\u{0016}", // SYN
"\u{0017}", // ETB
"\u{0018}", // CAN
"\u{0019}", // EM
"\u{001A}", // SUB
"\u{001B}", // escape
"\u{001C}", // file separator
"\u{001D}", // group separator
"\u{001E}", // record separator
"\u{001F}", // unit separator
"\u{007F}", // DEL
"\u{00A0}", // non-breaking space
"\u{1680}", // ogham space mark
"\u{180E}", // mongolian vowel separator
"\u{2000}", // en quad
"\u{2001}", // em quad
"\u{2002}", // en space
"\u{2003}", // em space
"\u{2004}", // three-per-em space
"\u{2005}", // four-per-em space
"\u{2006}", // six-per-em space
"\u{2007}", // figure space
"\u{2008}", // punctuation space
"\u{2009}", // thin space
"\u{200A}", // hair space
"\u{200B}", // zero width space
"\u{202F}", // narrow no-break space
"\u{3000}", // ideographic space
"\u{FEFF}", // zero width no -break space
"\x20", // plain old normal space
];
return str_replace($search, '', $iban);
}
/**
* @param array $transactions
* @param array $transactions
*
* @return array
*/
@ -242,13 +183,13 @@ class GenerateTransactions
$return = [];
/**
* @var string $accountId
* @var array $entries
* @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 int $index
* @var Transaction $entry
*/
foreach ($entries as $index => $entry) {
@ -264,114 +205,18 @@ class GenerateTransactions
}
/**
* TODO function is way too complex.
*
* @param string $accountId
* @param Transaction $entry
*
* @return array
* @throws ImporterHttpException
*/
private function generateTransaction(string $accountId, Transaction $entry): array
{
app('log')->debug(sprintf('Nordigen transaction: "%s" with amount %s %s', $entry->getDescription(), $entry->currencyCode, $entry->transactionAmount));
$return = [
'apply_rules' => $this->configuration->isRules(),
'error_if_duplicate_hash' => $this->configuration->isIgnoreDuplicateTransactions(),
'transactions' => [],
];
$valueDate = $entry->getValueDate();
$transaction = [
'type' => 'withdrawal',
'date' => $entry->getDate()->format('Y-m-d'),
'datetime' => $entry->getDate()->toW3cString(),
'amount' => $entry->transactionAmount,
'description' => $entry->getDescription(),
'payment_date' => is_null($valueDate) ? '' : $valueDate->format('Y-m-d'),
'order' => 0,
'currency_code' => $entry->currencyCode,
'tags' => [],
'category_name' => null,
'category_id' => null,
'notes' => $entry->getNotes(),
'external_id' => $entry->transactionId,
'internal_reference' => $entry->accountIdentifier,
];
if (1 === bccomp($entry->transactionAmount, '0')) {
app('log')->debug('Amount is positive: assume transfer or deposit.');
$transaction = $this->appendPositiveAmountInfo($accountId, $transaction, $entry);
}
if (-1 === bccomp($entry->transactionAmount, '0')) {
app('log')->debug('Amount is negative: assume transfer or withdrawal.');
$transaction = $this->appendNegativeAmountInfo($accountId, $transaction, $entry);
}
$return['transactions'][] = $transaction;
app('log')->debug(sprintf('Parsed Nordigen transaction "%s".', $entry->transactionId), $transaction);
return $return;
}
/**
* Handle transaction information when the amount is positive, and this is probably a deposit or a transfer.
*
* @param string $accountId
* @param array $transaction
* @param Transaction $entry
*
* @return array
* @throws ImporterHttpException
* @param Configuration $configuration
*/
private function appendPositiveAmountInfo(string $accountId, array $transaction, Transaction $entry): array
public function setConfiguration(Configuration $configuration): void
{
// amount is positive: deposit or transfer. Nordigen account is the destination
$transaction['type'] = 'deposit';
$transaction['amount'] = $entry->transactionAmount;
// destination is a Nordigen account (has to be!)
$transaction['destination_id'] = (int)$this->accounts[$accountId];
app('log')->debug(sprintf('Destination ID is now #%d, which should be a Firefly III asset account.', $transaction['destination_id']));
// append source iban and number (if present)
$transaction = $this->appendAccountFields($transaction, $entry, 'source');
// TODO clean up mapping
$mappedId = null;
if (isset($transaction['source_name'])) {
app('log')->debug(sprintf('Check if "%s" is mapped to an account by the user.', $transaction['source_name']));
$mappedId = $this->getMappedAccountId($transaction['source_name']);
}
if (null === $mappedId) {
app('log')->debug('Its not mapped by the user.');
}
if (null !== $mappedId && 0 !== $mappedId) {
app('log')->debug(sprintf('Account name "%s" is mapped to Firefly III account ID "%d"', $transaction['source_name'], $mappedId));
$mappedType = $this->getMappedAccountType($mappedId);
$originalSourceName = $transaction['source_name'];
$transaction['source_id'] = $mappedId;
// catch error here:
try {
$transaction['type'] = $this->getTransactionType($mappedType, 'asset');
app('log')->debug(sprintf('Transaction type seems to be %s', $transaction['type']));
} catch (ImporterErrorException $e) {
app('log')->error($e->getMessage());
app('log')->info('Will not use mapped ID, Firefly III account is of the wrong type.');
unset($transaction['source_id']);
$transaction['source_name'] = $originalSourceName;
}
}
return $transaction;
$this->configuration = $configuration;
$this->accounts = $configuration->getAccounts();
}
/**
* @param array $transaction
* @param Transaction $entry
* @param string $direction
* @param array $transaction
* @param Transaction $entry
* @param string $direction
*
* @return array
*/
@ -469,47 +314,219 @@ class GenerateTransactions
}
/**
* @param string $name
* Handle transaction information when the amount is negative, and this is probably a withdrawal or a transfer.
*
* @return int|null
* @param string $accountId
* @param array $transaction
* @param Transaction $entry
*
* @return array
* @throws ImporterHttpException
*/
private function getMappedAccountId(string $name): ?int
private function appendNegativeAmountInfo(string $accountId, array $transaction, Transaction $entry): array
{
if (isset($this->configuration->getMapping()['accounts'][$name])) {
return (int)$this->configuration->getMapping()['accounts'][$name];
$transaction['amount'] = bcmul($entry->transactionAmount, '-1');
$transaction['source_id'] = (int)$this->accounts[$accountId]; // TODO entry may not exist, then what?
// append source iban and number (if present)
$transaction = $this->appendAccountFields($transaction, $entry, 'destination');
$mappedId = null;
if (isset($transaction['destination_name'])) {
app('log')->debug(sprintf('Check if "%s" is mapped to an account by the user.', $transaction['destination_name']));
$mappedId = $this->getMappedAccountId($transaction['destination_name']);
}
if (null === $mappedId) {
app('log')->debug('Its not mapped by the user.');
}
return null;
if (null !== $mappedId && 0 !== $mappedId) {
app('log')->debug(sprintf('Account name "%s" is mapped to Firefly III account ID "%d"', $transaction['destination_name'], $mappedId));
$mappedType = $this->getMappedAccountType($mappedId);
$originalDestName = $transaction['destination_name'];
$transaction['destination_id'] = $mappedId;
// catch error here:
try {
$transaction['type'] = $this->getTransactionType('asset', $mappedType);
app('log')->debug(sprintf('Transaction type seems to be %s', $transaction['type']));
} catch (ImporterErrorException $e) {
app('log')->error($e->getMessage());
app('log')->info('Will not use mapped ID, Firefly III account is of the wrong type.');
unset($transaction['destination_id']);
$transaction['destination_name'] = $originalDestName;
}
}
return $transaction;
}
/**
* TODO Method "getAccountTypes" does not exist and I'm not sure what it is supposed to do.
* @param int $mappedId
* Handle transaction information when the amount is positive, and this is probably a deposit or a transfer.
*
* @param string $accountId
* @param array $transaction
* @param Transaction $entry
*
* @return array
* @throws ImporterHttpException
*/
private function appendPositiveAmountInfo(string $accountId, array $transaction, Transaction $entry): array
{
// amount is positive: deposit or transfer. Nordigen account is the destination
$transaction['type'] = 'deposit';
$transaction['amount'] = $entry->transactionAmount;
// destination is a Nordigen account (has to be!)
$transaction['destination_id'] = (int)$this->accounts[$accountId];
app('log')->debug(sprintf('Destination ID is now #%d, which should be a Firefly III asset account.', $transaction['destination_id']));
// append source iban and number (if present)
$transaction = $this->appendAccountFields($transaction, $entry, 'source');
// TODO clean up mapping
$mappedId = null;
if (isset($transaction['source_name'])) {
app('log')->debug(sprintf('Check if "%s" is mapped to an account by the user.', $transaction['source_name']));
$mappedId = $this->getMappedAccountId($transaction['source_name']);
}
if (null === $mappedId) {
app('log')->debug('Its not mapped by the user.');
}
if (null !== $mappedId && 0 !== $mappedId) {
app('log')->debug(sprintf('Account name "%s" is mapped to Firefly III account ID "%d"', $transaction['source_name'], $mappedId));
$mappedType = $this->getMappedAccountType($mappedId);
$originalSourceName = $transaction['source_name'];
$transaction['source_id'] = $mappedId;
// catch error here:
try {
$transaction['type'] = $this->getTransactionType($mappedType, 'asset');
app('log')->debug(sprintf('Transaction type seems to be %s', $transaction['type']));
} catch (ImporterErrorException $e) {
app('log')->error($e->getMessage());
app('log')->info('Will not use mapped ID, Firefly III account is of the wrong type.');
unset($transaction['source_id']);
$transaction['source_name'] = $originalSourceName;
}
}
return $transaction;
}
/**
* @param string $iban
*
* @return string
*/
private function filterSpaces(string $iban): string
{
$search = [
"\u{0001}", // start of heading
"\u{0002}", // start of text
"\u{0003}", // end of text
"\u{0004}", // end of transmission
"\u{0005}", // enquiry
"\u{0006}", // ACK
"\u{0007}", // BEL
"\u{0008}", // backspace
"\u{000E}", // shift out
"\u{000F}", // shift in
"\u{0010}", // data link escape
"\u{0011}", // DC1
"\u{0012}", // DC2
"\u{0013}", // DC3
"\u{0014}", // DC4
"\u{0015}", // NAK
"\u{0016}", // SYN
"\u{0017}", // ETB
"\u{0018}", // CAN
"\u{0019}", // EM
"\u{001A}", // SUB
"\u{001B}", // escape
"\u{001C}", // file separator
"\u{001D}", // group separator
"\u{001E}", // record separator
"\u{001F}", // unit separator
"\u{007F}", // DEL
"\u{00A0}", // non-breaking space
"\u{1680}", // ogham space mark
"\u{180E}", // mongolian vowel separator
"\u{2000}", // en quad
"\u{2001}", // em quad
"\u{2002}", // en space
"\u{2003}", // em space
"\u{2004}", // three-per-em space
"\u{2005}", // four-per-em space
"\u{2006}", // six-per-em space
"\u{2007}", // figure space
"\u{2008}", // punctuation space
"\u{2009}", // thin space
"\u{200A}", // hair space
"\u{200B}", // zero width space
"\u{202F}", // narrow no-break space
"\u{3000}", // ideographic space
"\u{FEFF}", // zero width no -break space
"\x20", // plain old normal space
];
return str_replace($search, '', $iban);
}
/**
* TODO function is way too complex.
*
* @param string $accountId
* @param Transaction $entry
*
* @return array
* @throws ImporterHttpException
*/
private function getMappedAccountType(int $mappedId): string
private function generateTransaction(string $accountId, Transaction $entry): array
{
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);
app('log')->debug(sprintf('Nordigen transaction: "%s" with amount %s %s', $entry->getDescription(), $entry->currencyCode, $entry->transactionAmount));
app('log')->debug(sprintf('Account type for Firefly III account #%d is "%s"', $mappedId, $accountType));
$return = [
'apply_rules' => $this->configuration->isRules(),
'error_if_duplicate_hash' => $this->configuration->isIgnoreDuplicateTransactions(),
'transactions' => [],
];
$valueDate = $entry->getValueDate();
$transaction = [
'type' => 'withdrawal',
'date' => $entry->getDate()->format('Y-m-d'),
'datetime' => $entry->getDate()->toW3cString(),
'amount' => $entry->transactionAmount,
'description' => $entry->getDescription(),
'payment_date' => is_null($valueDate) ? '' : $valueDate->format('Y-m-d'),
'order' => 0,
'currency_code' => $entry->currencyCode,
'tags' => [],
'category_name' => null,
'category_id' => null,
'notes' => $entry->getNotes(),
'external_id' => $entry->transactionId,
'internal_reference' => $entry->accountIdentifier,
];
return $accountType;
if (1 === bccomp($entry->transactionAmount, '0')) {
app('log')->debug('Amount is positive: assume transfer or deposit.');
$transaction = $this->appendPositiveAmountInfo($accountId, $transaction, $entry);
}
$type = $this->configuration->getAccountTypes()[$mappedId] ?? 'expense';
app('log')->debug(sprintf('Account type for Firefly III account #%d is "%s"', $mappedId, $type));
return $type;
if (-1 === bccomp($entry->transactionAmount, '0')) {
app('log')->debug('Amount is negative: assume transfer or withdrawal.');
$transaction = $this->appendNegativeAmountInfo($accountId, $transaction, $entry);
}
$return['transactions'][] = $transaction;
app('log')->debug(sprintf('Parsed Nordigen transaction "%s".', $entry->transactionId), $transaction);
return $return;
}
/**
* @param int $accountId
* @param int $accountId
*
* @return string
* @throws ImporterHttpException
@ -535,83 +552,66 @@ class GenerateTransactions
}
/**
* @param string $source
* @param string $destination
* @param string $name
*
* @return string
* @throws ImporterErrorException
* @return int|null
*/
private function getTransactionType(string $source, string $destination): string
private function getMappedAccountId(string $name): ?int
{
$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';
if (isset($this->configuration->getMapping()['accounts'][$name])) {
return (int)$this->configuration->getMapping()['accounts'][$name];
}
return null;
}
/**
* Handle transaction information when the amount is negative, and this is probably a withdrawal or a transfer.
*
* @param string $accountId
* @param array $transaction
* @param Transaction $entry
* TODO Method "getAccountTypes" does not exist and I'm not sure what it is supposed to do.
* @param int $mappedId
*
* @return array
* @return string
* @throws ImporterHttpException
*/
private function appendNegativeAmountInfo(string $accountId, array $transaction, Transaction $entry): array
private function getMappedAccountType(int $mappedId): string
{
$transaction['amount'] = bcmul($entry->transactionAmount, '-1');
$transaction['source_id'] = (int)$this->accounts[$accountId]; // TODO entry may not exist, then what?
// append source iban and number (if present)
$transaction = $this->appendAccountFields($transaction, $entry, 'destination');
$mappedId = null;
if (isset($transaction['destination_name'])) {
app('log')->debug(sprintf('Check if "%s" is mapped to an account by the user.', $transaction['destination_name']));
$mappedId = $this->getMappedAccountId($transaction['destination_name']);
}
if (null === $mappedId) {
app('log')->debug('Its not mapped by the user.');
}
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);
if (null !== $mappedId && 0 !== $mappedId) {
app('log')->debug(sprintf('Account name "%s" is mapped to Firefly III account ID "%d"', $transaction['destination_name'], $mappedId));
$mappedType = $this->getMappedAccountType($mappedId);
app('log')->debug(sprintf('Account type for Firefly III account #%d is "%s"', $mappedId, $accountType));
$originalDestName = $transaction['destination_name'];
$transaction['destination_id'] = $mappedId;
// catch error here:
try {
$transaction['type'] = $this->getTransactionType('asset', $mappedType);
app('log')->debug(sprintf('Transaction type seems to be %s', $transaction['type']));
} catch (ImporterErrorException $e) {
app('log')->error($e->getMessage());
app('log')->info('Will not use mapped ID, Firefly III account is of the wrong type.');
unset($transaction['destination_id']);
$transaction['destination_name'] = $originalDestName;
}
return $accountType;
}
$type = $this->configuration->getAccountTypes()[$mappedId] ?? 'expense';
app('log')->debug(sprintf('Account type for Firefly III account #%d is "%s"', $mappedId, $type));
return $transaction;
return $type;
}
/**
* @param Configuration $configuration
* @param string $source
* @param string $destination
*
* @return string
* @throws ImporterErrorException
*/
public function setConfiguration(Configuration $configuration): void
private function getTransactionType(string $source, string $destination): string
{
$this->configuration = $configuration;
$this->accounts = $configuration->getAccounts();
$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';
}
}
}

20
app/Services/Nordigen/Conversion/Routine/TransactionProcessor.php

@ -114,7 +114,15 @@ class TransactionProcessor
}
/**
* @param string $identifier
* @param Configuration $configuration
*/
public function setConfiguration(Configuration $configuration): void
{
$this->configuration = $configuration;
}
/**
* @param string $identifier
*/
public function setIdentifier(string $identifier): void
{
@ -122,7 +130,7 @@ class TransactionProcessor
}
/**
* @param GetTransactionsResponse $transactions
* @param GetTransactionsResponse $transactions
*
* @return array
*/
@ -167,12 +175,4 @@ class TransactionProcessor
return $return;
}
/**
* @param Configuration $configuration
*/
public function setConfiguration(Configuration $configuration): void
{
$this->configuration = $configuration;
}
}

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

@ -167,55 +167,55 @@ class RoutineManager implements RoutineManagerInterface
}
/**
* @param array $messages
* @param array $errors
*
* @return void
*/
private function mergeMessages(array $messages): void
private function mergeErrors(array $errors): void
{
foreach ($messages as $index => $array) {
$exists = array_key_exists($index, $this->allMessages);
foreach ($errors as $index => $array) {
$exists = array_key_exists($index, $this->allErrors);
if (true === $exists) {
$this->allMessages[$index] = array_merge($this->allMessages[$index], $array);
$this->allErrors[$index] = array_merge($this->allErrors[$index], $array);
}
if (false === $exists) {
$this->allMessages[$index] = $array;
$this->allErrors[$index] = $array;
}
}
}
/**
* @param array $warnings
* @param array $messages
*
* @return void
*/
private function mergeWarnings(array $warnings): void
private function mergeMessages(array $messages): void
{
foreach ($warnings as $index => $array) {
$exists = array_key_exists($index, $this->allWarnings);
foreach ($messages as $index => $array) {
$exists = array_key_exists($index, $this->allMessages);
if (true === $exists) {
$this->allWarnings[$index] = array_merge($this->allWarnings[$index], $array);
$this->allMessages[$index] = array_merge($this->allMessages[$index], $array);
}
if (false === $exists) {
$this->allWarnings[$index] = $array;
$this->allMessages[$index] = $array;
}
}
}
/**
* @param array $errors
* @param array $warnings
*
* @return void
*/
private function mergeErrors(array $errors): void
private function mergeWarnings(array $warnings): void
{
foreach ($errors as $index => $array) {
$exists = array_key_exists($index, $this->allErrors);
foreach ($warnings as $index => $array) {
$exists = array_key_exists($index, $this->allWarnings);
if (true === $exists) {
$this->allErrors[$index] = array_merge($this->allErrors[$index], $array);
$this->allWarnings[$index] = array_merge($this->allWarnings[$index], $array);
}
if (false === $exists) {
$this->allErrors[$index] = $array;
$this->allWarnings[$index] = $array;
}
}
}

126
app/Services/Nordigen/Model/Account.php

@ -83,7 +83,7 @@ class Account
}
/**
* @param array $array
* @param array $array
*
* @return static
*/
@ -116,7 +116,7 @@ class Account
}
/**
* @param Balance $balance
* @param Balance $balance
*/
public function addBalance(Balance $balance): void
{
@ -132,7 +132,7 @@ class Account
}
/**
* @param string $bban
* @param string $bban
*/
public function setBban(string $bban): void
{
@ -148,7 +148,7 @@ class Account
}
/**
* @param string $bic
* @param string $bic
*/
public function setBic(string $bic): void
{
@ -164,7 +164,7 @@ class Account
}
/**
* @param string $cashAccountType
* @param string $cashAccountType
*/
public function setCashAccountType(string $cashAccountType): void
{
@ -180,7 +180,7 @@ class Account
}
/**
* @param string $currency
* @param string $currency
*/
public function setCurrency(string $currency): void
{
@ -196,13 +196,29 @@ class Account
}
/**
* @param string $details
* @param string $details
*/
public function setDetails(string $details): void
{
$this->details = $details;
}
/**
* @return string
*/
public function getDisplayName(): string
{
return $this->displayName;
}
/**
* @param string $displayName
*/
public function setDisplayName(string $displayName): void
{
$this->displayName = $displayName;
}
/**
* @return string
*/
@ -238,129 +254,113 @@ class Account
/**
* @return string
*/
public function getName(): string
public function getIban(): string
{
return $this->name;
return $this->iban;
}
/**
* @param string $name
* @param string $iban
*/
public function setName(string $name): void
public function setIban(string $iban): void
{
$this->name = $name;
$this->iban = $iban;
}
/**
* @return string
*/
public function getDisplayName(): string
public function getIdentifier(): string
{
return $this->displayName;
return $this->identifier;
}
/**
* @param string $displayName
* @param string $identifier
*/
public function setDisplayName(string $displayName): void
public function setIdentifier(string $identifier): void
{
$this->displayName = $displayName;
$this->identifier = $identifier;
}
/**
* @return string
*/
public function getOwnerName(): string
public function getLinkedAccounts(): string
{
return $this->ownerName;
return $this->linkedAccounts;
}
/**
* @param string $ownerName
* @param string $linkedAccounts
*/
public function setOwnerName(string $ownerName): void
public function setLinkedAccounts(string $linkedAccounts): void
{
$this->ownerName = $ownerName;
$this->linkedAccounts = $linkedAccounts;
}
/**
* @return string
*/
public function getIban(): string
public function getMsisdn(): string
{
return $this->iban;
return $this->msisdn;
}
/**
* @param string $iban
* @param string $msisdn
*/
public function setIban(string $iban): void
public function setMsisdn(string $msisdn): void
{
$this->iban = $iban;
$this->msisdn = $msisdn;
}
/**
* @return string
*/
public function getIdentifier(): string
public function getName(): string
{
return $this->identifier;
return $this->name;
}
/**
* @param string $identifier
* @param string $name
*/
public function setIdentifier(string $identifier): void
public function setName(string $name): void
{
$this->identifier = $identifier;
$this->name = $name;
}
/**
* @return string
* @return array
*/
public function getLinkedAccounts(): string
public function getOwnerAddressUnstructured(): array
{
return $this->linkedAccounts;
return $this->ownerAddressUnstructured;
}
/**
* @param string $linkedAccounts
* @param array $ownerAddressUnstructured
*/
public function setLinkedAccounts(string $linkedAccounts): void
public function setOwnerAddressUnstructured(array $ownerAddressUnstructured): void
{
$this->linkedAccounts = $linkedAccounts;
$this->ownerAddressUnstructured = $ownerAddressUnstructured;
}
/**
* @return string
*/
public function getMsisdn(): string
{
return $this->msisdn;
}
/**
* @param string $msisdn
*/
public function setMsisdn(string $msisdn): void
{
$this->msisdn = $msisdn;
}
/**
* @return array
*/
public function getOwnerAddressUnstructured(): array
public function getOwnerName(): string
{
return $this->ownerAddressUnstructured;
return $this->ownerName;
}
/**
* @param array $ownerAddressUnstructured
* @param string $ownerName
*/
public function setOwnerAddressUnstructured(array $ownerAddressUnstructured): void
public function setOwnerName(string $ownerName): void
{
$this->ownerAddressUnstructured = $ownerAddressUnstructured;
$this->ownerName = $ownerName;
}
/**
@ -372,7 +372,7 @@ class Account
}
/**
* @param string $product
* @param string $product
*/
public function setProduct(string $product): void
{
@ -388,7 +388,7 @@ class Account
}
/**
* @param string $resourceId
* @param string $resourceId
*/
public function setResourceId(string $resourceId): void
{
@ -404,7 +404,7 @@ class Account
}
/**
* @param string $status
* @param string $status
*/
public function setStatus(string $status): void
{
@ -420,7 +420,7 @@ class Account
}
/**
* @param string $usage
* @param string $usage
*/
public function setUsage(string $usage): void
{

2
app/Services/Nordigen/Model/Balance.php

@ -36,7 +36,7 @@ class Balance
public string $type;
/**
* @param array $data
* @param array $data
*
* @return static
*/

2
app/Services/Nordigen/Model/Bank.php

@ -35,7 +35,7 @@ class Bank
public int $transactionTotalDays;
/**
* @param array $array
* @param array $array
*
* @return static
*/

6
app/Services/Nordigen/Model/Country.php

@ -32,15 +32,15 @@ use Illuminate\Support\Collection;
class Country
{
/**
* @param string $code
* @param Collection $banks
* @param string $code
* @param Collection $banks
*/
public function __construct(public string $code, public Collection $banks)
{
}
/**
* @param Bank $bank
* @param Bank $bank
*/
public function addBank(Bank $bank): void
{

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

@ -184,7 +184,7 @@ class Transaction
}
/**
* @param array $array
* @param array $array
*
* @return static
*/

30
app/Services/Nordigen/Request/GetAccountBalanceRequest.php

@ -38,9 +38,9 @@ class GetAccountBalanceRequest extends Request
private string $identifier;
/**
* @param string $url
* @param string $token
* @param string $identifier
* @param string $url
* @param string $token
* @param string $identifier
*/
public function __construct(string $url, string $token, string $identifier)
{
@ -66,34 +66,34 @@ class GetAccountBalanceRequest extends Request
}
/**
* @inheritDoc
* @return string
*/
public function post(): Response
public function getIdentifier(): string
{
// TODO: Implement post() method.
return $this->identifier;
}
/**
* @inheritDoc
* @param string $identifier
*/
public function put(): Response
public function setIdentifier(string $identifier): void
{
// TODO: Implement put() method.
$this->identifier = $identifier;
}
/**
* @return string
* @inheritDoc
*/
public function getIdentifier(): string
public function post(): Response
{
return $this->identifier;
// TODO: Implement post() method.
}
/**
* @param string $identifier
* @inheritDoc
*/
public function setIdentifier(string $identifier): void
public function put(): Response
{
$this->identifier = $identifier;
// TODO: Implement put() method.
}
}

30
app/Services/Nordigen/Request/GetAccountBasicRequest.php

@ -38,9 +38,9 @@ class GetAccountBasicRequest extends Request
private string $identifier;
/**
* @param string $url
* @param string $token
* @param string $identifier
* @param string $url
* @param string $token
* @param string $identifier
*/
public function __construct(string $url, string $token, string $identifier)
{
@ -66,34 +66,34 @@ class GetAccountBasicRequest extends Request
}
/**
* @inheritDoc
* @return string
*/
public function post(): Response
public function getIdentifier(): string
{
// Implement post() method.
return $this->identifier;
}
/**
* @inheritDoc
* @param string $identifier
*/
public function put(): Response
public function setIdentifier(string $identifier): void
{
// Implement put() method.
$this->identifier = $identifier;
}
/**
* @return string
* @inheritDoc
*/
public function getIdentifier(): string
public function post(): Response
{
return $this->identifier;
// Implement post() method.
}
/**
* @param string $identifier
* @inheritDoc
*/
public function setIdentifier(string $identifier): void
public function put(): Response
{
$this->identifier = $identifier;
// Implement put() method.
}
}

30
app/Services/Nordigen/Request/GetAccountInformationRequest.php

@ -37,9 +37,9 @@ class GetAccountInformationRequest extends Request
private string $identifier;
/**
* @param string $url
* @param string $token
* @param string $identifier
* @param string $url
* @param string $token
* @param string $identifier
*/
public function __construct(string $url, string $token, string $identifier)
{
@ -63,34 +63,34 @@ class GetAccountInformationRequest extends Request
}
/**
* @inheritDoc
* @return string
*/
public function post(): Response
public function getIdentifier(): string
{
// Implement post() method.
return $this->identifier;
}
/**
* @inheritDoc
* @param string $identifier
*/
public function put(): Response
public function setIdentifier(string $identifier): void
{
// Implement put() method.
$this->identifier = $identifier;
}
/**
* @return string
* @inheritDoc
*/
public function getIdentifier(): string
public function post(): Response
{
return $this->identifier;
// Implement post() method.
}
/**
* @param string $identifier
* @inheritDoc
*/
public function setIdentifier(string $identifier): void
public function put(): Response
{
$this->identifier = $identifier;
// Implement put() method.
}
}

6
app/Services/Nordigen/Request/GetRequisitionRequest.php

@ -40,9 +40,9 @@ class GetRequisitionRequest extends Request
private string $requisitionId;
/**
* @param string $url
* @param string $token
* @param string $requisitionId
* @param string $url
* @param string $token
* @param string $requisitionId
*/
public function __construct(string $url, string $token, string $requisitionId)
{

22
app/Services/Nordigen/Request/GetTransactionsRequest.php

@ -35,9 +35,9 @@ class GetTransactionsRequest extends Request
private string $identifier;
/**
* @param string $url
* @param string $token
* @param string $identifier
* @param string $url
* @param string $token
* @param string $identifier
*/
public function __construct(string $url, string $token, string $identifier)
{
@ -48,14 +48,6 @@ class GetTransactionsRequest extends Request
$this->setUrl(sprintf('api/v2/accounts/%s/transactions/', $identifier));
}
/**
* @param string $identifier
*/
public function setIdentifier(string $identifier): void
{
$this->identifier = $identifier;
}
/**
* @inheritDoc
*/
@ -94,4 +86,12 @@ class GetTransactionsRequest extends Request
{
// Implement put() method.
}
/**
* @param string $identifier
*/
public function setIdentifier(string $identifier): void
{
$this->identifier = $identifier;
}
}

22
app/Services/Nordigen/Request/ListAccountsRequest.php

@ -38,9 +38,9 @@ class ListAccountsRequest extends Request
private string $identifier;
/**
* @param string $url
* @param string $identifier
* @param string $token
* @param string $url
* @param string $identifier
* @param string $token
*/
public function __construct(string $url, string $identifier, string $token)
{
@ -51,14 +51,6 @@ class ListAccountsRequest extends Request
$this->setUrl(sprintf('api/v2/requisitions/%s/', $identifier));
}
/**
* @param string $identifier
*/
public function setIdentifier(string $identifier): void
{
$this->identifier = $identifier;
}
/**
* @inheritDoc
* @return Response
@ -88,4 +80,12 @@ class ListAccountsRequest extends Request
{
// Implement put() method.
}
/**
* @param string $identifier
*/
public function setIdentifier(string $identifier): void
{
$this->identifier = $identifier;
}
}

4
app/Services/Nordigen/Request/ListBanksRequest.php

@ -39,8 +39,8 @@ class ListBanksRequest extends Request
/**
* ListCustomersRequest constructor.
*
* @param string $url
* @param string $token
* @param string $url
* @param string $token
*/
public function __construct(string $url, string $token)
{

6
app/Services/Nordigen/Request/PostNewRequisitionRequest.php

@ -85,7 +85,7 @@ class PostNewRequisitionRequest extends Request
}
/**
* @param string $agreement
* @param string $agreement
*/
public function setAgreement(string $agreement): void
{
@ -93,7 +93,7 @@ class PostNewRequisitionRequest extends Request
}
/**
* @param string $bank
* @param string $bank
*/
public function setBank(string $bank): void
{
@ -101,7 +101,7 @@ class PostNewRequisitionRequest extends Request
}
/**
* @param string $reference
* @param string $reference
*/
public function setReference(string $reference): void
{

6
app/Services/Nordigen/Request/PostNewUserAgreement.php

@ -85,7 +85,7 @@ class PostNewUserAgreement extends Request
}
/**
* @param string $accessValidForDays
* @param string $accessValidForDays
*/
public function setAccessValidForDays(string $accessValidForDays): void
{
@ -93,7 +93,7 @@ class PostNewUserAgreement extends Request
}
/**
* @param string $bank
* @param string $bank
*/
public function setBank(string $bank): void
{
@ -101,7 +101,7 @@ class PostNewUserAgreement extends Request
}
/**
* @param string $maxHistoricalDays
* @param string $maxHistoricalDays
*/
public function setMaxHistoricalDays(string $maxHistoricalDays): void
{

132
app/Services/Nordigen/Request/Request.php

@ -53,6 +53,54 @@ abstract class Request
*/
abstract public function get(): Response;
/**
* @return string
*/
public function getBase(): string
{
return $this->base;
}
/**
* @param string $base
*/
public function setBase(string $base): void
{
$this->base = $base;
}
/**
* @return string
*/
public function getToken(): string
{
return $this->token;
}
/**
* @param string $token
*/
public function setToken(string $token): void
{
$this->token = $token;
}
/**
* @return string
*/
public function getUrl(): string
{
return $this->url;
}
/**
* @param string $url
*/
public function setUrl(string $url): void
{
$this->url = $url;
}
/**
* @return Response
* @throws ImporterHttpException
@ -66,7 +114,7 @@ abstract class Request
abstract public function put(): Response;
/**
* @param array $body
* @param array $body
*/
public function setBody(array $body): void
{
@ -74,7 +122,7 @@ abstract class Request
}
/**
* @param array $parameters
* @param array $parameters
*/
public function setParameters(array $parameters): void
{
@ -83,7 +131,7 @@ abstract class Request
}
/**
* @param float $timeOut
* @param float $timeOut
*/
public function setTimeOut(float $timeOut): void
{
@ -181,69 +229,7 @@ abstract class Request
}
/**
* @return string
*/
public function getBase(): string
{
return $this->base;
}
/**
* @param string $base
*/
public function setBase(string $base): void
{
$this->base = $base;
}
/**
* @return string
*/
public function getUrl(): string
{
return $this->url;
}
/**
* @param string $url
*/
public function setUrl(string $url): void
{
$this->url = $url;
}
/**
* @return Client
*/
private function getClient(): Client
{
// config here
return new Client(
[
'connect_timeout' => $this->timeOut,
]
);
}
/**
* @return string
*/
public function getToken(): string
{
return $this->token;
}
/**
* @param string $token
*/
public function setToken(string $token): void
{
$this->token = $token;
}
/**
* @param array $json
* @param array $json
*
* @return array
* @throws GuzzleException
@ -287,4 +273,18 @@ abstract class Request
return $json;
}
/**
* @return Client
*/
private function getClient(): Client
{
// config here
return new Client(
[
'connect_timeout' => $this->timeOut,
]
);
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save