Lots of import related code.

This commit is contained in:
James Cole 2016-07-15 22:26:08 +02:00
parent f5b3dc36e3
commit c9e46a4dd1
19 changed files with 609 additions and 41 deletions

View File

@ -0,0 +1,85 @@
<?php
/**
* Import.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\Console\Commands;
use FireflyIII\Import\Importer\ImporterInterface;
use FireflyIII\Import\Logging\CommandHandler;
use FireflyIII\Models\ImportJob;
use Illuminate\Console\Command;
use Log;
/**
* Class Import
*
* @package FireflyIII\Console\Commands
*/
class Import extends Command
{
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import stuff into Firefly III.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly:import {key}';
/**
* Create a new command instance.
*
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$jobKey = $this->argument('key');
$job = ImportJob::whereKey($jobKey)->first();
if (is_null($job)) {
$this->error('This job does not seem to exist.');
return;
}
if ($job->status != 'settings_complete') {
$this->error('This job is not ready to be imported.');
return;
}
$this->line('Going to import job with key "' . $job->key . '" of type ' . $job->file_type);
$class = config('firefly.import_formats.' . $job->file_type);
/** @var ImporterInterface $importer */
$importer = app($class);
$importer->setJob($job);
// intercept logging by importer.
$monolog = Log::getMonolog();
$handler = new CommandHandler($this);
$monolog->pushHandler($handler);
$importer->start();
$this->line('Something something import: ' . $jobKey);
}
}

View File

@ -25,7 +25,7 @@ class UpgradeFireflyInstructions extends Command
*
* @var string
*/
protected $description = 'Command description';
protected $description = 'Instructions in case of upgrade trouble.';
/**
* The name and signature of the console command.
*

View File

@ -11,6 +11,7 @@ declare(strict_types = 1);
namespace FireflyIII\Console;
use FireflyIII\Console\Commands\Import;
use FireflyIII\Console\Commands\UpgradeFireflyInstructions;
use FireflyIII\Console\Commands\VerifyDatabase;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -51,5 +52,6 @@ class Kernel extends ConsoleKernel
= [
UpgradeFireflyInstructions::class,
VerifyDatabase::class,
Import::class,
];
}

View File

@ -0,0 +1,48 @@
<?php
/**
* AssetAccountIban.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\Converter;
use FireflyIII\Crud\Account\AccountCrudInterface;
use Log;
/**
* Class AssetAccountIban
*
* @package FireflyIII\Import\Converter
*/
class AssetAccountIban extends BasicConverter implements ConverterInterface
{
/**
* @param $value
*
*/
public function convert($value)
{
Log::debug('Going to convert value ' . $value);
/** @var AccountCrudInterface $repository */
$repository = app(AccountCrudInterface::class, [$this->user]);
if (isset($this->mapping[$value])) {
Log::debug('Found account in mapping. Should exist.',['value' => $value]);
$account = $repository->find(intval($value));
Log::debug('Found account ', ['id' => $account->id]);
}
Log::debug('Given map is ', $this->mapping);
exit;
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* BasicConverter.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\Converter;
use FireflyIII\User;
/**
* Class BasicConverter
*
* @package FireflyIII\Import\Converter
*/
class BasicConverter
{
/** @var bool */
public $doMap;
/** @var array */
public $mapping = [];
/** @var User */
public $user;
/**
* @param mixed $doMap
*/
public function setDoMap(bool $doMap)
{
$this->doMap = $doMap;
}
/**
* @param array $mapping
*
*/
public function setMapping(array $mapping)
{
$this->mapping = $mapping;
}
/**
* @param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* ConverterInterface.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\Converter;
use FireflyIII\User;
/**
* Interface ConverterInterface
*
* @package FireflyIII\Import\Converter
*/
interface ConverterInterface
{
/**
* @param $value
*
*/
public function convert($value);
/**
* @param bool $doMap
*/
public function setDoMap(bool $doMap);
/**
* @param array $mapping
*
*/
public function setMapping(array $mapping);
/**
* @param User $user
*/
public function setUser(User $user);
}

View File

@ -0,0 +1,31 @@
<?php
/**
* SomeConverter.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\Converter;
/**
* Class SomeConverter
*
* @package FireflyIII\Import\Converter
*/
class SomeConverter extends BasicConverter implements ConverterInterface
{
/**
* @param $value
*
*/
public function convert($value)
{
// // TODO: Implement convert() method.
// throw new NotImplementedException;
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* ImportEntry.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;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
/**
* Class ImportEntry
*
* @package FireflyIII\Import
*/
class ImportEntry
{
/** @var Account */
public $assetAccount;
/**
* @param $role
* @param $value
*
* @throws FireflyException
*/
public function fromRawValue($role, $value)
{
switch ($role) {
default:
throw new FireflyException('Cannot handle role of type "' . $role . '".');
}
}
}

View File

@ -14,6 +14,8 @@ namespace FireflyIII\Import\Importer;
use ExpandedForm;
use FireflyIII\Crud\Account\AccountCrud;
use FireflyIII\Import\Converter\ConverterInterface;
use FireflyIII\Import\ImportEntry;
use FireflyIII\Import\Mapper\MapperInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\ImportJob;
@ -211,6 +213,32 @@ class CsvImporter implements ImporterInterface
$this->job = $job;
}
/**
* Run the actual import
*
* @return bool
*/
public function start(): bool
{
$config = $this->job->configuration;
$content = $this->job->uploadFileContents();
// create CSV reader.
$reader = Reader::createFromString($content);
$start = $config['has-headers'] ? 1 : 0;
$results = $reader->fetch();
foreach ($results as $index => $row) {
if ($index >= $start) {
Log::debug(sprintf('Now going to import row %d.', $index));
$this->importSingleRow($row);
}
}
Log::debug('This call should be intercepted somehow.');
return true;
}
/**
* Store the settings filled in by the user, if applicable.
*
@ -387,4 +415,44 @@ class CsvImporter implements ImporterInterface
}
/**
* @param array $row
*
* @return bool
*/
private function importSingleRow(array $row): bool
{
$object = new ImportEntry;
$config = $this->job->configuration;
foreach ($row as $index => $value) {
// find the role for this column:
$role = $config['column-roles'][$index] ?? '_ignore';
$doMap = $config['column-do-mapping'][$index] ?? false;
$converterClass = config('csv.import_roles.' . $role . '.converter');
$mapping = $config['column-mapping-config'][$index] ?? [];
/** @var ConverterInterface $converter */
$converter = app('FireflyIII\\Import\\Converter\\' . $converterClass);
// set some useful values for the converter:
$converter->setMapping($mapping);
$converter->setDoMap($doMap);
$converter->setUser($this->job->user);
// run the converter for this value:
$convertedValue = $converter->convert($value);
// log it.
Log::debug('Value ', ['index' => $index, 'value' => $value, 'role' => $role]);
// store in import entry:
// $object->fromRawValue($role, $value);
}
exit;
return true;
}
}

View File

@ -23,6 +23,14 @@ use Symfony\Component\HttpFoundation\FileBag;
*/
interface ImporterInterface
{
/**
* Run the actual import
*
* @return bool
*/
public function start(): bool;
/**
* After uploading, and after setJob(), prepare anything that is
* necessary for the configure() line.

View File

@ -0,0 +1,49 @@
<?php
/**
* Handler.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\Logging;
use Illuminate\Console\Command;
use Monolog\Handler\AbstractProcessingHandler;
/**
* Class CommandHandler
*
* @package FireflyIII\Import\Logging
*/
class CommandHandler extends AbstractProcessingHandler
{
/** @var Command */
private $command;
/**
* Handler constructor.
*
* @param Command $command
*/
public function __construct(Command $command)
{
$this->command = $command;
}
/**
* Writes the record down to the log of the implementing handler
*
* @param array $record
*
* @return void
*/
protected function write(array $record)
{
$this->command->line((string) $record['formatted']);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* AssetAccountIbans.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\Crud\Account\AccountCrudInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
/**
* Class AssetAccounts
*
* @package FireflyIII\Import\Mapper
*/
class AssetAccountIbans implements MapperInterface
{
/**
* @return array
*/
public function getMap(): array
{
/** @var AccountCrudInterface $crud */
$crud = app(AccountCrudInterface::class);
$set = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$topList = [];
$list = [];
/** @var Account $account */
foreach ($set as $account) {
$iban = $account->iban ?? '';
if (strlen($iban) > 0) {
$topList[$account->id] = $account->iban . ' (' . $account->name . ')';
}
if (strlen($iban) == 0) {
$list[$account->id] = $account->name;
}
}
asort($topList);
asort($list);
$list = $topList + $list;
$list = [0 => trans('csv.do_not_map')] + $list;
return $list;
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* OpposingAccountIbans.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\Crud\Account\AccountCrudInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
/**
* Class OpposingAccounts
*
* @package FireflyIII\Import\Mapper
*/
class OpposingAccountIbans implements MapperInterface
{
/**
* @return array
*/
public function getMap(): array
{
/** @var AccountCrudInterface $crud */
$crud = app(AccountCrudInterface::class);
$set = $crud->getAccountsByType(
[
AccountType::DEFAULT, AccountType::ASSET,
AccountType::EXPENSE, AccountType::BENEFICIARY,
AccountType::REVENUE
]);
$topList = [];
$list = [];
/** @var Account $account */
foreach ($set as $account) {
$iban = $account->iban ?? '';
if (strlen($iban) > 0) {
$topList[$account->id] = $account->iban . ' (' . $account->name . ')';
}
if (strlen($iban) == 0) {
$list[$account->id] = $account->name;
}
}
asort($topList);
asort($list);
$list = $topList + $list;
$list = [0 => trans('csv.do_not_map')] + $list;
return $list;
}
}

View File

@ -135,7 +135,7 @@ return [
'mappable' => true,
'field' => 'asset-account-iban',
'converter' => 'AssetAccountIban',
'mapper' => 'AssetAccounts',
'mapper' => 'AssetAccountIbans',
],
'account-number' => [
@ -160,7 +160,7 @@ return [
'mappable' => true,
'field' => 'opposing-account-iban',
'converter' => 'OpposingAccountIban',
'mapper' => 'OpposingAccounts',
'mapper' => 'OpposingAccountIbans',
],
'opposing-number' => [
'mappable' => true,

View File

@ -33,6 +33,15 @@ return [
'no_example_data' => 'No example data available',
'store_column_roles' => 'Continue import',
'do_not_map' => '(do not map)',
'map_title' => 'Connect data in your files',
'map_text' => 'Connect data in your files',
'field_value' => 'Field value',
'field_mapped_to' => 'Mapped to',
'store_column_mapping' => 'Store mapping',
// map things.
'column__ignore' => '(ignore this column)',
'column_account-iban' => 'Asset account (IBAN)',

View File

@ -759,4 +759,8 @@ return [
'configure_import' => 'Further configure your import',
'import_finish_configuration' => 'Finish configuration',
'settings_for_import' => 'Settings',
'import_complete' => 'Import configuration complete!',
'import_complete_text' => 'Download the config file. You can also run it from the command line.',
'import_download_config' => 'Download configuration',
'import_start_import' => 'Start import',
];

View File

@ -14,12 +14,15 @@
<p>
{{ 'import_complete_text'|_ }}
</p>
<p>
<code>php artisan firefly:import {{ job.key }}</code>
</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 class="col-lg-4">
<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 class="col-lg-4">
<a href="#" class="btn btn-default"><i class="fa fa-fw fa-gears"></i> {{ 'import_start_import'|_ }}</a>
</div>
</div>
</div>

View File

@ -32,44 +32,47 @@
<h3 class="box-title">{{ trans('csv.import_configure_form') }}</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-6">
{{ ExpandedForm.checkbox('has_headers',1,job.configuration['has-headers'],{helpText: trans('csv.header_help')}) }}
{{ ExpandedForm.text('date_format',job.configuration['date-format'],{helpText: trans('csv.date_help', {dateExample: phpdate('Ymd')}) }) }}
{{ ExpandedForm.select('csv_delimiter', data.delimiters, job.configuration['delimiter'], {helpText: trans('csv.delimiter_help') } ) }}
{{ ExpandedForm.select('csv_import_account', data.accounts, 0, {helpText: trans('csv.import_account_help')} ) }}
{{ ExpandedForm.checkbox('has_headers',1,job.configuration['has-headers'],{helpText: trans('csv.header_help')}) }}
{{ ExpandedForm.text('date_format',job.configuration['date-format'],{helpText: trans('csv.date_help', {dateExample: phpdate('Ymd')}) }) }}
{{ ExpandedForm.select('csv_delimiter', data.delimiters, job.configuration['delimiter'], {helpText: trans('csv.delimiter_help') } ) }}
{{ ExpandedForm.select('csv_import_account', data.accounts, 0, {helpText: trans('csv.import_account_help')} ) }}
{% for type, specific in data.specifics %}
<div class="form-group">
<label for="{{ type }}_label" class="col-sm-4 control-label">
{{ specific.name }}
</label>
<div class="col-sm-8">
<div class="radio"><label>
{{ Form.checkbox('specifics['~type~']', '1',
job.configuration.specifics[type] == '1', {'id': type ~ '_label'}) }}
{{ specific.description }}
{% for type, specific in data.specifics %}
<div class="form-group">
<label for="{{ type }}_label" class="col-sm-4 control-label">
{{ specific.name }}
</label>
<div class="col-sm-8">
<div class="radio"><label>
{{ Form.checkbox('specifics['~type~']', '1',
job.configuration.specifics[type] == '1', {'id': type ~ '_label'}) }}
{{ specific.description }}
</label>
</div>
</div>
</div>
</div>
{% endfor %}
{% if not data.is_upload_possible %}
<div class="form-group" id="csv_holder">
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-8">
<pre>{{ data.upload_path }}</pre>
<p class="text-danger">
{{ trans('csv.upload_not_writeable') }}
</p>
</div>
</div>
{% endif %}
</div>
{% endfor %}
{% if not data.is_upload_possible %}
<div class="form-group" id="csv_holder">
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-8">
<pre>{{ data.upload_path }}</pre>
<p class="text-danger">
{{ trans('csv.upload_not_writeable') }}
</p>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>

View File

@ -31,7 +31,7 @@
<div class="col-lg-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ field.name }}</h3>
<h3 class="box-title">{{ trans('csv.column_'~field.name) }}</h3>
</div>
<div class="box-body no-padding">
<table class="table table-hover">