Browse Source

First run up to conversion.

pull/1/head
James Cole 4 years ago
parent
commit
5d4e3bb9c3
No known key found for this signature in database GPG Key ID: BDE6667570EADBD5
  1. 12
      app/Console/AutoImports.php
  2. 4
      app/Console/Commands/AutoImport.php
  3. 4
      app/Console/StartImport.php
  4. 6
      app/Http/Controllers/AutoImportController.php
  5. 6
      app/Http/Controllers/AutoUploadController.php
  6. 37
      app/Http/Controllers/Import/CSV/ConvertController.php
  7. 4
      app/Http/Controllers/Import/CSV/MapController.php
  8. 48
      app/Http/Controllers/Import/CSV/RoleController.php
  9. 15
      app/Http/Controllers/Import/ConfigurationController.php
  10. 4
      app/Http/Controllers/Import/UploadController.php
  11. 2
      app/Http/Controllers/NavController.php
  12. 2
      app/Http/Middleware/ConfigComplete.php
  13. 2
      app/Http/Middleware/MappingComplete.php
  14. 2
      app/Http/Middleware/RolesComplete.php
  15. 48
      app/Http/Request/ConfigurationPostRequest.php
  16. 2
      app/Http/Request/RolesPostRequest.php
  17. 8
      app/Services/CSV/Configuration/ConfigFileProcessor.php
  18. 4
      app/Services/CSV/Mapper/AssetAccountIbans.php
  19. 4
      app/Services/CSV/Mapper/AssetAccounts.php
  20. 6
      app/Services/CSV/Mapper/Bills.php
  21. 6
      app/Services/CSV/Mapper/Budgets.php
  22. 6
      app/Services/CSV/Mapper/Categories.php
  23. 18
      app/Services/CSV/Mapper/GetAccounts.php
  24. 8
      app/Services/CSV/Mapper/MapperService.php
  25. 4
      app/Services/CSV/Mapper/OpposingAccountIbans.php
  26. 4
      app/Services/CSV/Mapper/OpposingAccounts.php
  27. 6
      app/Services/CSV/Mapper/TransactionCurrencies.php
  28. 4
      app/Services/Import/ImportRoutineManager.php
  29. 10
      app/Services/Import/Routine/CSVFileProcessor.php
  30. 10
      app/Services/Import/Routine/LineProcessor.php
  31. 14
      app/Services/Import/Routine/PseudoTransactionProcessor.php
  32. 20
      app/Services/Import/Task/Accounts.php
  33. 14
      app/Services/Session/Constants.php
  34. 14
      app/Support/Token.php
  35. 10
      package.json
  36. 47251
      public/js/app.js
  37. 8
      resources/js/app.js
  38. 2
      resources/js/components/import/ConversionMessages.vue
  39. 177
      resources/js/components/import/ConversionStatus.vue
  40. 168
      resources/js/components/import/ImportStatus.vue
  41. 12
      resources/views/import/003-upload/index.twig
  42. 198
      resources/views/import/004-configure/index.twig
  43. 154
      resources/views/import/005-roles/index.twig
  44. 0
      resources/views/import/006-mapping/index.twig
  45. 50
      resources/views/import/007-convert/index.twig
  46. 119
      resources/views/import/roles/index.twig
  47. 37
      resources/views/import/run/index.twig
  48. 2
      resources/views/index.twig
  49. 38
      routes/web.php
  50. 1
      webpack.mix.js

12
app/Console/AutoImports.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Console;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Mail\ImportFinished;
use Illuminate\Support\Facades\Mail;
use JsonException;
@ -102,7 +102,7 @@ trait AutoImports
/**
* @param array $files
*
* @throws ImportException
* @throws ImporterErrorException
*/
protected function importFiles(array $files): void
{
@ -115,7 +115,7 @@ trait AutoImports
/**
* @param string $file
*
* @throws ImportException
* @throws ImporterErrorException
*/
private function importFile(string $file): void
{
@ -134,7 +134,7 @@ trait AutoImports
$configuration = json_decode(file_get_contents($jsonFile), true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
Log::error($e->getMessage());
throw new ImportException(sprintf('Bad JSON in configuration file: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Bad JSON in configuration file: %s', $e->getMessage()));
}
$this->line(sprintf('Going to import from file %s using configuration %s.', $csvFile, $jsonFile));
// create importer
@ -169,7 +169,7 @@ trait AutoImports
/**
* @param string $file
*
* @throws ImportException
* @throws ImporterErrorException
*/
private function importUpload(string $csvFile, string $jsonFile): void
{
@ -185,7 +185,7 @@ trait AutoImports
$configuration = json_decode(file_get_contents($jsonFile), true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
Log::error($e->getMessage());
throw new ImportException(sprintf('Bad JSON in configuration file: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Bad JSON in configuration file: %s', $e->getMessage()));
}
$this->line(sprintf('Going to import from file %s using configuration %s.', $csvFile, $jsonFile));
// create importer

4
app/Console/Commands/AutoImport.php

@ -28,7 +28,7 @@ use App\Console\AutoImports;
use App\Console\HaveAccess;
use App\Console\StartImport;
use App\Console\VerifyJSON;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use Illuminate\Console\Command;
use Log;
@ -83,7 +83,7 @@ class AutoImport extends Command
$this->line(sprintf('Found %d CSV + JSON file sets in %s', count($files), $this->directory));
try {
$this->importFiles($files);
} catch (ImportException $e) {
} catch (ImporterErrorException $e) {
Log::error($e->getMessage());
$this->error(sprintf('Import exception (see the logs): %s', $e->getMessage()));
}

4
app/Console/StartImport.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Console;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Services\CSV\Configuration\Configuration;
use App\Services\CSV\File\FileReader;
use App\Services\Import\ImportRoutineManager;
@ -59,7 +59,7 @@ trait StartImport
try {
$manager->setConfiguration($configObject);
} catch (ImportException $e) {
} catch (ImporterErrorException $e) {
$this->error($e->getMessage());
return 1;

6
app/Http/Controllers/AutoImportController.php

@ -30,7 +30,7 @@ use App\Console\AutoImports;
use App\Console\HaveAccess;
use App\Console\StartImport;
use App\Console\VerifyJSON;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use Illuminate\Http\Request;
use Log;
@ -50,7 +50,7 @@ class AutoImportController extends Controller
{
$access = $this->haveAccess();
if (false === $access) {
throw new ImportException('Could not connect to your local Firefly III instance.');
throw new ImporterErrorException('Could not connect to your local Firefly III instance.');
}
$argument = (string) ($request->get('directory') ?? './');
@ -68,7 +68,7 @@ class AutoImportController extends Controller
$this->line(sprintf('Found %d CSV + JSON file sets in %s', count($files), $this->directory));
try {
$this->importFiles($files);
} catch (ImportException $e) {
} catch (ImporterErrorException $e) {
Log::error($e->getMessage());
$this->line(sprintf('Import exception (see the logs): %s', $e->getMessage()));
}

6
app/Http/Controllers/AutoUploadController.php

@ -30,7 +30,7 @@ use App\Console\AutoImports;
use App\Console\HaveAccess;
use App\Console\StartImport;
use App\Console\VerifyJSON;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Http\Request\AutoUploadRequest;
use Log;
@ -48,7 +48,7 @@ class AutoUploadController extends Controller
{
$access = $this->haveAccess();
if (false === $access) {
throw new ImportException('Could not connect to your local Firefly III instance.');
throw new ImporterErrorException('Could not connect to your local Firefly III instance.');
}
$json = $request->file('json');
@ -56,7 +56,7 @@ class AutoUploadController extends Controller
try {
$this->importUpload($csv->getPathname(), $json->getPathname());
} catch (ImportException $e) {
} catch (ImporterErrorException $e) {
Log::error($e->getMessage());
$this->line(sprintf('Import exception (see the logs): %s', $e->getMessage()));
}

37
app/Http/Controllers/Import/RunController.php → app/Http/Controllers/Import/CSV/ConvertController.php

@ -22,10 +22,10 @@
declare(strict_types=1);
namespace App\Http\Controllers\Import;
namespace App\Http\Controllers\Import\CSV;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Http\Controllers\Controller;
use App\Http\Middleware\ReadyForImport;
use App\Mail\ImportFinished;
@ -44,11 +44,10 @@ use Log;
use Mail;
use TypeError;
/**
* Class RunController
* Class ConvertController
*/
class RunController extends Controller
class ConvertController extends Controller
{
/**
@ -82,29 +81,30 @@ class RunController extends Controller
Log::debug('Will now verify configuration content.');
if ([] === $configuration->getDoMapping()) {
$jobBackUrl = route('back.mapping');
if (empty($configuration->getDoMapping())) {
// no mapping, back to roles
Log::debug('NO role info in config, will send you back to roles..');
$jobBackUrl = route('back.roles');
}
if ([] !== $configuration->getMapping()) {
if (empty($configuration->getMapping())) {
// back to mapping
Log::debug('NO mapping in file, will send you back to mapping..');
$jobBackUrl = route('back.mapping');
}
// job ID may be in session:
$identifier = session()->get(Constants::JOB_IDENTIFIER);
$identifier = session()->get(Constants::CSV_CONVERSION_JOB_IDENTIFIER);
$routine = new ImportRoutineManager($identifier);
$identifier = $routine->getIdentifier();
Log::debug(sprintf('Import routine manager identifier is "%s"', $identifier));
// store identifier in session so the status can get it.
session()->put(Constants::JOB_IDENTIFIER, $identifier);
Log::debug(sprintf('Stored "%s" under "%s"', $identifier, Constants::JOB_IDENTIFIER));
session()->put(Constants::CSV_CONVERSION_JOB_IDENTIFIER, $identifier);
Log::debug(sprintf('Stored "%s" under "%s"', $identifier, Constants::CSV_CONVERSION_JOB_IDENTIFIER));
return view('import.run.index', compact('mainTitle', 'subTitle', 'identifier', 'jobBackUrl'));
return view('import.007-convert.index', compact('mainTitle', 'subTitle', 'identifier', 'jobBackUrl'));
}
/**
@ -128,16 +128,19 @@ class RunController extends Controller
$configuration = Configuration::fromArray(session()->get(Constants::CONFIGURATION));
// read configuration from disk (to append data)
$diskArray = json_decode(StorageService::getContent(session()->get(Constants::UPLOAD_CONFIG_FILE)), true, JSON_THROW_ON_ERROR);
$diskConfig = Configuration::fromArray($diskArray);
$configuration->setMapping($diskConfig->getMapping());
$configuration->setDoMapping($diskConfig->getDoMapping());
$configuration->setRoles($diskConfig->getRoles());
$configurationFile = session()->get(Constants::UPLOAD_CONFIG_FILE);
if (null !== $configurationFile) {
$diskArray = json_decode(StorageService::getContent($configurationFile), true, JSON_THROW_ON_ERROR);
$diskConfig = Configuration::fromArray($diskArray);
$configuration->setMapping($diskConfig->getMapping());
$configuration->setDoMapping($diskConfig->getDoMapping());
$configuration->setRoles($diskConfig->getRoles());
}
$routine->setConfiguration($configuration);
$routine->setReader(FileReader::getReaderFromSession());
$routine->start();
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (ImportException | ErrorException | TypeError $e) {
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (ImporterErrorException | ErrorException | TypeError $e) {
// update job to error state.
ImportJobStatusManager::setJobStatus(ImportJobStatus::JOB_ERRORED);
$error = sprintf('Internal error: %s in file %s:%d', $e->getMessage(), $e->getFile(), $e->getLine());

4
app/Http/Controllers/Import/MapController.php → app/Http/Controllers/Import/CSV/MapController.php

@ -22,7 +22,7 @@
declare(strict_types=1);
namespace App\Http\Controllers\Import;
namespace App\Http\Controllers\Import\CSV;
use App\Http\Controllers\Controller;
@ -199,7 +199,7 @@ class MapController extends Controller
// set map config as complete.
session()->put(Constants::MAPPING_COMPLETE_INDICATOR, true);
return redirect()->route('import.run.index');
return redirect()->route('007-convert.index');
}
/**

48
app/Http/Controllers/Import/RoleController.php → app/Http/Controllers/Import/CSV/RoleController.php

@ -22,7 +22,7 @@
declare(strict_types=1);
namespace App\Http\Controllers\Import;
namespace App\Http\Controllers\Import\CSV;
use App\Http\Controllers\Controller;
@ -33,11 +33,10 @@ use App\Services\CSV\Configuration\Configuration;
use App\Services\CSV\Roles\RoleService;
use App\Services\Session\Constants;
use App\Services\Storage\StorageService;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use League\Csv\Exception;
use Log;
/**
@ -57,13 +56,15 @@ class RoleController extends Controller
/**
* @return Factory|View
* @throws FileNotFoundException
* @throws Exception
*/
public function index()
public function index(Request $request)
{
Log::debug('Now in role controller');
$mainTitle = 'Define roles';
$flow = $request->cookie(Constants::FLOW_COOKIE);
if('csv' !== $flow) {
die('redirect or something');
}
$mainTitle = 'Role definition';
$subTitle = 'Configure the role of each column in your file';
// get configuration object.
@ -72,11 +73,13 @@ class RoleController extends Controller
// append configuration from original file
// because the session omits 3 arrays: mapping, do_mapping and roles
$configFileName = session()->get(Constants::UPLOAD_CONFIG_FILE);
$fileConfiguration = ConfigFileProcessor::convertConfigFile($configFileName);
$configuration->setDoMapping($fileConfiguration->getDoMapping());
$configuration->setMapping($fileConfiguration->getMapping());
$configuration->setRoles($fileConfiguration->getRoles());
$configFileName = session()->get(Constants::UPLOAD_CONFIG_FILE);
if (null !== $configFileName) {
$fileConfiguration = ConfigFileProcessor::convertConfigFile($configFileName);
$configuration->setDoMapping($fileConfiguration->getDoMapping());
$configuration->setMapping($fileConfiguration->getMapping());
$configuration->setRoles($fileConfiguration->getRoles());
}
// get columns from file
$content = StorageService::getContent(session()->get(Constants::UPLOAD_CSV_FILE));
@ -87,7 +90,7 @@ class RoleController extends Controller
$mapping = base64_encode(json_encode($configuration->getMapping(), JSON_THROW_ON_ERROR, 512));
// roles
$roles = config('csv_importer.import_roles');
$roles = config('csv.import_roles');
ksort($roles);
// configuration (if it is set)
@ -95,7 +98,7 @@ class RoleController extends Controller
$configuredDoMapping = $configuration->getDoMapping();
return view(
'import.roles.index',
'import.005-roles.index',
compact('mainTitle', 'configuration', 'subTitle', 'columns', 'examples', 'roles', 'configuredRoles', 'configuredDoMapping', 'mapping')
);
}
@ -124,13 +127,16 @@ class RoleController extends Controller
// the 'mapping' array is missing because it was saved to disk and not used otherwise
// it must be restored, and THEN saved:
$diskArray = json_decode(StorageService::getContent(session()->get(Constants::UPLOAD_CONFIG_FILE)), true, JSON_THROW_ON_ERROR);
$diskConfig = Configuration::fromArray($diskArray);
// save the mapping from the diskConfig to our updated config:
$configuration->setMapping($diskConfig->getMapping());
$configFileName = session()->get(Constants::UPLOAD_CONFIG_FILE);
if (null !== $configFileName) {
$diskArray = json_decode(StorageService::getContent($configFileName), true, JSON_THROW_ON_ERROR);
$diskConfig = Configuration::fromArray($diskArray);
// save the mapping from the diskConfig to our updated config:
$configuration->setMapping($diskConfig->getMapping());
}
// then this is the new, full array:
$fullArray = $configuration->toArray();
$fullArray = $configuration->toArray();
// and it can be saved on disk:
$configFileName = StorageService::storeArray($fullArray);
@ -146,13 +152,13 @@ class RoleController extends Controller
// redirect to mapping thing.
if (true === $needsMapping) {
return redirect()->route('import.mapping.index');
return redirect()->route('006-mapping.index');
}
// otherwise, store empty mapping, and continue:
// set map config as complete.
session()->put(Constants::MAPPING_COMPLETE_INDICATOR, true);
return redirect()->route('import.run.index');
return redirect()->route('007-convert.index');
}
/**

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

@ -30,7 +30,6 @@ use App\Http\Middleware\ConfigComplete;
use App\Http\Request\ConfigurationPostRequest;
use App\Services\CSV\Configuration\Configuration;
use App\Services\CSV\Converter\Date;
use App\Services\CSV\Specifics\SpecificService;
use App\Services\Session\Constants;
use App\Services\Storage\StorageService;
use App\Support\Token;
@ -56,7 +55,7 @@ class ConfigurationController extends Controller
public function __construct()
{
parent::__construct();
app('view')->share('pageTitle', 'Import configuration');
app('view')->share('pageTitle', 'Configuration');
$this->middleware(ConfigComplete::class);
}
@ -66,9 +65,10 @@ class ConfigurationController extends Controller
public function index(Request $request)
{
Log::debug(sprintf('Now at %s', __METHOD__));
$mainTitle = 'Import routine';
$mainTitle = 'Configuration';
$subTitle = 'Configure your CSV file import';
$accounts = [];
$flow = $request->cookie(Constants::FLOW_COOKIE);
$configuration = null;
if (session()->has(Constants::CONFIGURATION)) {
@ -78,7 +78,7 @@ class ConfigurationController extends Controller
$overruleSkip = 'true' === $request->get('overruleskip');
if (null !== $configuration && true === $configuration->isSkipForm() && false === $overruleSkip) {
// skipForm
return redirect()->route('import.roles.index');
return redirect()->route('005-roles.index');
}
// get list of asset accounts:
@ -90,9 +90,6 @@ class ConfigurationController extends Controller
$request->setTimeOut(config('importer.connection.timeout'));
$response = $request->get();
// get list of specifics:
$specifics = SpecificService::getSpecifics();
/** @var Account $account */
foreach ($response as $account) {
$accounts['Asset accounts'][$account->id] = $account;
@ -118,7 +115,7 @@ class ConfigurationController extends Controller
return view(
'import.004-configure.index',
compact('mainTitle', 'subTitle', 'accounts', 'specifics', 'configuration')
compact('mainTitle', 'subTitle', 'accounts', 'configuration', 'flow')
);
}
@ -164,7 +161,7 @@ class ConfigurationController extends Controller
session()->put(Constants::CONFIG_COMPLETE_INDICATOR, true);
// redirect to import things?
return redirect()->route('import.roles.index');
return redirect()->route('005-roles.index');
}
}

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

@ -26,7 +26,7 @@ namespace App\Http\Controllers\Import;
use App\Exceptions\ImporterErrorException;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Http\Controllers\Controller;
use App\Http\Middleware\UploadedFiles;
use App\Services\CSV\Configuration\ConfigFileProcessor;
@ -140,7 +140,7 @@ class UploadController extends Controller
try {
$configuration = ConfigFileProcessor::convertConfigFile($configFileName);
session()->put(Constants::CONFIGURATION, $configuration->toSessionArray());
} catch (ImportException $e) {
} catch (ImporterErrorException $e) {
$errors->add('config_file', $e->getMessage());
}
}

2
app/Http/Controllers/NavController.php

@ -50,7 +50,7 @@ class NavController extends Controller
public function toRoles()
{
session()->forget(Constants::ROLES_COMPLETE_INDICATOR);
return redirect(route('import.roles.index'));
return redirect(route('005-roles.index'));
}
/**

2
app/Http/Middleware/ConfigComplete.php

@ -45,7 +45,7 @@ class ConfigComplete
public function handle(Request $request, Closure $next)
{
if (session()->has(Constants::CONFIG_COMPLETE_INDICATOR) && true === session()->get(Constants::CONFIG_COMPLETE_INDICATOR)) {
return redirect()->route('import.roles.index');
return redirect()->route('005-roles.index');
}
return $next($request);

2
app/Http/Middleware/MappingComplete.php

@ -45,7 +45,7 @@ class MappingComplete
public function handle(Request $request, Closure $next)
{
if (session()->has(Constants::MAPPING_COMPLETE_INDICATOR) && true === session()->get(Constants::MAPPING_COMPLETE_INDICATOR)) {
return redirect()->route('import.run.index');
return redirect()->route('007-convert.index');
}
return $next($request);

2
app/Http/Middleware/RolesComplete.php

@ -45,7 +45,7 @@ class RolesComplete
public function handle(Request $request, Closure $next)
{
if (session()->has(Constants::ROLES_COMPLETE_INDICATOR) && true === session()->get(Constants::ROLES_COMPLETE_INDICATOR)) {
return redirect()->route('import.mapping.index');
return redirect()->route('006-mapping.index');
}
return $next($request);

48
app/Http/Request/ConfigurationPostRequest.php

@ -26,8 +26,6 @@ namespace App\Http\Request;
use App\Services\CSV\Specifics\SpecificService;
use JsonException;
use Log;
/**
* Class ConfigurationPostRequest
@ -49,47 +47,11 @@ class ConfigurationPostRequest extends Request
*/
public function getAll(): array
{
try {
$roles = $this->get('roles') ?
json_decode(base64_decode($this->get('roles')), true, 512, JSON_THROW_ON_ERROR) : null;
} catch (JsonException $e) {
Log::warning(
sprintf(
'Could not decode roles JSON "%s" ("%s") but this is not a problem.',
$this->get('roles'),
$e->getMessage()
)
);
$roles = [];
}
try {
$mapping = $this->get('mapping') ?
json_decode(base64_decode($this->get('mapping')), true, 512, JSON_THROW_ON_ERROR) : null;
} catch (JsonException $e) {
Log::warning(
sprintf(
'Could not decode mapping JSON "%s" ("%s") but this is not a problem.',
$this->get('mapping'),
$e->getMessage()
)
);
$mapping = [];
}
try {
$doMapping = $this->get('do_mapping') ?
json_decode(base64_decode($this->get('do_mapping')), true, 512, JSON_THROW_ON_ERROR) : null;
} catch (JsonException $e) {
Log::warning(
sprintf(
'Could not decode doMapping JSON "%s" ("%s") but this is not a problem.',
$this->get('do_mapping'),
$e->getMessage()
)
);
$doMapping = [];
}
$result = [
$roles = [];
$mapping = [];
$doMapping = [];
$result = [
'headers' => $this->convertBoolean($this->get('headers')),
'delimiter' => $this->string('delimiter'),
'date' => $this->string('date'),
@ -137,7 +99,7 @@ class ConfigurationPostRequest extends Request
// duplicate detection:
'duplicate_detection_method' => 'required|in:cell,none,classic',
'unique_column_index' => 'numeric',
'unique_column_type' => sprintf('required|in:%s', join(',', array_keys(config('csv_importer.unique_column_options')))),
'unique_column_type' => sprintf('required|in:%s', join(',', array_keys(config('csv.unique_column_options')))),
];
// rules for specifics:
$specifics = SpecificService::getSpecifics();

2
app/Http/Request/RolesPostRequest.php

@ -66,7 +66,7 @@ class RolesPostRequest extends Request
*/
public function rules(): array
{
$keys = implode(',', array_keys(config('csv_importer.import_roles')));
$keys = implode(',', array_keys(config('csv.import_roles')));
return [
'roles.*' => sprintf('required|in:%s', $keys),

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

@ -25,7 +25,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Configuration;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Services\Storage\StorageService;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use JsonException;
@ -42,7 +42,7 @@ class ConfigFileProcessor
* @param string $fileName
*
* @return Configuration
* @throws ImportException
* @throws ImporterErrorException
*/
public static function convertConfigFile(string $fileName): Configuration
{
@ -51,13 +51,13 @@ class ConfigFileProcessor
$content = StorageService::getContent($fileName);
} catch (FileNotFoundException $e) {
Log::error($e->getMessage());
throw new ImportException(sprintf('Cpuld not find config file: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Cpuld not find config file: %s', $e->getMessage()));
}
try {
$json = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
Log::error($e->getMessage());
throw new ImportException(sprintf('Invalid JSON configuration file: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Invalid JSON configuration file: %s', $e->getMessage()));
}
return Configuration::fromFile($json);

4
app/Services/CSV/Mapper/AssetAccountIbans.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Mapper;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
/**
* Class AssetAccountIbans
@ -37,7 +37,7 @@ class AssetAccountIbans implements MapperInterface
* Get map of objects.
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
public function getMap(): array
{

4
app/Services/CSV/Mapper/AssetAccounts.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Mapper;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
/**
* Class AssetAccounts
@ -37,7 +37,7 @@ class AssetAccounts implements MapperInterface
* Get map of objects.
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
public function getMap(): array
{

6
app/Services/CSV/Mapper/Bills.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Mapper;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Support\Token;
use GrumpyDictator\FFIIIApiSupport\Exceptions\ApiHttpException;
use GrumpyDictator\FFIIIApiSupport\Model\Bill;
@ -39,7 +39,7 @@ class Bills implements MapperInterface
* Get map of objects.
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
public function getMap(): array
{
@ -56,7 +56,7 @@ class Bills implements MapperInterface
} catch (ApiHttpException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new ImportException(sprintf('Could not download bills: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Could not download bills: %s', $e->getMessage()));
}
/** @var Bill $bill */
foreach ($response as $bill) {

6
app/Services/CSV/Mapper/Budgets.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Mapper;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Support\Token;
use GrumpyDictator\FFIIIApiSupport\Exceptions\ApiHttpException;
use GrumpyDictator\FFIIIApiSupport\Model\Budget;
@ -41,7 +41,7 @@ class Budgets implements MapperInterface
* Get map of objects.
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
public function getMap(): array
{
@ -58,7 +58,7 @@ class Budgets implements MapperInterface
} catch (ApiHttpException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new ImportException(sprintf('Could not download budgets: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Could not download budgets: %s', $e->getMessage()));
}

6
app/Services/CSV/Mapper/Categories.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Mapper;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Support\Token;
use GrumpyDictator\FFIIIApiSupport\Exceptions\ApiHttpException;
use GrumpyDictator\FFIIIApiSupport\Model\Category;
@ -41,7 +41,7 @@ class Categories implements MapperInterface
* Get map of objects.
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
public function getMap(): array
{
@ -58,7 +58,7 @@ class Categories implements MapperInterface
} catch (ApiHttpException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new ImportException(sprintf('Could not download categories: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Could not download categories: %s', $e->getMessage()));
}
/** @var Category $category */
foreach ($response as $category) {

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

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Mapper;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Support\Token;
use GrumpyDictator\FFIIIApiSupport\Exceptions\ApiHttpException;
use GrumpyDictator\FFIIIApiSupport\Model\Account;
@ -42,7 +42,7 @@ trait GetAccounts
* Returns a combined list of asset accounts and all liability accounts.
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
protected function getAllAccounts(): array
{
@ -62,7 +62,7 @@ trait GetAccounts
} catch (ApiHttpException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new ImportException(sprintf('Could not download accounts: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Could not download accounts: %s', $e->getMessage()));
}
if ($response instanceof GetAccountsResponse) {
@ -70,7 +70,7 @@ trait GetAccounts
}
if (!$response instanceof GetAccountsResponse) {
throw new ImportException('Could not get list of ALL accounts.');
throw new ImporterErrorException('Could not get list of ALL accounts.');
}
return array_merge($accounts);
@ -95,7 +95,7 @@ trait GetAccounts
* Returns a combined list of asset accounts and all liability accounts.
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
protected function getAssetAccounts(): array
{
@ -116,7 +116,7 @@ trait GetAccounts
} catch (ApiHttpException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new ImportException(sprintf('Could not download asset accounts: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Could not download asset accounts: %s', $e->getMessage()));
}
if ($response instanceof GetAccountsResponse) {
@ -124,7 +124,7 @@ trait GetAccounts
}
if (!$response instanceof GetAccountsResponse) {
throw new ImportException('Could not get list of asset accounts.');
throw new ImporterErrorException('Could not get list of asset accounts.');
}
$request = new GetAccountsRequest($url, $token);
@ -139,7 +139,7 @@ trait GetAccounts
} catch (ApiHttpException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new ImportException(sprintf('Could not download liability accounts: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Could not download liability accounts: %s', $e->getMessage()));
}
if ($response instanceof GetAccountsResponse) {
@ -147,7 +147,7 @@ trait GetAccounts
}
if (!$response instanceof GetAccountsResponse) {
throw new ImportException('Could not get list of asset accounts.');
throw new ImporterErrorException('Could not get list of asset accounts.');
}
return array_merge($accounts, $liabilities);

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

@ -25,7 +25,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Mapper;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Services\CSV\Specifics\SpecificService;
use League\Csv\Exception;
use League\Csv\Reader;
@ -48,7 +48,7 @@ class MapperService
* @param array $data
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
public static function getMapData(string $content, string $delimiter, bool $hasHeaders, array $specifics, array $data): array
{
@ -62,7 +62,7 @@ class MapperService
} catch (Exception $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new ImportException(sprintf('Could not set delimiter: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Could not set delimiter: %s', $e->getMessage()));
}
$offset = 0;
@ -74,7 +74,7 @@ class MapperService
$records = $stmt->process($reader);
} catch (Exception $e) {
Log::error($e->getMessage());
throw new ImportException($e->getMessage());
throw new ImporterErrorException($e->getMessage());
}
// loop each row, apply specific:
Log::debug('Going to loop all records to collect information');

4
app/Services/CSV/Mapper/OpposingAccountIbans.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Mapper;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
/**
* Class OpposingAccountIbans
@ -37,7 +37,7 @@ class OpposingAccountIbans implements MapperInterface
* Get map of objects.
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
public function getMap(): array
{

4
app/Services/CSV/Mapper/OpposingAccounts.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Mapper;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
/**
* Class OpposingAccounts
@ -37,7 +37,7 @@ class OpposingAccounts implements MapperInterface
* Get map of objects.
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
public function getMap(): array
{

6
app/Services/CSV/Mapper/TransactionCurrencies.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Mapper;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Support\Token;
use GrumpyDictator\FFIIIApiSupport\Exceptions\ApiHttpException;
use GrumpyDictator\FFIIIApiSupport\Model\TransactionCurrency;
@ -41,7 +41,7 @@ class TransactionCurrencies implements MapperInterface
* Get map of objects.
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
public function getMap(): array
{
@ -58,7 +58,7 @@ class TransactionCurrencies implements MapperInterface
} catch (ApiHttpException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new ImportException(sprintf('Could not download currencies: %s', $e->getMessage()));
throw new ImporterErrorException(sprintf('Could not download currencies: %s', $e->getMessage()));
}
/** @var TransactionCurrency $currency */
foreach ($response as $currency) {

4
app/Services/Import/ImportRoutineManager.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\Import;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Services\CSV\Configuration\Configuration;
use App\Services\Import\ImportJobStatus\ImportJobStatusManager;
use App\Services\Import\Routine\APISubmitter;
@ -110,7 +110,7 @@ class ImportRoutineManager
/**
* @param Configuration $configuration
*
* @throws ImportException
* @throws ImporterErrorException
*/
public function setConfiguration(Configuration $configuration): void
{

10
app/Services/Import/Routine/CSVFileProcessor.php

@ -23,7 +23,7 @@
declare(strict_types=1);
namespace App\Services\Import\Routine;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Services\CSV\Configuration\Configuration;
use App\Services\CSV\Specifics\SpecificService;
use App\Services\Import\Support\ProgressInformation;
@ -105,7 +105,7 @@ class CSVFileProcessor
try {
return $this->processCSVLines($records);
} catch (ImportException $e) {
} catch (ImporterErrorException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$message = sprintf('Could not parse CSV: %s', $e->getMessage());
@ -120,7 +120,7 @@ class CSVFileProcessor
* @param ResultSet $records
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
private function processCSVLines(ResultSet $records): array
{
@ -170,7 +170,7 @@ class CSVFileProcessor
* @param array $array
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
private function removeDuplicateLines(array $array): array
{
@ -182,7 +182,7 @@ class CSVFileProcessor
} catch (JsonException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new ImportException(sprintf('Could not decode JSON line #%d: %s', $index, $e->getMessage()));
throw new ImporterErrorException(sprintf('Could not decode JSON line #%d: %s', $index, $e->getMessage()));
}
if (in_array($hash, $hashes, true)) {
$message = sprintf('Going to skip line #%d because it\'s in the file twice. This may reset the count below.', $index);

10
app/Services/Import/Routine/LineProcessor.php

@ -25,7 +25,7 @@ declare(strict_types=1);
namespace App\Services\Import\Routine;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Services\CSV\Configuration\Configuration;
use App\Services\Import\ColumnValue;
use App\Services\Import\Support\ProgressInformation;
@ -87,7 +87,7 @@ class LineProcessor
Log::debug(sprintf('Now processing CSV line #%d/#%d', $index + 1, $count));
try {
$processed[] = $this->process($line);
} catch (ImportException $e) {
} catch (ImporterErrorException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->addError(0, $e->getMessage());
@ -106,7 +106,7 @@ class LineProcessor
* @param array $line
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
private function process(array $line): array
{
@ -179,7 +179,7 @@ class LineProcessor
* @param int $mapped
*
* @return string
* @throws ImportException
* @throws ImporterErrorException
*/
private function getRoleForColumn(int $column, int $mapped): string
{
@ -221,7 +221,7 @@ class LineProcessor
'opposing-number' => 'opposing-id',
];
if (!isset($roleMapping[$role])) {
throw new ImportException(sprintf('Cannot indicate new role for mapped role "%s"', $role)); // @codeCoverageIgnore
throw new ImporterErrorException(sprintf('Cannot indicate new role for mapped role "%s"', $role)); // @codeCoverageIgnore
}
$newRole = $roleMapping[$role];
if ($newRole !== $role) {

14
app/Services/Import/Routine/PseudoTransactionProcessor.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Services\Import\Routine;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Services\Import\Support\ProgressInformation;
use App\Services\Import\Task\AbstractTask;
use App\Support\Token;
@ -60,7 +60,7 @@ class PseudoTransactionProcessor
*
* @param int|null $defaultAccountId
*
* @throws ImportException
* @throws ImporterErrorException
*/
public function __construct(?int $defaultAccountId)
{
@ -72,7 +72,7 @@ class PseudoTransactionProcessor
/**
* @param int|null $accountId
*
* @throws ImportException
* @throws ImporterErrorException
*/
private function getDefaultAccount(?int $accountId): void
{
@ -89,14 +89,14 @@ class PseudoTransactionProcessor
$result = $accountRequest->get();
} catch (ApiHttpException $e) {
Log::error($e->getMessage());
throw new ImportException(sprintf('The default account in your configuration file (%d) does not exist.', $accountId));
throw new ImporterErrorException(sprintf('The default account in your configuration file (%d) does not exist.', $accountId));
}
$this->defaultAccount = $result->getAccount();
}
}
/**
* @throws ImportException
* @throws ImporterErrorException
*/
private function getDefaultCurrency(): void
{
@ -113,7 +113,7 @@ class PseudoTransactionProcessor
$response = $prefRequest->get();
} catch (ApiHttpException $e) {
Log::error($e->getMessage());
throw new ImportException('Could not load the users currency preference.');
throw new ImporterErrorException('Could not load the users currency preference.');
}
$code = $response->getPreference()->data ?? 'EUR';
$currencyRequest = new GetCurrencyRequest($url, $token);
@ -126,7 +126,7 @@ class PseudoTransactionProcessor
$this->defaultCurrency = $result->getCurrency();
} catch (ApiHttpException $e) {
Log::error($e->getMessage());
throw new ImportException(sprintf('The default currency ("%s") could not be loaded.', $code));
throw new ImporterErrorException(sprintf('The default currency ("%s") could not be loaded.', $code));
}
}

20
app/Services/Import/Task/Accounts.php

@ -25,7 +25,7 @@ declare(strict_types=1);
namespace App\Services\Import\Task;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
use App\Services\Import\DeterminesTransactionType;
use App\Support\Token;
use GrumpyDictator\FFIIIApiSupport\Exceptions\ApiException;
@ -196,7 +196,7 @@ class Accounts extends AbstractTask
* @param Account|null $defaultAccount
*
* @return array
* @throws ImportException
* @throws ImporterErrorException
*/
private function findAccount(array $array, ?Account $defaultAccount): array
{
@ -286,7 +286,7 @@ class Accounts extends AbstractTask
* @param string $value
*
* @return Account|null
* @throws ImportException
* @throws ImporterErrorException
*/
private function findById(string $value): ?Account
{
@ -302,14 +302,14 @@ class Accounts extends AbstractTask
try {
$response = $request->get();
} catch (GrumpyApiHttpException $e) {
throw new ImportException($e->getMessage());
throw new ImporterErrorException($e->getMessage());
}
if (1 === count($response)) {
/** @var Account $account */
try {
$account = $response->current();
} catch (ApiException $e) {
throw new ImportException($e->getMessage());
throw new ImporterErrorException($e->getMessage());
}
Log::debug(sprintf('[a] Found %s account #%d based on ID "%s"', $account->type, $account->id, $value));
@ -327,7 +327,7 @@ class Accounts extends AbstractTask
* @param string $transactionType
*
* @return Account|null
* @throws ImportException
* @throws ImporterErrorException
*/
private function findByIban(string $iban, string $transactionType): ?Account
{
@ -343,7 +343,7 @@ class Accounts extends AbstractTask
try {
$response = $request->get();
} catch (GrumpyApiHttpException $e) {
throw new ImportException($e->getMessage());
throw new ImporterErrorException($e->getMessage());
}
if (0 === count($response)) {
Log::debug('Found NOTHING.');
@ -356,7 +356,7 @@ class Accounts extends AbstractTask
try {
$account = $response->current();
} catch (ApiException $e) {
throw new ImportException($e->getMessage());
throw new ImporterErrorException($e->getMessage());
}
// catch impossible combination "expense" with "deposit"
if ('expense' === $account->type && 'deposit' === $transactionType) {
@ -396,7 +396,7 @@ class Accounts extends AbstractTask
* @param string $name
*
* @return Account|null
* @throws ImportException
* @throws ImporterErrorException
*/
private function findByName(string $name): ?Account
{
@ -412,7 +412,7 @@ class Accounts extends AbstractTask
try {
$response = $request->get();
} catch (GrumpyApiHttpException $e) {
throw new ImportException($e->getMessage());
throw new ImporterErrorException($e->getMessage());
}
if (0 === count($response)) {
Log::debug('Found NOTHING.');

14
app/Services/Session/Constants.php

@ -42,7 +42,6 @@ class Constants
public const SESSION_NORDIGEN_KEY = 'nordigen_key';
// upload config values:
public const HAS_UPLOAD = 'has_uploaded_file';
public const UPLOAD_CSV_FILE = 'csv_file_path';
public const UPLOAD_CONFIG_FILE = 'config_file_path';
@ -52,18 +51,21 @@ class Constants
// stores the configuration array
public const CONFIGURATION = 'configuration';
// if the user is done configuring the import
public const CONFIG_COMPLETE_INDICATOR = 'config_complete';
// if the user is done with specific steps:
public const HAS_UPLOAD = 'has_uploaded_file';
public const CONFIG_COMPLETE_INDICATOR = 'config_complete';
public const ROLES_COMPLETE_INDICATOR = 'role_config_complete';
public const MAPPING_COMPLETE_INDICATOR = 'mapping_config_complete';
// constants for CSV conversion job:
public const CSV_CONVERSION_JOB_IDENTIFIER = 'csv_c_job_id';
// /** @var string */
// public const JOB_IDENTIFIER = 'import_job_id';
// /** @var string */
// public const JOB_STATUS = 'import_job_status';
// /** @var string */
// public const MAPPING_COMPLETE_INDICATOR = 'mapping_config_complete';
// /** @var string string */
// public const ROLES_COMPLETE_INDICATOR = 'role_config_complete';
// /** @var string */
// /** @var string */

14
app/Support/Token.php

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace App\Support;
use App\Exceptions\ImportException;
use App\Exceptions\ImporterErrorException;
/**
* Class Token
@ -33,7 +33,7 @@ class Token
{
/**
* @return string
* @throws ImportException
* @throws ImporterErrorException
*/
public static function getAccessToken(): string
{
@ -43,14 +43,14 @@ class Token
$value = (string) config('csv_importer.access_token');
}
if ('' === (string) $value) {
throw new ImportException('No valid access token value.');
throw new ImporterErrorException('No valid access token value.');
}
return (string) $value;
}
/**
* @return string
* @throws ImportException
* @throws ImporterErrorException
*/
public static function getVanityURL(): string
{
@ -59,14 +59,14 @@ class Token
$value = self::getURL();
}
if ('' === (string) $value) {
throw new ImportException('No valid URL value.');
throw new ImporterErrorException('No valid URL value.');
}
return (string) $value;
}
/**
* @return string
* @throws ImportException
* @throws ImporterErrorException
*/
public static function getURL(): string
{
@ -76,7 +76,7 @@ class Token
$value = (string) config('csv_importer.url');
}
if ('' === (string) $value) {
throw new ImportException('No valid URL value.');
throw new ImporterErrorException('No valid URL value.');
}
return (string) $value;
}

10
package.json

@ -2,12 +2,12 @@
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --config=node_modules/laravel-mix/setup/webpack.config.js"
"production": "mix --production"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.2.19",

47251
public/js/app.js
File diff suppressed because it is too large
View File

8
resources/js/app.js

@ -28,7 +28,7 @@
require('./bootstrap');
window.Vue = require('vue');
import ImportStatus from "./components/import/ImportStatus";
import ConversionStatus from "./components/import/ConversionStatus";
/**
* The following block of code may be used to automatically register your
@ -41,8 +41,8 @@ import ImportStatus from "./components/import/ImportStatus";
// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
Vue.component('import-messages', require('./components/import/ImportMessages.vue').default);
Vue.component('import-status', ImportStatus);
Vue.component('conversion-messages', require('./components/import/ConversionMessages.vue').default);
Vue.component('conversion-status', ConversionStatus);
/**
* Next, we will create a fresh Vue application instance and attach it to
@ -53,6 +53,6 @@ let props = {};
new Vue({
el: "#app",
render: (createElement) => {
return createElement(ImportStatus, { props: props })
return createElement(ConversionStatus, { props: props })
},
});

2
resources/js/components/import/ImportMessages.vue → resources/js/components/import/ConversionMessages.vue

@ -59,7 +59,7 @@
<script>
export default {
name: "ImportMessages",
name: "ConversionMessages",
props: {
messages: {
type: [Array, Object],

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

@ -0,0 +1,177 @@
<!--
- ImportStatus.vue
- Copyright (c) 2021 james@firefly-iii.org
-
- This file is part of the Firefly III Data Importer
- (https://github.com/firefly-iii/data-importer).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="row mt-3">
<div class="col-lg-10 offset-lg-1">
<div class="card">
<div class="card-header">Data conversion</div>
<div class="card-body" v-if="'waiting_to_start' === this.status && false === this.triedToStart">
<p>
Your CSV file will be converted so it can be imported. Press "start job" to start.
</p>
<p>
<button class="btn btn-success float-end" v-on:click="callStart" type="button">Start job
&rarr;
</button>
</p>
</div>
<div class="card-body" v-if="'waiting_to_start' === this.status && true === this.triedToStart">
<p>Waiting for the job to start..</p>
</div>
<div class="card-body" v-if="'job_running' === this.status">
<p>
Job is running, please wait.
</p>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
aria-valuenow="100" aria-valuemin="0"
aria-valuemax="100" style="width: 100%"></div>
</div>
<conversion-messages
:messages="this.messages"
:warnings="this.warnings"
:errors="this.errors"
></conversion-messages>
</div>
<div class="card-body" v-if="'job_done' === this.status ">
<p>
The import routine has finished 🎉. You can <a :href="this.flushUrl"
class="btn btn-success btn-sm">start a new
import</a>,
<a class="btn btn-info btn-sm" :href="this.downloadUrl" title="Download configuration file.">download
the import configuration</a>
or inspect the results of the import further below:
</p>
<conversion-messages
:messages="this.messages"
:warnings="this.warnings"
:errors="this.errors"
></conversion-messages>
<p>
Thank you for using this tool. <a rel="noopener noreferrer"
href="https://github.com/firefly-iii/firefly-iii/issues"
target="_blank">Please share any feedback you may have</a>.
</p>
</div>
<div class="card-body" v-if="'job_errored' === this.status">
<p class="text-danger">
The job could not be started, or failed due to an error. Please check the log files. Sorry about
this :(.
</p>
<conversion-messages
:messages="this.messages"
:warnings="this.warnings"
:errors="this.errors"
></conversion-messages>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "ConversionStatus",
/*
* The component's data.
*/
data() {
return {
triedToStart: false,
status: '',
messages: [],
warnings: [],
errors: [],
downloadUrl: window.configDownloadUrl,
jobBackUrl: window.jobBackUrl,
flushUrl: window.flushUrl
};
},
props: [],
mounted() {
console.log(`Mounted, check job at ${jobStatusUrl}.`);
this.getJobStatus();
},
methods: {
getJobStatus: function () {
console.log('getJobStatus');
axios.get(jobStatusUrl).then((response) => {
// first try post result:
if (true === this.triedToStart && 'job_errored' === this.status) {
console.error('Job failed!!!');
return;
}
// handle success
this.errors = response.data.errors;
this.warnings = response.data.warnings;
this.messages = response.data.messages;
console.log(`Job status is ${this.status}.`);
if (false === this.triedToStart && 'waiting_to_start' === response.data.status) {
// call to job start.
console.log('Job hasn\'t started yet. Show user some info');
this.status = response.data.status;
return;
}
if (true === this.triedToStart && 'waiting_to_start' === response.data.status) {
console.log('Job hasn\'t started yet, but its been tried.');
}
if (true === this.triedToStart && 'job_errored' === response.data.status) {
console.error('Job failed');
this.status = response.data.status;
return;
}
if ('job_done' === response.data.status) {
console.log('Job is done!');
this.status = response.data.status;
return;
}
if ('job_errored' === response.data.status) {
console.error('Job is kill.');
console.error(response.data);
return;
}
setTimeout(function () {
console.log('Fired on setTimeout');
this.getJobStatus();
}.bind(this), 1000);
});
},
callStart: function () {
console.log('Call job start URL: ' + jobStartUrl);
axios.post(jobStartUrl).then((response) => {
console.log('POST was OK');
this.getJobStatus();
}).catch((error) => {
console.error('JOB HAS FAILED :(');
this.triedToStart = true;
this.status = 'job_errored';
});
this.getJobStatus();
this.triedToStart = true;
},
},
watch: {}
}
</script>

168
resources/js/components/import/ImportStatus.vue

@ -1,168 +0,0 @@
<!--
- ImportStatus.vue
- Copyright (c) 2021 james@firefly-iii.org
-
- This file is part of the Firefly III Data Importer
- (https://github.com/firefly-iii/data-importer).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">Import status</div>
<div class="card-body" v-if="'waiting_to_start' === this.status && false === this.triedToStart">
<p>
The tool is ready to import your data. Press "start job" to start.
<a :href="this.downloadUrl" title="Download configuration file.">
You can download a configuration file of your import</a>, so you can make a quick start the next time you import.
</p>
<div class="row">
<div class="col-lg-6">
<!-- go back to upload -->
<a :href="this.jobBackUrl" class="btn btn-secondary">&larr; Go back</a>
</div>
<div class="col-lg-6">
<button
class="btn btn-success float-right"
v-on:click="callStart" type="button">Start job &rarr;
</button>
</div>
</div>
<p>
</p>
</div>
<div class="card-body" v-if="'waiting_to_start' === this.status && true === this.triedToStart">
<p>Waiting for the job to start..
</p>
</div>
<div class="card-body" v-if="'job_running' === this.status">
<p>
Job is running, please wait.
</p>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0"
aria-valuemax="100" style="width: 100%"></div>
</div>
<import-messages
:messages="this.messages"
:warnings="this.warnings"
:errors="this.errors"
></import-messages>
</div>
<div class="card-body" v-if="'job_done' === this.status ">
<p>
The import routine has finished 🎉. You can <a :href="this.flushUrl" class="btn btn-success btn-sm">start a new import</a>,
<a class="btn btn-info btn-sm" :href="this.downloadUrl" title="Download configuration file.">download the import configuration</a>
or inspect the results of the import further below:
</p>
<import-messages
:messages="this.messages"
:warnings="this.warnings"
:errors="this.errors"
></import-messages>
<p>
Thank you for using this tool. <a rel="noopener noreferrer" href="https://github.com/firefly-iii/firefly-iii/issues" target="_blank">Please share any feedback you may have</a>.
</p>
</div>
<div class="card-body" v-if="'job_errored' === this.status">
<p class="text-danger">
The job could not be started, or failed due to an error. Please check the log files. Sorry about this :(.
</p>
<import-messages
:messages="this.messages"
:warnings="this.warnings"
:errors="this.errors"
></import-messages>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "ImportStatus",
/*
* The component's data.
*/
data() {
return {
triedToStart: false,
status: '',
messages: [],
warnings: [],
errors: [],
downloadUrl: window.configDownloadUrl,
jobBackUrl: window.jobBackUrl,
flushUrl: window.flushUrl
};
},
props: [],
mounted() {
console.log(`Mounted, check job at ${jobStatusUrl}.`);
this.getJobStatus();
},
methods: {
getJobStatus: function () {
console.log('getJobStatus');
axios.get(jobStatusUrl).then((response) => {
// handle success
this.status = response.data.status;
this.errors = response.data.errors;
this.warnings = response.data.warnings;
this.messages = response.data.messages;
console.log(`Job status is ${this.status}.`);
if (false === this.triedToStart && 'waiting_to_start' === this.status) {
// call to job start.
console.log('Job hasn\'t started yet. Show user some info');
return;
}
if (true === this.triedToStart && 'waiting_to_start' === this.status) {
console.log('Job hasn\'t started yet.');
}
if ('job_done' === this.status) {
console.log('Job is done!');
return;
}
if('job_errored' === this.status) {
console.error('Job is kill.');
console.error(response.data);
return;
}
setTimeout(function () {
console.log('Fired on setTimeout');
this.getJobStatus();
}.bind(this), 1000);
});
},
callStart: function () {
console.log('Call job start URL: ' + jobStartUrl);
axios.post(jobStartUrl).then((response) => {
this.getJobStatus();
}).catch((error) => {
this.triedToStart = true;
this.status = 'job_errored';
});
this.getJobStatus();
this.triedToStart = true;
},
},
watch: {}
}
</script>

12
resources/views/import/003-upload/index.twig

@ -94,12 +94,12 @@
</div>
<div class="card mt-3">
<div class="card-body">
<div class="col-6">
<div class="btn-group btn-group-sm">
<a href="{{ route('back.start') }}" class="btn btn-secondary"><span class="fas fa-arrow-left"></span> Go back to
index</a>
<a href="{{ route('flush') }}" class="btn btn-danger"><span class="fas fa-redo-alt"></span> Start over entirely</a>
</div>
<div class="btn-group btn-group-sm">
<a href="{{ route('back.start') }}" class="btn btn-secondary"><span
class="fas fa-arrow-left"></span> Go back to
index</a>
<a href="{{ route('flush') }}" class="btn btn-danger"><span class="fas fa-redo-alt"></span>
Start over entirely</a>
</div>
</div>
</div>

198
resources/views/import/004-configure/index.twig

@ -2,47 +2,70 @@
{% block content %}
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="col-12">
&nbsp;
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="col-lg-10 offset-lg-1">
<h1>{{ mainTitle }}</h1>
<h2>{{ subTitle }}</h2>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<p class="lead">Configure your import.</p>
{% if 'csv' == flow %}
<p>
CSV files come in many shapes and forms. Some of the most important settings are below. They
apply
to all lines in the file, and what to do with double lines or troubles during the import.
If you would like some support, <a href="https://docs.firefly-iii.org/csv/usage/configure/"
target="_blank">check out the documentation for this
page.</a>
</p>
{% endif %}
{% if not errors.isEmpty %}
<p class="text-danger">Some error(s) occurred:</p>
<ul>
{% for error in errors.all %}
<li class="text-danger">{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<hr/>
<div class="row">
<div class="col-lg-12">
<form method="post" action="{{ route('004-configure.post') }}" accept-charset="UTF-8"
id="store">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<div class="col-lg-10 offset-lg-1">
<div class="card">
<div class="card-header">
{{ subTitle }}
</div>
<div class="card-body">
{% if 'csv' == flow %}
<p>
TODO files come in many shapes and forms. Some of the most important settings are below.
They
apply
to all lines in the file, and what to do with double lines or troubles during the
import.
If you would like some support, <a
href="https://docs.firefly-iii.org/csv/usage/configure/"
target="_blank">TODO check out the documentation for this
page.</a>
</p>
{% endif %}
</div>
</div>
</div>
</div>
{% if not errors.isEmpty %}
<div class="row mt-3">
<div class="col-lg-10 offset-lg-1">
<div class="card">
<div class="card-header">
Errors :(
</div>
<div class="card-body">
<p class="text-danger">Some error(s) occurred:</p>
<ul>
{% for error in errors.all %}
<li class="text-danger">{{ error }}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% endif %}
<form method="post" action="{{ route('004-configure.post') }}" accept-charset="UTF-8" id="store">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<div class="row mt-3">
<div class="col-lg-10 offset-lg-1">
<div class="card">
<div class="card-header">
Form
</div>
<div class="card-body">
{% if 'csv' == flow %}
<h4>File options</h4>
<div class="form-group row">
<h5>File options</h5>
<div class="form-group row mb-3">
<div class="col-sm-3">Headers</div>
<div class="col-sm-9">
<div class="form-check">
@ -51,17 +74,34 @@
id="headers" name="headers" value="1" aria-describedby="headersHelp">
<label class="form-check-label" for="headers">
Yes
</label>
</label><br>
<small id="headersHelp" class="form-text text-muted">
Select this checkbox when your CSV file has headers on the first line of
the
the TODO
file.
</small>
</div>
</div>
</div>
<div class="form-group row mb-3">
<div class="col-sm-3">Convert to UTF-8</div>
<div class="col-sm-9">
<div class="form-check">
<input class="form-check-input"
{% if configuration.isConversion %}checked{% endif %} type="checkbox"
id="conversion" name="conversion" value="1"
aria-describedby="conversionHelp">
<label class="form-check-label" for="conversion">
Yes
</label><br>
<small id="conversionHelp" class="form-text text-muted">
Try to convert your TODO file to UTF-8. May lead to weird characters.
</small>
</div>
</div>
</div>
<div class="form-group row">
<div class="form-group row mb-3">
<label for="delimiter" class="col-sm-3 col-form-label">CSV delimiter</label>
<div class="col-sm-9">
<select id="delimiter" name="delimiter" class="form-control"
@ -81,11 +121,12 @@
</select>
<small id="delimiterHelp" class="form-text text-muted">
Select the field separator of our CSV file. This is almost always a comma.
TODO
</small>
</div>
</div>
<div class="form-group row">
<div class="form-group row mb-3">
<label for="date" class="col-sm-3 col-form-label">Date format</label>
<div class="col-sm-9">
<input type="text" name="date" class="form-control" id="date"
@ -107,7 +148,7 @@
<h4>Import options</h4>
<div class="form-group row">
<div class="form-group row mb-3">
<label for="default_account" class="col-sm-3 col-form-label">Default import
account</label>
<div class="col-sm-9">
@ -130,7 +171,7 @@
</small>
</div>
</div>
<div class="form-group row">
<div class="form-group row mb-3">
<div class="col-sm-3">Rules</div>
<div class="col-sm-9">
<div class="form-check">
@ -142,12 +183,12 @@
Yes
</label>
<small id="rulesHelp" class="form-text text-muted">
Select if you want Firefly III to apply your rules to the import.
<br>Select if you want Firefly III to apply your rules to the import.
</small>
</div>
</div>
</div>
<div class="form-group row">
<div class="form-group row mb-3">
<div class="col-sm-3">Import tag</div>
<div class="col-sm-9">
<div class="form-check">
@ -159,22 +200,25 @@
Yes
</label>
<small id="add_import_tagHelp" class="form-text text-muted">
When selected Firefly III will add a tag to each imported transaction
<br>When selected Firefly III will add a tag to each imported transaction
denoting the import; this groups your import under a tag.
</small>
</div>
</div>
</div>
<h4>Duplicate transaction detection</h4>
<p class="text-muted">
Firefly III can automatically detect duplicate transactions. This is pretty foolproof.
In some special cases however,
you want more control over this process. Read more about the options below in <a
href="https://docs.firefly-iii.org/csv/usage/configure/" target="_blank">the
documentation</a>.
</p>
<div class="col-sm-9 offset-sm-3">
<p class="text-muted">
Firefly III can automatically detect duplicate transactions. This is pretty
foolproof.
In some special cases however,
you want more control over this process. Read more about the options below in <a
href="https://docs.firefly-iii.org/csv/usage/configure/" target="_blank">the
documentation</a>.
</p>
</div>
<div class="form-group row">
<div class="form-group row mb-3">
<label for="X" class="col-sm-3 col-form-label">General detection options</label>
<div class="col-sm-9">
<div class="form-check">
@ -185,17 +229,20 @@
<label class="form-check-label" for="ignore_duplicate_lines">
Do not import duplicate lines in the import.
</label>
<br>
<small class="form-text text-muted" id="duplicateHelp">
Whatever method you choose ahead, it's smart to make the importer ignore any
duplicated lines in your CSV file.
</small>
</div>
<small class="form-text text-muted" id="duplicateHelp">
Whatever method you choose ahead, it's smart to make the importer ignore any
duplicated lines in your CSV file.
</small>
</div>
</div>
<div class="form-group row">
<label for="default_account" class="col-sm-3 col-form-label">Detection method</label>
<div class="form-group row mb-3">
<label for="duplicate_detection_method" class="col-sm-3 col-form-label">Detection
method</label>
<div class="col-sm-9">
<select id="duplicate_detection_method" name="duplicate_detection_method"
class="form-control" aria-describedby="duplicate_detection_method_help">
@ -221,7 +268,7 @@
</div>
</div>
{% if 'csv' == flow %}
<div class="form-group row" id="unique_column_index_holder">
<div class="form-group row mb-3" id="unique_column_index_holder">
<label for="unique_column_index" class="col-sm-3 col-form-label">Unique column
index</label>
<div class="col-sm-9">
@ -243,7 +290,7 @@
<div class="col-sm-9">
<select id="unique_column_type" name="unique_column_type" class="form-control"
aria-describedby="unique_column_type_help">
{% for columnType, columnName in config('csv_importer.unique_column_options') %}
{% for columnType, columnName in config('csv.unique_column_options') %}
<option label="{{ columnName }}"
{% if configuration.getUniqueColumnType == columnType %}selected{% endif %}
value="{{ columnType }}">{{ columnName }}</option>
@ -261,7 +308,7 @@
{% endif %}
<h4>Other options</h4>
<div class="form-group row">
<div class="form-group row mb-3">
<div class="col-sm-3">Skip form</div>
<div class="col-sm-9">
<div class="form-check">
@ -272,34 +319,41 @@
Yes
</label>
<small id="skipHelp" class="form-text text-muted">
Skip the options the next time you import and go straight to processing.
<br>Skip the options the next time you import and go straight to processing.
</small>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<!-- go back to upload -->
<a href="{{ route('back.upload') }}" class="btn btn-secondary">&larr; Go back to
upload</a>
<br>
<small class="text-muted">Changes on this page will not be saved.</small>
<br/><br/>
<a href="{{ route('flush') }}" class="btn btn-danger btn-sm">Start over</a>
</div>
<div class="col-lg-6">
<div class="col-lg-12">
<button type="submit" class="float-end btn btn-primary">Submit &rarr;</button>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</form>
<p>&nbsp;</p>
<p>&nbsp;</p>
<div class="row mt-3">
<div class="col-lg-10 offset-lg-1">
<div class="card">
<div class="card-body">
<div class="btn-group btn-group-sm">
<a href="{{ route('back.upload') }}" class="btn btn-secondary"><span
class="fas fa-arrow-left"></span> Go back to upload</a>
<a href="{{ route('flush') }}" class="btn btn-danger btn-sm"><span
class="fas fa-redo-alt"></span> Start over</a>
</div>
</div>
</div>
</div>
</div>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p><p>&nbsp;</p>
{% endblock %}
{% block scripts %}
<script type="text/javascript">

154
resources/views/import/005-roles/index.twig

@ -0,0 +1,154 @@
{% extends "./layout/default" %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-lg-12">
&nbsp;
</div>
</div>
<div class="row">
<div class="col-lg-10 offset-lg-1">
<div class="card">
<div class="card-header">
{{ subTitle }}
</div>
<div class="card-body">
<p>
Set up the the meaning of each column in your file.
</p>
<p>
Each column in your TODO file has a role, it contains a specific type of content.
By configuring these roles here, you tell the importer how to approach and treat
the data in each column. <a target="_blank"
href="https://docs.firefly-iii.org/csv/usage/roles/">TODO Read
the documentation</a> to learn more
about this process.
TODO explain account / opposing account
</p>
</div>
</div>
</div>
</div>
{% if not errors.isEmpty %}
<div class="row mt-3">
<div class="col-lg-10 offset-lg-1">
<div class="card">
<div class="card-header">
Errors :(
</div>
<div class="card-body">
<p class="text-danger">Some error(s) occurred:</p>
<ul>
{% for error in errors.all %}
<li class="text-danger">{{ error }}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% endif %}
<div class="row mt-3">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
Role configuration
</div>
<div class="card-body">
<form method="post" action="{{ route('005-roles.post') }}" accept-charset="UTF-8">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<table class="table">
<tr>
<th>Column</th>
<th>Example data</th>
<th>Role</th>
<th>Map data?</th>
</tr>
{% for index, column in columns %}
<tr>
<td>{{ column }}
{% if index == configuration.getUniqueColumnIndex and 'cell' == configuration.getDuplicateDetectionMethod %}
<br/>
<span class="text-muted small">This is the unique column</span>
{% endif %}</td>
<td>
{% if examples[index]|length > 0 %}
{% for example in examples[index] %}
<pre style="color:#e83e8c;margin-bottom:0;">{{ example }}</pre>
{% endfor %}
{% endif %}
</td>
<td>
{% if index == configuration.getUniqueColumnIndex and 'cell' == configuration.getDuplicateDetectionMethod %}
{#
User cannot select a role because its the unique column so it MUST be this role.
#}
<p class="form-text">
<span class="text-muted small">
This column is your unique identifier, so it will be fixed to
</span>
<code class="small">{{ configuration.getUniqueColumnType }}</code>
</p>
{# smart users can overrule this of course. #}
<input type="hidden" name="roles[{{ index }}]"
value="{{ configuration.getUniqueColumnType }}"/>
{% else %}
<select name="roles[{{ index }}]" id="roles_{{ index }}"
class="form-control">
{% for key, role in roles %}
<option value="{{ key }}"
{% if configuredRoles[index] == key %}selected{% endif %}
label="{{ trans('import.column_'~key) }}">{{ trans('import.column_'~key) }}</option>
{% endfor %}
</select>
{% endif %}
</td>
<td>
<label for="do_mapping_{{ index }}">
{# reverse if statement is pretty sloppy but OK. #}
{% if index == configuration.getUniqueColumnIndex and 'cell' == configuration.getDuplicateDetectionMethod %}
&nbsp;
{% else %}
<input type="checkbox"
{% if configuredDoMapping[index] %}checked{% endif %}
name="do_mapping[{{ index }}]" id="do_mapping_{{ index }}"
value="1"/>
{% endif %}
</label>
</td>
</tr>
{% endfor %}
</table>
<button type="submit" class="float-end btn btn-primary">Submit &rarr;</button>
</form>
</div>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-lg-10 offset-lg-1">
<div class="card">
<div class="card-body">
<div class="btn-group btn-group-sm">
<a href="{{ route('back.config') }}" class="btn btn-secondary"><span class="fas fa-arrow-left"></span> Go back to configuration</a>
<a href="{{ route('flush') }}" class="btn btn-danger btn-sm"><span class="fas fa-redo-alt"></span> Start over</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!--
<div class="row">
<div class="col-lg-6">
</div>
<div class="col-lg-6">
</div>
</div>
-->
{% endblock %}

0
resources/views/import/map/index.twig → resources/views/import/006-mapping/index.twig

50
resources/views/import/007-convert/index.twig

@ -0,0 +1,50 @@
{% extends "./layout/default" %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-lg-12">
&nbsp;
</div>
</div>
<div class="row">
<div class="col-lg-12">
<h1>{{ mainTitle }}</h1>
</div>
</div>
<div id="app">
<conversion-status></conversion-status>
</div>
<div class="row mt-3">
<div class="col-lg-10 offset-lg-1">
<div class="card">
<div class="card-body">
<div class="btn-group btn-group-sm">
<a class="btn btn-danger btn-sm" href="{{ route('flush') }}" data-bs-toggle="tooltip" data-bs-placement="top" title="If the importer seems stuck, you can reset it.">Start over</a>
<a class="btn btn-info text-white btn-sm" href="{{ route('004-configure.download') }}" data-bs-toggle="tooltip" data-bs-placement="top" title="You can download a configuration file of your import, so you can make a quick start the next time you import.">
Download configuration file
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script type="text/javascript">
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})
var jobStatusUrl = '{{ route('007-convert.status') }}?identifier={{ identifier }}';
var jobStartUrl = '{{ route('007-convert.start') }}?identifier={{ identifier }}';
var jobBackUrl = '{{ jobBackUrl }}';
var configDownloadUrl = '{{ route('004-configure.download') }}?identifier={{ identifier }}';
var flushUrl = '{{ route('flush') }}';
</script>
<script src="{{ asset('js/app.js') }}?version={{ version }}"></script>
{% endblock %}

119
resources/views/import/roles/index.twig

@ -1,119 +0,0 @@
{% extends "./layout/default" %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-lg-12">
&nbsp;
</div>
</div>
<div class="row">
<div class="col-lg-12">
<h1>{{ mainTitle }}</h1>
<h2>{{ subTitle }}</h2>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<p class="lead">Set up the the meaning of each column in your file.</p>
<p>
Each column in your CSV file has a role, it contains a specific type of content.
By configuring these roles here, you tell the importer how to approach and treat
the data in each column. <a target="_blank" href="https://docs.firefly-iii.org/csv/usage/roles/">Read the documentation</a> to learn more
about this process.
</p>
{% if not errors.isEmpty %}
<p class="text-danger">Some error(s) occurred:</p>
<ul>
{% for error in errors.all %}
<li class="text-danger">{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<hr/>
<div class="row">
<div class="col-lg-12">
<form method="post" action="{{ route('import.roles.post') }}" accept-charset="UTF-8">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="mapping" value="{{ mapping }}"/>
<table class="table">
<tr>
<th>Column</th>
<th>Example data</th>
<th>Role</th>
<th>Map data?</th>
</tr>
{% for index, column in columns %}
<tr>
<td>{{ column }}
{% if index == configuration.getUniqueColumnIndex and 'cell' == configuration.getDuplicateDetectionMethod %}
<br />
<span class="text-muted small">This is the unique column</span>
{% endif %}</td>
<td>
{% if examples[index]|length > 0 %}
{% for example in examples[index] %}
<pre style="color:#e83e8c;margin-bottom:0;">{{ example }}</pre>
{% endfor %}
{% endif %}
</td>
<td>
{% if index == configuration.getUniqueColumnIndex and 'cell' == configuration.getDuplicateDetectionMethod %}
{#
User cannot select a role because its the unique column so it MUST be this role.
#}
<p class="form-text">
<span class="text-muted small">
This column is your unique identifier, so it will be fixed to
</span>
<code class="small">{{ configuration.getUniqueColumnType }}</code>
</p>
{# smart users can overrule this of course. #}
<input type="hidden" name="roles[{{ index }}]" value="{{ configuration.getUniqueColumnType }}" />
{% else %}
<select name="roles[{{ index }}]" id="roles_{{ index }}" class="form-control">
{% for key, role in roles %}
<option value="{{ key }}" {% if configuredRoles[index] == key %}selected{% endif %}
label="{{ trans('import.column_'~key) }}">{{ trans('import.column_'~key) }}</option>
{% endfor %}
</select>
{% endif %}
</td>
<td>
<label for="do_mapping_{{ index }}">
{# reverse if statement is pretty sloppy but OK. #}
{% if index == configuration.getUniqueColumnIndex and 'cell' == configuration.getDuplicateDetectionMethod %}
&nbsp;
{% else %}
<input type="checkbox" {% if configuredDoMapping[index] %}checked{% endif %} name="do_mapping[{{ index }}]" id="do_mapping_{{ index }}" value="1"/>
{% endif %}
</label>
</td>
</tr>
{% endfor %}
</table>
<p>
&nbsp;
</p>
<div class="row">
<div class="col-lg-6">
<!-- go back to config -->
<a href="{{ route('back.config') }}" class="btn btn-secondary">&larr; Go back to configuration</a>
<br>
<small class="text-muted">Changes on this page will not be saved.</small>
<br /><br />
<a href="{{ route('flush') }}" class="btn btn-danger btn-sm">Start over</a>
</div>
<div class="col-lg-6">
<button type="submit" class="float-end btn btn-primary">Submit &rarr;</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

37
resources/views/import/run/index.twig

@ -1,37 +0,0 @@
{% extends "./layout/default" %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-lg-12">
&nbsp;
</div>
</div>
<div class="row">
<div class="col-lg-12">
<h1>{{ mainTitle }}</h1>
<h2>{{ subTitle }}</h2>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<p>
<small>If the importer seems stuck,
<a href="{{ route('flush') }}" class="text-danger">you can reset it</a>.</small>
</p>
</div>
</div>
<div id="app">
<import-status></import-status>
</div>
</div>
{% endblock %}
{% block scripts %}
<script type="text/javascript">
var jobStatusUrl = '{{ route('import.job.status') }}?identifier={{ identifier }}';
var jobStartUrl = '{{ route('import.job.start') }}?identifier={{ identifier }}';
var jobBackUrl = '{{ jobBackUrl }}';
var configDownloadUrl = '{{ route('import.job.configuration.download') }}?identifier={{ identifier }}';
var flushUrl = '{{ route('flush') }}';
</script>
<script src="{{ asset('js/app.js') }}?version={{ version }}"></script>
{% endblock %}

2
resources/views/index.twig

@ -135,8 +135,6 @@
{% endblock %}
{% block scripts %}
<script type="text/javascript">
// validate Firefly III information:
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)

38
routes/web.php

@ -50,15 +50,30 @@ Route::post('/upload', ['uses' => 'Import\UploadController@upload', 'as' => '003
// step 4: Configure import
Route::get('/import/configure', ['uses' => 'Import\ConfigurationController@index', 'as' => '004-configure.index']);
Route::post('/import/configure', ['uses' => 'Import\ConfigurationController@postIndex', 'as' => '004-configure.post']);
Route::get('/import/configure/download', ['uses' => 'Import\DownloadController@download', 'as' => '004-configure.download']);
Route::get('/import/php_date', ['uses' => 'Import\ConfigurationController@phpDate', 'as' => '004-configure.php_date']);
// step 5: Set column roles (CSV)
Route::get('/import/roles', ['uses' => 'Import\CSV\RoleController@index', 'as' => '005-roles.index']);
Route::post('/import/roles', ['uses' => 'Import\CSV\RoleController@postIndex', 'as' => '005-roles.post']);
// step 6: mapping (CSV)
Route::get('/import/mapping', ['uses' => 'Import\CSV\MapController@index', 'as' => '006-mapping.index']);
Route::post('/import/mapping', ['uses' => 'Import\CSV\MapController@postIndex', 'as' => '006-mapping.post']);
// step 7: convert CSV to JSON transactions (CSV)
Route::get('/import/convert', ['uses' => 'Import\CSV\ConvertController@index', 'as' => '007-convert.index']);
Route::post('/import/convert/start', ['uses' => 'Import\CSV\ConvertController@start', 'as' => '007-convert.start']);
Route::get('/import/convert/status', ['uses' => 'Import\CSV\ConvertController@status', 'as' => '007-convert.status']);
// routes to go back to other steps (also takes care of session vars)
Route::get('/back/start', 'NavController@toStart')->name('back.start');
Route::get('/back/upload', 'NavController@toUpload')->name('back.upload');
//Route::get('/back/config', 'NavController@toConfig')->name('back.config');
//Route::get('/back/roles', 'NavController@toRoles')->name('back.roles');
//Route::get('/back/mapping', 'NavController@toRoles')->name('back.mapping');
Route::get('/back/config', 'NavController@toConfig')->name('back.config');
Route::get('/back/mapping', 'NavController@toRoles')->name('back.mapping');
Route::get('/back/roles', 'NavController@toRoles')->name('back.roles');
//
//// import by POST
//Route::post('/autoimport', 'AutoImportController@index')->name('autoimport');
@ -73,20 +88,9 @@ Route::get('/back/upload', 'NavController@toUpload')->name('back.upload');
//
//// import config helper
//
//// roles
//Route::get('/import/roles', ['uses' => 'Import\RoleController@index', 'as' => 'import.roles.index']);
//Route::post('/import/roles', ['uses' => 'Import\RoleController@postIndex', 'as' => 'import.roles.post']);
//
//// download config:
//Route::get('/configuration/download', ['uses' => 'Import\DownloadController@download', 'as' => 'import.job.configuration.download']);
//
//// mapping
//Route::get('/import/mapping', ['uses' => 'Import\MapController@index', 'as' => 'import.mapping.index']);
//Route::post('/import/mapping', ['uses' => 'Import\MapController@postIndex', 'as' => 'import.mapping.post']);
//
//// run import
//Route::get('/import/run', ['uses' => 'Import\RunController@index', 'as' => 'import.run.index']);
//
//// start import routine.
//Route::any('/import/job/start', ['uses' => 'Import\RunController@start', 'as' => 'import.job.start']);
//Route::get('/import/job/status', ['uses' => 'Import\RunController@status', 'as' => 'import.job.status']);

1
webpack.mix.js

@ -42,4 +42,3 @@ mix.webpackConfig({
});
mix.js('resources/js/app.js', 'public/js').vue({ version: 2 });
mix.sass('resources/sass/app.scss', 'public/css');
Loading…
Cancel
Save