diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php new file mode 100644 index 0000000000..8a139083df --- /dev/null +++ b/app/Console/Commands/Import.php @@ -0,0 +1,85 @@ +argument('key'); + $job = ImportJob::whereKey($jobKey)->first(); + if (is_null($job)) { + $this->error('This job does not seem to exist.'); + + return; + } + + if ($job->status != 'settings_complete') { + $this->error('This job is not ready to be imported.'); + + return; + } + + $this->line('Going to import job with key "' . $job->key . '" of type ' . $job->file_type); + $class = config('firefly.import_formats.' . $job->file_type); + /** @var ImporterInterface $importer */ + $importer = app($class); + $importer->setJob($job); + // intercept logging by importer. + $monolog = Log::getMonolog(); + $handler = new CommandHandler($this); + $monolog->pushHandler($handler); + $importer->start(); + + + $this->line('Something something import: ' . $jobKey); + } +} diff --git a/app/Console/Commands/UpgradeFireflyInstructions.php b/app/Console/Commands/UpgradeFireflyInstructions.php index 4535d17a7f..7881f87646 100644 --- a/app/Console/Commands/UpgradeFireflyInstructions.php +++ b/app/Console/Commands/UpgradeFireflyInstructions.php @@ -25,7 +25,7 @@ class UpgradeFireflyInstructions extends Command * * @var string */ - protected $description = 'Command description'; + protected $description = 'Instructions in case of upgrade trouble.'; /** * The name and signature of the console command. * diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index ea6b228eea..5e9147a429 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -11,6 +11,7 @@ declare(strict_types = 1); namespace FireflyIII\Console; +use FireflyIII\Console\Commands\Import; use FireflyIII\Console\Commands\UpgradeFireflyInstructions; use FireflyIII\Console\Commands\VerifyDatabase; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -51,5 +52,6 @@ class Kernel extends ConsoleKernel = [ UpgradeFireflyInstructions::class, VerifyDatabase::class, + Import::class, ]; } diff --git a/app/Import/Converter/AssetAccountIban.php b/app/Import/Converter/AssetAccountIban.php new file mode 100644 index 0000000000..a0fcd9e40d --- /dev/null +++ b/app/Import/Converter/AssetAccountIban.php @@ -0,0 +1,48 @@ +user]); + + + if (isset($this->mapping[$value])) { + Log::debug('Found account in mapping. Should exist.',['value' => $value]); + $account = $repository->find(intval($value)); + Log::debug('Found account ', ['id' => $account->id]); + + } + + Log::debug('Given map is ', $this->mapping); + + exit; + } +} \ No newline at end of file diff --git a/app/Import/Converter/BasicConverter.php b/app/Import/Converter/BasicConverter.php new file mode 100644 index 0000000000..29e5c70924 --- /dev/null +++ b/app/Import/Converter/BasicConverter.php @@ -0,0 +1,57 @@ +doMap = $doMap; + } + + /** + * @param array $mapping + * + */ + public function setMapping(array $mapping) + { + $this->mapping = $mapping; + } + + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + } +} \ No newline at end of file diff --git a/app/Import/Converter/ConverterInterface.php b/app/Import/Converter/ConverterInterface.php new file mode 100644 index 0000000000..c0d9b187fe --- /dev/null +++ b/app/Import/Converter/ConverterInterface.php @@ -0,0 +1,43 @@ +job = $job; } + /** + * Run the actual import + * + * @return bool + */ + public function start(): bool + { + $config = $this->job->configuration; + $content = $this->job->uploadFileContents(); + + // create CSV reader. + $reader = Reader::createFromString($content); + $start = $config['has-headers'] ? 1 : 0; + $results = $reader->fetch(); + foreach ($results as $index => $row) { + if ($index >= $start) { + Log::debug(sprintf('Now going to import row %d.', $index)); + $this->importSingleRow($row); + } + } + + Log::debug('This call should be intercepted somehow.'); + + return true; + } + /** * Store the settings filled in by the user, if applicable. * @@ -387,4 +415,44 @@ class CsvImporter implements ImporterInterface } + + /** + * @param array $row + * + * @return bool + */ + private function importSingleRow(array $row): bool + { + $object = new ImportEntry; + $config = $this->job->configuration; + + foreach ($row as $index => $value) { + // find the role for this column: + $role = $config['column-roles'][$index] ?? '_ignore'; + $doMap = $config['column-do-mapping'][$index] ?? false; + $converterClass = config('csv.import_roles.' . $role . '.converter'); + $mapping = $config['column-mapping-config'][$index] ?? []; + /** @var ConverterInterface $converter */ + $converter = app('FireflyIII\\Import\\Converter\\' . $converterClass); + // set some useful values for the converter: + $converter->setMapping($mapping); + $converter->setDoMap($doMap); + $converter->setUser($this->job->user); + + // run the converter for this value: + $convertedValue = $converter->convert($value); + + // log it. + Log::debug('Value ', ['index' => $index, 'value' => $value, 'role' => $role]); + + // store in import entry: + // $object->fromRawValue($role, $value); + + + } + + exit; + + return true; + } } \ No newline at end of file diff --git a/app/Import/Importer/ImporterInterface.php b/app/Import/Importer/ImporterInterface.php index 6e332da8fe..66b72a6f08 100644 --- a/app/Import/Importer/ImporterInterface.php +++ b/app/Import/Importer/ImporterInterface.php @@ -23,6 +23,14 @@ use Symfony\Component\HttpFoundation\FileBag; */ interface ImporterInterface { + + /** + * Run the actual import + * + * @return bool + */ + public function start(): bool; + /** * After uploading, and after setJob(), prepare anything that is * necessary for the configure() line. diff --git a/app/Import/Logging/CommandHandler.php b/app/Import/Logging/CommandHandler.php new file mode 100644 index 0000000000..6b4d3106c1 --- /dev/null +++ b/app/Import/Logging/CommandHandler.php @@ -0,0 +1,49 @@ +command = $command; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * + * @return void + */ + protected function write(array $record) + { + $this->command->line((string) $record['formatted']); + } +} \ No newline at end of file diff --git a/app/Import/Mapper/AssetAccountIbans.php b/app/Import/Mapper/AssetAccountIbans.php new file mode 100644 index 0000000000..d3a4c246e6 --- /dev/null +++ b/app/Import/Mapper/AssetAccountIbans.php @@ -0,0 +1,56 @@ +getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + $topList = []; + $list = []; + + /** @var Account $account */ + foreach ($set as $account) { + $iban = $account->iban ?? ''; + if (strlen($iban) > 0) { + $topList[$account->id] = $account->iban . ' (' . $account->name . ')'; + } + if (strlen($iban) == 0) { + $list[$account->id] = $account->name; + } + } + asort($topList); + asort($list); + + $list = $topList + $list; + $list = [0 => trans('csv.do_not_map')] + $list; + + return $list; + + } +} \ No newline at end of file diff --git a/app/Import/Mapper/OpposingAccountIbans.php b/app/Import/Mapper/OpposingAccountIbans.php new file mode 100644 index 0000000000..f788345483 --- /dev/null +++ b/app/Import/Mapper/OpposingAccountIbans.php @@ -0,0 +1,61 @@ +getAccountsByType( + [ + AccountType::DEFAULT, AccountType::ASSET, + AccountType::EXPENSE, AccountType::BENEFICIARY, + AccountType::REVENUE + ]); + $topList = []; + $list = []; + + /** @var Account $account */ + foreach ($set as $account) { + $iban = $account->iban ?? ''; + if (strlen($iban) > 0) { + $topList[$account->id] = $account->iban . ' (' . $account->name . ')'; + } + if (strlen($iban) == 0) { + $list[$account->id] = $account->name; + } + } + asort($topList); + asort($list); + + $list = $topList + $list; + $list = [0 => trans('csv.do_not_map')] + $list; + + + return $list; + } +} \ No newline at end of file diff --git a/config/csv.php b/config/csv.php index 3481ca2769..20fb48a91d 100644 --- a/config/csv.php +++ b/config/csv.php @@ -135,7 +135,7 @@ return [ 'mappable' => true, 'field' => 'asset-account-iban', 'converter' => 'AssetAccountIban', - 'mapper' => 'AssetAccounts', + 'mapper' => 'AssetAccountIbans', ], 'account-number' => [ @@ -160,7 +160,7 @@ return [ 'mappable' => true, 'field' => 'opposing-account-iban', 'converter' => 'OpposingAccountIban', - 'mapper' => 'OpposingAccounts', + 'mapper' => 'OpposingAccountIbans', ], 'opposing-number' => [ 'mappable' => true, diff --git a/resources/lang/en_US/csv.php b/resources/lang/en_US/csv.php index 3f2003ff22..ae51cf1f68 100644 --- a/resources/lang/en_US/csv.php +++ b/resources/lang/en_US/csv.php @@ -33,6 +33,15 @@ return [ 'no_example_data' => 'No example data available', 'store_column_roles' => 'Continue import', 'do_not_map' => '(do not map)', + 'map_title' => 'Connect data in your files', + 'map_text' => 'Connect data in your files', + + 'field_value' => 'Field value', + 'field_mapped_to' => 'Mapped to', + 'store_column_mapping' => 'Store mapping', + + // map things. + 'column__ignore' => '(ignore this column)', 'column_account-iban' => 'Asset account (IBAN)', diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index ecb93831af..c7982f10b1 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -759,4 +759,8 @@ return [ 'configure_import' => 'Further configure your import', 'import_finish_configuration' => 'Finish configuration', 'settings_for_import' => 'Settings', + 'import_complete' => 'Import configuration complete!', + 'import_complete_text' => 'Download the config file. You can also run it from the command line.', + 'import_download_config' => 'Download configuration', + 'import_start_import' => 'Start import', ]; diff --git a/resources/views/import/complete.twig b/resources/views/import/complete.twig index 0a7aaaff7b..e7e7a82629 100644 --- a/resources/views/import/complete.twig +++ b/resources/views/import/complete.twig @@ -14,12 +14,15 @@

{{ 'import_complete_text'|_ }}

+

+ php artisan firefly:import {{ job.key }} +

-
- {{ 'import_download_config' }} + -
diff --git a/resources/views/import/csv/configure.twig b/resources/views/import/csv/configure.twig index 0cbab054f3..fd3949e778 100644 --- a/resources/views/import/csv/configure.twig +++ b/resources/views/import/csv/configure.twig @@ -32,44 +32,47 @@

{{ trans('csv.import_configure_form') }}

+
+
- {{ ExpandedForm.checkbox('has_headers',1,job.configuration['has-headers'],{helpText: trans('csv.header_help')}) }} - {{ ExpandedForm.text('date_format',job.configuration['date-format'],{helpText: trans('csv.date_help', {dateExample: phpdate('Ymd')}) }) }} - {{ ExpandedForm.select('csv_delimiter', data.delimiters, job.configuration['delimiter'], {helpText: trans('csv.delimiter_help') } ) }} - {{ ExpandedForm.select('csv_import_account', data.accounts, 0, {helpText: trans('csv.import_account_help')} ) }} + {{ ExpandedForm.checkbox('has_headers',1,job.configuration['has-headers'],{helpText: trans('csv.header_help')}) }} + {{ ExpandedForm.text('date_format',job.configuration['date-format'],{helpText: trans('csv.date_help', {dateExample: phpdate('Ymd')}) }) }} + {{ ExpandedForm.select('csv_delimiter', data.delimiters, job.configuration['delimiter'], {helpText: trans('csv.delimiter_help') } ) }} + {{ ExpandedForm.select('csv_import_account', data.accounts, 0, {helpText: trans('csv.import_account_help')} ) }} - {% for type, specific in data.specifics %} -
- - -
-
+ {% endfor %} + + {% if not data.is_upload_possible %} +
+
+   +
+ +
+
{{ data.upload_path }}
+

+ {{ trans('csv.upload_not_writeable') }} +

+
+
+ {% endif %}
- {% endfor %} - - {% if not data.is_upload_possible %} -
-
-   -
- -
-
{{ data.upload_path }}
-

- {{ trans('csv.upload_not_writeable') }} -

-
-
- {% endif %} - +
diff --git a/resources/views/import/csv/map.twig b/resources/views/import/csv/map.twig index 7119e382aa..5f21f1a9d0 100644 --- a/resources/views/import/csv/map.twig +++ b/resources/views/import/csv/map.twig @@ -31,7 +31,7 @@
-

{{ field.name }}

+

{{ trans('csv.column_'~field.name) }}