diff --git a/app/Http/Controllers/Import/ConfigurationController.php b/app/Http/Controllers/Import/ConfigurationController.php index 3ef3d16620..a86bd382bf 100644 --- a/app/Http/Controllers/Import/ConfigurationController.php +++ b/app/Http/Controllers/Import/ConfigurationController.php @@ -78,7 +78,9 @@ class ConfigurationController extends Controller return redirect(route('import.status', [$job->key])); } + $this->repository->updateStatus($job, 'configuring'); + $view = $configurator->getNextView(); $data = $configurator->getNextData(); $subTitle = trans('firefly.import_config_bread_crumb'); diff --git a/app/Import/Configuration/FileConfigurator.php b/app/Import/Configuration/FileConfigurator.php index 43d2b63666..7255d0cc54 100644 --- a/app/Import/Configuration/FileConfigurator.php +++ b/app/Import/Configuration/FileConfigurator.php @@ -24,11 +24,12 @@ namespace FireflyIII\Import\Configuration; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Support\Import\Configuration\ConfigurationInterface; use FireflyIII\Support\Import\Configuration\File\Initial; use FireflyIII\Support\Import\Configuration\File\Map; use FireflyIII\Support\Import\Configuration\File\Roles; -use FireflyIII\Support\Import\Configuration\File\Upload; +use FireflyIII\Support\Import\Configuration\File\UploadConfig; use Log; /** @@ -36,9 +37,31 @@ use Log; */ class FileConfigurator implements ConfiguratorInterface { + /** @var array */ + private $defaultConfig + = [ + 'stage' => 'initial', + 'has-headers' => false, // assume + 'date-format' => 'Ymd', // assume + 'delimiter' => ',', // assume + 'import-account' => 0, // none, + 'specifics' => [], // none + 'column-count' => 0, // unknown + 'column-roles' => [], // unknown + 'column-do-mapping' => [], // not yet set which columns must be mapped + 'column-mapping-config' => [], // no mapping made yet. + 'has-config-file' => true, + 'apply-rules' => true, + 'match-bills' => false, + 'auto-start' => false, + ]; /** @var ImportJob */ private $job; + /** @var ImportJobRepositoryInterface */ + private $repository; + + // give job default config: /** @var string */ private $warning = ''; @@ -61,6 +84,9 @@ class FileConfigurator implements ConfiguratorInterface */ public function configureJob(array $data): bool { + if (is_null($this->job)) { + throw new FireflyException('Cannot call configureJob() without a job.'); + } $class = $this->getConfigurationClass(); $job = $this->job; /** @var ConfigurationInterface $object */ @@ -81,6 +107,10 @@ class FileConfigurator implements ConfiguratorInterface */ public function getNextData(): array { + if (is_null($this->job)) { + throw new FireflyException('Cannot call getNextData() without a job.'); + } + $class = $this->getConfigurationClass(); $job = $this->job; /** @var ConfigurationInterface $object */ @@ -97,50 +127,51 @@ class FileConfigurator implements ConfiguratorInterface */ public function getNextView(): string { - if (!$this->job->configuration['has-file-upload']) { - return 'import.file.upload'; + if (is_null($this->job)) { + throw new FireflyException('Cannot call getNextView() without a job.'); } - if (!$this->job->configuration['initial-config-complete']) { - return 'import.file.initial'; + $config = $this->getConfig(); + $stage = $config['stage'] ?? 'initial'; + switch($stage) { + case 'initial': // has nothing, no file upload or anything. + return 'import.file.initial'; + case 'upload-config': // has file, needs file config. + return 'import.file.upload-config'; + case 'roles': // has configured file, needs roles. + return 'import.file.roles'; + case 'map': // has roles, needs mapping. + return 'import.file.map'; } - if (!$this->job->configuration['column-roles-complete']) { - return 'import.file.roles'; - } - if (!$this->job->configuration['column-mapping-complete']) { - return 'import.file.map'; - } - - throw new FireflyException('No view for state'); + throw new FireflyException(sprintf('No view for stage "%s"', $stage)); } /** * Return possible warning to user. * * @return string + * @throws FireflyException */ public function getWarningMessage(): string { + if (is_null($this->job)) { + throw new FireflyException('Cannot call getWarningMessage() without a job.'); + } + return $this->warning; } /** * @return bool + * @throws FireflyException */ public function isJobConfigured(): bool { - $config = $this->job->configuration; - $config['has-file-upload'] = $config['has-file-upload'] ?? false; - $config['initial-config-complete'] = $config['initial-config-complete'] ?? false; - $config['column-roles-complete'] = $config['column-roles-complete'] ?? false; - $config['column-mapping-complete'] = $config['column-mapping-complete'] ?? false; - $this->job->configuration = $config; - $this->job->save(); - - if ($config['initial-config-complete'] - && $config['column-roles-complete'] - && $config['column-mapping-complete'] - && $config['has-file-upload'] - ) { + if (is_null($this->job)) { + throw new FireflyException('Cannot call isJobConfigured() without a job.'); + } + $config = $this->getConfig(); + $stage = $config['stage'] ?? 'initial'; + if ($stage === 'ready') { Log::debug('isJobConfigured returns true'); return true; @@ -155,30 +186,20 @@ class FileConfigurator implements ConfiguratorInterface */ public function setJob(ImportJob $job) { - $this->job = $job; - // give job default config: - $defaultConfig = [ - 'initial-config-complete' => false, - 'has-headers' => false, // assume - 'date-format' => 'Ymd', // assume - 'delimiter' => ',', // assume - 'import-account' => 0, // none, - 'specifics' => [], // none - 'column-count' => 0, // unknown - 'column-roles' => [], // unknown - 'column-do-mapping' => [], // not yet set which columns must be mapped - 'column-roles-complete' => false, // not yet configured roles for columns - 'column-mapping-config' => [], // no mapping made yet. - 'column-mapping-complete' => false, // so mapping is not complete. - 'has-config-file' => true, - 'apply-rules' => true, - 'match-bills' => false, - 'auto-start' => false, - ]; - $config = $this->job->configuration ?? []; - $finalConfig = array_merge($defaultConfig, $config); - $this->job->configuration = $finalConfig; - $this->job->save(); + $this->job = $job; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($job->user); + $this->repository->setConfiguration($job, $this->defaultConfig); + } + + /** + * Short hand method. + * + * @return array + */ + private function getConfig(): array + { + return $this->repository->getConfiguration($this->job); } /** @@ -188,21 +209,22 @@ class FileConfigurator implements ConfiguratorInterface */ private function getConfigurationClass(): string { + $config = $this->getConfig(); + $stage = $config['stage'] ?? 'initial'; + $class = false; + Log::debug(sprintf('Now in getConfigurationClass() for stage "%s"', $stage)); - $class = false; - switch (true) { - case !$this->job->configuration['has-file-upload']: - $class = Upload::class; - break; - case !$this->job->configuration['initial-config-complete']: - Log::debug(sprintf('Class is %s', Initial::class)); - Log::debug(sprintf('initial-config-complete is %s', var_export($this->job->configuration['initial-config-complete'], true))); + switch ($stage) { + case 'initial': // has nothing, no file upload or anything. $class = Initial::class; break; - case !$this->job->configuration['column-roles-complete']: + case 'upload-config': // has file, needs file config. + $class = UploadConfig::class; + break; + case 'roles': // has configured file, needs roles. $class = Roles::class; break; - case !$this->job->configuration['column-mapping-complete']: + case 'map': // has roles, needs mapping. $class = Map::class; break; default: diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index 90aebe515a..c564b4a9b6 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -86,6 +86,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface */ public function findByKey(string $key): ImportJob { + /** @var ImportJob $result */ $result = $this->user->importJobs()->where('key', $key)->first(['import_jobs.*']); if (null === $result) { return new ImportJob; @@ -94,6 +95,23 @@ class ImportJobRepository implements ImportJobRepositoryInterface return $result; } + /** + * Return configuration of job. + * + * @param ImportJob $job + * + * @return array + */ + public function getConfiguration(ImportJob $job): array + { + $config = $job->configuration; + if (is_array($config)) { + return $config; + } + + return []; + } + /** * @param ImportJob $job * @param UploadedFile $file diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php index 6138806ffa..6a6edc5c23 100644 --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php @@ -45,6 +45,15 @@ interface ImportJobRepositoryInterface */ public function findByKey(string $key): ImportJob; + /** + * Return configuration of job. + * + * @param ImportJob $job + * + * @return array + */ + public function getConfiguration(ImportJob $job): array; + /** * @param ImportJob $job * @param UploadedFile $file diff --git a/app/Support/Import/Configuration/File/Initial.php b/app/Support/Import/Configuration/File/Initial.php index d5db1a8992..99fef10c57 100644 --- a/app/Support/Import/Configuration/File/Initial.php +++ b/app/Support/Import/Configuration/File/Initial.php @@ -22,62 +22,40 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\Configuration\File; -use ExpandedForm; -use FireflyIII\Models\AccountType; use FireflyIII\Models\ImportJob; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Support\Import\Configuration\ConfigurationInterface; use Log; /** - * Class CsvInitial. + * Class Initial. */ class Initial implements ConfigurationInterface { - /** - * @var ImportJob - */ + /** @var ImportJob */ private $job; + /** @var string */ + private $warning = ''; + /** + * Get the data necessary to show the configuration screen. + * * @return array */ public function getData(): array { - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - $delimiters = [ - ',' => trans('form.csv_comma'), - ';' => trans('form.csv_semicolon'), - 'tab' => trans('form.csv_tab'), - ]; + $importFileTypes = []; + $defaultImportType = config('import.options.file.default_import_format'); - // update job with default date format: - $config = $this->job->configuration; - if (!isset($config['date-format'])) { - $config['date-format'] = 'Ymd'; - $this->job->configuration = $config; - $this->job->save(); - } - $specifics = []; - - // collect specifics. - foreach (config('csv.import_specifics') as $name => $className) { - $specifics[$name] = [ - 'name' => $className::getName(), - 'description' => $className::getDescription(), - ]; + foreach (config('import.options.file.import_formats') as $type) { + $importFileTypes[$type] = trans('import.import_file_type_' . $type); } - $data = [ - 'accounts' => ExpandedForm::makeSelectList($accounts), - 'specifix' => [], - 'delimiters' => $delimiters, - 'specifics' => $specifics, + return [ + 'default_type' => $defaultImportType, + 'file_types' => $importFileTypes, ]; - - return $data; } /** @@ -87,7 +65,7 @@ class Initial implements ConfigurationInterface */ public function getWarningMessage(): string { - return ''; + return $this->warning; } /** @@ -111,67 +89,28 @@ class Initial implements ConfigurationInterface */ public function storeConfiguration(array $data): bool { - Log::debug('Now in Initial::storeConfiguration()'); - - // get config from job: - $config = $this->job->configuration; - - // find import account: - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $importId = intval($data['csv_import_account'] ?? 0); - $account = $repository->find($importId); - - // set "headers": - $config['initial-config-complete'] = true; - $config['has-headers'] = intval($data['has_headers'] ?? 0) === 1; - $config['date-format'] = $data['date_format']; - $config['delimiter'] = $data['csv_delimiter']; - $config['delimiter'] = 'tab' === $config['delimiter'] ? "\t" : $config['delimiter']; - $config['apply-rules'] = intval($data['apply_rules'] ?? 0) === 1; - $config['match-bills'] = intval($data['match_bills'] ?? 0) === 1; - - Log::debug('Entered import account.', ['id' => $importId]); - - - if (null !== $account->id) { - Log::debug('Found account.', ['id' => $account->id, 'name' => $account->name]); - $config['import-account'] = $account->id; - } - - if (null === $account->id) { - Log::error('Could not find anything for csv_import_account.', ['id' => $importId]); - } - - $config = $this->storeSpecifics($data, $config); - Log::debug('Final config is ', $config); - - $this->job->configuration = $config; + Log::debug('Now in storeConfiguration for file Upload.'); + /** @var ImportJobRepositoryInterface $repository */ + $repository = app(ImportJobRepositoryInterface::class); + $type = $data['import_file_type'] ?? 'unknown'; + $config = $this->job->configuration; + $config['file-type'] = in_array($type, config('import.options.file.import_formats')) ? $type : 'unknown'; + $repository->setConfiguration($this->job, $config); + $uploaded = $repository->processFile($this->job, $data['import_file'] ?? null); $this->job->save(); + Log::debug(sprintf('Result of upload is %s', var_export($uploaded, true))); + // process config, if present: + if (isset($data['configuration_file'])) { + $repository->processConfiguration($this->job, $data['configuration_file']); + } + $config = $this->job->configuration; + $config['has-file-upload'] = $uploaded; + $repository->setConfiguration($this->job, $config); + + if (false === $uploaded) { + $this->warning = 'No valid upload.'; + } return true; } - - /** - * @param array $data - * @param array $config - * - * @return array - */ - private function storeSpecifics(array $data, array $config): array - { - // loop specifics. - if (isset($data['specifics']) && is_array($data['specifics'])) { - $names = array_keys($data['specifics']); - foreach ($names as $name) { - // verify their content. - $className = sprintf('FireflyIII\Import\Specifics\%s', $name); - if (class_exists($className)) { - $config['specifics'][$name] = 1; - } - } - } - - return $config; - } } diff --git a/app/Support/Import/Configuration/File/Upload.php b/app/Support/Import/Configuration/File/Upload.php deleted file mode 100644 index 8b6943489d..0000000000 --- a/app/Support/Import/Configuration/File/Upload.php +++ /dev/null @@ -1,116 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Import\Configuration\File; - -use FireflyIII\Models\ImportJob; -use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Support\Import\Configuration\ConfigurationInterface; -use Log; - -/** - * Class Upload. - */ -class Upload implements ConfigurationInterface -{ - /** @var ImportJob */ - private $job; - - /** @var string */ - private $warning = ''; - - /** - * Get the data necessary to show the configuration screen. - * - * @return array - */ - public function getData(): array - { - $importFileTypes = []; - $defaultImportType = config('import.options.file.default_import_format'); - - foreach (config('import.options.file.import_formats') as $type) { - $importFileTypes[$type] = trans('import.import_file_type_' . $type); - } - - return [ - 'default_type' => $defaultImportType, - 'file_types' => $importFileTypes, - ]; - } - - /** - * Return possible warning to user. - * - * @return string - */ - public function getWarningMessage(): string - { - return $this->warning; - } - - /** - * @param ImportJob $job - * - * @return ConfigurationInterface - */ - public function setJob(ImportJob $job): ConfigurationInterface - { - $this->job = $job; - - return $this; - } - - /** - * Store the result. - * - * @param array $data - * - * @return bool - */ - public function storeConfiguration(array $data): bool - { - Log::debug('Now in storeConfiguration for file Upload.'); - /** @var ImportJobRepositoryInterface $repository */ - $repository = app(ImportJobRepositoryInterface::class); - $type = $data['import_file_type'] ?? 'unknown'; - $config = $this->job->configuration; - $config['file-type'] = in_array($type, config('import.options.file.import_formats')) ? $type : 'unknown'; - $repository->setConfiguration($this->job, $config); - $uploaded = $repository->processFile($this->job, $data['import_file'] ?? null); - $this->job->save(); - Log::debug(sprintf('Result of upload is %s', var_export($uploaded, true))); - // process config, if present: - if (isset($data['configuration_file'])) { - $repository->processConfiguration($this->job, $data['configuration_file']); - } - $config = $this->job->configuration; - $config['has-file-upload'] = $uploaded; - $repository->setConfiguration($this->job, $config); - - if (false === $uploaded) { - $this->warning = 'No valid upload.'; - } - - return true; - } -} diff --git a/app/Support/Import/Configuration/File/UploadConfig.php b/app/Support/Import/Configuration/File/UploadConfig.php new file mode 100644 index 0000000000..f5e8f21f35 --- /dev/null +++ b/app/Support/Import/Configuration/File/UploadConfig.php @@ -0,0 +1,177 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Configuration\File; + +use ExpandedForm; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Import\Configuration\ConfigurationInterface; +use Log; + +/** + * Class UploadConfig. + */ +class UploadConfig implements ConfigurationInterface +{ + /** + * @var ImportJob + */ + private $job; + + /** + * @return array + */ + public function getData(): array + { + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + $delimiters = [ + ',' => trans('form.csv_comma'), + ';' => trans('form.csv_semicolon'), + 'tab' => trans('form.csv_tab'), + ]; + + // update job with default date format: + $config = $this->job->configuration; + if (!isset($config['date-format'])) { + $config['date-format'] = 'Ymd'; + $this->job->configuration = $config; + $this->job->save(); + } + $specifics = []; + + // collect specifics. + foreach (config('csv.import_specifics') as $name => $className) { + $specifics[$name] = [ + 'name' => $className::getName(), + 'description' => $className::getDescription(), + ]; + } + + $data = [ + 'accounts' => ExpandedForm::makeSelectList($accounts), + 'specifix' => [], + 'delimiters' => $delimiters, + 'specifics' => $specifics, + ]; + + return $data; + } + + /** + * Return possible warning to user. + * + * @return string + */ + public function getWarningMessage(): string + { + return ''; + } + + /** + * @param ImportJob $job + * + * @return ConfigurationInterface + */ + public function setJob(ImportJob $job): ConfigurationInterface + { + $this->job = $job; + + return $this; + } + + /** + * Store the result. + * + * @param array $data + * + * @return bool + */ + public function storeConfiguration(array $data): bool + { + Log::debug('Now in Initial::storeConfiguration()'); + + // get config from job: + $config = $this->job->configuration; + + // find import account: + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $importId = intval($data['csv_import_account'] ?? 0); + $account = $repository->find($importId); + + // set "headers": + $config['initial-config-complete'] = true; + $config['has-headers'] = intval($data['has_headers'] ?? 0) === 1; + $config['date-format'] = $data['date_format']; + $config['delimiter'] = $data['csv_delimiter']; + $config['delimiter'] = 'tab' === $config['delimiter'] ? "\t" : $config['delimiter']; + $config['apply-rules'] = intval($data['apply_rules'] ?? 0) === 1; + $config['match-bills'] = intval($data['match_bills'] ?? 0) === 1; + + Log::debug('Entered import account.', ['id' => $importId]); + + + if (null !== $account->id) { + Log::debug('Found account.', ['id' => $account->id, 'name' => $account->name]); + $config['import-account'] = $account->id; + } + + if (null === $account->id) { + Log::error('Could not find anything for csv_import_account.', ['id' => $importId]); + } + + $config = $this->storeSpecifics($data, $config); + Log::debug('Final config is ', $config); + + $this->job->configuration = $config; + $this->job->save(); + + return true; + } + + /** + * @param array $data + * @param array $config + * + * @return array + */ + private function storeSpecifics(array $data, array $config): array + { + // loop specifics. + if (isset($data['specifics']) && is_array($data['specifics'])) { + $names = array_keys($data['specifics']); + foreach ($names as $name) { + // verify their content. + $className = sprintf('FireflyIII\Import\Specifics\%s', $name); + if (class_exists($className)) { + $config['specifics'][$name] = 1; + } + } + } + + return $config; + } +} diff --git a/resources/views/import/file/initial.twig b/resources/views/import/file/initial.twig index fed3029dc1..8f07bfe1d1 100644 --- a/resources/views/import/file/initial.twig +++ b/resources/views/import/file/initial.twig @@ -1,20 +1,18 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.render(Route.getCurrentRoute.getName, job) }} + {{ Breadcrumbs.render }} {% endblock %} - {% block content %} -
- {{ trans('import.csv_initial_text') }} + {{ trans('import.file_upload_text') }}
+ {{ trans('import.csv_initial_text') }} +
+- {{ trans('import.file_upload_text') }} -
-