mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-10 23:45:48 -06:00
Merge branch 'feature/csv-import' into develop
Conflicts: composer.json composer.lock resources/lang/en/form.php resources/lang/nl/form.php
This commit is contained in:
commit
74c50930bd
51
app/Helpers/Csv/Converter/AccountIban.php
Normal file
51
app/Helpers/Csv/Converter/AccountIban.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: sander
|
||||
* Date: 05/07/15
|
||||
* Time: 05:49
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Converter;
|
||||
|
||||
use Auth;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class AccountIban
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Converter
|
||||
*/
|
||||
class AccountIban extends BasicConverter implements ConverterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return Account|null
|
||||
*/
|
||||
public function convert()
|
||||
{
|
||||
// is mapped? Then it's easy!
|
||||
if (isset($this->mapped[$this->index][$this->value])) {
|
||||
$account = Auth::user()->accounts()->find($this->mapped[$this->index][$this->value]);
|
||||
} else {
|
||||
// find or create new account:
|
||||
$accountType = AccountType::where('type', 'Asset account')->first();
|
||||
$account = Account::firstOrCreateEncrypted(
|
||||
[
|
||||
'name' => $this->value,
|
||||
//'iban' => $this->value,
|
||||
'user_id' => Auth::user()->id,
|
||||
'account_type_id' => $accountType->id,
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
if ($account->getErrors()->count() > 0) {
|
||||
Log::error('Create or find asset account: ' . json_encode($account->getErrors()->all()));
|
||||
}
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
}
|
32
app/Helpers/Csv/Converter/Amount.php
Normal file
32
app/Helpers/Csv/Converter/Amount.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: sander
|
||||
* Date: 05/07/15
|
||||
* Time: 05:49
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Converter;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
|
||||
/**
|
||||
* Class Amount
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Converter
|
||||
*/
|
||||
class Amount extends BasicConverter implements ConverterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return Account|null
|
||||
*/
|
||||
public function convert()
|
||||
{
|
||||
if (is_numeric($this->value)) {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
122
app/Helpers/Csv/Converter/BasicConverter.php
Normal file
122
app/Helpers/Csv/Converter/BasicConverter.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Converter;
|
||||
|
||||
/**
|
||||
* Class BasicConverter
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Converter
|
||||
*/
|
||||
class BasicConverter
|
||||
{
|
||||
/** @var array */
|
||||
protected $data;
|
||||
/** @var string */
|
||||
protected $field;
|
||||
/** @var int */
|
||||
protected $index;
|
||||
/** @var array */
|
||||
protected $mapped;
|
||||
/** @var string */
|
||||
protected $role;
|
||||
/** @var string */
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*/
|
||||
public function setData(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getField()
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*/
|
||||
public function setField($field)
|
||||
{
|
||||
$this->field = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index
|
||||
*/
|
||||
public function setIndex($index)
|
||||
{
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMapped()
|
||||
{
|
||||
return $this->mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $mapped
|
||||
*/
|
||||
public function setMapped($mapped)
|
||||
{
|
||||
$this->mapped = $mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRole()
|
||||
{
|
||||
return $this->role;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
*/
|
||||
public function setRole($role)
|
||||
{
|
||||
$this->role = $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
|
||||
}
|
55
app/Helpers/Csv/Converter/ConverterInterface.php
Normal file
55
app/Helpers/Csv/Converter/ConverterInterface.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: sander
|
||||
* Date: 05/07/15
|
||||
* Time: 05:42
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Converter;
|
||||
|
||||
/**
|
||||
* Interface ConverterInterface
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Converter
|
||||
*/
|
||||
interface ConverterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function convert();
|
||||
|
||||
/**
|
||||
* @param int $index
|
||||
*/
|
||||
public function setIndex($index);
|
||||
|
||||
/**
|
||||
* @param array $mapped
|
||||
*/
|
||||
public function setMapped($mapped);
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
*/
|
||||
public function setRole($role);
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function setValue($value);
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*/
|
||||
public function setData(array $data);
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*
|
||||
*/
|
||||
public function setField($field);
|
||||
|
||||
}
|
27
app/Helpers/Csv/Converter/CurrencyCode.php
Normal file
27
app/Helpers/Csv/Converter/CurrencyCode.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Converter;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
|
||||
/**
|
||||
* Class CurrencyCode
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Converter
|
||||
*/
|
||||
class CurrencyCode extends BasicConverter implements ConverterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return mixed|static
|
||||
*/
|
||||
public function convert()
|
||||
{
|
||||
if (isset($this->mapped[$this->index][$this->value])) {
|
||||
$currency = TransactionCurrency::find($this->mapped[$this->index][$this->value]);
|
||||
} else {
|
||||
$currency = TransactionCurrency::whereCode($this->value)->first();
|
||||
}
|
||||
|
||||
return $currency;
|
||||
}
|
||||
}
|
33
app/Helpers/Csv/Converter/Date.php
Normal file
33
app/Helpers/Csv/Converter/Date.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: sander
|
||||
* Date: 05/07/15
|
||||
* Time: 05:49
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Converter;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* Class Date
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Converter
|
||||
*/
|
||||
class Date extends BasicConverter implements ConverterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return Carbon
|
||||
*/
|
||||
public function convert()
|
||||
{
|
||||
$format = Session::get('csv-date-format');
|
||||
|
||||
$date = Carbon::createFromFormat($format, $this->value);
|
||||
|
||||
return $date;
|
||||
}
|
||||
}
|
21
app/Helpers/Csv/Converter/Description.php
Normal file
21
app/Helpers/Csv/Converter/Description.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Converter;
|
||||
|
||||
/**
|
||||
* Class Description
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Converter
|
||||
*/
|
||||
class Description extends BasicConverter implements ConverterInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function convert()
|
||||
{
|
||||
return trim($this->data['description'] . ' ' . $this->value);
|
||||
}
|
||||
}
|
22
app/Helpers/Csv/Converter/Ignore.php
Normal file
22
app/Helpers/Csv/Converter/Ignore.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Converter;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
|
||||
/**
|
||||
* Class Amount
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Converter
|
||||
*/
|
||||
class Ignore extends BasicConverter implements ConverterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return Account|null
|
||||
*/
|
||||
public function convert()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
24
app/Helpers/Csv/Converter/OpposingName.php
Normal file
24
app/Helpers/Csv/Converter/OpposingName.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Converter;
|
||||
|
||||
/**
|
||||
* Class OpposingName
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Converter
|
||||
*/
|
||||
class OpposingName extends BasicConverter implements ConverterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* This method cannot search yet for the correct account (Expense account or Revenue account) because simply put,
|
||||
* Firefly doesn't know yet if this account needs to be an Expense account or a Revenue account. This depends
|
||||
* on the amount which is in the current row and that's a big unknown.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function convert()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
27
app/Helpers/Csv/Converter/RabobankDebetCredit.php
Normal file
27
app/Helpers/Csv/Converter/RabobankDebetCredit.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: sander
|
||||
* Date: 05/07/15
|
||||
* Time: 06:12
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Converter;
|
||||
|
||||
|
||||
class RabobankDebetCredit extends BasicConverter implements ConverterInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function convert()
|
||||
{
|
||||
if ($this->value == 'D') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
235
app/Helpers/Csv/Data.php
Normal file
235
app/Helpers/Csv/Data.php
Normal file
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv;
|
||||
|
||||
use Crypt;
|
||||
use League\Csv\Reader;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* Class Data
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv
|
||||
*/
|
||||
class Data
|
||||
{
|
||||
|
||||
/** @var string */
|
||||
protected $csvFileContent;
|
||||
|
||||
/** @var string */
|
||||
protected $csvFileLocation;
|
||||
/** @var string */
|
||||
protected $dateFormat;
|
||||
/** @var bool */
|
||||
protected $hasHeaders;
|
||||
|
||||
/** @var array */
|
||||
protected $map;
|
||||
/** @var array */
|
||||
protected $mapped;
|
||||
/** @var Reader */
|
||||
protected $reader;
|
||||
/** @var array */
|
||||
protected $roles;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->sessionHasHeaders();
|
||||
$this->sessionDateFormat();
|
||||
$this->sessionCsvFileLocation();
|
||||
$this->sessionMap();
|
||||
$this->sessionRoles();
|
||||
$this->sessionMapped();
|
||||
}
|
||||
|
||||
protected function sessionHasHeaders()
|
||||
{
|
||||
if (Session::has('csv-has-headers')) {
|
||||
$this->hasHeaders = (bool)Session::get('csv-has-headers');
|
||||
}
|
||||
}
|
||||
|
||||
protected function sessionDateFormat()
|
||||
{
|
||||
if (Session::has('csv-date-format')) {
|
||||
$this->dateFormat = (string)Session::get('csv-date-format');
|
||||
}
|
||||
}
|
||||
|
||||
protected function sessionCsvFileLocation()
|
||||
{
|
||||
if (Session::has('csv-file')) {
|
||||
$this->csvFileLocation = (string)Session::get('csv-file');
|
||||
}
|
||||
}
|
||||
|
||||
protected function sessionMap()
|
||||
{
|
||||
if (Session::has('csv-map')) {
|
||||
$this->map = (array)Session::get('csv-map');
|
||||
}
|
||||
}
|
||||
|
||||
protected function sessionRoles()
|
||||
{
|
||||
if (Session::has('csv-roles')) {
|
||||
$this->roles = (array)Session::get('csv-roles');
|
||||
}
|
||||
}
|
||||
|
||||
protected function sessionMapped()
|
||||
{
|
||||
if (Session::has('csv-mapped')) {
|
||||
$this->mapped = (array)Session::get('csv-mapped');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDateFormat()
|
||||
{
|
||||
return $this->dateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $dateFormat
|
||||
*/
|
||||
public function setDateFormat($dateFormat)
|
||||
{
|
||||
Session::put('csv-date-format', $dateFormat);
|
||||
$this->dateFormat = $dateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getHasHeaders()
|
||||
{
|
||||
return $this->hasHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $hasHeaders
|
||||
*/
|
||||
public function setHasHeaders($hasHeaders)
|
||||
{
|
||||
Session::put('csv-has-headers', $hasHeaders);
|
||||
$this->hasHeaders = $hasHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMap()
|
||||
{
|
||||
return $this->map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $map
|
||||
*/
|
||||
public function setMap(array $map)
|
||||
{
|
||||
Session::put('csv-map', $map);
|
||||
$this->map = $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMapped()
|
||||
{
|
||||
return $this->mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $mapped
|
||||
*/
|
||||
public function setMapped(array $mapped)
|
||||
{
|
||||
Session::put('csv-mapped', $mapped);
|
||||
$this->mapped = $mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Reader
|
||||
*/
|
||||
public function getReader()
|
||||
{
|
||||
|
||||
if (strlen($this->csvFileContent) === 0) {
|
||||
$this->loadCsvFile();
|
||||
}
|
||||
|
||||
if (is_null($this->reader)) {
|
||||
$this->reader = Reader::createFromString($this->getCsvFileContent());
|
||||
}
|
||||
|
||||
return $this->reader;
|
||||
}
|
||||
|
||||
protected function loadCsvFile()
|
||||
{
|
||||
$file = $this->getCsvFileLocation();
|
||||
$content = file_get_contents($file);
|
||||
$contentDecrypted = Crypt::decrypt($content);
|
||||
$this->setCsvFileContent($contentDecrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCsvFileLocation()
|
||||
{
|
||||
return $this->csvFileLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $csvFileLocation
|
||||
*/
|
||||
public function setCsvFileLocation($csvFileLocation)
|
||||
{
|
||||
Session::put('csv-file', $csvFileLocation);
|
||||
$this->csvFileLocation = $csvFileLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCsvFileContent()
|
||||
{
|
||||
return $this->csvFileContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $csvFileContent
|
||||
*/
|
||||
public function setCsvFileContent($csvFileContent)
|
||||
{
|
||||
$this->csvFileContent = $csvFileContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRoles()
|
||||
{
|
||||
return $this->roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $roles
|
||||
*/
|
||||
public function setRoles(array $roles)
|
||||
{
|
||||
Session::put('csv-roles', $roles);
|
||||
$this->roles = $roles;
|
||||
}
|
||||
|
||||
|
||||
}
|
274
app/Helpers/Csv/Importer.php
Normal file
274
app/Helpers/Csv/Importer.php
Normal file
@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv;
|
||||
|
||||
use App;
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Csv\Converter\ConverterInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use Illuminate\Support\MessageBag;
|
||||
use Log;
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
/**
|
||||
* Class Importer
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv
|
||||
*/
|
||||
class Importer
|
||||
{
|
||||
|
||||
/** @var Data */
|
||||
protected $data;
|
||||
/** @var array */
|
||||
protected $errors;
|
||||
/** @var array */
|
||||
protected $map;
|
||||
/** @var array */
|
||||
protected $mapped;
|
||||
/** @var array */
|
||||
protected $roles;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->map = $this->data->getMap();
|
||||
$this->roles = $this->data->getRoles();
|
||||
$this->mapped = $this->data->getMapped();
|
||||
foreach ($this->data->getReader() as $index => $row) {
|
||||
Log::debug('Now at row ' . $index);
|
||||
$result = $this->importRow($row);
|
||||
if (!($result === true)) {
|
||||
Log::error('Caught error at row #' . $index . ': ' . $result);
|
||||
$this->errors[$index] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
return count($this->errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $row
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function importRow($row)
|
||||
{
|
||||
/*
|
||||
* These fields are necessary to create a new transaction journal. Some are optional:
|
||||
*/
|
||||
$data = $this->getFiller();
|
||||
foreach ($row as $index => $value) {
|
||||
$role = isset($this->roles[$index]) ? $this->roles[$index] : '_ignore';
|
||||
$class = Config::get('csv.roles.' . $role . '.converter');
|
||||
$field = Config::get('csv.roles.' . $role . '.field');
|
||||
|
||||
if (is_null($class)) {
|
||||
throw new FireflyException('No converter for field of type "' . $role . '".');
|
||||
}
|
||||
if (is_null($field)) {
|
||||
throw new FireflyException('No place to store value of type "' . $role . '".');
|
||||
}
|
||||
/** @var ConverterInterface $converter */
|
||||
$converter = App::make('FireflyIII\Helpers\Csv\Converter\\' . $class);
|
||||
$converter->setData($data); // the complete array so far.
|
||||
$converter->setField($field);
|
||||
$converter->setIndex($index);
|
||||
$converter->setMapped($this->mapped);
|
||||
$converter->setValue($value);
|
||||
$converter->setRole($role);
|
||||
$data[$field] = $converter->convert();
|
||||
|
||||
}
|
||||
$data = $this->postProcess($data, $row);
|
||||
$result = $this->validateData($data);
|
||||
if ($result === true) {
|
||||
$result = $this->createTransactionJournal($data);
|
||||
} else {
|
||||
Log::error('Validator: ' . $result);
|
||||
}
|
||||
if ($result instanceof TransactionJournal) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Not a journal.';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getFiller()
|
||||
{
|
||||
return [
|
||||
'description' => '',
|
||||
'asset-account' => null,
|
||||
'opposing-account' => '',
|
||||
'opposing-account-object' => null,
|
||||
'date' => null,
|
||||
'currency' => null,
|
||||
'amount' => null,
|
||||
'amount-modifier' => 1,
|
||||
'ignored' => null,
|
||||
'date-rent' => null,
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Row denotes the original data.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $row
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function postProcess(array $data, array $row)
|
||||
{
|
||||
bcscale(2);
|
||||
$data['description'] = trim($data['description']);
|
||||
$data['amount'] = bcmul($data['amount'], $data['amount-modifier']);
|
||||
if ($data['amount'] < 0) {
|
||||
// create expense account:
|
||||
$accountType = AccountType::where('type', 'Expense account')->first();
|
||||
} else {
|
||||
// create revenue account:
|
||||
$accountType = AccountType::where('type', 'Revenue account')->first();
|
||||
}
|
||||
|
||||
if(strlen($data['description']) == 0) {
|
||||
$data['description'] = trans('firefly.csv_empty_description');
|
||||
}
|
||||
|
||||
// do bank specific fixes:
|
||||
|
||||
$specifix = new Specifix();
|
||||
$specifix->setData($data);
|
||||
$specifix->setRow($row);
|
||||
$specifix->fix($data, $row);
|
||||
|
||||
// get data back:
|
||||
$data = $specifix->getData();
|
||||
|
||||
$data['opposing-account-object'] = Account::firstOrCreateEncrypted(
|
||||
[
|
||||
'user_id' => Auth::user()->id,
|
||||
'name' => ucwords($data['opposing-account']),
|
||||
'account_type_id' => $accountType->id,
|
||||
'active' => 1,
|
||||
]
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function validateData($data)
|
||||
{
|
||||
if (is_null($data['date']) && is_null($data['date-rent'])) {
|
||||
return 'No date value for this row.';
|
||||
}
|
||||
if (strlen($data['description']) == 0) {
|
||||
return 'No valid description';
|
||||
}
|
||||
if (is_null($data['opposing-account-object'])) {
|
||||
return 'Opposing account is null';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
protected function createTransactionJournal(array $data)
|
||||
{
|
||||
bcscale(2);
|
||||
$date = $data['date'];
|
||||
if (is_null($data['date'])) {
|
||||
$date = $data['date-rent'];
|
||||
}
|
||||
if ($data['amount'] < 0) {
|
||||
$transactionType = TransactionType::where('type', 'Withdrawal')->first();
|
||||
} else {
|
||||
$transactionType = TransactionType::where('type', 'Deposit')->first();
|
||||
}
|
||||
$errors = new MessageBag;
|
||||
$journal = TransactionJournal::create(
|
||||
[
|
||||
'user_id' => Auth::user()->id,
|
||||
'transaction_type_id' => $transactionType->id,
|
||||
'bill_id' => null,
|
||||
'transaction_currency_id' => $data['currency']->id,
|
||||
'description' => $data['description'],
|
||||
'completed' => 0,
|
||||
'date' => $date,
|
||||
]
|
||||
);
|
||||
$errors = $journal->getErrors()->merge($errors);
|
||||
if ($journal->getErrors()->count() == 0) {
|
||||
// create both transactions:
|
||||
$transaction = Transaction::create(
|
||||
[
|
||||
'transaction_journal_id' => $journal->id,
|
||||
'account_id' => $data['asset-account']->id,
|
||||
'amount' => $data['amount']
|
||||
]
|
||||
);
|
||||
$errors = $transaction->getErrors()->merge($errors);
|
||||
|
||||
$transaction = Transaction::create(
|
||||
[
|
||||
'transaction_journal_id' => $journal->id,
|
||||
'account_id' => $data['opposing-account-object']->id,
|
||||
'amount' => bcmul($data['amount'], -1)
|
||||
]
|
||||
);
|
||||
$errors = $transaction->getErrors()->merge($errors);
|
||||
}
|
||||
if ($errors->count() == 0) {
|
||||
$journal->completed = 1;
|
||||
$journal->save();
|
||||
}
|
||||
|
||||
return $journal;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Data $data
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*
|
||||
* @return Carbon
|
||||
*/
|
||||
protected function parseDate($value)
|
||||
{
|
||||
return Carbon::createFromFormat($this->data->getDateFormat(), $value);
|
||||
}
|
||||
|
||||
}
|
42
app/Helpers/Csv/Mapper/AssetAccount.php
Normal file
42
app/Helpers/Csv/Mapper/AssetAccount.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: sander
|
||||
* Date: 05/07/15
|
||||
* Time: 08:35
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Mapper;
|
||||
|
||||
use Auth;
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* Class AssetAccount
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Mapper
|
||||
*/
|
||||
class AssetAccount implements MapperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMap()
|
||||
{
|
||||
$result = Auth::user()->accounts()->with(
|
||||
['accountmeta' => function (HasMany $query) {
|
||||
$query->where('name', 'accountRole');
|
||||
}]
|
||||
)->accountTypeIn(['Default account', 'Asset account'])->orderBy('accounts.name', 'ASC')->get(['accounts.*']);
|
||||
|
||||
$list = [];
|
||||
/** @var Account $account */
|
||||
foreach ($result as $account) {
|
||||
$list[$account->id] = $account->name;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
16
app/Helpers/Csv/Mapper/MapperInterface.php
Normal file
16
app/Helpers/Csv/Mapper/MapperInterface.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Mapper;
|
||||
|
||||
/**
|
||||
* Interface MapperInterface
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Mapper
|
||||
*/
|
||||
interface MapperInterface
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMap();
|
||||
}
|
28
app/Helpers/Csv/Mapper/TransactionCurrency.php
Normal file
28
app/Helpers/Csv/Mapper/TransactionCurrency.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv\Mapper;
|
||||
|
||||
use FireflyIII\Models\TransactionCurrency as TC;
|
||||
|
||||
/**
|
||||
* Class TransactionCurrency
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv\Mapper
|
||||
*/
|
||||
class TransactionCurrency implements MapperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMap()
|
||||
{
|
||||
$currencies = TC::get();
|
||||
$list = [];
|
||||
foreach ($currencies as $currency) {
|
||||
$list[$currency->id] = $currency->name . ' (' . $currency->code . ')';
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
73
app/Helpers/Csv/Specifix.php
Normal file
73
app/Helpers/Csv/Specifix.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv;
|
||||
|
||||
/**
|
||||
* Class Specifix
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv
|
||||
*/
|
||||
class Specifix
|
||||
{
|
||||
/** @var array */
|
||||
protected $data;
|
||||
|
||||
/** @var array */
|
||||
protected $row;
|
||||
|
||||
|
||||
/**
|
||||
* Implement bank and locale related fixes.
|
||||
*/
|
||||
public function fix()
|
||||
{
|
||||
$this->rabobankFixEmptyOpposing();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes Rabobank specific thing.
|
||||
*/
|
||||
protected function rabobankFixEmptyOpposing()
|
||||
{
|
||||
if (strlen($this->data['opposing-account']) == 0) {
|
||||
$this->data['opposing-account'] = $this->row[10];
|
||||
}
|
||||
$this->data['description'] = trim(str_replace($this->row[10], '', $this->data['description']));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRow()
|
||||
{
|
||||
return $this->row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $row
|
||||
*/
|
||||
public function setRow($row)
|
||||
{
|
||||
$this->row = $row;
|
||||
}
|
||||
|
||||
|
||||
}
|
171
app/Helpers/Csv/Wizard.php
Normal file
171
app/Helpers/Csv/Wizard.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
namespace FireflyIII\Helpers\Csv;
|
||||
|
||||
use App;
|
||||
use Auth;
|
||||
use Config;
|
||||
use Crypt;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Csv\Mapper\MapperInterface;
|
||||
use League\Csv\Reader;
|
||||
use ReflectionException;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* Class Wizard
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv
|
||||
*/
|
||||
class Wizard implements WizardInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @param Reader $reader
|
||||
* @param array $map
|
||||
* @param bool $hasHeaders
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMappableValues($reader, array $map, $hasHeaders)
|
||||
{
|
||||
$values = [];
|
||||
/*
|
||||
* Loop over the CSV and collect mappable data:
|
||||
*/
|
||||
foreach ($reader as $index => $row) {
|
||||
if (($hasHeaders && $index > 1) || !$hasHeaders) {
|
||||
// collect all map values
|
||||
foreach ($map as $column => $irrelevant) {
|
||||
// check if $irrelevant is mappable!
|
||||
$values[$column][] = $row[$column];
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Make each one unique.
|
||||
*/
|
||||
foreach ($values as $column => $found) {
|
||||
$values[$column] = array_unique($found);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $roles
|
||||
* @param mixed $map
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function processSelectedMapping(array $roles, $map)
|
||||
{
|
||||
$configRoles = Config::get('csv.roles');
|
||||
$maps = [];
|
||||
|
||||
|
||||
if (is_array($map)) {
|
||||
foreach ($map as $index => $field) {
|
||||
if (isset($roles[$index])) {
|
||||
$name = $roles[$index];
|
||||
if ($configRoles[$name]['mappable']) {
|
||||
$maps[$index] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $maps;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $input
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function processSelectedRoles($input)
|
||||
{
|
||||
$roles = [];
|
||||
|
||||
|
||||
/*
|
||||
* Store all rows for each column:
|
||||
*/
|
||||
if (is_array($input)) {
|
||||
foreach ($input as $index => $role) {
|
||||
if ($role != '_ignore') {
|
||||
$roles[$index] = $role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sessionHasValues(array $fields)
|
||||
{
|
||||
foreach ($fields as $field) {
|
||||
if (!Session::has($field)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $map
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function showOptions(array $map)
|
||||
{
|
||||
$options = [];
|
||||
foreach ($map as $index => $columnRole) {
|
||||
|
||||
$mapper = Config::get('csv.roles.' . $columnRole . '.mapper');
|
||||
if (is_null($mapper)) {
|
||||
throw new FireflyException('Cannot map field of type "' . $columnRole . '".');
|
||||
}
|
||||
$class = 'FireflyIII\Helpers\Csv\Mapper\\' . $mapper;
|
||||
try {
|
||||
/** @var MapperInterface $mapObject */
|
||||
$mapObject = App::make($class);
|
||||
} catch (ReflectionException $e) {
|
||||
throw new FireflyException('Column "' . $columnRole . '" cannot be mapped because class ' . $mapper . ' does not exist.');
|
||||
}
|
||||
$set = $mapObject->getMap();
|
||||
$options[$index] = $set;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function storeCsvFile($path)
|
||||
{
|
||||
$time = str_replace(' ', '-', microtime());
|
||||
$fileName = 'csv-upload-' . Auth::user()->id . '-' . $time . '.csv.encrypted';
|
||||
$fullPath = storage_path('upload') . DIRECTORY_SEPARATOR . $fileName;
|
||||
$content = file_get_contents($path);
|
||||
$contentEncrypted = Crypt::encrypt($content);
|
||||
file_put_contents($fullPath, $contentEncrypted);
|
||||
|
||||
return $fullPath;
|
||||
|
||||
|
||||
}
|
||||
}
|
58
app/Helpers/Csv/WizardInterface.php
Normal file
58
app/Helpers/Csv/WizardInterface.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Helpers\Csv;
|
||||
use League\Csv\Reader;
|
||||
|
||||
/**
|
||||
* Interface WizardInterface
|
||||
*
|
||||
* @package FireflyIII\Helpers\Csv
|
||||
*/
|
||||
interface WizardInterface
|
||||
{
|
||||
/**
|
||||
* @param Reader $reader
|
||||
* @param array $map
|
||||
* @param bool $hasHeaders
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMappableValues($reader, array $map, $hasHeaders);
|
||||
|
||||
/**
|
||||
* @param array $roles
|
||||
* @param mixed $map
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function processSelectedMapping(array $roles, $map);
|
||||
|
||||
/**
|
||||
* @param mixed $input
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function processSelectedRoles($input);
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sessionHasValues(array $fields);
|
||||
|
||||
/**
|
||||
* @param array $map
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function showOptions(array $map);
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function storeCsvFile($path);
|
||||
|
||||
}
|
413
app/Http/Controllers/CsvController.php
Normal file
413
app/Http/Controllers/CsvController.php
Normal file
@ -0,0 +1,413 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: sander
|
||||
* Date: 03/07/15
|
||||
* Time: 10:37
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use App;
|
||||
use Config;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Csv\Data;
|
||||
use FireflyIII\Helpers\Csv\Importer;
|
||||
use FireflyIII\Helpers\Csv\WizardInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Input;
|
||||
use Log;
|
||||
use Redirect;
|
||||
use Session;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class CsvController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers
|
||||
*/
|
||||
class CsvController extends Controller
|
||||
{
|
||||
|
||||
/** @var Data */
|
||||
protected $data;
|
||||
/** @var WizardInterface */
|
||||
protected $wizard;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
View::share('title', trans('firefly.csv'));
|
||||
View::share('mainTitleIcon', 'fa-file-text-o');
|
||||
|
||||
if (Config::get('firefly.csv_import_enabled') === false) {
|
||||
throw new FireflyException('CSV Import is not enabled.');
|
||||
}
|
||||
|
||||
$this->wizard = App::make('FireflyIII\Helpers\Csv\WizardInterface');
|
||||
$this->data = App::make('FireflyIII\Helpers\Csv\Data');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Define column roles and mapping.
|
||||
*
|
||||
*
|
||||
* STEP THREE
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function columnRoles()
|
||||
{
|
||||
|
||||
$fields = ['csv-file', 'csv-date-format', 'csv-has-headers'];
|
||||
if (!$this->wizard->sessionHasValues($fields)) {
|
||||
Session::flash('warning', 'Could not recover upload.');
|
||||
|
||||
return Redirect::route('csv.index');
|
||||
}
|
||||
|
||||
$subTitle = trans('firefly.csv_process');
|
||||
$firstRow = $this->data->getReader()->fetchOne();
|
||||
$count = count($firstRow);
|
||||
$headers = [];
|
||||
$example = $this->data->getReader()->fetchOne();
|
||||
$availableRoles = [];
|
||||
$roles = $this->data->getRoles();
|
||||
$map = $this->data->getMap();
|
||||
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
$headers[] = trans('firefly.csv_column') . ' #' . $i;
|
||||
}
|
||||
if ($this->data->getHasHeaders()) {
|
||||
$headers = $firstRow;
|
||||
}
|
||||
|
||||
foreach (Config::get('csv.roles') as $name => $role) {
|
||||
$availableRoles[$name] = $role['name'];
|
||||
}
|
||||
ksort($availableRoles);
|
||||
|
||||
return view('csv.column-roles', compact('availableRoles', 'map', 'roles', 'headers', 'example', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional download of mapping.
|
||||
*
|
||||
* STEP FOUR THREE-A
|
||||
*/
|
||||
public function downloadConfig()
|
||||
{
|
||||
$fields = ['csv-date-format', 'csv-has-headers'];
|
||||
if (!$this->wizard->sessionHasValues($fields)) {
|
||||
Session::flash('warning', 'Could not recover upload.');
|
||||
|
||||
return Redirect::route('csv.index');
|
||||
}
|
||||
$data = [
|
||||
'date-format' => Session::get('date-format'),
|
||||
'has-headers' => Session::get('csv-has-headers')
|
||||
];
|
||||
// $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-map', 'csv-roles', 'csv-mapped'];
|
||||
if (Session::has('csv-map')) {
|
||||
$data['map'] = Session::get('csv-map');
|
||||
}
|
||||
if (Session::has('csv-roles')) {
|
||||
$data['roles'] = Session::get('csv-roles');
|
||||
}
|
||||
if (Session::has('csv-mapped')) {
|
||||
$data['mapped'] = Session::get('csv-mapped');
|
||||
}
|
||||
|
||||
$result = json_encode($data, JSON_PRETTY_PRINT);
|
||||
$name = 'csv-configuration-' . date('Y-m-d') . '.json';
|
||||
|
||||
header('Content-disposition: attachment; filename=' . $name);
|
||||
header('Content-type: application/json');
|
||||
echo $result;
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
public function downloadConfigPage()
|
||||
{
|
||||
return view('csv.download-config');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method shows the initial upload form.
|
||||
*
|
||||
* STEP ONE
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$subTitle = trans('firefly.csv_import');
|
||||
|
||||
Session::forget('csv-date-format');
|
||||
Session::forget('csv-has-headers');
|
||||
Session::forget('csv-file');
|
||||
Session::forget('csv-map');
|
||||
Session::forget('csv-roles');
|
||||
Session::forget('csv-mapped');
|
||||
|
||||
// get values which are yet unsaveable or unmappable:
|
||||
$unsupported = [];
|
||||
foreach (Config::get('csv.roles') as $role) {
|
||||
if (!isset($role['converter'])) {
|
||||
$unsupported[] = trans('firefly.csv_unsupported_value', ['columnRole' => $role['name']]);
|
||||
}
|
||||
if ($role['mappable'] === true && !isset($role['mapper'])) {
|
||||
$unsupported[] = trans('firefly.csv_unsupported_map', ['columnRole' => $role['name']]);
|
||||
}
|
||||
}
|
||||
sort($unsupported);
|
||||
|
||||
|
||||
// can actually upload?
|
||||
$uploadPossible = is_writable(storage_path('upload'));
|
||||
$path = storage_path('upload');
|
||||
|
||||
return view('csv.index', compact('subTitle', 'uploadPossible', 'path', 'unsupported'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the file.
|
||||
*
|
||||
* STEP FOUR
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function initialParse()
|
||||
{
|
||||
$fields = ['csv-file', 'csv-date-format', 'csv-has-headers'];
|
||||
if (!$this->wizard->sessionHasValues($fields)) {
|
||||
Session::flash('warning', 'Could not recover upload.');
|
||||
|
||||
return Redirect::route('csv.index');
|
||||
}
|
||||
|
||||
|
||||
// process given roles and mapping:
|
||||
$roles = $this->wizard->processSelectedRoles(Input::get('role'));
|
||||
$maps = $this->wizard->processSelectedMapping($roles, Input::get('map'));
|
||||
|
||||
Session::put('csv-map', $maps);
|
||||
Session::put('csv-roles', $roles);
|
||||
|
||||
/*
|
||||
* Go back when no roles defined:
|
||||
*/
|
||||
if (count($roles) === 0) {
|
||||
Session::flash('warning', 'Please select some roles.');
|
||||
|
||||
return Redirect::route('csv.column-roles');
|
||||
}
|
||||
|
||||
/*
|
||||
* Continue with map specification when necessary.
|
||||
*/
|
||||
if (count($maps) > 0) {
|
||||
return Redirect::route('csv.map');
|
||||
}
|
||||
|
||||
/*
|
||||
* Or simply start processing.
|
||||
*/
|
||||
|
||||
// proceed to download config
|
||||
return Redirect::route('csv.download-config-page');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Map first if necessary,
|
||||
*
|
||||
* STEP FIVE.
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|View
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function map()
|
||||
{
|
||||
|
||||
/*
|
||||
* Make sure all fields we need are accounted for.
|
||||
*/
|
||||
$fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-map', 'csv-roles'];
|
||||
if (!$this->wizard->sessionHasValues($fields)) {
|
||||
Session::flash('warning', 'Could not recover upload.');
|
||||
|
||||
return Redirect::route('csv.index');
|
||||
}
|
||||
|
||||
/*
|
||||
* The "options" array contains all options the user has
|
||||
* per column, where the key represents the column.
|
||||
*
|
||||
* For each key there is an array which in turn represents
|
||||
* all the options available: grouped by ID.
|
||||
*
|
||||
* Aka:
|
||||
*
|
||||
* options[column index] = [
|
||||
* field id => field identifier.
|
||||
* ]
|
||||
*/
|
||||
try {
|
||||
$options = $this->wizard->showOptions($this->data->getMap());
|
||||
} catch (FireflyException $e) {
|
||||
return view('error', ['message' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
/*
|
||||
* After these values are prepped, read the actual CSV file
|
||||
*/
|
||||
$reader = $this->data->getReader();
|
||||
$map = $this->data->getMap();
|
||||
$hasHeaders = $this->data->getHasHeaders();
|
||||
$values = $this->wizard->getMappableValues($reader, $map, $hasHeaders);
|
||||
$map = $this->data->getMap();
|
||||
$mapped = $this->data->getMapped();
|
||||
|
||||
return view('csv.map', compact('map', 'options', 'values', 'mapped'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finally actually process the CSV file.
|
||||
*
|
||||
* STEP SEVEN
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
/*
|
||||
* Make sure all fields we need are accounted for.
|
||||
*/
|
||||
$fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-map', 'csv-roles', 'csv-mapped'];
|
||||
if (!$this->wizard->sessionHasValues($fields)) {
|
||||
Session::flash('warning', 'Could not recover upload.');
|
||||
|
||||
return Redirect::route('csv.index');
|
||||
}
|
||||
|
||||
Log::debug('Created importer');
|
||||
$importer = new Importer;
|
||||
$importer->setData($this->data);
|
||||
try {
|
||||
$importer->run();
|
||||
} catch (FireflyException $e) {
|
||||
Log::error('Catch error: ' . $e->getMessage());
|
||||
|
||||
return view('error', ['message' => $e->getMessage()]);
|
||||
}
|
||||
Log::debug('Done importing!');
|
||||
|
||||
echo 'display result';
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the mapping the user has made. This is
|
||||
*
|
||||
* STEP SIX
|
||||
*/
|
||||
public function saveMapping()
|
||||
{
|
||||
/*
|
||||
* Make sure all fields we need are accounted for.
|
||||
*/
|
||||
$fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-map', 'csv-roles'];
|
||||
if (!$this->wizard->sessionHasValues($fields)) {
|
||||
Session::flash('warning', 'Could not recover upload.');
|
||||
|
||||
return Redirect::route('csv.index');
|
||||
}
|
||||
|
||||
// save mapping to session.
|
||||
$mapped = [];
|
||||
if (!is_array(Input::get('mapping'))) {
|
||||
Session::flash('warning', 'Invalid mapping.');
|
||||
|
||||
return Redirect::route('csv.map');
|
||||
}
|
||||
|
||||
foreach (Input::get('mapping') as $index => $data) {
|
||||
$mapped[$index] = [];
|
||||
foreach ($data as $value => $mapping) {
|
||||
$mapped[$index][$value] = $mapping;
|
||||
}
|
||||
}
|
||||
Session::put('csv-mapped', $mapped);
|
||||
|
||||
// proceed to process.
|
||||
return Redirect::route('csv.download-config-page');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This method processes the file, puts it away somewhere safe
|
||||
* and sends you onwards.
|
||||
*
|
||||
* STEP TWO
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function upload(Request $request)
|
||||
{
|
||||
if (!$request->hasFile('csv')) {
|
||||
Session::flash('warning', 'No file uploaded.');
|
||||
|
||||
return Redirect::route('csv.index');
|
||||
}
|
||||
|
||||
/*
|
||||
* Store CSV and put in session.
|
||||
*/
|
||||
$fullPath = $this->wizard->storeCsvFile($request->file('csv')->getRealPath());
|
||||
$dateFormat = Input::get('date_format');
|
||||
$hasHeaders = intval(Input::get('has_headers')) === 1;
|
||||
$map = [];
|
||||
$roles = [];
|
||||
$mapped = [];
|
||||
|
||||
|
||||
/*
|
||||
* Process config file if present.
|
||||
*/
|
||||
if ($request->hasFile('csv_config')) {
|
||||
|
||||
$data = file_get_contents($request->file('csv_config')->getRealPath());
|
||||
$json = json_decode($data, true);
|
||||
|
||||
if (!is_null($json)) {
|
||||
$dateFormat = isset($json['date-format']) ? $json['date-format'] : $dateFormat;
|
||||
$hasHeaders = isset($json['has-headers']) ? $json['has-headers'] : $hasHeaders;
|
||||
$map = isset($json['map']) && is_array($json['map']) ? $json['map'] : [];
|
||||
$mapped = isset($json['mapped']) && is_array($json['mapped']) ? $json['mapped'] : [];
|
||||
$roles = isset($json['roles']) && is_array($json['roles']) ? $json['roles'] : [];
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->setCsvFileLocation($fullPath);
|
||||
$this->data->setDateFormat($dateFormat);
|
||||
$this->data->setHasHeaders($hasHeaders);
|
||||
$this->data->setMap($map);
|
||||
$this->data->setMapped($mapped);
|
||||
$this->data->setRoles($roles);
|
||||
|
||||
|
||||
return Redirect::route('csv.column-roles');
|
||||
|
||||
}
|
||||
}
|
@ -219,6 +219,21 @@ Route::group(
|
||||
Route::post('/categories/update/{category}', ['uses' => 'CategoryController@update', 'as' => 'categories.update']);
|
||||
Route::post('/categories/destroy/{category}', ['uses' => 'CategoryController@destroy', 'as' => 'categories.destroy']);
|
||||
|
||||
|
||||
/**
|
||||
* CSV controller
|
||||
*/
|
||||
Route::get('/csv', ['uses' => 'CsvController@index', 'as' => 'csv.index']);
|
||||
Route::post('/csv/upload', ['uses' => 'CsvController@upload', 'as' => 'csv.upload']);
|
||||
Route::get('/csv/column_roles', ['uses' => 'CsvController@columnRoles', 'as' => 'csv.column-roles']);
|
||||
Route::post('/csv/initial_parse', ['uses' => 'CsvController@initialParse', 'as' => 'csv.initial_parse']);
|
||||
Route::get('/csv/map', ['uses' => 'CsvController@map', 'as' => 'csv.map']);
|
||||
Route::get('/csv/download-config', ['uses' => 'CsvController@downloadConfig', 'as' => 'csv.download-config']);
|
||||
Route::get('/csv/download', ['uses' => 'CsvController@downloadConfigPage', 'as' => 'csv.download-config-page']);
|
||||
Route::post('/csv/save_mapping', ['uses' => 'CsvController@saveMapping', 'as' => 'csv.save_mapping']);
|
||||
|
||||
Route::get('/csv/process', ['uses' => 'CsvController@process', 'as' => 'csv.process']);
|
||||
|
||||
/**
|
||||
* Currency Controller
|
||||
*/
|
||||
|
@ -91,6 +91,9 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
$this->app->bind('FireflyIII\Repositories\Tag\TagRepositoryInterface', 'FireflyIII\Repositories\Tag\TagRepository');
|
||||
$this->app->bind('FireflyIII\Support\Search\SearchInterface', 'FireflyIII\Support\Search\Search');
|
||||
|
||||
// CSV import
|
||||
$this->app->bind('FireflyIII\Helpers\Csv\WizardInterface', 'FireflyIII\Helpers\Csv\Wizard');
|
||||
|
||||
// make charts:
|
||||
// alternative is Google instead of ChartJs
|
||||
$this->app->bind('FireflyIII\Generator\Chart\Account\AccountChartGenerator', 'FireflyIII\Generator\Chart\Account\ChartJsAccountChartGenerator');
|
||||
|
@ -347,6 +347,24 @@ class ExpandedForm
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param null $value
|
||||
* @param array $options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function file($name, array $options = [])
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
$classes = $this->getHolderClasses($name);
|
||||
$html = View::make('form.file', compact('classes', 'name', 'label', 'options'))->render();
|
||||
|
||||
return $html;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param null $value
|
||||
|
965
composer.lock
generated
965
composer.lock
generated
File diff suppressed because it is too large
Load Diff
139
config/csv.php
Normal file
139
config/csv.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
return [
|
||||
'roles' => [
|
||||
'_ignore' => [
|
||||
'name' => '(ignore this column)',
|
||||
'mappable' => false,
|
||||
'converter' => 'Ignore',
|
||||
'field' => 'ignored',
|
||||
],
|
||||
'bill-id' => [
|
||||
'name' => 'Bill ID (matching Firefly)',
|
||||
'mappable' => true,
|
||||
],
|
||||
'bill-name' => [
|
||||
'name' => 'Bill name',
|
||||
'mappable' => true,
|
||||
],
|
||||
'currency-id' => [
|
||||
'name' => 'Currency ID (matching Firefly)',
|
||||
'mappable' => true,
|
||||
],
|
||||
'currency-name' => [
|
||||
'name' => 'Currency name (matching Firefly)',
|
||||
'mappable' => true,
|
||||
],
|
||||
'currency-code' => [
|
||||
'name' => 'Currency code (ISO 4217)',
|
||||
'mappable' => true,
|
||||
'converter' => 'CurrencyCode',
|
||||
'field' => 'currency',
|
||||
'mapper' => 'TransactionCurrency'
|
||||
],
|
||||
'currency-symbol' => [
|
||||
'name' => 'Currency symbol (matching Firefly)',
|
||||
'mappable' => true,
|
||||
],
|
||||
'description' => [
|
||||
'name' => 'Description',
|
||||
'mappable' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'description',
|
||||
],
|
||||
'date-transaction' => [
|
||||
'name' => 'Date',
|
||||
'mappable' => false,
|
||||
'converter' => 'Date',
|
||||
'field' => 'date',
|
||||
],
|
||||
'date-rent' => [
|
||||
'name' => 'Rent calculation date',
|
||||
'mappable' => false,
|
||||
'converter' => 'Date',
|
||||
'field' => 'date-rent',
|
||||
],
|
||||
'budget-id' => [
|
||||
'name' => 'Budget ID (matching Firefly)',
|
||||
'mappable' => true,
|
||||
],
|
||||
'budget-name' => [
|
||||
'name' => 'Budget name',
|
||||
'mappable' => true,
|
||||
],
|
||||
'rabo-debet-credit' => [
|
||||
'name' => 'Rabobank specific debet/credit indicator',
|
||||
'mappable' => false,
|
||||
'converter' => 'RabobankDebetCredit',
|
||||
'field' => 'amount-modifier',
|
||||
],
|
||||
'category-id' => [
|
||||
'name' => 'Category ID (matching Firefly)',
|
||||
'mappable' => true,
|
||||
],
|
||||
'category-name' => [
|
||||
'name' => 'Category name',
|
||||
'mappable' => true,
|
||||
],
|
||||
'tags-comma' => [
|
||||
'name' => 'Tags (comma separated)',
|
||||
'mappable' => true,
|
||||
],
|
||||
'tags-space' => [
|
||||
'name' => 'Tags (space separated)',
|
||||
'mappable' => true,
|
||||
],
|
||||
'account-id' => [
|
||||
'name' => 'Asset account ID (matching Firefly)',
|
||||
'mappable' => true,
|
||||
],
|
||||
'account-name' => [
|
||||
'name' => 'Asset account name',
|
||||
'mappable' => true,
|
||||
],
|
||||
'account-iban' => [
|
||||
'name' => 'Asset account IBAN',
|
||||
'mappable' => true,
|
||||
'converter' => 'AccountIban',
|
||||
'field' => 'asset-account',
|
||||
'mapper' => 'AssetAccount'
|
||||
],
|
||||
'opposing-id' => [
|
||||
'name' => 'Expense or revenue account ID (matching Firefly)',
|
||||
'mappable' => true,
|
||||
],
|
||||
'opposing-name' => [
|
||||
'name' => 'Expense or revenue account name',
|
||||
'mappable' => true,
|
||||
'converter' => 'OpposingName',
|
||||
'field' => 'opposing-account'
|
||||
],
|
||||
'opposing-iban' => [
|
||||
'name' => 'Expense or revenue account IBAN',
|
||||
'mappable' => true,
|
||||
],
|
||||
'amount' => [
|
||||
'name' => 'Amount',
|
||||
'mappable' => false,
|
||||
'converter' => 'Amount',
|
||||
'field' => 'amount',
|
||||
],
|
||||
'sepa-ct-id' => [
|
||||
'name' => 'SEPA Credit Transfer end-to-end ID',
|
||||
'mappable' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'description',
|
||||
],
|
||||
'sepa-ct-op' => [
|
||||
'name' => 'SEPA Credit Transfer opposing account',
|
||||
'mappable' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'description',
|
||||
],
|
||||
'sepa-db' => [
|
||||
'name' => 'SEPA Direct Debet',
|
||||
'mappable' => false,
|
||||
'converter' => 'Description',
|
||||
'field' => 'description',
|
||||
],
|
||||
]
|
||||
];
|
@ -5,6 +5,7 @@ return [
|
||||
'version' => '3.4.6',
|
||||
'index_periods' => ['1D', '1W', '1M', '3M', '6M', '1Y', 'custom'],
|
||||
'budget_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
|
||||
'csv_import_enabled' => false,
|
||||
'piggy_bank_periods' => [
|
||||
'week' => 'Week',
|
||||
'month' => 'Month',
|
||||
|
@ -145,12 +145,12 @@ return [
|
||||
'ExpandedForm' => [
|
||||
'is_safe' => [
|
||||
'date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location',
|
||||
'multiRadio'
|
||||
'multiRadio','file'
|
||||
]
|
||||
],
|
||||
'Form' => [
|
||||
'is_safe' => [
|
||||
'input', 'select', 'checkbox', 'model', 'open', 'radio', 'textarea'
|
||||
'input', 'select', 'checkbox', 'model', 'open', 'radio', 'textarea','file'
|
||||
]
|
||||
],
|
||||
],
|
||||
|
@ -98,7 +98,7 @@ class TestDataSeeder extends Seeder
|
||||
|
||||
protected function createAssetAccounts()
|
||||
{
|
||||
$assets = ['MyBank Checking Account', 'Savings', 'Shared', 'Creditcard'];
|
||||
$assets = ['MyBank Checking Account', 'Savings', 'Shared', 'Creditcard', 'Emergencies', 'STE'];
|
||||
$assetMeta = [
|
||||
[
|
||||
'accountRole' => 'defaultAsset',
|
||||
@ -114,6 +114,12 @@ class TestDataSeeder extends Seeder
|
||||
'ccMonthlyPaymentDate' => '2015-05-27',
|
||||
'ccType' => 'monthlyFull'
|
||||
],
|
||||
[
|
||||
'accountRole' => 'savingAsset',
|
||||
],
|
||||
[
|
||||
'accountRole' => 'savingAsset',
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
|
@ -19,6 +19,57 @@ return [
|
||||
'never' => 'Never',
|
||||
'search_results_for' => 'Search results for ":query"',
|
||||
|
||||
// csv import:
|
||||
'csv_import' => 'Import CSV file',
|
||||
'csv' => 'CSV',
|
||||
'csv_index_title' => 'Upload and import a CSV file',
|
||||
'csv_index_text' =>
|
||||
'This form allows you to import a CSV file with transactions into Firefly. It is based on the excellent CSV importer made by' .
|
||||
' the folks at <a href="https://www.atlassian.com/">Atlassian</a>. Simply upload your CSV file and follow the instructions.',
|
||||
'csv_index_beta_warning' => 'This tool is very much in beta. Please proceed with caution',
|
||||
'csv_header_help' => 'Check this box when your CSV file\'s first row consists of column names, not actual data',
|
||||
'csv_date_help' => 'Date time format in your CSV. Follow the format like <a href="https://secure.' .
|
||||
'php.net/manual/en/datetime.createfromformat.php#refsect1-datetime.createfromformat-parameters">this' .
|
||||
' page</a> indicates. The default value will parse dates that look like this: ' . date('Ymd'),
|
||||
'csv_csv_file_help' => 'Select the CSV file here. You can only upload one file at a time',
|
||||
'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_upload_button' => 'Start importing CSV',
|
||||
'csv_column_roles_title' => 'Define column roles',
|
||||
'csv_column_roles_text' => 'Firefly does not know what each column means. You need to indicate what every column is. Please check out the example '
|
||||
. 'data if you\'re not sure yourself. Click on the question mark (top right of the page) to learn what'
|
||||
. ' each column means. If you want to map imported data onto existing data in Firefly, use the checkbox. '
|
||||
. 'The next step will show you what this button does.',
|
||||
'csv_column_roles_table' => 'Column roles',
|
||||
'csv_column' => 'CSV column',
|
||||
'cvs_column_name' => 'CSV column name',
|
||||
'cvs_column_example' => 'Column example data',
|
||||
'cvs_column_role' => 'Column contains?',
|
||||
'csv_do_map_value' => 'Map value?',
|
||||
'csv_continue' => 'Continue to the next step',
|
||||
'csv_go_back' => 'Go back to the previous step',
|
||||
'csv_map_title' => 'Map found values to existing values',
|
||||
'csv_map_text' =>
|
||||
'This page allows you to map the values from the CSV file to existing entries in your database. This ensures that accounts and other'
|
||||
. ' things won\'t be created twice.',
|
||||
'cvs_field_value' => 'Field value from CSV',
|
||||
'csv_field_mapped_to' => 'Must be mapped to...',
|
||||
'csv_download_config_title' => 'Download CSV configuration',
|
||||
'csv_download_config_text' => 'Everything you\'ve just set up can be downloaded as a configuration file. Click the button to do so.',
|
||||
'csv_more_information_text' => 'If the import fails, you can use this configuration file so you don\'t have to start all over again.',
|
||||
'csv_do_download_config' => 'Download configuration file.',
|
||||
'csv_empty_description' => '(empty description)',
|
||||
'csv_upload_form' => 'CSV upload form',
|
||||
'csv_index_unsupported_warning' => 'The CSV importer is yet incapable of doing the following:',
|
||||
'csv_unsupported_map' => 'The importer cannot map the column ":columnRole" to existing values in the database.',
|
||||
'csv_unsupported_value' => 'The importer does not know how to handle values in columns marked as ":columnRole".',
|
||||
// 'csv_index_text' => 'Here be explanation.',
|
||||
// 'csv_upload_form' => 'Upload form',
|
||||
// 'upload_csv_file' => 'Upload CSV file',
|
||||
// 'csv_header_help' => 'Check this when bla bla',
|
||||
// 'csv_date_help' =>
|
||||
// 'csv_row' => 'row',
|
||||
'csv_upload_not_writeable' => 'Cannot write to the path mentioned here. Cannot upload',
|
||||
|
||||
// create new stuff:
|
||||
'create_new_withdrawal' => 'Create new withdrawal',
|
||||
'create_new_deposit' => 'Create new deposit',
|
||||
|
@ -46,6 +46,10 @@ return [
|
||||
'symbol' => 'Symbol',
|
||||
'code' => 'Code',
|
||||
'iban' => 'IBAN',
|
||||
'csv' => 'CSV file',
|
||||
'has_headers' => 'Headers',
|
||||
'date_format' => 'Date format',
|
||||
'csv_config' => 'CSV import configuration',
|
||||
|
||||
'store_new_withdrawal' => 'Store new withdrawal',
|
||||
'store_new_deposit' => 'Store new deposit',
|
||||
|
@ -19,6 +19,16 @@ return [
|
||||
'never' => 'Nooit',
|
||||
'search_results_for' => 'Zoekresultaten voor ":query"',
|
||||
|
||||
// csv import:
|
||||
'csv_import' => 'Importeer CSV-bestand',
|
||||
'csv' => 'CSV',
|
||||
'csv_index_text' => 'Hier komt uitleg.',
|
||||
'csv_upload_form' => 'Upload formulier',
|
||||
'upload_csv_file' => 'Upload CSV-bestand',
|
||||
'csv_header_help' => 'Check dit als bla bla',
|
||||
'csv_row' => 'rij',
|
||||
'upload_not_writeable' => 'Cannot write to the path mentioned here. Cannot upload',
|
||||
|
||||
// create new stuff:
|
||||
'create_new_withdrawal' => 'Nieuwe uitgave',
|
||||
'create_new_deposit' => 'Nieuwe inkomsten',
|
||||
|
@ -46,6 +46,9 @@ return [
|
||||
'symbol' => 'Symbool',
|
||||
'code' => 'Code',
|
||||
'iban' => 'IBAN',
|
||||
'csv' => 'CSV-bestand',
|
||||
'has_headers' => 'Eerste rij zijn kolomnamen',
|
||||
'date_format' => 'Datumformaat',
|
||||
|
||||
'store_new_withdrawal' => 'Nieuwe uitgave opslaan',
|
||||
'store_new_deposit' => 'Nieuwe inkomsten opslaan',
|
||||
|
87
resources/twig/csv/column-roles.twig
Normal file
87
resources/twig/csv/column-roles.twig
Normal file
@ -0,0 +1,87 @@
|
||||
{% extends "./layout/default.twig" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'csv_column_roles_title'|_ }}</h3>
|
||||
|
||||
<!-- ACTIONS MENU -->
|
||||
<div class="box-tools pull-right">
|
||||
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>{{ 'csv_column_roles_text'|_ }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('csv.initial_parse') }}" method="post">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'csv_column_roles_table'|_ }}</h3>
|
||||
|
||||
<!-- ACTIONS MENU -->
|
||||
<div class="box-tools pull-right">
|
||||
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="box-body">
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>{{ 'cvs_column_name'|_ }}</th>
|
||||
<th>{{ 'cvs_column_example'|_ }}</th>
|
||||
<th>{{ 'cvs_column_role'|_ }}</th>
|
||||
<th>{{ 'csv_do_map_value'|_ }}</th>
|
||||
</thead>
|
||||
{% for index,header in headers %}
|
||||
<tr>
|
||||
<td>{{ header }}</td>
|
||||
<td>{{ example[index] }}</td>
|
||||
<td>
|
||||
{{ Form.select(('role['~index~']'), availableRoles,roles[index]) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ Form.checkbox(('map['~index~']'),1,map[index]) }}
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<a href="{{ route('csv.index') }}" class="btn btn-danger"><i class="fa fa-arrow-left"></i> {{ 'csv_go_back'|_ }}</a>
|
||||
<button type="submit" class="btn btn-success pull-right">
|
||||
{{ 'csv_continue'|_ }} <i class="fa fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
50
resources/twig/csv/download-config.twig
Normal file
50
resources/twig/csv/download-config.twig
Normal file
@ -0,0 +1,50 @@
|
||||
{% extends "./layout/default.twig" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'csv_download_config_title'|_ }}</h3>
|
||||
|
||||
<!-- ACTIONS MENU -->
|
||||
<div class="box-tools pull-right">
|
||||
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>
|
||||
{{ 'csv_download_config_text'|_ }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="{{ route('csv.download-config') }}" class="btn btn-info"><i class="fa fa-download"></i> {{ 'csv_do_download_config'|_ }}</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ 'csv_more_information_text'|_ }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<a href="{{ route('csv.index') }}" class="btn btn-danger"><i class="fa fa-arrow-left"></i> {{ 'csv_go_back'|_ }}</a>
|
||||
<a href="{{ route('csv.process') }}" class="btn btn-success pull-right">{{ 'csv_continue'|_ }} <i class="fa fa-arrow-right"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
100
resources/twig/csv/index.twig
Normal file
100
resources/twig/csv/index.twig
Normal file
@ -0,0 +1,100 @@
|
||||
{% extends "./layout/default.twig" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'csv_index_title'|_ }}</h3>
|
||||
|
||||
<!-- ACTIONS MENU -->
|
||||
<div class="box-tools pull-right">
|
||||
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ 'csv_index_text'|_ }}
|
||||
<p class="text-info">{{ 'csv_index_beta_warning'|_ }}</p>
|
||||
{% if unsupported|length > 0 %}
|
||||
<p class="text-danger">{{ 'csv_index_unsupported_warning'|_ }}</p>
|
||||
<ul>
|
||||
{% for message in unsupported %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="form-horizontal" action="{{ route('csv.upload') }}" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'csv_upload_form'|_ }}</h3>
|
||||
|
||||
<!-- ACTIONS MENU -->
|
||||
<div class="box-tools pull-right">
|
||||
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="box-body">
|
||||
|
||||
|
||||
{{ ExpandedForm.checkbox('has_headers',false,null,{helpText: 'csv_header_help'|_}) }}
|
||||
{{ ExpandedForm.text('date_format','Ymd',{helpText: 'csv_date_help'|_}) }}
|
||||
|
||||
{{ ExpandedForm.file('csv',{helpText: 'csv_csv_file_help'|_}) }}
|
||||
|
||||
{{ ExpandedForm.file('csv_config',{helpText: 'csv_csv_config_file_help'|_}) }}
|
||||
|
||||
{% if not uploadPossible %}
|
||||
<div class="form-group" id="csv_holder">
|
||||
<div class="col-sm-4">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<pre>{{ path }}</pre>
|
||||
<p class="text-danger">
|
||||
{{ 'csv_upload_not_writeable'|_ }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<button type="submit" class="pull-right btn btn-success">
|
||||
{{ 'csv_upload_button'|_ }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
92
resources/twig/csv/map.twig
Normal file
92
resources/twig/csv/map.twig
Normal file
@ -0,0 +1,92 @@
|
||||
{% extends "./layout/default.twig" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ 'csv_map_title'|_ }}</h3>
|
||||
|
||||
<!-- ACTIONS MENU -->
|
||||
<div class="box-tools pull-right">
|
||||
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>
|
||||
{{ 'csv_map_text'|_ }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('csv.save_mapping') }}" method="post">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
{% for index,columnName in map %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ Config.get('csv.roles.'~columnName~'.name') }}</h3>
|
||||
|
||||
<!-- ACTIONS MENU -->
|
||||
<div class="box-tools pull-right">
|
||||
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>{{ 'cvs_field_value'|_ }}</th>
|
||||
<th>{{ 'csv_field_mapped_to'|_ }}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for value in values[index] %}
|
||||
<tr>
|
||||
<td>{{ value }}</td>
|
||||
<td>
|
||||
{{ Form.select('mapping['~index~']['~value~']',options[index], mapped[index][value]) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<a href="{{ route('csv.column-roles') }}" class="btn btn-danger"><i class="fa fa-arrow-left"></i> {{ 'csv_go_back'|_ }}</a>
|
||||
<button type="submit" class="btn btn-success pull-right">
|
||||
{{ 'csv_continue'|_ }} <i class="fa fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
{% endblock %}
|
@ -4,9 +4,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<h1 class="text-danger">Firefly<br/>
|
||||
<small>Error</small>
|
||||
</h1>
|
||||
<h3 class="text-danger">Sorry, an error occurred.</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
{{ Form.checkbox(name, value, options.checked, options) }}
|
||||
</label>
|
||||
</div>
|
||||
{% include 'form/help.twig' %}
|
||||
{% include 'form/feedback.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
|
9
resources/twig/form/file.twig
Normal file
9
resources/twig/form/file.twig
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="{{ classes }}" id="{{ name }}_holder">
|
||||
<label for="{{ options.id }}" class="col-sm-4 control-label">{{ label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{{ Form.file(name, options) }}
|
||||
{% include 'form/help.twig' %}
|
||||
{% include 'form/feedback.twig' %}
|
||||
</div>
|
||||
</div>
|
@ -1,3 +1,3 @@
|
||||
{% if options.helpText %}
|
||||
<p class="help-block">{{ options.helpText }}</p>
|
||||
<p class="help-block">{{ options.helpText|raw }}</p>
|
||||
{% endif %}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
<div class="col-sm-8">
|
||||
{{ Form.input('text', name, value, options) }}
|
||||
{% include 'form/help.twig' %}
|
||||
{% include 'form/feedback.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -123,6 +123,11 @@
|
||||
<li class="{{ activeRoutePartial('currency') }}">
|
||||
<a class="{{ activeRoutePartial('currency') }}" href="{{ route('currency.index') }}"><i class="fa fa-usd fa-fw"></i> {{ 'currencies'|_ }}</a>
|
||||
</li>
|
||||
{% if Config.get('firefly.csv_import_enabled') %}
|
||||
<li class="{{ activeRoutePartial('csv') }}">
|
||||
<a href="{{ route('csv.index') }}"><i class="fa fa-file-text-o fa-fw"></i> {{ 'csv_import'|_ }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
|
||||
|
2
storage/upload/.gitignore
vendored
Normal file
2
storage/upload/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
Loading…
Reference in New Issue
Block a user