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); diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 9d1d579bce..b585c0d1d2 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); @@ -250,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(); diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 3416a5a6fd..589d7a1a5a 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; /** @@ -31,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 @@ -43,16 +66,50 @@ 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); } // 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')); + + + } + + /** + * 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)); } @@ -64,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 = []; @@ -87,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); @@ -106,6 +166,29 @@ 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) + { + Log::debug('Now in postSettings()', ['job' => $job->key]); + 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. @@ -117,27 +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); } - $importer = $this->makeImporter($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')); + 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. @@ -154,9 +242,13 @@ 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); + $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 +257,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])); } @@ -177,14 +293,19 @@ 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': return $job->status === 'import_status_never_started'; break; case 'settings': + case 'store-settings': return $job->status === 'import_configuration_saved'; break; + case 'complete': + return $job->status === 'settings_complete'; + break; } return false; @@ -216,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 ece4c9e527..cccfcdde74 100644 --- a/app/Import/Importer/CsvImporter.php +++ b/app/Import/Importer/CsvImporter.php @@ -14,10 +14,12 @@ namespace FireflyIII\Import\Importer; use ExpandedForm; use FireflyIII\Crud\Account\AccountCrud; -use FireflyIII\Import\Role\Map; +use FireflyIII\Import\Mapper\MapperInterface; 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 +34,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; } @@ -83,48 +112,22 @@ class CsvImporter implements ImporterInterface */ public function getDataForSettings(): array { - $config = $this->job->configuration; - $data = [ - 'columns' => [], - 'columnCount' => 0, - ]; - if (!isset($config['columns'])) { - - // show user column 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 - 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 - 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); - } + if ($this->doColumnRoles()) { + $data = $this->getDataForColumnRoles(); return $data; } + if ($this->doColumnMapping()) { + $data = $this->getDataForColumnMapping(); + + return $data; + } + + echo 'no settings to do.'; + exit; + } /** @@ -135,18 +138,15 @@ class CsvImporter implements ImporterInterface */ public function getViewForSettings(): string { - return 'import.csv.map'; - } + if ($this->doColumnRoles()) { + return 'import.csv.roles'; + } - /** - * 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->doColumnMapping()) { + return 'import.csv.map'; + } + + echo 'no view for settings'; exit; } @@ -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; } /** @@ -174,33 +177,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 +210,181 @@ 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; + $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 ($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; + } + } + 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(); + } + } + + /** + * @return bool + */ + private function doColumnMapping(): bool + { + return $this->job->configuration['column-mapping-complete'] === false; + } + + /** + * @return bool + */ + private function doColumnRoles(): bool + { + return $this->job->configuration['column-roles-complete'] === false; + } + + /** + * @return array + */ + private function getDataForColumnMapping(): array + { + $config = $this->job->configuration; + $data = []; + $indexes = []; + + 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; + $indexes[] = $index; + $data[$index] = [ + 'name' => $column, + 'mapper' => $mapperName, + 'index' => $index, + 'options' => $mapper->getMap(), + 'values' => [], + ]; + } + } + } + + // 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(); + + 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; + } + + /** + * @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/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/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/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/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/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/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 3622624496..3481ca2769 100644 --- a/config/csv.php +++ b/config/csv.php @@ -15,47 +15,48 @@ return [ /* * Configuration for possible column roles. */ - 'import_roles' => [ + 'import_roles' => [ '_ignore' => [ 'mappable' => false, - 'converter' => 'Ignore', 'field' => 'ignored', + 'converter' => 'Ignore', + ], 'bill-id' => [ 'mappable' => false, 'field' => 'bill', 'converter' => 'BillId', - 'mapper' => 'Bill', + 'mapper' => 'Bills', ], 'bill-name' => [ 'mappable' => true, - 'converter' => 'BillName', 'field' => 'bill', - 'mapper' => 'Bill', + 'converter' => 'BillName', + 'mapper' => 'Bills', ], 'currency-id' => [ 'mappable' => true, - 'converter' => 'CurrencyId', 'field' => 'currency', - 'mapper' => 'TransactionCurrency' + 'converter' => 'CurrencyId', + '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,20 +77,20 @@ 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, 'converter' => 'RabobankDebetCredit', 'field' => 'amount-modifier', ], - 'ing-debet-credit' => [ + 'ing-debet-credit' => [ 'mappable' => false, 'converter' => 'INGDebetCredit', 'field' => 'amount-modifier', @@ -98,84 +99,80 @@ 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', 'field' => 'asset-account-id', - 'converter' => 'AccountId' + 'converter' => 'AssetAccountId', + 'mapper' => 'AssetAccounts', ], 'account-name' => [ 'mappable' => true, - 'mapper' => 'AssetAccount', 'field' => 'asset-account-name', - 'converter' => 'AssetAccountName' + 'converter' => 'AssetAccountName', + 'mapper' => 'AssetAccounts', ], 'account-iban' => [ 'mappable' => true, - 'converter' => 'AssetAccountIban', 'field' => 'asset-account-iban', - 'mapper' => 'AssetAccount' + 'converter' => 'AssetAccountIban', + 'mapper' => 'AssetAccounts', + ], - 'account-number' => [ + 'account-number' => [ 'mappable' => true, - 'converter' => 'AssetAccountNumber', 'field' => 'asset-account-number', - 'mapper' => 'AssetAccount' + 'converter' => 'AssetAccountNumber', + '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' => [ + 'opposing-number' => [ 'mappable' => true, 'field' => 'opposing-account-number', 'converter' => 'OpposingAccountNumber', - 'mapper' => 'AnyAccount', + 'mapper' => 'OpposingAccounts', ], 'amount' => [ 'mappable' => false, '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/lang/en_US/csv.php b/resources/lang/en_US/csv.php index f9878e6938..3f2003ff22 100644 --- a/resources/lang/en_US/csv.php +++ b/resources/lang/en_US/csv.php @@ -10,35 +10,59 @@ 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', + 'do_not_map' => '(do not map)', + + '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/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/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/map.twig index fb141d4432..7119e382aa 100644 --- a/resources/views/import/csv/map.twig +++ b/resources/views/import/csv/map.twig @@ -6,14 +6,17 @@ {% block content %} +
-

{{ 'csv_column_roles_title'|_ }}

+

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

-

{{ 'csv_column_roles_text'|_ }}

+

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

@@ -21,67 +24,98 @@
+ -
-
-
-
-

{{ 'csv_column_roles_table'|_ }}

-
-
- - - - - - - - - - - {% for i in 0..data.columnCount %} + {% for field in data %} +
+
+
+
+

{{ field.name }}

+
+
+
{{ 'csv_column_name'|_ }}{{ 'csv_column_example'|_ }}{{ 'csv_column_role'|_ }}{{ 'csv_do_map_value'|_ }}
+ - - - - + + - {% endfor %} - - -
Column #{{ loop.index }} - {% if data.columns[i]|length == 0 %} - No example data available - {% else %} - {% for example in data.columns[i] %} - {{ example }}
- {% endfor %} - {% endif %} - -
- {{ Form.select(('role['~index~']'), data.available_roles,data.set_roles[index],{class: 'form-control'}) }} - - {# Form.checkbox(('map['~index~']'),1,map[index]) #} - {{ trans('csv.field_value') }}{{ trans('csv.field_mapped_to') }}
- - + + + {% for option in field.values %} + + + {{ option }} + + + {{ Form.select('mapping['~field.index~']['~option~']', + field.options, + job.configuration['column-mapping-config'][field.index][option], {class: 'form-control'}) }} + + + {% endfor %} + + +
-
+ {% endfor %} + + + {# + + {% for index,columnName in map %} + +
+
+
+
+

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

+
+
+ + + + + + + + + {% 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 %} + #} +
- {{ 'csv_go_back'|_ }}
+ diff --git a/resources/views/import/csv/roles.twig b/resources/views/import/csv/roles.twig new file mode 100644 index 0000000000..e73f172e43 --- /dev/null +++ b/resources/views/import/csv/roles.twig @@ -0,0 +1,95 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} +{% endblock %} + +{% block content %} + +
+
+
+
+

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

+
+
+

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

+
+
+ +
+
+
+ + + +
+
+
+
+

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

+
+
+ + + + + + + + + + + {% for i in 0..(data.columnCount-1) %} + + + + + + + + {% endfor %} + + +
{{ trans('csv.column_name') }}{{ trans('csv.column_example') }}{{ trans('csv.column_role') }}{{ trans('csv.do_map_value') }}
{{ trans('csv.column') }} #{{ loop.index }} + {% if data.columns[i]|length == 0 %} + {{ trans('csv.no_example_data') }} + {% else %} + {% for example in data.columns[i] %} + {{ example }}
+ {% endfor %} + {% endif %} + +
+ {{ Form.select(('role['~loop.index0~']'), + data.available_roles, + job.configuration['column-roles'][loop.index0], + {class: 'form-control'}) }} + + {{ Form.checkbox(('map['~loop.index0~']'),1, + job.configuration['column-do-mapping'][loop.index0] + + ) }} +
+ + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ + +{% endblock %} diff --git a/resources/views/import/index.twig b/resources/views/import/index.twig index 8449e5a93f..b378cfe026 100644 --- a/resources/views/import/index.twig +++ b/resources/views/import/index.twig @@ -6,7 +6,6 @@ {% block content %}
-

{{ 'import'|_ }}

@@ -26,6 +25,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 }}