diff --git a/app/Helpers/Csv/Data.php b/app/Helpers/Csv/Data.php index b58bbfeb57..9d088abd40 100644 --- a/app/Helpers/Csv/Data.php +++ b/app/Helpers/Csv/Data.php @@ -25,16 +25,19 @@ class Data protected $hasHeaders; /** @var array */ - protected $map; + protected $map = []; /** @var array */ - protected $mapped; + protected $mapped = []; /** @var Reader */ protected $reader; /** @var array */ - protected $roles; + protected $roles = []; /** @var array */ - protected $specifix; + protected $specifix = []; + + /** @var int */ + protected $importAccount = 0; /** * @@ -48,6 +51,7 @@ class Data $this->sessionRoles(); $this->sessionMapped(); $this->sessionSpecifix(); + $this->sessionImportAccount(); } protected function sessionHasHeaders() @@ -57,6 +61,13 @@ class Data } } + protected function sessionImportAccount() + { + if (Session::has('csv-import-account')) { + $this->importAccount = intval(Session::get('csv-import-account')); + } + } + protected function sessionDateFormat() { if (Session::has('csv-date-format')) { @@ -116,6 +127,15 @@ class Data $this->dateFormat = $dateFormat; } + /** + * @param int $importAccount + */ + public function setImportAccount($importAccount) + { + Session::put('csv-import-account', $importAccount); + $this->importAccount = $importAccount; + } + /** * @return bool */ diff --git a/app/Helpers/Csv/Importer.php b/app/Helpers/Csv/Importer.php index 9cc778e39e..7cff6a7ed1 100644 --- a/app/Helpers/Csv/Importer.php +++ b/app/Helpers/Csv/Importer.php @@ -171,6 +171,7 @@ class Importer // some extra's: $filler['bill-id'] = null; $filler['opposing-account-object'] = null; + $filler['asset-account-object'] = null; $filler['amount-modifier'] = '1'; return $filler; @@ -241,7 +242,7 @@ class Importer $date = $this->importData['date-rent']; } - if (!($this->importData['asset-account'] instanceof Account)) { + if (!($this->importData['asset-account-object'] instanceof Account)) { return 'No asset account to import into.'; } @@ -252,10 +253,13 @@ class Importer 'description' => $this->importData['description'], 'completed' => 0, 'date' => $date, 'bill_id' => $this->importData['bill-id'],] ); if ($journal->getErrors()->count() == 0) { - $accountId = $this->importData['asset-account']->id; // create first transaction: + // first transaction + $accountId = $this->importData['asset-account-object']->id; // create first transaction: $amount = $this->importData['amount']; $transaction = Transaction::create(['transaction_journal_id' => $journal->id, 'account_id' => $accountId, 'amount' => $amount]); $errors = $transaction->getErrors(); + + // second transaction $accountId = $this->importData['opposing-account-object']->id; // create second transaction: $amount = bcmul($this->importData['amount'], -1); $transaction = Transaction::create(['transaction_journal_id' => $journal->id, 'account_id' => $accountId, 'amount' => $amount]); diff --git a/app/Helpers/Csv/PostProcessing/AssetAccount.php b/app/Helpers/Csv/PostProcessing/AssetAccount.php new file mode 100644 index 0000000000..d276a73de8 --- /dev/null +++ b/app/Helpers/Csv/PostProcessing/AssetAccount.php @@ -0,0 +1,185 @@ +checkIdNameObject(); // has object in ID or Name? + if (!is_null($result)) { + return $result; + } + + $result = $this->checkIbanString(); + if (!is_null($result)) { + return $result; + } + + $result = $this->checkNameString(); + if (!is_null($result)) { + return $result; + } + + return null; + } + + /** + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } + + /** + * @return array + */ + protected function checkIdNameObject() + { + if ($this->data['asset-account-id'] instanceof Account) { // first priority. try to find the account based on ID, if any + $this->data['asset-account-object'] = $this->data['asset-account-id']; + + return $this->data; + } + if ($this->data['asset-account-iban'] instanceof Account) { // second: try to find the account based on IBAN, if any. + $this->data['asset-account-object'] = $this->data['asset-account-iban']; + + return $this->data; + } + + return null; + } + + /** + * @return array|null + */ + protected function checkIbanString() + { + $rules = ['iban' => 'iban']; + $check = ['iban' => $this->data['asset-account-iban']]; + $validator = Validator::make($check, $rules); + if (!$validator->fails()) { + $this->data['asset-account-object'] = $this->parseIbanString(); + + return $this->data; + } + + return null; + } + + /** + * @return Account|null + */ + protected function parseIbanString() + { + // create by name and/or iban. + $accounts = Auth::user()->accounts()->get(); + foreach ($accounts as $entry) { + if ($entry->iban == $this->data['asset-account-iban']) { + + return $entry; + } + } + $account = $this->createAccount(); + + return $account; + } + + /** + * @return Account|null + */ + protected function createAccount() + { + $accountType = $this->getAccountType(); + + // create if not exists: + $name = is_string($this->data['asset-account-name']) && strlen($this->data['asset-account-name']) > 0 ? $this->data['asset-account-name'] + : $this->data['asset-account-iban']; + $account = Account::firstOrCreateEncrypted( + [ + 'user_id' => Auth::user()->id, + 'account_type_id' => $accountType->id, + 'name' => $name, + 'iban' => $this->data['asset-account-iban'], + 'active' => true, + ] + ); + + return $account; + } + + /** + * + * @return AccountType + */ + protected function getAccountType() + { + return AccountType::where('type', 'Asset account')->first(); + } + + /** + * @return array|null + */ + protected function checkNameString() + { + if ($this->data['asset-account-name'] instanceof Account) { // third: try to find account based on name, if any. + $this->data['asset-account-object'] = $this->data['asset-account-name']; + + return $this->data; + } + if (is_string($this->data['asset-account-name'])) { + $this->data['asset-account-object'] = $this->parseNameString(); + + return $this->data; + } + + return null; + } + + /** + * @return Account|null + */ + protected function parseNameString() + { + $accountType = $this->getAccountType(); + $accounts = Auth::user()->accounts()->where('account_type_id', $accountType->id)->get(); + foreach ($accounts as $entry) { + if ($entry->name == $this->data['asset-account-name']) { + Log::debug('Found an asset account with this name (#' . $entry->id . ': ' . $entry->name . ')'); + + return $entry; + } + } + // create if not exists: + $account = Account::firstOrCreateEncrypted( + [ + 'user_id' => Auth::user()->id, + 'account_type_id' => $accountType->id, + 'name' => $this->data['asset-account-name'], + 'iban' => '', + 'active' => true, + ] + ); + + return $account; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/CsvController.php b/app/Http/Controllers/CsvController.php index 564d2a6f73..6e2dff8387 100644 --- a/app/Http/Controllers/CsvController.php +++ b/app/Http/Controllers/CsvController.php @@ -3,10 +3,12 @@ namespace FireflyIII\Http\Controllers; use Config; +use ExpandedForm; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Csv\Data; use FireflyIII\Helpers\Csv\Importer; use FireflyIII\Helpers\Csv\WizardInterface; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Illuminate\Http\Request; use Input; use Log; @@ -55,7 +57,7 @@ class CsvController extends Controller public function columnRoles() { - $fields = ['csv-file', 'csv-date-format', 'csv-has-headers']; + $fields = ['csv-file', 'csv-date-format', 'csv-has-headers','csv-import-account']; if (!$this->wizard->sessionHasValues($fields)) { Session::flash('warning', 'Could not recover upload.'); @@ -142,13 +144,14 @@ class CsvController extends Controller * * @return \Illuminate\View\View */ - public function index() + public function index(AccountRepositoryInterface $repository) { $subTitle = trans('firefly.csv_import'); Session::forget('csv-date-format'); Session::forget('csv-has-headers'); Session::forget('csv-file'); + Session::forget('csv-import-account'); Session::forget('csv-map'); Session::forget('csv-roles'); Session::forget('csv-mapped'); @@ -160,11 +163,14 @@ class CsvController extends Controller $specifix[$entry] = trans('firefly.csv_specifix_' . $entry); } + // get a list of asset accounts: + $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Asset account', 'Default account'])); + // can actually upload? $uploadPossible = is_writable(storage_path('upload')); $path = storage_path('upload'); - return view('csv.index', compact('subTitle', 'uploadPossible', 'path', 'specifix')); + return view('csv.index', compact('subTitle', 'uploadPossible', 'path', 'specifix', 'accounts')); } /** @@ -366,19 +372,17 @@ class CsvController extends Controller return redirect(route('csv.index')); } - $fullPath = $this->wizard->storeCsvFile($request->file('csv')->getRealPath()); - $settings = []; - $settings['date-format'] = Input::get('date_format'); - $settings['has-headers'] = intval(Input::get('has_headers')) === 1; - $settings['specifix'] = Input::get('specifix'); - $settings['map'] = []; - $settings['mapped'] = []; - $settings['roles'] = []; + $fullPath = $this->wizard->storeCsvFile($request->file('csv')->getRealPath()); + $settings = []; + $settings['date-format'] = Input::get('date_format'); + $settings['has-headers'] = intval(Input::get('has_headers')) === 1; + $settings['specifix'] = Input::get('specifix'); + $settings['import-account'] = intval(Input::get('csv_import_account')); + $settings['map'] = []; + $settings['mapped'] = []; + $settings['roles'] = []; - /* - * Process config file if present. - */ - if ($request->hasFile('csv_config')) { + if ($request->hasFile('csv_config')) { // Process config file if present. $data = file_get_contents($request->file('csv_config')->getRealPath()); $json = json_decode($data, true); if (is_array($json)) { @@ -393,6 +397,7 @@ class CsvController extends Controller $this->data->setMapped($settings['mapped']); $this->data->setRoles($settings['roles']); $this->data->setSpecifix($settings['specifix']); + $this->data->setImportAccount($settings['import-account']); return redirect(route('csv.column-roles')); diff --git a/config/csv.php b/config/csv.php index e9d57e473a..2aec11f478 100644 --- a/config/csv.php +++ b/config/csv.php @@ -131,21 +131,21 @@ return [ 'name' => 'Asset account ID (matching Firefly)', 'mappable' => true, 'mapper' => 'AssetAccount', - 'field' => 'asset-account', + 'field' => 'asset-account-id', 'converter' => 'AccountId' ], 'account-name' => [ 'name' => 'Asset account name', 'mappable' => true, 'mapper' => 'AssetAccount', - 'field' => 'asset-account', + 'field' => 'asset-account-name', 'converter' => 'AssetAccountName' ], 'account-iban' => [ 'name' => 'Asset account IBAN', 'mappable' => true, 'converter' => 'AssetAccountIban', - 'field' => 'asset-account', + 'field' => 'asset-account-iban', 'mapper' => 'AssetAccount' ], 'opposing-id' => [ diff --git a/resources/lang/en/firefly.php b/resources/lang/en/firefly.php index 81c8a5cc7f..f35eb5b9f2 100644 --- a/resources/lang/en/firefly.php +++ b/resources/lang/en/firefly.php @@ -117,6 +117,9 @@ return [ 'csv_column_tags-space' => 'Tags (space separated)', 'csv_specifix_RabobankDescription' => 'Select this when you\'re importing Rabobank CSV export files.', 'csv_specifix_Dummy' => 'Checking this has no effect whatsoever.', + 'csv_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which' . + ' account the transactions in the CSV belong to.', + // create new stuff: 'create_new_withdrawal' => 'Create new withdrawal', diff --git a/resources/lang/en/form.php b/resources/lang/en/form.php index 9172e7520c..935ce49bfb 100644 --- a/resources/lang/en/form.php +++ b/resources/lang/en/form.php @@ -51,6 +51,7 @@ return [ 'date_format' => 'Date format', 'csv_config' => 'CSV import configuration', 'specifix' => 'Bank- or file specific fixes', + 'csv_import_account' => 'Default import account', 'store_new_withdrawal' => 'Store new withdrawal', 'store_new_deposit' => 'Store new deposit', diff --git a/resources/twig/csv/index.twig b/resources/twig/csv/index.twig index 1de0109ae5..4bd88729ce 100644 --- a/resources/twig/csv/index.twig +++ b/resources/twig/csv/index.twig @@ -61,8 +61,12 @@ {{ ExpandedForm.file('csv_config',{helpText: 'csv_csv_config_file_help'|_}) }} + {{ ExpandedForm.select('csv_import_account', accounts, 0, {helpText: 'csv_import_account_help'|_} ) }} + {{ ExpandedForm.multiCheckbox('specifix', specifix) }} + + {% if not uploadPossible %}