Works up until actual import.

This commit is contained in:
James Cole 2016-07-02 23:08:47 +02:00
parent 162c762973
commit ae768a8525
12 changed files with 444 additions and 71 deletions

View File

@ -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);

View File

@ -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']);
/**

View File

@ -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 '<pre>';
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;
}
/**

View File

@ -0,0 +1,46 @@
<?php
/**
* Bills.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Import\Mapper;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
/**
* Class Bills
*
* @package FireflyIII\Import\Mapper
*/
class Bills implements MapperInterface
{
/**
* @return array
*/
public function getMap(): array
{
/** @var BillRepositoryInterface $repository */
$repository = app(BillRepositoryInterface::class);
$result = $repository->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;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Budgets.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Import\Mapper;
use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
/**
* Class Budgets
*
* @package FireflyIII\Import\Mapper
*/
class Budgets implements MapperInterface
{
/**
* @return array
*/
public function getMap(): array
{
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$result = $repository->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;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Categories.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Import\Mapper;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
/**
* Class Categories
*
* @package FireflyIII\Import\Mapper
*/
class Categories implements MapperInterface
{
/**
* @return array
*/
public function getMap(): array
{
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
$result = $repository->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;
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* Tags.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Import\Mapper;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
/**
* Class Tags
*
* @package FireflyIII\Import\Mapper
*/
class Tags implements MapperInterface
{
/**
* @return array
*/
public function getMap(): array
{
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$result = $repository->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;
}
}

View File

@ -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 [
],
/*

View File

@ -0,0 +1,33 @@
{% extends "./layout/default.twig" %}
{% block breadcrumbs %}
{{ Breadcrumbs.renderIfExists }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">{{ 'import_complete'|_ }}</h3>
</div>
<div class="box-body">
<p>
{{ 'import_complete_text'|_ }}
</p>
<div class="row">
<div class="col-lg-2">
<a href="{{ route('import.download', [job.key]) }}" class="btn btn-default"><i class="fa fa-fw fa-download"></i> {{ 'import_download_config' }}</a>
</div>
<div class="col-lg-2">
<a href="#" class="btn btn-default"><i class="fa fa-fw fa-gears"></i> {{ 'import_start_import' }}</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}
{% block styles %}
{% endblock %}

View File

@ -24,6 +24,44 @@
</div>
<form action="{{ route('import.postSettings', job.key) }}" method="post">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="settings" value="map"/>
{% for field in data %}
<div class="row">
<div class="col-lg-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ field.name }}</h3>
</div>
<div class="box-body no-padding">
<table class="table table-hover">
<thead>
<tr>
<th style="width:50%;">{{ trans('csv.field_value') }}</th>
<th>{{ trans('csv.field_mapped_to') }}</th>
</tr>
</thead>
<tbody>
{% for option in field.values %}
<tr>
<td>
<code>{{ option }}</code>
</td>
<td>
{{ Form.select('mapping['~field.index~']['~option~']',
field.options,
job.configuration['column-mapping-config'][field.index][option], {class: 'form-control'}) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endfor %}
{#

View File

@ -54,10 +54,16 @@
{% endif %}
<td>
{{ 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'}) }}
</td>
<td>
{{ Form.checkbox(('map['~loop.index0~']'),1,map[index]) }}
{{ Form.checkbox(('map['~loop.index0~']'),1,
job.configuration['column-do-mapping'][loop.index0]
) }}
</td>
</tr>

View File

@ -6,7 +6,6 @@
{% block content %}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<!-- ACCOUNTS -->
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">{{ 'import'|_ }}</h3>