From a56a5fc2288911ee1800be8859768c6b6c36f11c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 2 Jul 2016 17:33:57 +0200 Subject: [PATCH 1/6] New code for import routine. --- app/Http/Controllers/ImportController.php | 70 ++++++++- app/Import/Importer/CsvImporter.php | 137 +++++++++++++----- app/Import/Importer/ImporterInterface.php | 17 ++- config/csv.php | 10 +- resources/lang/en_US/csv.php | 85 +++++++---- resources/lang/en_US/firefly.php | 16 +- resources/lang/en_US/form.php | 2 +- resources/views/import/csv/configure.twig | 24 ++- .../views/import/csv/{map.twig => roles.twig} | 29 ++-- resources/views/import/index.twig | 1 + resources/views/index.twig | 2 +- 11 files changed, 264 insertions(+), 129 deletions(-) rename resources/views/import/csv/{map.twig => roles.twig} (65%) diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 3416a5a6fd..c3c1763bc9 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -10,8 +10,10 @@ use FireflyIII\Import\Importer\ImporterInterface; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use Illuminate\Http\Request; +use Log; use SplFileObject; use Storage; +use Symfony\Component\HttpFoundation\File\UploadedFile; use View; /** @@ -50,9 +52,11 @@ class ImportController extends Controller // actual code $importer = $this->makeImporter($job); $importer->configure(); - $data = $importer->getConfigurationData(); + $data = $importer->getConfigurationData(); + $subTitle = trans('firefly.configure_import'); + $subTitleIcon = 'fa-wrench'; - return view('import.' . $job->file_type . '.configure', compact('data', 'job')); + return view('import.' . $job->file_type . '.configure', compact('data', 'job', 'subTitle', 'subTitleIcon')); } @@ -106,6 +110,28 @@ class ImportController extends Controller return redirect(route('import.settings', $job->key)); } + /** + * This step 6. Depending on the importer, this will process the + * settings given and store them. + * + * @param Request $request + * @param ImportJob $job + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws FireflyException + */ + public function postSettings(Request $request, ImportJob $job) + { + if (!$this->jobInCorrectStep($job, 'store-settings')) { + return $this->redirectToCorrectStep($job); + } + $importer = $this->makeImporter($job); + $importer->storeSettings($request); + + // return redirect to settings (for more settings perhaps) + return redirect(route('import.settings', [$job->key])); + } + /** * Step 5. Depending on the importer, this will show the user settings to * fill in. @@ -120,18 +146,20 @@ class ImportController extends Controller if (!$this->jobInCorrectStep($job, 'settings')) { return $this->redirectToCorrectStep($job); } - $importer = $this->makeImporter($job); + $importer = $this->makeImporter($job); + $subTitle = trans('firefy.settings_for_import'); + $subTitleIcon = 'fa-wrench'; // now show settings screen to user. if ($importer->requireUserSettings()) { $data = $importer->getDataForSettings(); $view = $importer->getViewForSettings(); - return view($view, compact('data', 'job')); + return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon')); } // if no more settings, save job and continue to process thing. - + echo 'now in settings (done)'; exit; @@ -155,8 +183,11 @@ class ImportController extends Controller public function upload(ImportUploadRequest $request, ImportJobRepositoryInterface $repository) { // create import job: - $type = $request->get('import_file_type'); - $job = $repository->create($type); + $type = $request->get('import_file_type'); + $job = $repository->create($type); + Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]); + + /** @var UploadedFile $upload */ $upload = $request->files->get('import_file'); $newName = $job->key . '.upload'; $uploaded = new SplFileObject($upload->getRealPath()); @@ -165,6 +196,30 @@ class ImportController extends Controller $disk = Storage::disk('upload'); $disk->put($newName, $contentEncrypted); + Log::debug('Uploaded file', ['name' => $upload->getClientOriginalName(), 'size' => $upload->getSize(), 'mime' => $upload->getClientMimeType()]); + + // store configuration file's content into the job's configuration + // thing. + // otherwise, leave it empty. + if ($request->files->has('configuration_file')) { + /** @var UploadedFile $configFile */ + $configFile = $request->files->get('configuration_file'); + Log::debug( + 'Uploaded configuration file', + ['name' => $configFile->getClientOriginalName(), 'size' => $configFile->getSize(), 'mime' => $configFile->getClientMimeType()] + ); + + $configFileObject = new SplFileObject($configFile->getRealPath()); + $configRaw = $configFileObject->fread($configFileObject->getSize()); + $configuration = json_decode($configRaw, true); + + if (!is_null($configuration) && is_array($configuration)) { + Log::debug('Found configuration', $configuration); + $job->configuration = $configuration; + $job->save(); + } + } + return redirect(route('import.configure', [$job->key])); } @@ -183,6 +238,7 @@ class ImportController extends Controller return $job->status === 'import_status_never_started'; break; case 'settings': + case 'store-settings': return $job->status === 'import_configuration_saved'; break; } diff --git a/app/Import/Importer/CsvImporter.php b/app/Import/Importer/CsvImporter.php index ece4c9e527..7a592442a4 100644 --- a/app/Import/Importer/CsvImporter.php +++ b/app/Import/Importer/CsvImporter.php @@ -14,10 +14,11 @@ namespace FireflyIII\Import\Importer; use ExpandedForm; use FireflyIII\Crud\Account\AccountCrud; -use FireflyIII\Import\Role\Map; use FireflyIII\Models\AccountType; use FireflyIII\Models\ImportJob; +use Illuminate\Http\Request; use League\Csv\Reader; +use Log; use Symfony\Component\HttpFoundation\FileBag; /** @@ -32,11 +33,38 @@ class CsvImporter implements ImporterInterface public $job; /** + * Create initial (empty) configuration array. + * + * + * * @return bool */ public function configure(): bool { + if (is_null($this->job->configuration) || (is_array($this->job->configuration) && count($this->job->configuration) === 0)) { + Log::debug('No config detected, will create empty one.'); + + $config = [ + '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. + ]; + $this->job->configuration = $config; + $this->job->save(); + + return true; + } + // need to do nothing, for now. + Log::debug('Detected config in upload, will use that one. ', $this->job->configuration); return true; } @@ -89,15 +117,17 @@ class CsvImporter implements ImporterInterface 'columnCount' => 0, ]; - if (!isset($config['columns'])) { + if ($this->doColumnRoles()) { - // show user column configuration. + // show user column role configuration. $content = $this->job->uploadFileContents(); // create CSV reader. $reader = Reader::createFromString($content); - $start = $config['has_headers'] ? 1 : 0; + $start = $config['has-headers'] ? 1 : 0; $end = $start + self::EXAMPLE_ROWS; // first X rows + + // collect example data in $data['columns'] while ($start < $end) { $row = $reader->fetchOne($start); foreach ($row as $index => $value) { @@ -110,20 +140,28 @@ class CsvImporter implements ImporterInterface $data['columnCount'] = count($row); } - // make unique + // make unique example data foreach ($data['columns'] as $index => $values) { $data['columns'][$index] = array_unique($values); } - // TODO preset roles from config + $data['set_roles'] = []; // collect possible column roles: $data['available_roles'] = []; foreach (array_keys(config('csv.import_roles')) as $role) { - $data['available_roles'][$role] = trans('csv.csv_column_'.$role); + $data['available_roles'][$role] = trans('csv.column_' . $role); } + $config['column-count'] = $data['columnCount']; + $this->job->configuration = $config; + $this->job->save(); + return $data; } + + + echo 'no settings to do.'; + exit; } @@ -135,18 +173,10 @@ class CsvImporter implements ImporterInterface */ public function getViewForSettings(): string { - return 'import.csv.map'; - } - - /** - * Returns a Map thing used to allow the user to - * define roles for each entry. - * - * @return Map - */ - public function prepareRoles(): Map - { - return 'do not work'; + if ($this->doColumnRoles()) { + return 'import.csv.roles'; + } + echo 'no view for settings'; exit; } @@ -174,33 +204,25 @@ class CsvImporter implements ImporterInterface */ public function saveImportConfiguration(array $data, FileBag $files): bool { - /* - * TODO file upload is ignored for now. - */ - /** @var AccountCrud $repository */ - $repository = app(AccountCrud::class); - $account = $repository->find(intval($data['csv_import_account'])); - $hasHeaders = isset($data['has_headers']) && intval($data['has_headers']) === 1 ? true : false; - $configuration = [ - 'has_headers' => $hasHeaders, - 'date_format' => $data['date_format'], - 'csv_delimiter' => $data['csv_delimiter'], - 'csv_import_account' => 0, - 'specifics' => [], - - ]; + $repository = app(AccountCrud::class); + $account = $repository->find(intval($data['csv_import_account'])); + $hasHeaders = isset($data['has_headers']) && intval($data['has_headers']) === 1 ? true : false; + $config = $this->job->configuration; + $config['has-headers'] = $hasHeaders; + $config['date-format'] = $data['date_format']; + $config['delimiter'] = $data['csv_delimiter']; if (!is_null($account->id)) { - $configuration['csv_import_account'] = $account->id; + $config['import-account'] = $account->id; } // loop specifics. - if (is_array($data['specifics'])) { + if (isset($data['specifics']) && is_array($data['specifics'])) { foreach ($data['specifics'] as $name => $enabled) { - $configuration['specifics'][] = $name; + $config['specifics'][$name] = 1; } } - $this->job->configuration = $configuration; + $this->job->configuration = $config; $this->job->save(); return true; @@ -215,4 +237,43 @@ class CsvImporter implements ImporterInterface { $this->job = $job; } + + /** + * Store the settings filled in by the user, if applicable. + * + * @param Request $request + * + */ + public function storeSettings(Request $request) + { + $config = $this->job->configuration; + $count = $config['column-count']; + $all = $request->all(); + $roleSet = 0; + for ($i = 0; $i < $count; $i++) { + $selectedRole = $all['role'][$i] ?? '_ignore'; + $doMapping = isset($all['map'][$i]) && $all['map'][$i] == '1' ? true : false; + if ($selectedRole == '_ignore' && $doMapping === true) { + $doMapping = false; // cannot map ignored columns. + } + if ($selectedRole != '_ignore') { + $roleSet++; + } + $config['column-roles'][$i] = $selectedRole; + $config['column-do-mapping'][$i] = $doMapping; + } + if ($roleSet > 0) { + $config['column-roles-complete'] = true; + $this->job->configuration = $config; + $this->job->save(); + } + } + + /** + * @return bool + */ + private function doColumnRoles(): bool + { + return $this->job->configuration['column-roles-complete'] === false; + } } \ No newline at end of file diff --git a/app/Import/Importer/ImporterInterface.php b/app/Import/Importer/ImporterInterface.php index 45135fc26b..6e332da8fe 100644 --- a/app/Import/Importer/ImporterInterface.php +++ b/app/Import/Importer/ImporterInterface.php @@ -13,6 +13,7 @@ namespace FireflyIII\Import\Importer; use FireflyIII\Import\Role\Map; use FireflyIII\Models\ImportJob; +use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\FileBag; /** @@ -44,6 +45,14 @@ interface ImporterInterface */ public function getDataForSettings(): array; + /** + * Store the settings filled in by the user, if applicable. + * + * @param Request $request + * + */ + public function storeSettings(Request $request); + /** * This method returns the name of the view that will be shown to the user to further configure * the import job. @@ -52,14 +61,6 @@ interface ImporterInterface */ public function getViewForSettings(): string; - /** - * Returns a Map thing used to allow the user to - * define roles for each entry. - * - * @return Map - */ - public function prepareRoles(): Map; - /** * This method returns whether or not the user must configure this import * job further. diff --git a/config/csv.php b/config/csv.php index 3622624496..e235133f32 100644 --- a/config/csv.php +++ b/config/csv.php @@ -171,11 +171,11 @@ return [ 'converter' => 'Amount', 'field' => 'amount', ], - 'amount-comma-separated' => [ - 'mappable' => false, - 'converter' => 'AmountComma', - 'field' => 'amount', - ], +// 'amount-comma-separated' => [ +// 'mappable' => false, +// 'converter' => 'AmountComma', +// 'field' => 'amount', +// ], 'sepa-ct-id' => [ 'mappable' => false, 'converter' => 'Description', diff --git a/resources/lang/en_US/csv.php b/resources/lang/en_US/csv.php index f9878e6938..c27e945dce 100644 --- a/resources/lang/en_US/csv.php +++ b/resources/lang/en_US/csv.php @@ -10,35 +10,58 @@ declare(strict_types = 1); return [ - 'csv_column__ignore' => '(ignore this column)', - 'csv_column_account-iban' => 'Asset account (IBAN)', - 'csv_column_account-id' => 'Asset account ID (matching Firefly)', - 'csv_column_account-name' => 'Asset account (name)', - 'csv_column_amount' => 'Amount', - 'csv_column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'csv_column_bill-id' => 'Bill ID (matching Firefly)', - 'csv_column_bill-name' => 'Bill name', - 'csv_column_budget-id' => 'Budget ID (matching Firefly)', - 'csv_column_budget-name' => 'Budget name', - 'csv_column_category-id' => 'Category ID (matching Firefly)', - 'csv_column_category-name' => 'Category name', - 'csv_column_currency-code' => 'Currency code (ISO 4217)', - 'csv_column_currency-id' => 'Currency ID (matching Firefly)', - 'csv_column_currency-name' => 'Currency name (matching Firefly)', - 'csv_column_currency-symbol' => 'Currency symbol (matching Firefly)', - 'csv_column_date-rent' => 'Rent calculation date', - 'csv_column_date-transaction' => 'Date', - 'csv_column_description' => 'Description', - 'csv_column_opposing-iban' => 'Opposing account (IBAN)', - 'csv_column_opposing-id' => 'Opposing account ID (matching Firefly)', - 'csv_column_opposing-name' => 'Opposing account (name)', - 'csv_column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', - 'csv_column_ing-debet-credit' => 'ING specific debet/credit indicator', - 'csv_column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'csv_column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', - 'csv_column_sepa-db' => 'SEPA Direct Debet', - 'csv_column_tags-comma' => 'Tags (comma separated)', - 'csv_column_tags-space' => 'Tags (space separated)', - 'csv_column_account-number' => 'Asset account (account number)', - 'csv_column_opposing-number' => 'Opposing account (account number)', + + 'import_configure_title' => 'Configure your import', + 'import_configure_intro' => 'There are some options for your CSV import.', + 'import_configure_form' => 'Form', + 'header_help' => 'Check this if the first row of your CSV file are the column titles', + 'date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'config_file_help' => 'Select your CSV import configuration here. If you do not know what this is, ignore it. It will be explained later.', + 'import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'upload_not_writeable' => 'The grey box contains a file path. It should be writeable. Please make sure it is.', + + // roles + 'column_roles_title' => 'Define column roles', + 'column_roles_text' => 'Each column contains some data. What data?', + 'column_roles_table' => 'Table', + 'column_name' => 'Name of column', + 'column_example' => 'Column example data', + 'column_role' => 'Column data meaning', + 'do_map_value' => 'Map these values', + 'column' => 'Column', + 'no_example_data' => 'No example data available', + 'store_column_roles' => 'Continue import', + + 'column__ignore' => '(ignore this column)', + 'column_account-iban' => 'Asset account (IBAN)', + 'column_account-id' => 'Asset account ID (matching Firefly)', + 'column_account-name' => 'Asset account (name)', + 'column_amount' => 'Amount', + 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', + 'column_bill-id' => 'Bill ID (matching Firefly)', + 'column_bill-name' => 'Bill name', + 'column_budget-id' => 'Budget ID (matching Firefly)', + 'column_budget-name' => 'Budget name', + 'column_category-id' => 'Category ID (matching Firefly)', + 'column_category-name' => 'Category name', + 'column_currency-code' => 'Currency code (ISO 4217)', + 'column_currency-id' => 'Currency ID (matching Firefly)', + 'column_currency-name' => 'Currency name (matching Firefly)', + 'column_currency-symbol' => 'Currency symbol (matching Firefly)', + 'column_date-rent' => 'Rent calculation date', + 'column_date-transaction' => 'Date', + 'column_description' => 'Description', + 'column_opposing-iban' => 'Opposing account (IBAN)', + 'column_opposing-id' => 'Opposing account ID (matching Firefly)', + 'column_opposing-name' => 'Opposing account (name)', + 'column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', + 'column_ing-debet-credit' => 'ING specific debet/credit indicator', + 'column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', + 'column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', + 'column_sepa-db' => 'SEPA Direct Debet', + 'column_tags-comma' => 'Tags (comma separated)', + 'column_tags-space' => 'Tags (space separated)', + 'column_account-number' => 'Asset account (account number)', + 'column_opposing-number' => 'Opposing account (account number)', ]; \ No newline at end of file diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index ede1f038f6..077c75cb33 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -749,18 +749,12 @@ return [ 'split_this_transfer' => 'Split this transfer', // import + 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you.', + 'import_data_index' => 'Index', 'import_file_type_csv' => 'CSV (comma separated values)', 'import_file_type_help' => 'Select the type of file you will upload', 'import_start' => 'Start the import', - 'import_csv_configure_title' => 'Configure your import', - 'import_csv_configure_intro' => 'There are some options for your CSV import.', - 'import_csv_configure_form' => 'Form', - 'csv_header_help' => 'Check this if the first row of your CSV file are the column titles', - 'csv_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'csv_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'csv_csv_config_file_help' => 'Select your CSV import configuration here. If you do not know what this is, ignore it. It will be explained later.', - 'csv_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'csv_upload_not_writeable' => 'The grey box contains a file path. It should be writeable. Please make sure it is.', - - + 'configure_import' => 'Further configure your import', + 'import_finish_configuration' => 'Finish configuration', + 'settings_for_import' => 'Settings', ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index c2bf0cb5e9..0caec98f1c 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -133,6 +133,7 @@ return [ // import 'import_file' => 'Import file', + 'configuration_file' => 'Configuration file', 'import_file_type' => 'Import file type', 'csv_comma' => 'A comma (,)', 'csv_semicolon' => 'A semicolon (;)', @@ -142,5 +143,4 @@ return [ 'csv_config' => 'CSV import configuration', - ]; diff --git a/resources/views/import/csv/configure.twig b/resources/views/import/csv/configure.twig index 64adea984a..0cbab054f3 100644 --- a/resources/views/import/csv/configure.twig +++ b/resources/views/import/csv/configure.twig @@ -10,11 +10,11 @@
-

{{ 'import_csv_configure_title'|_ }}

+

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

- {{ 'import_csv_configure_intro'|_ }} + {{ trans('csv.import_configure_intro') }}

@@ -29,17 +29,14 @@
-

{{ 'import_csv_configure_form'|_ }}

+

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

- {{ ExpandedForm.checkbox('has_headers',1,null,{helpText: 'csv_header_help'|_}) }} - {{ ExpandedForm.text('date_format','Ymd',{helpText: trans('firefly.csv_date_help', {dateExample: phpdate('Ymd')}) }) }} - {{ ExpandedForm.select('csv_delimiter', data.delimiters, 0, {helpText: 'csv_delimiter_help'|_} ) }} - - {{ ExpandedForm.file('csv_config',{helpText: 'csv_csv_config_file_help'|_}) }} - - {{ ExpandedForm.select('csv_import_account', data.accounts, 0, {helpText: '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 %}
@@ -49,7 +46,8 @@
@@ -66,7 +64,7 @@
{{ data.upload_path }}

- {{ 'csv_upload_not_writeable'|_ }} + {{ trans('csv.upload_not_writeable') }}

@@ -82,7 +80,7 @@
diff --git a/resources/views/import/csv/map.twig b/resources/views/import/csv/roles.twig similarity index 65% rename from resources/views/import/csv/map.twig rename to resources/views/import/csv/roles.twig index fb141d4432..c1f317cbbf 100644 --- a/resources/views/import/csv/map.twig +++ b/resources/views/import/csv/roles.twig @@ -10,10 +10,10 @@
-

{{ 'csv_column_roles_title'|_ }}

+

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

-

{{ 'csv_column_roles_text'|_ }}

+

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

@@ -21,30 +21,32 @@
+
-

{{ 'csv_column_roles_table'|_ }}

+

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

- - - - + + + + - {% for i in 0..data.columnCount %} + {% for i in 0..(data.columnCount-1) %} + - + @@ -74,9 +76,8 @@
- {{ 'csv_go_back'|_ }}
diff --git a/resources/views/import/index.twig b/resources/views/import/index.twig index 8449e5a93f..b69d953026 100644 --- a/resources/views/import/index.twig +++ b/resources/views/import/index.twig @@ -26,6 +26,7 @@
{{ ExpandedForm.file('import_file', {helpText: 'import_file_help'|_}) }} + {{ ExpandedForm.file('configuration_file', {helpText: 'configuration_file_help'|_}) }} {{ ExpandedForm.select('import_file_type', importFileTypes, defaultImportType, {'helpText' : 'import_file_type_help'|_}) }} diff --git a/resources/views/index.twig b/resources/views/index.twig index 4363bbf924..64a8e1d873 100644 --- a/resources/views/index.twig +++ b/resources/views/index.twig @@ -59,7 +59,7 @@ {% for data in transactions %}
-

{{ data[1].name }}

+

{{ data[1].name }}

From 189b11befab8d33b044f51abcb9385d50703f884 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 2 Jul 2016 17:36:46 +0200 Subject: [PATCH 2/6] Extra page number check for issue #276 --- app/Http/Controllers/AccountController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 28a8aa3e25..f5381b4512 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -103,7 +103,7 @@ class AccountController extends Controller $typeName = config('firefly.shortNamesByFullName.' . $type); $name = $account->name; $moveTo = $crud->find(intval(Input::get('move_account_before_delete'))); - + $crud->destroy($account, $moveTo); Session::flash('success', strval(trans('firefly.' . $typeName . '_deleted', ['name' => $name]))); @@ -271,6 +271,7 @@ class AccountController extends Controller $end = Navigation::endOfPeriod($carbon, $range); $subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')'; $page = intval(Input::get('page')); + $page = $page === 0 ? 1 : $page; $pageSize = Preferences::get('transactionPageSize', 50)->data; $offset = ($page - 1) * $pageSize; $set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end); From 275d19e71d9eb230c3a0c34d0cc1253e3b38c128 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 2 Jul 2016 17:39:58 +0200 Subject: [PATCH 3/6] Fix #266 for all-chart. --- app/Http/Controllers/Chart/CategoryController.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 9d1d579bce..e4fd807e0d 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -13,8 +13,10 @@ namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; +use FireflyIII\Crud\Account\AccountCrudInterface; use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\AccountType; use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; use FireflyIII\Support\CacheProperties; @@ -48,12 +50,13 @@ class CategoryController extends Controller /** * Show an overview for a category for all time, per month/week/year. * - * @param CRI $repository - * @param Category $category + * @param CRI $repository + * @param AccountCrudInterface $crud + * @param Category $category * * @return \Symfony\Component\HttpFoundation\Response */ - public function all(CRI $repository, Category $category) + public function all(CRI $repository, AccountCrudInterface $crud, Category $category) { $start = $repository->firstUseDate($category, new Collection); $range = Preferences::get('viewRange', '1M')->data; @@ -62,6 +65,7 @@ class CategoryController extends Controller $end = new Carbon; $entries = new Collection; $cache = new CacheProperties; + $accounts = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty('all'); @@ -72,8 +76,8 @@ class CategoryController extends Controller while ($start <= $end) { $currentEnd = Navigation::endOfPeriod($start, $range); - $spent = $repository->spentInPeriod($categoryCollection, new Collection, $start, $currentEnd); - $earned = $repository->earnedInPeriod($categoryCollection, new Collection, $start, $currentEnd); + $spent = $repository->spentInPeriod($categoryCollection, $accounts, $start, $currentEnd); + $earned = $repository->earnedInPeriod($categoryCollection, $accounts, $start, $currentEnd); $date = Navigation::periodShow($start, $range); $entries->push([clone $start, $date, $spent, $earned]); $start = Navigation::addPeriod($start, $range, 0); From 57b5981904f16468deadb2086aaa7e0aa446ae2d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 2 Jul 2016 17:42:27 +0200 Subject: [PATCH 4/6] Fix #266 for period-chart. --- app/Http/Controllers/Chart/CategoryController.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index e4fd807e0d..b585c0d1d2 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -254,18 +254,24 @@ class CategoryController extends Controller { $categoryCollection = new Collection([$category]); $cache = new CacheProperties; + /** @var AccountCrudInterface $crud */ + $crud = app(AccountCrudInterface::class); + $accounts = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + $cache->addProperty($start); $cache->addProperty($end); + $cache->addProperty($accounts); $cache->addProperty($category->id); $cache->addProperty('specific-period'); + if ($cache->has()) { return $cache->get(); } $entries = new Collection; while ($start <= $end) { - $spent = $repository->spentInPeriod($categoryCollection, new Collection, $start, $start); - $earned = $repository->earnedInPeriod($categoryCollection, new Collection, $start, $start); + $spent = $repository->spentInPeriod($categoryCollection, $accounts, $start, $start); + $earned = $repository->earnedInPeriod($categoryCollection, $accounts, $start, $start); $date = Navigation::periodShow($start, '1D'); $entries->push([clone $start, $date, $spent, $earned]); $start->addDay(); From 162c762973c1814ddab91c0b18d93fb1cb677129 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 2 Jul 2016 20:40:23 +0200 Subject: [PATCH 5/6] First set of data mappers. --- app/Import/Importer/CsvImporter.php | 154 ++++++++++++++------ app/Import/Mapper/AssetAccounts.php | 53 +++++++ app/Import/Mapper/MapperInterface.php | 26 ++++ app/Import/Mapper/OpposingAccounts.php | 57 ++++++++ app/Import/Mapper/TransactionCurrencies.php | 42 ++++++ app/Models/Account.php | 10 +- config/csv.php | 44 +++--- resources/lang/en_US/csv.php | 1 + resources/views/import/csv/map.twig | 84 +++++++++++ 9 files changed, 404 insertions(+), 67 deletions(-) create mode 100644 app/Import/Mapper/AssetAccounts.php create mode 100644 app/Import/Mapper/MapperInterface.php create mode 100644 app/Import/Mapper/OpposingAccounts.php create mode 100644 app/Import/Mapper/TransactionCurrencies.php create mode 100644 resources/views/import/csv/map.twig diff --git a/app/Import/Importer/CsvImporter.php b/app/Import/Importer/CsvImporter.php index 7a592442a4..3b1f2fc704 100644 --- a/app/Import/Importer/CsvImporter.php +++ b/app/Import/Importer/CsvImporter.php @@ -14,6 +14,7 @@ namespace FireflyIII\Import\Importer; use ExpandedForm; use FireflyIII\Crud\Account\AccountCrud; +use FireflyIII\Import\Mapper\MapperInterface; use FireflyIII\Models\AccountType; use FireflyIII\Models\ImportJob; use Illuminate\Http\Request; @@ -111,54 +112,18 @@ class CsvImporter implements ImporterInterface */ public function getDataForSettings(): array { - $config = $this->job->configuration; - $data = [ - 'columns' => [], - 'columnCount' => 0, - ]; if ($this->doColumnRoles()) { - - // show user column role configuration. - $content = $this->job->uploadFileContents(); - - // create CSV reader. - $reader = Reader::createFromString($content); - $start = $config['has-headers'] ? 1 : 0; - $end = $start + self::EXAMPLE_ROWS; // first X rows - - // collect example data in $data['columns'] - while ($start < $end) { - $row = $reader->fetchOne($start); - foreach ($row as $index => $value) { - $value = trim($value); - if (strlen($value) > 0) { - $data['columns'][$index][] = $value; - } - } - $start++; - $data['columnCount'] = count($row); - } - - // make unique example data - foreach ($data['columns'] as $index => $values) { - $data['columns'][$index] = array_unique($values); - } - - $data['set_roles'] = []; - // collect possible column roles: - $data['available_roles'] = []; - foreach (array_keys(config('csv.import_roles')) as $role) { - $data['available_roles'][$role] = trans('csv.column_' . $role); - } - - $config['column-count'] = $data['columnCount']; - $this->job->configuration = $config; - $this->job->save(); + $data = $this->getDataForColumnRoles(); + + return $data; + } + + if ($this->doColumnMapping()) { + $data = $this->getDataForColumnMapping(); return $data; } - echo 'no settings to do.'; exit; @@ -176,6 +141,11 @@ class CsvImporter implements ImporterInterface if ($this->doColumnRoles()) { return 'import.csv.roles'; } + + if ($this->doColumnMapping()) { + return 'import.csv.map'; + } + echo 'no view for settings'; exit; } @@ -269,6 +239,14 @@ class CsvImporter implements ImporterInterface } } + /** + * @return bool + */ + private function doColumnMapping(): bool + { + return $this->job->configuration['column-mapping-complete'] === false; + } + /** * @return bool */ @@ -276,4 +254,94 @@ class CsvImporter implements ImporterInterface { return $this->job->configuration['column-roles-complete'] === false; } + + /** + * @return array + */ + private function getDataForColumnMapping(): array + { + $config = $this->job->configuration; + $data = []; + + foreach ($config['column-do-mapping'] as $index => $mustBeMapped) { + if ($mustBeMapped) { + $column = $config['column-roles'][$index] ?? '_ignore'; + $canBeMapped = config('csv.import_roles.' . $column . '.mappable'); + if ($canBeMapped) { + $mapperName = '\FireflyIII\Import\Mapper\\' . config('csv.import_roles.' . $column . '.mapper'); + /** @var MapperInterface $mapper */ + $mapper = new $mapperName; + $data[$index] = [ + 'name' => $column, + 'mapper' => $mapperName, + 'options' => $mapper->getMap(), + 'values' => [], + ]; + } + } + } + + + echo '
';
+        var_dump($data);
+        var_dump($config);
+
+
+        exit;
+
+
+    }
+
+    /**
+     * @return array
+     */
+    private function getDataForColumnRoles():array
+    {
+        $config = $this->job->configuration;
+        $data   = [
+            'columns'     => [],
+            'columnCount' => 0,
+        ];
+
+        // show user column role configuration.
+        $content = $this->job->uploadFileContents();
+
+        // create CSV reader.
+        $reader = Reader::createFromString($content);
+        $start  = $config['has-headers'] ? 1 : 0;
+        $end    = $start + self::EXAMPLE_ROWS; // first X rows
+
+        // collect example data in $data['columns']
+        while ($start < $end) {
+            $row = $reader->fetchOne($start);
+            foreach ($row as $index => $value) {
+                $value = trim($value);
+                if (strlen($value) > 0) {
+                    $data['columns'][$index][] = $value;
+                }
+            }
+            $start++;
+            $data['columnCount'] = count($row);
+        }
+
+        // make unique example data
+        foreach ($data['columns'] as $index => $values) {
+            $data['columns'][$index] = array_unique($values);
+        }
+
+        $data['set_roles'] = [];
+        // collect possible column roles:
+        $data['available_roles'] = [];
+        foreach (array_keys(config('csv.import_roles')) as $role) {
+            $data['available_roles'][$role] = trans('csv.column_' . $role);
+        }
+
+        $config['column-count']   = $data['columnCount'];
+        $this->job->configuration = $config;
+        $this->job->save();
+
+        return $data;
+
+
+    }
 }
\ No newline at end of file
diff --git a/app/Import/Mapper/AssetAccounts.php b/app/Import/Mapper/AssetAccounts.php
new file mode 100644
index 0000000000..fdf0a64daa
--- /dev/null
+++ b/app/Import/Mapper/AssetAccounts.php
@@ -0,0 +1,53 @@
+getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
+        $list = [];
+
+        /** @var Account $account */
+        foreach ($set as $account) {
+            $name = $account->name;
+            $iban = $account->iban ?? '';
+            if (strlen($iban) > 0) {
+                $name .= ' (' . $account->iban . ')';
+            }
+            $list[$account->id] = $name;
+        }
+
+        asort($list);
+
+        $list = [0 => trans('csv.do_not_map')] + $list;
+
+        return $list;
+
+    }
+}
\ No newline at end of file
diff --git a/app/Import/Mapper/MapperInterface.php b/app/Import/Mapper/MapperInterface.php
new file mode 100644
index 0000000000..c285c9b31f
--- /dev/null
+++ b/app/Import/Mapper/MapperInterface.php
@@ -0,0 +1,26 @@
+getAccountsByType(
+            [
+                AccountType::DEFAULT, AccountType::ASSET,
+                AccountType::EXPENSE, AccountType::BENEFICIARY,
+                AccountType::REVENUE
+            ]);
+        $list = [];
+
+        /** @var Account $account */
+        foreach ($set as $account) {
+            $name = $account->name;
+            $iban = $account->iban ?? '';
+            if (strlen($iban) > 0) {
+                $name .= ' (' . $account->iban . ')';
+            }
+            $list[$account->id] = $name;
+        }
+
+        asort($list);
+
+        $list = [0 => trans('csv.do_not_map')] + $list;
+
+        return $list;
+    }
+}
\ No newline at end of file
diff --git a/app/Import/Mapper/TransactionCurrencies.php b/app/Import/Mapper/TransactionCurrencies.php
new file mode 100644
index 0000000000..6f21ebb355
--- /dev/null
+++ b/app/Import/Mapper/TransactionCurrencies.php
@@ -0,0 +1,42 @@
+id] = $currency->name . ' (' . $currency->code . ')';
+        }
+
+        asort($list);
+
+        $list = [0 => trans('csv.do_not_map')] + $list;
+
+        return $list;
+
+    }
+}
\ No newline at end of file
diff --git a/app/Models/Account.php b/app/Models/Account.php
index 9070be24e4..fdb7b3c2e9 100644
--- a/app/Models/Account.php
+++ b/app/Models/Account.php
@@ -13,6 +13,8 @@ namespace FireflyIII\Models;
 
 use Auth;
 use Crypt;
+use FireflyIII\Exceptions\FireflyException;
+use Illuminate\Contracts\Encryption\DecryptException;
 use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -183,10 +185,14 @@ class Account extends Model
      */
     public function getIbanAttribute($value): string
     {
-        if (is_null($value)) {
+        if (is_null($value) || strlen(strval($value)) === 0) {
             return '';
         }
-        $result = Crypt::decrypt($value);
+        try {
+            $result = Crypt::decrypt($value);
+        } catch (DecryptException $e) {
+            throw new FireflyException('Cannot decrypt value "' . $value . '" for account #' . $this->id);
+        }
         if (is_null($result)) {
             return '';
         }
diff --git a/config/csv.php b/config/csv.php
index e235133f32..6e0a7edd6f 100644
--- a/config/csv.php
+++ b/config/csv.php
@@ -25,37 +25,37 @@ return [
             'mappable'  => false,
             'field'     => 'bill',
             'converter' => 'BillId',
-            'mapper'    => 'Bill',
+            'mapper'    => 'Bills',
         ],
         'bill-name'         => [
             'mappable'  => true,
             'converter' => 'BillName',
             'field'     => 'bill',
-            'mapper'    => 'Bill',
+            'mapper'    => 'Bills',
         ],
         'currency-id'       => [
             'mappable'  => true,
             'converter' => 'CurrencyId',
             'field'     => 'currency',
-            'mapper'    => 'TransactionCurrency'
+            'mapper'    => 'TransactionCurrencies'
         ],
         'currency-name'     => [
             'mappable'  => true,
             'converter' => 'CurrencyName',
             'field'     => 'currency',
-            'mapper'    => 'TransactionCurrency'
+            'mapper'    => 'TransactionCurrencies'
         ],
         'currency-code'     => [
             'mappable'  => true,
             'converter' => 'CurrencyCode',
             'field'     => 'currency',
-            'mapper'    => 'TransactionCurrency'
+            'mapper'    => 'TransactionCurrencies'
         ],
         'currency-symbol'   => [
             'mappable'  => true,
             'converter' => 'CurrencySymbol',
             'field'     => 'currency',
-            'mapper'    => 'TransactionCurrency'
+            'mapper'    => 'TransactionCurrencies'
         ],
         'description'       => [
             'mappable'  => false,
@@ -76,13 +76,13 @@ return [
             'mappable'  => true,
             'converter' => 'BudgetId',
             'field'     => 'budget',
-            'mapper'    => 'Budget',
+            'mapper'    => 'Budgets',
         ],
         'budget-name'       => [
             'mappable'  => true,
             'converter' => 'BudgetName',
             'field'     => 'budget',
-            'mapper'    => 'Budget',
+            'mapper'    => 'Budgets',
         ],
         'rabo-debet-credit' => [
             'mappable'  => false,
@@ -98,73 +98,73 @@ return [
             'mappable'  => true,
             'converter' => 'CategoryId',
             'field'     => 'category',
-            'mapper'    => 'Category',
+            'mapper'    => 'Categories',
         ],
         'category-name'     => [
             'mappable'  => true,
             'converter' => 'CategoryName',
             'field'     => 'category',
-            'mapper'    => 'Category',
+            'mapper'    => 'Categories',
         ],
         'tags-comma'        => [
             'mappable'  => true,
             'field'     => 'tags',
             'converter' => 'TagsComma',
-            'mapper'    => 'Tag',
+            'mapper'    => 'Tags',
         ],
         'tags-space'        => [
             'mappable'  => true,
             'field'     => 'tags',
             'converter' => 'TagsSpace',
-            'mapper'    => 'Tag',
+            'mapper'    => 'Tags',
         ],
         'account-id'        => [
             'mappable'  => true,
-            'mapper'    => 'AssetAccount',
+            'mapper'    => 'AssetAccountId',
             'field'     => 'asset-account-id',
-            'converter' => 'AccountId'
+            'converter' => 'AssetAccounts'
         ],
         'account-name'      => [
             'mappable'  => true,
-            'mapper'    => 'AssetAccount',
+            'mapper'    => 'AssetAccountName',
             'field'     => 'asset-account-name',
-            'converter' => 'AssetAccountName'
+            'converter' => 'AssetAccounts'
         ],
         'account-iban'      => [
             'mappable'  => true,
             'converter' => 'AssetAccountIban',
             'field'     => 'asset-account-iban',
-            'mapper'    => 'AssetAccount'
+            'mapper'    => 'AssetAccounts'
         ],
         'account-number'      => [
             'mappable'  => true,
             'converter' => 'AssetAccountNumber',
             'field'     => 'asset-account-number',
-            'mapper'    => 'AssetAccount'
+            'mapper'    => 'AssetAccounts'
         ],
         'opposing-id'       => [
             'mappable'  => true,
             'field'     => 'opposing-account-id',
             'converter' => 'OpposingAccountId',
-            'mapper'    => 'AnyAccount',
+            'mapper'    => 'OpposingAccounts',
         ],
         'opposing-name'     => [
             'mappable'  => true,
             'field'     => 'opposing-account-name',
             'converter' => 'OpposingAccountName',
-            'mapper'    => 'AnyAccount',
+            'mapper'    => 'OpposingAccounts',
         ],
         'opposing-iban'     => [
             'mappable'  => true,
             'field'     => 'opposing-account-iban',
             'converter' => 'OpposingAccountIban',
-            'mapper'    => 'AnyAccount',
+            'mapper'    => 'OpposingAccounts',
         ],
         'opposing-number'     => [
             'mappable'  => true,
             'field'     => 'opposing-account-number',
             'converter' => 'OpposingAccountNumber',
-            'mapper'    => 'AnyAccount',
+            'mapper'    => 'OpposingAccounts',
         ],
         'amount'            => [
             'mappable'  => false,
diff --git a/resources/lang/en_US/csv.php b/resources/lang/en_US/csv.php
index c27e945dce..3f2003ff22 100644
--- a/resources/lang/en_US/csv.php
+++ b/resources/lang/en_US/csv.php
@@ -32,6 +32,7 @@ return [
     'column'                 => 'Column',
     'no_example_data'        => 'No example data available',
     'store_column_roles'     => 'Continue import',
+    'do_not_map'             => '(do not map)',
 
     'column__ignore'                => '(ignore this column)',
     'column_account-iban'           => 'Asset account (IBAN)',
diff --git a/resources/views/import/csv/map.twig b/resources/views/import/csv/map.twig
new file mode 100644
index 0000000000..bc6a5cea31
--- /dev/null
+++ b/resources/views/import/csv/map.twig
@@ -0,0 +1,84 @@
+{% extends "./layout/default.twig" %}
+
+{% block breadcrumbs %}
+    {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }}
+{% endblock %}
+
+{% block content %}
+
+
+    
+
+
+
+

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

+
+
+

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

+
+
+ +
+
+ + + + {# + + {% for index,columnName in map %} + +
+
+
+
+

{{ Config.get('csv.roles.'~columnName~'.name') }}

+
+
+
{{ 'csv_column_name'|_ }}{{ 'csv_column_example'|_ }}{{ 'csv_column_role'|_ }}{{ 'csv_do_map_value'|_ }}{{ trans('csv.column_name') }}{{ trans('csv.column_example') }}{{ trans('csv.column_role') }}{{ trans('csv.do_map_value') }}
Column #{{ loop.index }}{{ trans('csv.column') }} #{{ loop.index }} {% if data.columns[i]|length == 0 %} - No example data available + {{ trans('csv.no_example_data') }} {% else %} {% for example in data.columns[i] %} {{ example }}
@@ -52,10 +54,10 @@ {% endif %}
- {{ Form.select(('role['~index~']'), data.available_roles,data.set_roles[index],{class: 'form-control'}) }} + {{ Form.select(('role['~loop.index0~']'), data.available_roles,data.set_roles[index],{class: 'form-control'}) }} - {# Form.checkbox(('map['~index~']'),1,map[index]) #} + {{ Form.checkbox(('map['~loop.index0~']'),1,map[index]) }}
+ + + + + + + + {% for value in values[index] %} + + + + + {% endfor %} + + + +
{{ 'csv_field_value'|_ }}{{ 'csv_field_mapped_to'|_ }}
{{ value }} + {{ Form.select('mapping['~index~']['~value~']',options[index], mapped[index][value], {class: 'form-control'}) }} +
+ + +
+
+
+
+ {% endfor %} + #} + + +
+
+
+
+ +
+
+
+
+ +
+ + +{% endblock %} From ae768a8525c5b71a0c0ce748ea8cff01a793fb05 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 2 Jul 2016 23:08:47 +0200 Subject: [PATCH 6/6] Works up until actual import. --- app/Http/Controllers/ImportController.php | 89 ++++++++++++++++-- app/Http/routes.php | 2 + app/Import/Importer/CsvImporter.php | 105 +++++++++++++++------- app/Import/Mapper/Bills.php | 46 ++++++++++ app/Import/Mapper/Budgets.php | 47 ++++++++++ app/Import/Mapper/Categories.php | 47 ++++++++++ app/Import/Mapper/Tags.php | 46 ++++++++++ config/csv.php | 51 +++++------ resources/views/import/complete.twig | 33 +++++++ resources/views/import/csv/map.twig | 38 ++++++++ resources/views/import/csv/roles.twig | 10 ++- resources/views/import/index.twig | 1 - 12 files changed, 444 insertions(+), 71 deletions(-) create mode 100644 app/Import/Mapper/Bills.php create mode 100644 app/Import/Mapper/Budgets.php create mode 100644 app/Import/Mapper/Categories.php create mode 100644 app/Import/Mapper/Tags.php create mode 100644 resources/views/import/complete.twig diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index c3c1763bc9..589d7a1a5a 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -33,6 +33,27 @@ class ImportController extends Controller View::share('title', trans('firefly.import_data')); } + /** + * This is the last step before the import starts. + * + * @param ImportJob $job + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws FireflyException + */ + public function complete(ImportJob $job) + { + Log::debug('Now in complete()', ['job' => $job->key]); + if (!$this->jobInCorrectStep($job, 'complete')) { + return $this->redirectToCorrectStep($job); + } + $importer = $this->makeImporter($job); + $subTitle = trans('firefy.import_complete'); + $subTitleIcon = 'fa-star'; + + return view('import.complete', compact('job', 'subTitle', 'subTitleIcon')); + } + /** * This is step 3. * This is the first step in configuring the job. It can only be executed @@ -45,7 +66,10 @@ class ImportController extends Controller */ public function configure(ImportJob $job) { + Log::debug('Now at start of configure()'); if (!$this->jobInCorrectStep($job, 'configure')) { + Log::debug('Job is not in correct state for configure()', ['status' => $job->status]); + return $this->redirectToCorrectStep($job); } @@ -59,6 +83,35 @@ class ImportController extends Controller return view('import.' . $job->file_type . '.configure', compact('data', 'job', 'subTitle', 'subTitleIcon')); + } + + /** + * Generate a JSON file of the job's config and send it to the user. + * + * @param ImportJob $job + * + * @return mixed + */ + public function download(ImportJob $job) + { + Log::debug('Now in download()', ['job' => $job->key]); + $config = $job->configuration; + $config['column-roles-complete'] = false; + $config['column-mapping-complete'] = false; + $result = json_encode($config, JSON_PRETTY_PRINT); + $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); + + return response($result, 200) + ->header('Content-disposition', 'attachment; filename=' . $name) + ->header('Content-Type', 'application/json') + ->header('Content-Description', 'File Transfer') + ->header('Connection', 'Keep-Alive') + ->header('Expires', '0') + ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->header('Pragma', 'public') + ->header('Content-Length', strlen($result)); + + } /** @@ -68,6 +121,7 @@ class ImportController extends Controller */ public function index() { + Log::debug('Now at index'); $subTitle = trans('firefly.import_data_index'); $subTitleIcon = 'fa-home'; $importFileTypes = []; @@ -91,9 +145,11 @@ class ImportController extends Controller */ public function postConfigure(Request $request, ImportJob $job) { + Log::debug('Now in postConfigure()', ['job' => $job->key]); if (!$this->jobInCorrectStep($job, 'process')) { return $this->redirectToCorrectStep($job); } + Log::debug('Continue postConfigure()', ['job' => $job->key]); // actual code $importer = $this->makeImporter($job); @@ -122,6 +178,7 @@ class ImportController extends Controller */ public function postSettings(Request $request, ImportJob $job) { + Log::debug('Now in postSettings()', ['job' => $job->key]); if (!$this->jobInCorrectStep($job, 'store-settings')) { return $this->redirectToCorrectStep($job); } @@ -143,29 +200,32 @@ class ImportController extends Controller */ public function settings(ImportJob $job) { + Log::debug('Now in settings()', ['job' => $job->key]); if (!$this->jobInCorrectStep($job, 'settings')) { + Log::debug('Job should not be in settings()'); + return $this->redirectToCorrectStep($job); } + Log::debug('Continue in settings()'); $importer = $this->makeImporter($job); $subTitle = trans('firefy.settings_for_import'); $subTitleIcon = 'fa-wrench'; // now show settings screen to user. if ($importer->requireUserSettings()) { + Log::debug('Job requires user config.'); $data = $importer->getDataForSettings(); $view = $importer->getViewForSettings(); return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon')); } + Log::debug('Job does NOT require user config.'); + + $job->status = 'settings_complete'; + $job->save(); // if no more settings, save job and continue to process thing. - - - echo 'now in settings (done)'; - exit; - - // actual code - + return redirect(route('import.complete', [$job->key])); // ask the importer for the requested action. // for example pick columns or map data. @@ -182,6 +242,7 @@ class ImportController extends Controller */ public function upload(ImportUploadRequest $request, ImportJobRepositoryInterface $repository) { + Log::debug('Now in upload()'); // create import job: $type = $request->get('import_file_type'); $job = $repository->create($type); @@ -232,6 +293,7 @@ class ImportController extends Controller */ private function jobInCorrectStep(ImportJob $job, string $method): bool { + Log::debug('Now in jobInCorrectStep()', ['job' => $job->key, 'method' => $method]); switch ($method) { case 'configure': case 'process': @@ -241,6 +303,9 @@ class ImportController extends Controller case 'store-settings': return $job->status === 'import_configuration_saved'; break; + case 'complete': + return $job->status === 'settings_complete'; + break; } return false; @@ -272,13 +337,23 @@ class ImportController extends Controller */ private function redirectToCorrectStep(ImportJob $job) { + Log::debug('Now in redirectToCorrectStep()', ['job' => $job->key]); switch ($job->status) { case 'import_status_never_started': + Log::debug('Will redirect to configure()'); + return redirect(route('import.configure', [$job->key])); break; case 'import_configuration_saved': + Log::debug('Will redirect to settings()'); + return redirect(route('import.settings', [$job->key])); break; + case 'settings_complete': + Log::debug('Will redirect to complete()'); + + return redirect(route('import.complete', [$job->key])); + break; } throw new FireflyException('Cannot redirect for job state ' . $job->status); diff --git a/app/Http/routes.php b/app/Http/routes.php index 662fafce7e..b2496d42cd 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -229,6 +229,8 @@ Route::group( Route::post('/import/configure/{importJob}', ['uses' => 'ImportController@postConfigure', 'as' => 'import.process_configuration']); Route::get('/import/settings/{importJob}', ['uses' => 'ImportController@settings', 'as' => 'import.settings']); Route::post('/import/settings/{importJob}', ['uses' => 'ImportController@postSettings', 'as' => 'import.postSettings']); + Route::get('/import/complete/{importJob}', ['uses' => 'ImportController@complete', 'as' => 'import.complete']); + Route::get('/import/download/{importJob}', ['uses' => 'ImportController@download', 'as' => 'import.download']); /** diff --git a/app/Import/Importer/CsvImporter.php b/app/Import/Importer/CsvImporter.php index 3b1f2fc704..cccfcdde74 100644 --- a/app/Import/Importer/CsvImporter.php +++ b/app/Import/Importer/CsvImporter.php @@ -158,13 +158,16 @@ class CsvImporter implements ImporterInterface */ public function requireUserSettings(): bool { - // does the job have both a 'map' array and a 'columns' array. - $config = $this->job->configuration; - if (isset($config['map']) && isset($config['columns'])) { - return false; - } + Log::debug('doColumnMapping is ' . ($this->doColumnMapping() ? 'true' : 'false')); + Log::debug('doColumnRoles is ' . ($this->doColumnRoles() ? 'true' : 'false')); + if ($this->doColumnMapping() || $this->doColumnRoles()) { + Log::debug('Return true'); - return true; + return true; + } + Log::debug('Return false'); + + return false; } /** @@ -216,25 +219,52 @@ class CsvImporter implements ImporterInterface */ public function storeSettings(Request $request) { - $config = $this->job->configuration; - $count = $config['column-count']; - $all = $request->all(); - $roleSet = 0; - for ($i = 0; $i < $count; $i++) { - $selectedRole = $all['role'][$i] ?? '_ignore'; - $doMapping = isset($all['map'][$i]) && $all['map'][$i] == '1' ? true : false; - if ($selectedRole == '_ignore' && $doMapping === true) { - $doMapping = false; // cannot map ignored columns. + $config = $this->job->configuration; + $all = $request->all(); + if ($request->get('settings') == 'roles') { + $count = $config['column-count']; + + $roleSet = 0; // how many roles have been defined + $mapSet = 0; // how many columns must be mapped + for ($i = 0; $i < $count; $i++) { + $selectedRole = $all['role'][$i] ?? '_ignore'; + $doMapping = isset($all['map'][$i]) && $all['map'][$i] == '1' ? true : false; + if ($selectedRole == '_ignore' && $doMapping === true) { + $doMapping = false; // cannot map ignored columns. + } + if ($selectedRole != '_ignore') { + $roleSet++; + } + if ($doMapping === true) { + $mapSet++; + } + $config['column-roles'][$i] = $selectedRole; + $config['column-do-mapping'][$i] = $doMapping; } - if ($selectedRole != '_ignore') { - $roleSet++; + if ($roleSet > 0) { + $config['column-roles-complete'] = true; + $this->job->configuration = $config; + $this->job->save(); + } + if ($mapSet === 0) { + // skip setting of map: + $config['column-mapping-complete'] = true; } - $config['column-roles'][$i] = $selectedRole; - $config['column-do-mapping'][$i] = $doMapping; } - if ($roleSet > 0) { - $config['column-roles-complete'] = true; - $this->job->configuration = $config; + if ($request->get('settings') == 'map') { + foreach ($all['mapping'] as $index => $data) { + $config['column-mapping-config'][$index] = []; + foreach ($data as $value => $mapId) { + $mapId = intval($mapId); + if ($mapId !== 0) { + $config['column-mapping-config'][$index][$value] = intval($mapId); + } + } + } + + // set thing to be completed. + $config['column-mapping-complete'] = true; + $this->job->configuration = $config; $this->job->save(); } } @@ -260,8 +290,9 @@ class CsvImporter implements ImporterInterface */ private function getDataForColumnMapping(): array { - $config = $this->job->configuration; - $data = []; + $config = $this->job->configuration; + $data = []; + $indexes = []; foreach ($config['column-do-mapping'] as $index => $mustBeMapped) { if ($mustBeMapped) { @@ -271,9 +302,11 @@ class CsvImporter implements ImporterInterface $mapperName = '\FireflyIII\Import\Mapper\\' . config('csv.import_roles.' . $column . '.mapper'); /** @var MapperInterface $mapper */ $mapper = new $mapperName; + $indexes[] = $index; $data[$index] = [ 'name' => $column, 'mapper' => $mapperName, + 'index' => $index, 'options' => $mapper->getMap(), 'values' => [], ]; @@ -281,15 +314,25 @@ class CsvImporter implements ImporterInterface } } + // in order to actually map we also need all possible values from the CSV file. + $content = $this->job->uploadFileContents(); + $reader = Reader::createFromString($content); + $results = $reader->fetch(); - echo '
';
-        var_dump($data);
-        var_dump($config);
-
-
-        exit;
-
+        foreach ($results as $row) {
+            //do something here
+            foreach ($indexes as $index) {
+                $value = $row[$index];
+                if (strlen($value) > 0) {
+                    $data[$index]['values'][] = $row[$index];
+                }
+            }
+        }
+        foreach ($data as $index => $entry) {
+            $data[$index]['values'] = array_unique($data[$index]['values']);
+        }
 
+        return $data;
     }
 
     /**
diff --git a/app/Import/Mapper/Bills.php b/app/Import/Mapper/Bills.php
new file mode 100644
index 0000000000..0dcdc06f10
--- /dev/null
+++ b/app/Import/Mapper/Bills.php
@@ -0,0 +1,46 @@
+getBills();
+        $list       = [];
+
+        /** @var Bill $bill */
+        foreach ($result as $bill) {
+            $list[$bill->id] = $bill->name . ' [' . $bill->match . ']';
+        }
+        asort($list);
+
+        $list = [0 => trans('csv.do_not_map')] + $list;
+
+        return $list;
+
+    }
+}
\ No newline at end of file
diff --git a/app/Import/Mapper/Budgets.php b/app/Import/Mapper/Budgets.php
new file mode 100644
index 0000000000..a1b9c2edd8
--- /dev/null
+++ b/app/Import/Mapper/Budgets.php
@@ -0,0 +1,47 @@
+getBudgets();
+        $list       = [];
+
+        /** @var Budget $budget */
+        foreach ($result as $budget) {
+            $list[$budget->id] = $budget->name;
+        }
+        asort($list);
+
+        $list = [0 => trans('csv.do_not_map')] + $list;
+
+        return $list;
+
+    }
+}
\ No newline at end of file
diff --git a/app/Import/Mapper/Categories.php b/app/Import/Mapper/Categories.php
new file mode 100644
index 0000000000..0f85392c8f
--- /dev/null
+++ b/app/Import/Mapper/Categories.php
@@ -0,0 +1,47 @@
+getCategories();
+        $list       = [];
+
+        /** @var Category $category */
+        foreach ($result as $category) {
+            $list[$category->id] = $category->name;
+        }
+        asort($list);
+
+        $list = [0 => trans('csv.do_not_map')] + $list;
+
+        return $list;
+
+    }
+}
\ No newline at end of file
diff --git a/app/Import/Mapper/Tags.php b/app/Import/Mapper/Tags.php
new file mode 100644
index 0000000000..4f608f2146
--- /dev/null
+++ b/app/Import/Mapper/Tags.php
@@ -0,0 +1,46 @@
+get();
+        $list       = [];
+
+        /** @var Tag $tag */
+        foreach ($result as $tag) {
+            $list[$tag->id] = $tag->tag;
+        }
+        asort($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 6e0a7edd6f..3481ca2769 100644
--- a/config/csv.php
+++ b/config/csv.php
@@ -15,11 +15,12 @@ return [
     /*
      * Configuration for possible column roles.
      */
-    'import_roles' => [
+    'import_roles'     => [
         '_ignore'           => [
             'mappable'  => false,
-            'converter' => 'Ignore',
             'field'     => 'ignored',
+            'converter' => 'Ignore',
+
         ],
         'bill-id'           => [
             'mappable'  => false,
@@ -29,33 +30,33 @@ return [
         ],
         'bill-name'         => [
             'mappable'  => true,
-            'converter' => 'BillName',
             'field'     => 'bill',
+            'converter' => 'BillName',
             'mapper'    => 'Bills',
         ],
         'currency-id'       => [
             'mappable'  => true,
-            'converter' => 'CurrencyId',
             'field'     => 'currency',
-            'mapper'    => 'TransactionCurrencies'
+            'converter' => 'CurrencyId',
+            'mapper'    => 'TransactionCurrencies',
         ],
         'currency-name'     => [
             'mappable'  => true,
             'converter' => 'CurrencyName',
             'field'     => 'currency',
-            'mapper'    => 'TransactionCurrencies'
+            'mapper'    => 'TransactionCurrencies',
         ],
         'currency-code'     => [
             'mappable'  => true,
             'converter' => 'CurrencyCode',
             'field'     => 'currency',
-            'mapper'    => 'TransactionCurrencies'
+            'mapper'    => 'TransactionCurrencies',
         ],
         'currency-symbol'   => [
             'mappable'  => true,
             'converter' => 'CurrencySymbol',
             'field'     => 'currency',
-            'mapper'    => 'TransactionCurrencies'
+            'mapper'    => 'TransactionCurrencies',
         ],
         'description'       => [
             'mappable'  => false,
@@ -89,7 +90,7 @@ return [
             'converter' => 'RabobankDebetCredit',
             'field'     => 'amount-modifier',
         ],
-        'ing-debet-credit' => [
+        'ing-debet-credit'  => [
             'mappable'  => false,
             'converter' => 'INGDebetCredit',
             'field'     => 'amount-modifier',
@@ -120,27 +121,28 @@ return [
         ],
         'account-id'        => [
             'mappable'  => true,
-            'mapper'    => 'AssetAccountId',
             'field'     => 'asset-account-id',
-            'converter' => 'AssetAccounts'
+            'converter' => 'AssetAccountId',
+            'mapper'    => 'AssetAccounts',
         ],
         'account-name'      => [
             'mappable'  => true,
-            'mapper'    => 'AssetAccountName',
             'field'     => 'asset-account-name',
-            'converter' => 'AssetAccounts'
+            'converter' => 'AssetAccountName',
+            'mapper'    => 'AssetAccounts',
         ],
         'account-iban'      => [
             'mappable'  => true,
-            'converter' => 'AssetAccountIban',
             'field'     => 'asset-account-iban',
-            'mapper'    => 'AssetAccounts'
+            'converter' => 'AssetAccountIban',
+            'mapper'    => 'AssetAccounts',
+
         ],
-        'account-number'      => [
+        'account-number'    => [
             'mappable'  => true,
-            'converter' => 'AssetAccountNumber',
             'field'     => 'asset-account-number',
-            'mapper'    => 'AssetAccounts'
+            'converter' => 'AssetAccountNumber',
+            'mapper'    => 'AssetAccounts',
         ],
         'opposing-id'       => [
             'mappable'  => true,
@@ -160,7 +162,7 @@ return [
             'converter' => 'OpposingAccountIban',
             'mapper'    => 'OpposingAccounts',
         ],
-        'opposing-number'     => [
+        'opposing-number'   => [
             'mappable'  => true,
             'field'     => 'opposing-account-number',
             'converter' => 'OpposingAccountNumber',
@@ -171,11 +173,6 @@ return [
             'converter' => 'Amount',
             'field'     => 'amount',
         ],
-//        'amount-comma-separated' => [
-//            'mappable'  => false,
-//            'converter' => 'AmountComma',
-//            'field'     => 'amount',
-//        ],
         'sepa-ct-id'        => [
             'mappable'  => false,
             'converter' => 'Description',
@@ -194,12 +191,6 @@ return [
     ],
 
 
-
-
-
-
-
-
     /*
 
 
diff --git a/resources/views/import/complete.twig b/resources/views/import/complete.twig
new file mode 100644
index 0000000000..0a7aaaff7b
--- /dev/null
+++ b/resources/views/import/complete.twig
@@ -0,0 +1,33 @@
+{% extends "./layout/default.twig" %}
+
+{% block breadcrumbs %}
+    {{ Breadcrumbs.renderIfExists }}
+{% endblock %}
+{% block content %}
+    
+
+
+
+

{{ 'import_complete'|_ }}

+
+
+

+ {{ 'import_complete_text'|_ }} +

+ +
+
+
+
+{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} diff --git a/resources/views/import/csv/map.twig b/resources/views/import/csv/map.twig index bc6a5cea31..7119e382aa 100644 --- a/resources/views/import/csv/map.twig +++ b/resources/views/import/csv/map.twig @@ -24,6 +24,44 @@
+ + + {% for field in data %} +
+
+
+
+

{{ field.name }}

+
+
+ + + + + + + + + {% for option in field.values %} + + + + + {% endfor %} + +
{{ trans('csv.field_value') }}{{ trans('csv.field_mapped_to') }}
+ {{ option }} + + {{ Form.select('mapping['~field.index~']['~option~']', + field.options, + job.configuration['column-mapping-config'][field.index][option], {class: 'form-control'}) }} +
+
+
+
+
+ {% endfor %} + {# diff --git a/resources/views/import/csv/roles.twig b/resources/views/import/csv/roles.twig index c1f317cbbf..e73f172e43 100644 --- a/resources/views/import/csv/roles.twig +++ b/resources/views/import/csv/roles.twig @@ -54,10 +54,16 @@ {% endif %} - {{ Form.select(('role['~loop.index0~']'), data.available_roles,data.set_roles[index],{class: 'form-control'}) }} + {{ Form.select(('role['~loop.index0~']'), + data.available_roles, + job.configuration['column-roles'][loop.index0], + {class: 'form-control'}) }} - {{ Form.checkbox(('map['~loop.index0~']'),1,map[index]) }} + {{ Form.checkbox(('map['~loop.index0~']'),1, + job.configuration['column-do-mapping'][loop.index0] + + ) }} diff --git a/resources/views/import/index.twig b/resources/views/import/index.twig index b69d953026..b378cfe026 100644 --- a/resources/views/import/index.twig +++ b/resources/views/import/index.twig @@ -6,7 +6,6 @@ {% block content %}
-

{{ 'import'|_ }}