From 288e713f9443820cb5091a5adab48dad372d408b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 May 2016 20:18:16 +0200 Subject: [PATCH 01/12] Remove all code related to the CSV importer in preparation of #262 Signed-off-by: James Cole --- app/Export/Collector/UploadCollector.php | 3 + app/Helpers/Csv/Converter/AccountId.php | 35 -- app/Helpers/Csv/Converter/Amount.php | 32 -- app/Helpers/Csv/Converter/AmountComma.php | 36 -- .../Csv/Converter/AssetAccountIban.php | 89 ---- .../Csv/Converter/AssetAccountName.php | 65 --- .../Csv/Converter/AssetAccountNumber.php | 77 --- app/Helpers/Csv/Converter/BasicConverter.php | 114 ----- app/Helpers/Csv/Converter/BillId.php | 36 -- app/Helpers/Csv/Converter/BillName.php | 47 -- app/Helpers/Csv/Converter/BudgetId.php | 36 -- app/Helpers/Csv/Converter/BudgetName.php | 44 -- app/Helpers/Csv/Converter/CategoryId.php | 36 -- app/Helpers/Csv/Converter/CategoryName.php | 49 -- .../Csv/Converter/ConverterInterface.php | 52 -- app/Helpers/Csv/Converter/CurrencyCode.php | 43 -- app/Helpers/Csv/Converter/CurrencyId.php | 36 -- app/Helpers/Csv/Converter/CurrencyName.php | 42 -- app/Helpers/Csv/Converter/CurrencySymbol.php | 43 -- app/Helpers/Csv/Converter/Date.php | 46 -- app/Helpers/Csv/Converter/Description.php | 31 -- app/Helpers/Csv/Converter/INGDebetCredit.php | 34 -- app/Helpers/Csv/Converter/Ignore.php | 28 -- .../Csv/Converter/OpposingAccountIban.php | 64 --- .../Csv/Converter/OpposingAccountId.php | 35 -- .../Csv/Converter/OpposingAccountName.php | 41 -- .../Csv/Converter/RabobankDebetCredit.php | 34 -- app/Helpers/Csv/Converter/TagsComma.php | 53 --- app/Helpers/Csv/Converter/TagsSpace.php | 54 --- app/Helpers/Csv/Data.php | 336 ------------- app/Helpers/Csv/Importer.php | 384 --------------- app/Helpers/Csv/Mapper/AnyAccount.php | 42 -- app/Helpers/Csv/Mapper/AssetAccount.php | 54 --- app/Helpers/Csv/Mapper/Bill.php | 42 -- app/Helpers/Csv/Mapper/Budget.php | 42 -- app/Helpers/Csv/Mapper/Category.php | 42 -- app/Helpers/Csv/Mapper/MapperInterface.php | 24 - app/Helpers/Csv/Mapper/Tag.php | 42 -- .../Csv/Mapper/TransactionCurrency.php | 40 -- app/Helpers/Csv/PostProcessing/Amount.php | 44 -- .../Csv/PostProcessing/AssetAccount.php | 274 ----------- app/Helpers/Csv/PostProcessing/Bill.php | 45 -- app/Helpers/Csv/PostProcessing/Currency.php | 49 -- .../Csv/PostProcessing/Description.php | 47 -- .../Csv/PostProcessing/OpposingAccount.php | 210 --------- .../PostProcessing/PostProcessorInterface.php | 31 -- .../Csv/Specifix/AbnAmroDescription.php | 226 --------- app/Helpers/Csv/Specifix/Dummy.php | 60 --- .../Csv/Specifix/RabobankDescription.php | 76 --- app/Helpers/Csv/Specifix/Specifix.php | 46 -- .../Csv/Specifix/SpecifixInterface.php | 49 -- app/Helpers/Csv/Wizard.php | 202 -------- app/Helpers/Csv/WizardInterface.php | 68 --- app/Http/Controllers/CsvController.php | 444 ------------------ app/Http/breadcrumbs.php | 38 -- app/Http/routes.php | 15 - app/Providers/FireflyServiceProvider.php | 1 - config/firefly.php | 12 +- resources/views/csv/column-roles.twig | 77 --- resources/views/csv/download-config.twig | 44 -- resources/views/csv/index.twig | 96 ---- resources/views/csv/map.twig | 82 ---- resources/views/csv/process.twig | 58 --- resources/views/partials/menu-sidebar.twig | 14 +- 64 files changed, 14 insertions(+), 4677 deletions(-) delete mode 100644 app/Helpers/Csv/Converter/AccountId.php delete mode 100644 app/Helpers/Csv/Converter/Amount.php delete mode 100644 app/Helpers/Csv/Converter/AmountComma.php delete mode 100644 app/Helpers/Csv/Converter/AssetAccountIban.php delete mode 100644 app/Helpers/Csv/Converter/AssetAccountName.php delete mode 100644 app/Helpers/Csv/Converter/AssetAccountNumber.php delete mode 100644 app/Helpers/Csv/Converter/BasicConverter.php delete mode 100644 app/Helpers/Csv/Converter/BillId.php delete mode 100644 app/Helpers/Csv/Converter/BillName.php delete mode 100644 app/Helpers/Csv/Converter/BudgetId.php delete mode 100644 app/Helpers/Csv/Converter/BudgetName.php delete mode 100644 app/Helpers/Csv/Converter/CategoryId.php delete mode 100644 app/Helpers/Csv/Converter/CategoryName.php delete mode 100644 app/Helpers/Csv/Converter/ConverterInterface.php delete mode 100644 app/Helpers/Csv/Converter/CurrencyCode.php delete mode 100644 app/Helpers/Csv/Converter/CurrencyId.php delete mode 100644 app/Helpers/Csv/Converter/CurrencyName.php delete mode 100644 app/Helpers/Csv/Converter/CurrencySymbol.php delete mode 100644 app/Helpers/Csv/Converter/Date.php delete mode 100644 app/Helpers/Csv/Converter/Description.php delete mode 100644 app/Helpers/Csv/Converter/INGDebetCredit.php delete mode 100644 app/Helpers/Csv/Converter/Ignore.php delete mode 100644 app/Helpers/Csv/Converter/OpposingAccountIban.php delete mode 100644 app/Helpers/Csv/Converter/OpposingAccountId.php delete mode 100644 app/Helpers/Csv/Converter/OpposingAccountName.php delete mode 100644 app/Helpers/Csv/Converter/RabobankDebetCredit.php delete mode 100644 app/Helpers/Csv/Converter/TagsComma.php delete mode 100644 app/Helpers/Csv/Converter/TagsSpace.php delete mode 100644 app/Helpers/Csv/Data.php delete mode 100644 app/Helpers/Csv/Importer.php delete mode 100644 app/Helpers/Csv/Mapper/AnyAccount.php delete mode 100644 app/Helpers/Csv/Mapper/AssetAccount.php delete mode 100644 app/Helpers/Csv/Mapper/Bill.php delete mode 100644 app/Helpers/Csv/Mapper/Budget.php delete mode 100644 app/Helpers/Csv/Mapper/Category.php delete mode 100644 app/Helpers/Csv/Mapper/MapperInterface.php delete mode 100644 app/Helpers/Csv/Mapper/Tag.php delete mode 100644 app/Helpers/Csv/Mapper/TransactionCurrency.php delete mode 100644 app/Helpers/Csv/PostProcessing/Amount.php delete mode 100644 app/Helpers/Csv/PostProcessing/AssetAccount.php delete mode 100644 app/Helpers/Csv/PostProcessing/Bill.php delete mode 100644 app/Helpers/Csv/PostProcessing/Currency.php delete mode 100644 app/Helpers/Csv/PostProcessing/Description.php delete mode 100644 app/Helpers/Csv/PostProcessing/OpposingAccount.php delete mode 100644 app/Helpers/Csv/PostProcessing/PostProcessorInterface.php delete mode 100644 app/Helpers/Csv/Specifix/AbnAmroDescription.php delete mode 100644 app/Helpers/Csv/Specifix/Dummy.php delete mode 100644 app/Helpers/Csv/Specifix/RabobankDescription.php delete mode 100644 app/Helpers/Csv/Specifix/Specifix.php delete mode 100644 app/Helpers/Csv/Specifix/SpecifixInterface.php delete mode 100644 app/Helpers/Csv/Wizard.php delete mode 100644 app/Helpers/Csv/WizardInterface.php delete mode 100644 app/Http/Controllers/CsvController.php delete mode 100644 resources/views/csv/column-roles.twig delete mode 100644 resources/views/csv/download-config.twig delete mode 100644 resources/views/csv/index.twig delete mode 100644 resources/views/csv/map.twig delete mode 100644 resources/views/csv/process.twig diff --git a/app/Export/Collector/UploadCollector.php b/app/Export/Collector/UploadCollector.php index 0b7cca980a..3841a52a9d 100644 --- a/app/Export/Collector/UploadCollector.php +++ b/app/Export/Collector/UploadCollector.php @@ -33,6 +33,7 @@ class UploadCollector extends BasicCollector implements CollectorInterface private $uploadDisk; /** + * * AttachmentCollector constructor. * * @param ExportJob $job @@ -44,6 +45,8 @@ class UploadCollector extends BasicCollector implements CollectorInterface // make storage: $this->uploadDisk = Storage::disk('upload'); $this->exportDisk = Storage::disk('export'); + + // todo needs work for new importer (potentially collect other types as well) $this->expected = 'csv-upload-' . Auth::user()->id . '-'; } diff --git a/app/Helpers/Csv/Converter/AccountId.php b/app/Helpers/Csv/Converter/AccountId.php deleted file mode 100644 index 908cd132de..0000000000 --- a/app/Helpers/Csv/Converter/AccountId.php +++ /dev/null @@ -1,35 +0,0 @@ -mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; - $account = $crud->find($var); - - return $account; - } -} diff --git a/app/Helpers/Csv/Converter/Amount.php b/app/Helpers/Csv/Converter/Amount.php deleted file mode 100644 index 8e2cfc4d0e..0000000000 --- a/app/Helpers/Csv/Converter/Amount.php +++ /dev/null @@ -1,32 +0,0 @@ -value)) { - return strval($this->value); - } - - return '0'; - } -} diff --git a/app/Helpers/Csv/Converter/AmountComma.php b/app/Helpers/Csv/Converter/AmountComma.php deleted file mode 100644 index 8cddad6dc8..0000000000 --- a/app/Helpers/Csv/Converter/AmountComma.php +++ /dev/null @@ -1,36 +0,0 @@ -value)); - - if (is_numeric($value)) { - return strval($value); - } - - return '0'; - } -} diff --git a/app/Helpers/Csv/Converter/AssetAccountIban.php b/app/Helpers/Csv/Converter/AssetAccountIban.php deleted file mode 100644 index 4da3a73625..0000000000 --- a/app/Helpers/Csv/Converter/AssetAccountIban.php +++ /dev/null @@ -1,89 +0,0 @@ -mapped[$this->index][$this->value])) { - $account = $crud->find(intval($this->mapped[$this->index][$this->value])); - - return $account; - } - - - if (strlen($this->value) > 0) { - $account = $this->searchOrCreate($crud); - - return $account; - } - - return new Account; - } - - /** - * @param AccountCrudInterface $crud - * - * @return Account - */ - private function searchOrCreate(AccountCrudInterface $crud) - { - // find or create new account: - $set = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - /** @var Account $entry */ - foreach ($set as $entry) { - if ($entry->iban == $this->value) { - - return $entry; - } - } - - - // create it if doesn't exist. - $accountData = [ - 'name' => $this->value, - 'accountType' => 'asset', - 'virtualBalance' => 0, - 'virtualBalanceCurrency' => 1, // hard coded. - 'active' => true, - 'user' => Auth::user()->id, - 'iban' => $this->value, - 'accountNumber' => $this->value, - 'accountRole' => null, - 'openingBalance' => 0, - 'openingBalanceDate' => new Carbon, - 'openingBalanceCurrency' => 1, // hard coded. - ]; - - $account = $crud->store($accountData); - - return $account; - } -} diff --git a/app/Helpers/Csv/Converter/AssetAccountName.php b/app/Helpers/Csv/Converter/AssetAccountName.php deleted file mode 100644 index e56620415e..0000000000 --- a/app/Helpers/Csv/Converter/AssetAccountName.php +++ /dev/null @@ -1,65 +0,0 @@ -mapped[$this->index][$this->value])) { - $account = $crud->find(intval($this->mapped[$this->index][$this->value])); - - return $account; - } - - $set = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - /** @var Account $entry */ - foreach ($set as $entry) { - if ($entry->name == $this->value) { - return $entry; - } - } - $accountData = [ - 'name' => $this->value, - 'accountType' => 'asset', - 'virtualBalance' => 0, - 'virtualBalanceCurrency' => 1, // hard coded. - 'active' => true, - 'user' => Auth::user()->id, - 'iban' => null, - 'accountNumber' => $this->value, - 'accountRole' => null, - 'openingBalance' => 0, - 'openingBalanceDate' => new Carbon, - 'openingBalanceCurrency' => 1, // hard coded. - ]; - - $account = $crud->store($accountData); - - return $account; - } -} diff --git a/app/Helpers/Csv/Converter/AssetAccountNumber.php b/app/Helpers/Csv/Converter/AssetAccountNumber.php deleted file mode 100644 index 5b8efed50a..0000000000 --- a/app/Helpers/Csv/Converter/AssetAccountNumber.php +++ /dev/null @@ -1,77 +0,0 @@ -mapped[$this->index][$this->value])) { - $account = $crud->find(intval($this->mapped[$this->index][$this->value])); - - return $account; - } - // if not, search for it (or create it): - $value = $this->value ?? ''; - if (strlen($value) > 0) { - // find or create new account: - $set = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - /** @var Account $entry */ - foreach ($set as $entry) { - $accountNumber = $entry->getMeta('accountNumber'); - if ($accountNumber == $this->value) { - - return $entry; - } - } - - $accountData = [ - 'name' => $this->value, - 'accountType' => 'asset', - 'virtualBalance' => 0, - 'virtualBalanceCurrency' => 1, // hard coded. - 'active' => true, - 'user' => Auth::user()->id, - 'iban' => null, - 'accountNumber' => $this->value, - 'accountRole' => null, - 'openingBalance' => 0, - 'openingBalanceDate' => new Carbon, - 'openingBalanceCurrency' => 1, // hard coded. - - ]; - - $account = $crud->store($accountData); - - return $account; - } - - return null; // is this accepted? - } - -} diff --git a/app/Helpers/Csv/Converter/BasicConverter.php b/app/Helpers/Csv/Converter/BasicConverter.php deleted file mode 100644 index 63e5f8bb96..0000000000 --- a/app/Helpers/Csv/Converter/BasicConverter.php +++ /dev/null @@ -1,114 +0,0 @@ -data; - } - - /** - * @param array $data - */ - public function setData(array $data) - { - $this->data = $data; - } - - /** - * @return string - */ - public function getField(): string - { - return $this->field; - } - - /** - * @param string $field - */ - public function setField(string $field) - { - $this->field = $field; - } - - /** - * @return int - */ - public function getIndex(): int - { - return $this->index; - } - - /** - * @param int $index - */ - public function setIndex(int $index) - { - $this->index = $index; - } - - /** - * @return array - */ - public function getMapped(): array - { - return $this->mapped; - } - - /** - * @param array $mapped - */ - public function setMapped(array $mapped) - { - $this->mapped = $mapped; - } - - /** - * @return string - */ - public function getValue(): string - { - return $this->value; - } - - /** - * @param string $value - */ - public function setValue(string $value) - { - $this->value = $value; - } - - -} diff --git a/app/Helpers/Csv/Converter/BillId.php b/app/Helpers/Csv/Converter/BillId.php deleted file mode 100644 index 3e9102ccec..0000000000 --- a/app/Helpers/Csv/Converter/BillId.php +++ /dev/null @@ -1,36 +0,0 @@ -mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; - $bill = $repository->find($value); - - return $bill; - } -} diff --git a/app/Helpers/Csv/Converter/BillName.php b/app/Helpers/Csv/Converter/BillName.php deleted file mode 100644 index a1328ef6c1..0000000000 --- a/app/Helpers/Csv/Converter/BillName.php +++ /dev/null @@ -1,47 +0,0 @@ -mapped[$this->index][$this->value])) { - return $repository->find($this->mapped[$this->index][$this->value]); - } - $bills = $repository->getBills(); - - /** @var Bill $bill */ - foreach ($bills as $bill) { - if ($bill->name == $this->value) { - return $bill; - } - } - - return new Bill; - } -} diff --git a/app/Helpers/Csv/Converter/BudgetId.php b/app/Helpers/Csv/Converter/BudgetId.php deleted file mode 100644 index d9302d7734..0000000000 --- a/app/Helpers/Csv/Converter/BudgetId.php +++ /dev/null @@ -1,36 +0,0 @@ -mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; - $budget = $repository->find($value); - - return $budget; - } -} diff --git a/app/Helpers/Csv/Converter/BudgetName.php b/app/Helpers/Csv/Converter/BudgetName.php deleted file mode 100644 index 84f777775b..0000000000 --- a/app/Helpers/Csv/Converter/BudgetName.php +++ /dev/null @@ -1,44 +0,0 @@ -mapped[$this->index][$this->value])) { - $budget = $repository->find($this->mapped[$this->index][$this->value]); - - return $budget; - } - $budget = $repository->store(['name' => $this->value, 'user' => Auth::user()->id]); - - - return $budget; - } -} diff --git a/app/Helpers/Csv/Converter/CategoryId.php b/app/Helpers/Csv/Converter/CategoryId.php deleted file mode 100644 index 8ffedd9ae1..0000000000 --- a/app/Helpers/Csv/Converter/CategoryId.php +++ /dev/null @@ -1,36 +0,0 @@ -mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; - $category = $repository->find($value); - - return $category; - } -} diff --git a/app/Helpers/Csv/Converter/CategoryName.php b/app/Helpers/Csv/Converter/CategoryName.php deleted file mode 100644 index d322f7b02f..0000000000 --- a/app/Helpers/Csv/Converter/CategoryName.php +++ /dev/null @@ -1,49 +0,0 @@ -mapped[$this->index][$this->value])) { - $category = $repository->find($this->mapped[$this->index][$this->value]); - - return $category; - } - - $data = [ - 'name' => $this->value, - 'user' => Auth::user()->id, - ]; - - $category = $repository->store($data); - - return $category; - } -} diff --git a/app/Helpers/Csv/Converter/ConverterInterface.php b/app/Helpers/Csv/Converter/ConverterInterface.php deleted file mode 100644 index c8fb526b83..0000000000 --- a/app/Helpers/Csv/Converter/ConverterInterface.php +++ /dev/null @@ -1,52 +0,0 @@ -mapped[$this->index][$this->value])) { - $currency = $repository->find(intval($this->mapped[$this->index][$this->value])); - - return $currency; - } - - $currency = $repository->findByCode($this->value); - - - return $currency; - } -} diff --git a/app/Helpers/Csv/Converter/CurrencyId.php b/app/Helpers/Csv/Converter/CurrencyId.php deleted file mode 100644 index a18082872a..0000000000 --- a/app/Helpers/Csv/Converter/CurrencyId.php +++ /dev/null @@ -1,36 +0,0 @@ -mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; - $currency = $repository->find($value); - - return $currency; - } -} diff --git a/app/Helpers/Csv/Converter/CurrencyName.php b/app/Helpers/Csv/Converter/CurrencyName.php deleted file mode 100644 index 62876fa9ba..0000000000 --- a/app/Helpers/Csv/Converter/CurrencyName.php +++ /dev/null @@ -1,42 +0,0 @@ -mapped[$this->index][$this->value])) { - $currency = $repository->find($this->mapped[$this->index][$this->value]); - - return $currency; - } - $currency = $repository->findByName($this->value); - - - return $currency; - } -} diff --git a/app/Helpers/Csv/Converter/CurrencySymbol.php b/app/Helpers/Csv/Converter/CurrencySymbol.php deleted file mode 100644 index 51037ece2e..0000000000 --- a/app/Helpers/Csv/Converter/CurrencySymbol.php +++ /dev/null @@ -1,43 +0,0 @@ -mapped[$this->index][$this->value])) { - $currency = $repository->find($this->mapped[$this->index][$this->value]); - - return $currency; - } - - $currency = $repository->findBySymbol($this->value); - - - return $currency; - } -} diff --git a/app/Helpers/Csv/Converter/Date.php b/app/Helpers/Csv/Converter/Date.php deleted file mode 100644 index b164e9df5f..0000000000 --- a/app/Helpers/Csv/Converter/Date.php +++ /dev/null @@ -1,46 +0,0 @@ -value); - } catch (InvalidArgumentException $e) { - Log::error('Date conversion error: ' . $e->getMessage() . '. Value was "' . $this->value . '", format was "' . $format . '".'); - - $message = trans('firefly.csv_date_parse_error', ['format' => $format, 'value' => $this->value]); - - throw new FireflyException($message); - } - - - return $date; - } -} diff --git a/app/Helpers/Csv/Converter/Description.php b/app/Helpers/Csv/Converter/Description.php deleted file mode 100644 index ea864a3e3e..0000000000 --- a/app/Helpers/Csv/Converter/Description.php +++ /dev/null @@ -1,31 +0,0 @@ -data['description'] ?? ''; - - return trim($description . ' ' . $this->value); - } -} diff --git a/app/Helpers/Csv/Converter/INGDebetCredit.php b/app/Helpers/Csv/Converter/INGDebetCredit.php deleted file mode 100644 index 59fd76248a..0000000000 --- a/app/Helpers/Csv/Converter/INGDebetCredit.php +++ /dev/null @@ -1,34 +0,0 @@ -value === 'Af') { - return -1; - } - - return 1; - } -} diff --git a/app/Helpers/Csv/Converter/Ignore.php b/app/Helpers/Csv/Converter/Ignore.php deleted file mode 100644 index c2da8c3033..0000000000 --- a/app/Helpers/Csv/Converter/Ignore.php +++ /dev/null @@ -1,28 +0,0 @@ -mapped[$this->index][$this->value])) { - $account = $crud->find($this->mapped[$this->index][$this->value]); - - return $account; - } - - return $this->findAccount($crud); - } - - /** - * @param AccountCrudInterface $crud - * - * @return Account|string - */ - private function findAccount(AccountCrudInterface $crud) - { - if (strlen($this->value) > 0) { - - $set = $crud->getAccountsByType([]); - /** @var Account $account */ - foreach ($set as $account) { - if ($account->iban == $this->value) { - - return $account; - } - } - } - - return $this->value; - } - -} diff --git a/app/Helpers/Csv/Converter/OpposingAccountId.php b/app/Helpers/Csv/Converter/OpposingAccountId.php deleted file mode 100644 index 17836907bf..0000000000 --- a/app/Helpers/Csv/Converter/OpposingAccountId.php +++ /dev/null @@ -1,35 +0,0 @@ -mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; - $account = $crud->find($value); - - return $account; - } -} diff --git a/app/Helpers/Csv/Converter/OpposingAccountName.php b/app/Helpers/Csv/Converter/OpposingAccountName.php deleted file mode 100644 index 7c3d392985..0000000000 --- a/app/Helpers/Csv/Converter/OpposingAccountName.php +++ /dev/null @@ -1,41 +0,0 @@ -mapped[$this->index][$this->value])) { - $account = $crud->find($this->mapped[$this->index][$this->value]); - - return $account; - } - - return $this->value; - - } -} diff --git a/app/Helpers/Csv/Converter/RabobankDebetCredit.php b/app/Helpers/Csv/Converter/RabobankDebetCredit.php deleted file mode 100644 index 854f812d45..0000000000 --- a/app/Helpers/Csv/Converter/RabobankDebetCredit.php +++ /dev/null @@ -1,34 +0,0 @@ -value == 'D') { - return -1; - } - - return 1; - } -} diff --git a/app/Helpers/Csv/Converter/TagsComma.php b/app/Helpers/Csv/Converter/TagsComma.php deleted file mode 100644 index c9477e7ada..0000000000 --- a/app/Helpers/Csv/Converter/TagsComma.php +++ /dev/null @@ -1,53 +0,0 @@ -value); - foreach ($strings as $string) { - $data = [ - 'tag' => $string, - 'date' => null, - 'description' => null, - 'latitude' => null, - 'longitude' => null, - 'zoomLevel' => null, - 'tagMode' => 'nothing', - ]; - if (strlen($string) > 0) { - $tag = $repository->store($data); // should validate first? - $tags->push($tag); - } - } - $tags = $tags->merge($this->data['tags']); - - return $tags; - } -} diff --git a/app/Helpers/Csv/Converter/TagsSpace.php b/app/Helpers/Csv/Converter/TagsSpace.php deleted file mode 100644 index aa2694a330..0000000000 --- a/app/Helpers/Csv/Converter/TagsSpace.php +++ /dev/null @@ -1,54 +0,0 @@ -value); - foreach ($strings as $string) { - $data = [ - 'tag' => $string, - 'date' => null, - 'description' => null, - 'latitude' => null, - 'longitude' => null, - 'zoomLevel' => null, - 'tagMode' => 'nothing', - ]; - if (strlen($string) > 0) { - $tag = $repository->store($data); // should validate first? - $tags->push($tag); - } - } - $tags = $tags->merge($this->data['tags']); - - return $tags; - } -} diff --git a/app/Helpers/Csv/Data.php b/app/Helpers/Csv/Data.php deleted file mode 100644 index d56f790204..0000000000 --- a/app/Helpers/Csv/Data.php +++ /dev/null @@ -1,336 +0,0 @@ -sessionHasHeaders(); - $this->sessionDateFormat(); - $this->sessionCsvFileLocation(); - $this->sessionMap(); - $this->sessionRoles(); - $this->sessionMapped(); - $this->sessionSpecifix(); - $this->sessionImportAccount(); - $this->sessionDelimiter(); - } - - /** - * - * @return string - */ - public function getCsvFileContent(): string - { - return $this->csvFileContent ?? ''; - } - - /** - * - * @param string $csvFileContent - */ - public function setCsvFileContent(string $csvFileContent) - { - $this->csvFileContent = $csvFileContent; - } - - /** - * FIXxME may return null - * - * @return string - */ - public function getCsvFileLocation(): string - { - return $this->csvFileLocation; - } - - /** - * - * @param string $csvFileLocation - */ - public function setCsvFileLocation(string $csvFileLocation) - { - Session::put('csv-file', $csvFileLocation); - $this->csvFileLocation = $csvFileLocation; - } - - /** - * FIXxME may return null - * - * @return string - */ - public function getDateFormat(): string - { - return $this->dateFormat; - } - - /** - * - * @param string $dateFormat - */ - public function setDateFormat(string $dateFormat) - { - Session::put('csv-date-format', $dateFormat); - $this->dateFormat = $dateFormat; - } - - /** - * FIXxME may return null - * - * @return string - */ - public function getDelimiter(): string - { - return $this->delimiter; - } - - /** - * - * @param string $delimiter - */ - public function setDelimiter(string $delimiter) - { - Session::put('csv-delimiter', $delimiter); - $this->delimiter = $delimiter; - } - - /** - * - * @return array - */ - public function getMap(): array - { - return $this->map; - } - - /** - * - * @param array $map - */ - public function setMap(array $map) - { - Session::put('csv-map', $map); - $this->map = $map; - } - - /** - * - * @return array - */ - public function getMapped(): array - { - return $this->mapped; - } - - /** - * - * @param array $mapped - */ - public function setMapped(array $mapped) - { - Session::put('csv-mapped', $mapped); - $this->mapped = $mapped; - } - - /** - * - * @return Reader - */ - public function getReader(): Reader - { - if (!is_null($this->csvFileContent) && strlen($this->csvFileContent) === 0) { - $this->loadCsvFile(); - } - - if (is_null($this->reader)) { - $this->reader = Reader::createFromString($this->getCsvFileContent()); - $this->reader->setDelimiter($this->delimiter); - } - - return $this->reader; - } - - /** - * - * @return array - */ - public function getRoles(): array - { - return $this->roles; - } - - /** - * - * @param array $roles - */ - public function setRoles(array $roles) - { - Session::put('csv-roles', $roles); - $this->roles = $roles; - } - - /** - * - * @return array - */ - public function getSpecifix(): array - { - return is_array($this->specifix) ? $this->specifix : []; - } - - /** - * - * @param array $specifix - */ - public function setSpecifix(array $specifix) - { - Session::put('csv-specifix', $specifix); - $this->specifix = $specifix; - } - - /** - * - * @return bool - */ - public function hasHeaders(): bool - { - return $this->hasHeaders; - } - - /** - * - * @param bool $hasHeaders - */ - public function setHasHeaders(bool $hasHeaders) - { - Session::put('csv-has-headers', $hasHeaders); - $this->hasHeaders = $hasHeaders; - } - - /** - * - * @param int $importAccount - */ - public function setImportAccount(int $importAccount) - { - Session::put('csv-import-account', $importAccount); - $this->importAccount = $importAccount; - } - - protected function loadCsvFile() - { - $file = $this->getCsvFileLocation(); - $disk = Storage::disk('upload'); - $content = $disk->get($file); - $contentDecrypted = Crypt::decrypt($content); - $this->setCsvFileContent($contentDecrypted); - } - - protected function sessionCsvFileLocation() - { - if (Session::has('csv-file')) { - $this->csvFileLocation = (string)session('csv-file'); - } - } - - protected function sessionDateFormat() - { - if (Session::has('csv-date-format')) { - $this->dateFormat = (string)session('csv-date-format'); - } - } - - protected function sessionDelimiter() - { - if (Session::has('csv-delimiter')) { - $this->delimiter = session('csv-delimiter'); - } - } - - protected function sessionHasHeaders() - { - if (Session::has('csv-has-headers')) { - $this->hasHeaders = (bool)session('csv-has-headers'); - } - } - - protected function sessionImportAccount() - { - if (Session::has('csv-import-account')) { - $this->importAccount = intval(session('csv-import-account')); - } - } - - protected function sessionMap() - { - if (Session::has('csv-map')) { - $this->map = (array)session('csv-map'); - } - } - - protected function sessionMapped() - { - if (Session::has('csv-mapped')) { - $this->mapped = (array)session('csv-mapped'); - } - } - - protected function sessionRoles() - { - if (Session::has('csv-roles')) { - $this->roles = (array)session('csv-roles'); - } - } - - protected function sessionSpecifix() - { - if (Session::has('csv-specifix')) { - $this->specifix = (array)session('csv-specifix'); - } - } -} diff --git a/app/Helpers/Csv/Importer.php b/app/Helpers/Csv/Importer.php deleted file mode 100644 index 9e50f02a46..0000000000 --- a/app/Helpers/Csv/Importer.php +++ /dev/null @@ -1,384 +0,0 @@ -errors; - } - - /** - * Used by CsvController - * - * @return int - */ - public function getImported(): int - { - return $this->imported; - } - - /** - * @return Collection - */ - public function getJournals(): Collection - { - return $this->journals; - } - - /** - * Used by CsvController - * - * @return int - */ - public function getRows(): int - { - return $this->rows; - } - - /** - * @return array - */ - public function getSpecifix(): array - { - return is_array($this->specifix) ? $this->specifix : []; - } - - /** - * @throws FireflyException - */ - public function run() - { - set_time_limit(0); - - $this->journals = new Collection; - $this->map = $this->data->getMap(); - $this->roles = $this->data->getRoles(); - $this->mapped = $this->data->getMapped(); - $this->specifix = $this->data->getSpecifix(); - - foreach ($this->data->getReader() as $index => $row) { - if ($this->parseRow($index)) { - $this->rows++; - $result = $this->importRow($row); - if (!($result instanceof TransactionJournal)) { - Log::error('Caught error at row #' . $index . ': ' . $result); - $this->errors[$index] = $result; - } else { - $this->imported++; - $this->journals->push($result); - event(new TransactionJournalStored($result, 0)); - } - } - } - } - - /** - * @param Data $data - */ - public function setData(Data $data) - { - $this->data = $data; - } - - /** - * @return TransactionJournal|string - */ - protected function createTransactionJournal() - { - $date = $this->importData['date']; - if (is_null($this->importData['date'])) { - $date = $this->importData['date-rent']; - } - - - $transactionType = $this->getTransactionType(); // defaults to deposit - $errors = new MessageBag; - $journal = TransactionJournal::create( - [ - 'user_id' => Auth::user()->id, - 'transaction_type_id' => $transactionType->id, - 'transaction_currency_id' => $this->importData['currency']->id, - 'description' => $this->importData['description'], - 'completed' => 0, - 'date' => $date, - 'bill_id' => $this->importData['bill-id'], - ] - ); - if ($journal->getErrors()->count() == 0) { - // 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]); - $errors = $transaction->getErrors()->merge($errors); - } - if ($errors->count() == 0) { - $journal->completed = 1; - $journal->save(); - } else { - $text = join(',', $errors->all()); - - return $text; - } - $this->saveBudget($journal); - $this->saveCategory($journal); - $this->saveTags($journal); - - // some debug info: - $journalId = $journal->id; - $type = $journal->transaction_type_type ?? $journal->transactionType->type; - /** @var Account $asset */ - $asset = $this->importData['asset-account-object']; - /** @var Account $opposing */ - $opposing = $this->importData['opposing-account-object']; - - Log::info('Created journal #' . $journalId . ' of type ' . $type . '!'); - Log::info('Asset account #' . $asset->id . ' lost/gained: ' . $this->importData['amount']); - Log::info($opposing->accountType->type . ' #' . $opposing->id . ' lost/gained: ' . bcmul($this->importData['amount'], '-1')); - - return $journal; - } - - /** - * @return TransactionType - */ - protected function getTransactionType() - { - $transactionType = TransactionType::where('type', TransactionType::DEPOSIT)->first(); - if ($this->importData['amount'] < 0) { - $transactionType = TransactionType::where('type', TransactionType::WITHDRAWAL)->first(); - } - - if (in_array($this->importData['opposing-account-object']->accountType->type, ['Asset account', 'Default account'])) { - $transactionType = TransactionType::where('type', TransactionType::TRANSFER)->first(); - } - - return $transactionType; - } - - /** - * @param array $row - * - * @throws FireflyException - * @return string|bool - */ - protected function importRow(array $row) - { - - $data = $this->getFiller(); // These fields are necessary to create a new transaction journal. Some are optional - foreach ($row as $index => $value) { - $role = $this->roles[$index] ?? '_ignore'; - $class = config('csv.roles.' . $role . '.converter'); - $field = config('csv.roles.' . $role . '.field'); - - - // here would be the place where preprocessors would fire. - - /** @var ConverterInterface $converter */ - $converter = app('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); - $data[$field] = $converter->convert(); - } - // move to class vars. - $this->importData = $data; - $this->importRow = $row; - unset($data, $row); - // post processing and validating. - $this->postProcess(); - $result = $this->validateData(); - - if (!($result === true)) { - return $result; // return error. - } - $journal = $this->createTransactionJournal(); - - return $journal; - } - - /** - * @param int $index - * - * @return bool - */ - protected function parseRow(int $index) - { - return (($this->data->hasHeaders() && $index >= 1) || !$this->data->hasHeaders()); - } - - /** - * Row denotes the original data. - * - * @return void - */ - protected function postProcess() - { - // do bank specific fixes (must be enabled but now all of them. - - foreach ($this->getSpecifix() as $className) { - /** @var SpecifixInterface $specifix */ - $specifix = app('FireflyIII\Helpers\Csv\Specifix\\' . $className); - if ($specifix->getProcessorType() == SpecifixInterface::POST_PROCESSOR) { - $specifix->setData($this->importData); - $specifix->setRow($this->importRow); - $this->importData = $specifix->fix(); - } - } - - - $set = config('csv.post_processors'); - foreach ($set as $className) { - /** @var PostProcessorInterface $postProcessor */ - $postProcessor = app('FireflyIII\Helpers\Csv\PostProcessing\\' . $className); - $array = $this->importData ?? []; - $postProcessor->setData($array); - $this->importData = $postProcessor->process(); - } - - } - - /** - * @param TransactionJournal $journal - */ - protected function saveBudget(TransactionJournal $journal) - { - // add budget: - if (!is_null($this->importData['budget'])) { - $journal->budgets()->save($this->importData['budget']); - } - } - - /** - * @param TransactionJournal $journal - */ - protected function saveCategory(TransactionJournal $journal) - { - // add category: - if (!is_null($this->importData['category'])) { - $journal->categories()->save($this->importData['category']); - } - } - - /** - * @param TransactionJournal $journal - */ - protected function saveTags(TransactionJournal $journal) - { - if (!is_null($this->importData['tags'])) { - foreach ($this->importData['tags'] as $tag) { - $journal->tags()->save($tag); - } - } - } - - /** - * - * @return bool|string - */ - protected function validateData() - { - $date = $this->importData['date'] ?? null; - $rentDate = $this->importData['date-rent'] ?? null; - if (is_null($date) && is_null($rentDate)) { - return 'No date value for this row.'; - } - if (is_null($this->importData['opposing-account-object'])) { - return 'Opposing account is null'; - } - - if (!($this->importData['asset-account-object'] instanceof Account)) { - return 'No asset account to import into.'; - } - - return true; - } - - /** - * @return array - */ - private function getFiller() - { - $filler = []; - foreach (config('csv.roles') as $role) { - if (isset($role['field'])) { - $fieldName = $role['field']; - $filler[$fieldName] = null; - } - } - // some extra's: - $filler['bill-id'] = null; - $filler['opposing-account-object'] = null; - $filler['asset-account-object'] = null; - $filler['amount-modifier'] = '1'; - - return $filler; - - } - -} diff --git a/app/Helpers/Csv/Mapper/AnyAccount.php b/app/Helpers/Csv/Mapper/AnyAccount.php deleted file mode 100644 index 05c5bab9ea..0000000000 --- a/app/Helpers/Csv/Mapper/AnyAccount.php +++ /dev/null @@ -1,42 +0,0 @@ -accounts()->with('accountType')->orderBy('accounts.name', 'ASC')->get(['accounts.*']); - - $list = []; - /** @var Account $account */ - foreach ($result as $account) { - $list[$account->id] = $account->name . ' (' . $account->accountType->type . ')'; - } - asort($list); - - $list = [0 => trans('firefly.csv_do_not_map')] + $list; - - return $list; - } -} diff --git a/app/Helpers/Csv/Mapper/AssetAccount.php b/app/Helpers/Csv/Mapper/AssetAccount.php deleted file mode 100644 index 8f32bbad57..0000000000 --- a/app/Helpers/Csv/Mapper/AssetAccount.php +++ /dev/null @@ -1,54 +0,0 @@ -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) { - $name = $account->name; - $iban = $account->iban ?? ''; - if (strlen($iban) > 0) { - $name .= ' (' . $account->iban . ')'; - } - $list[$account->id] = $name; - } - - asort($list); - - $list = [0 => trans('firefly.csv_do_not_map')] + $list; - - return $list; - } -} diff --git a/app/Helpers/Csv/Mapper/Bill.php b/app/Helpers/Csv/Mapper/Bill.php deleted file mode 100644 index 37946bf394..0000000000 --- a/app/Helpers/Csv/Mapper/Bill.php +++ /dev/null @@ -1,42 +0,0 @@ -bills()->get(['bills.*']); - $list = []; - - /** @var BillModel $bill */ - foreach ($result as $bill) { - $list[$bill->id] = $bill->name . ' [' . $bill->match . ']'; - } - asort($list); - - $list = [0 => trans('firefly.csv_do_not_map')] + $list; - - return $list; - } -} diff --git a/app/Helpers/Csv/Mapper/Budget.php b/app/Helpers/Csv/Mapper/Budget.php deleted file mode 100644 index 7664cc3378..0000000000 --- a/app/Helpers/Csv/Mapper/Budget.php +++ /dev/null @@ -1,42 +0,0 @@ -budgets()->get(['budgets.*']); - $list = []; - - /** @var BudgetModel $budget */ - foreach ($result as $budget) { - $list[$budget->id] = $budget->name; - } - asort($list); - - $list = [0 => trans('firefly.csv_do_not_map')] + $list; - - return $list; - } -} diff --git a/app/Helpers/Csv/Mapper/Category.php b/app/Helpers/Csv/Mapper/Category.php deleted file mode 100644 index d0a104523a..0000000000 --- a/app/Helpers/Csv/Mapper/Category.php +++ /dev/null @@ -1,42 +0,0 @@ -categories()->get(['categories.*']); - $list = []; - - /** @var CategoryModel $category */ - foreach ($result as $category) { - $list[$category->id] = $category->name; - } - asort($list); - - $list = [0 => trans('firefly.csv_do_not_map')] + $list; - - return $list; - } -} diff --git a/app/Helpers/Csv/Mapper/MapperInterface.php b/app/Helpers/Csv/Mapper/MapperInterface.php deleted file mode 100644 index eb6286d4f2..0000000000 --- a/app/Helpers/Csv/Mapper/MapperInterface.php +++ /dev/null @@ -1,24 +0,0 @@ -budgets()->get(['tags.*']); - $list = []; - - /** @var TagModel $tag */ - foreach ($result as $tag) { - $list[$tag->id] = $tag->tag; - } - asort($list); - - $list = [0 => trans('firefly.csv_do_not_map')] + $list; - - return $list; - } -} diff --git a/app/Helpers/Csv/Mapper/TransactionCurrency.php b/app/Helpers/Csv/Mapper/TransactionCurrency.php deleted file mode 100644 index f4257b1510..0000000000 --- a/app/Helpers/Csv/Mapper/TransactionCurrency.php +++ /dev/null @@ -1,40 +0,0 @@ -id] = $currency->name . ' (' . $currency->code . ')'; - } - - asort($list); - - $list = [0 => trans('firefly.csv_do_not_map')] + $list; - - return $list; - } -} diff --git a/app/Helpers/Csv/PostProcessing/Amount.php b/app/Helpers/Csv/PostProcessing/Amount.php deleted file mode 100644 index 504c887331..0000000000 --- a/app/Helpers/Csv/PostProcessing/Amount.php +++ /dev/null @@ -1,44 +0,0 @@ -data['amount'] ?? '0'; - $modifier = strval($this->data['amount-modifier']); - $this->data['amount'] = bcmul($amount, $modifier); - - return $this->data; - } - - /** - * @param array $data - */ - public function setData(array $data) - { - $this->data = $data; - } -} diff --git a/app/Helpers/Csv/PostProcessing/AssetAccount.php b/app/Helpers/Csv/PostProcessing/AssetAccount.php deleted file mode 100644 index e57e0f73f1..0000000000 --- a/app/Helpers/Csv/PostProcessing/AssetAccount.php +++ /dev/null @@ -1,274 +0,0 @@ -checkIdNameObject(); // has object in ID or Name? - if (!is_null($result)) { - return $result; - } - - // no object? maybe asset-account-iban is a string and we can find the matching account. - $result = $this->checkIbanString(); - if (!is_null($result)) { - return $result; - } - - // no object still? maybe we can find the account by name. - $result = $this->checkNameString(); - if (!is_null($result)) { - return $result; - } - // still nothing? Perhaps the account number can lead us to an account: - $result = $this->checkAccountNumberString(); - if (!is_null($result)) { - return $result; - } - - return null; - } - - /** - * @param array $data - */ - public function setData(array $data) - { - $this->data = $data; - } - - /** - * @return array|null - */ - protected function checkAccountNumberString() - { - $accountNumber = $this->data['asset-account-number'] ?? null; - if ($accountNumber instanceof Account) { // fourth: try to find account based on name, if any. - $this->data['asset-account-object'] = $accountNumber; - - return $this->data; - } - if (is_string($accountNumber)) { // it's an actual account number - $this->data['asset-account-object'] = $this->parseAccountNumberString(); - - return $this->data; - } - - return null; - } - - /** - * @return array|null - */ - protected function checkIbanString() - { - $iban = $this->data['asset-account-iban'] ?? ''; - $rules = ['iban' => 'iban']; - $check = ['iban' => $iban]; - $validator = Validator::make($check, $rules); - if (!$validator->fails()) { - $this->data['asset-account-object'] = $this->parseIbanString(); - - return $this->data; - } - - return null; - } - - /** - * @return array - */ - protected function checkIdNameObject() - { - $accountId = $this->data['asset-account-id'] ?? null; - $accountIban = $this->data['asset-account-iban'] ?? null; - $accountNumber = $this->data['asset-account-number'] ?? null; - if ($accountId instanceof Account) { // first priority. try to find the account based on ID, if any - $this->data['asset-account-object'] = $accountId; - - return $this->data; - } - if ($accountIban instanceof Account) { // second: try to find the account based on IBAN, if any. - $this->data['asset-account-object'] = $accountIban; - - return $this->data; - } - - if ($accountNumber instanceof Account) { // second: try to find the account based on account number, if any. - $this->data['asset-account-object'] = $accountNumber; - - return $this->data; - } - - - return null; - } - - /** - * @return array|null - */ - protected function checkNameString() - { - $accountName = $this->data['asset-account-name'] ?? null; - if ($accountName instanceof Account) { // third: try to find account based on name, if any. - $this->data['asset-account-object'] = $accountName; - - return $this->data; - } - if (is_string($accountName)) { - $this->data['asset-account-object'] = $this->parseNameString(); - - return $this->data; - } - - return null; - } - - /** - * @return Account|null - */ - protected function createAccount() - { - $accountType = $this->getAccountType(); - $name = $this->data['asset-account-name'] ?? ''; - $iban = $this->data['asset-account-iban'] ?? ''; - - // create if not exists: // See issue #180 - $name = strlen($name) > 0 ? $name : $iban; - $account = Account::firstOrCreateEncrypted( - [ - 'user_id' => Auth::user()->id, - 'account_type_id' => $accountType->id, - 'name' => $name, - 'iban' => $iban, - 'active' => true, - ] - ); - - return $account; - } - - /** - * - * @return AccountType - */ - protected function getAccountType() - { - return AccountType::where('type', 'Asset account')->first(); - } - - /** - * @return Account|null - */ - protected function parseIbanString() - { - // create by name and/or iban. - $iban = $this->data['asset-account-iban'] ?? ''; - $accounts = Auth::user()->accounts()->get(); - foreach ($accounts as $entry) { - if ($iban !== '' && $entry->iban === $iban) { - - return $entry; - } - } - $account = $this->createAccount(); - - return $account; - } - - /** - * @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']) { - - return $entry; - } - } - // create if not exists: - // See issue #180 - $account = Account::firstOrCreateEncrypted( - [ - 'user_id' => Auth::user()->id, - 'account_type_id' => $accountType->id, - 'name' => $this->data['asset-account-name'], - 'iban' => '', - 'active' => true, - ] - ); - - return $account; - } - - /** - * @return Account|null - */ - private function parseAccountNumberString() - { - /** @var AccountCrudInterface $crud */ - $crud = app(AccountCrudInterface::class); - - $accountNumber = $this->data['asset-account-number'] ?? ''; - $accountType = $this->getAccountType(); - $accounts = Auth::user()->accounts()->with(['accountmeta'])->where('account_type_id', $accountType->id)->get(); - /** @var Account $entry */ - foreach ($accounts as $entry) { - $metaFieldValue = $entry->getMeta('accountNumber'); - if ($metaFieldValue === $accountNumber && $metaFieldValue !== '') { - - return $entry; - } - } - // create new if not exists and return that one: - $accountData = [ - 'name' => $accountNumber, - 'accountType' => 'asset', - 'virtualBalance' => 0, - 'virtualBalanceCurrency' => 1, // hard coded. - 'active' => true, - 'user' => Auth::user()->id, - 'iban' => null, - 'accountNumber' => $accountNumber, - 'accountRole' => null, - 'openingBalance' => 0, - 'openingBalanceDate' => new Carbon, - 'openingBalanceCurrency' => 1, // hard coded. - ]; - $account = $crud->store($accountData); - - return $account; - } -} diff --git a/app/Helpers/Csv/PostProcessing/Bill.php b/app/Helpers/Csv/PostProcessing/Bill.php deleted file mode 100644 index 60392b2446..0000000000 --- a/app/Helpers/Csv/PostProcessing/Bill.php +++ /dev/null @@ -1,45 +0,0 @@ -data['bill']) && !is_null($this->data['bill']->id)) { - $this->data['bill-id'] = $this->data['bill']->id; - } - - return $this->data; - } - - /** - * @param array $data - */ - public function setData(array $data) - { - $this->data = $data; - } -} diff --git a/app/Helpers/Csv/PostProcessing/Currency.php b/app/Helpers/Csv/PostProcessing/Currency.php deleted file mode 100644 index 2a6f46cfd5..0000000000 --- a/app/Helpers/Csv/PostProcessing/Currency.php +++ /dev/null @@ -1,49 +0,0 @@ -data['currency'])) { - $currencyPreference = Preferences::get('currencyPreference', env('DEFAULT_CURRENCY', 'EUR')); - $this->data['currency'] = TransactionCurrency::whereCode($currencyPreference->data)->first(); - } - - return $this->data; - } - - /** - * @param array $data - */ - public function setData(array $data) - { - $this->data = $data; - } -} diff --git a/app/Helpers/Csv/PostProcessing/Description.php b/app/Helpers/Csv/PostProcessing/Description.php deleted file mode 100644 index 8c4843a58b..0000000000 --- a/app/Helpers/Csv/PostProcessing/Description.php +++ /dev/null @@ -1,47 +0,0 @@ -data['description'] ?? ''; - $this->data['description'] = trim($description); - if (strlen($this->data['description']) == 0) { - $this->data['description'] = trans('firefly.csv_empty_description'); - } - - - return $this->data; - } - - /** - * @param array $data - */ - public function setData(array $data) - { - - $this->data = $data; - } -} diff --git a/app/Helpers/Csv/PostProcessing/OpposingAccount.php b/app/Helpers/Csv/PostProcessing/OpposingAccount.php deleted file mode 100644 index 279328b24e..0000000000 --- a/app/Helpers/Csv/PostProcessing/OpposingAccount.php +++ /dev/null @@ -1,210 +0,0 @@ -checkIdNameObject(); - 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|null - */ - protected function checkIbanString() - { - $rules = ['iban' => 'iban']; - $iban = $this->data['opposing-account-iban']; - $check = ['iban' => $iban]; - $validator = Validator::make($check, $rules); - if (is_string($iban) && strlen($iban) > 0 && !$validator->fails()) { - - $this->data['opposing-account-object'] = $this->parseIbanString(); - - return $this->data; - } - - return null; - } - - /** - * @return array - */ - protected function checkIdNameObject() - { - if ($this->data['opposing-account-id'] instanceof Account) { // first priority. try to find the account based on ID, if any - $this->data['opposing-account-object'] = $this->data['opposing-account-id']; - - return $this->data; - } - if ($this->data['opposing-account-iban'] instanceof Account) { // second: try to find the account based on IBAN, if any. - $this->data['opposing-account-object'] = $this->data['opposing-account-iban']; - - return $this->data; - } - - return null; - } - - /** - * @return array|null - */ - protected function checkNameString() - { - if ($this->data['opposing-account-name'] instanceof Account) { // third: try to find account based on name, if any. - $this->data['opposing-account-object'] = $this->data['opposing-account-name']; - - return $this->data; - } - if (is_string($this->data['opposing-account-name'])) { - - $this->data['opposing-account-object'] = $this->parseNameString(); - - return $this->data; - } - - return null; - } - - /** - * @return Account|null - */ - protected function createAccount() - { - $accountType = $this->getAccountType(); - - // create if not exists: - $name = is_string($this->data['opposing-account-name']) && strlen($this->data['opposing-account-name']) > 0 ? $this->data['opposing-account-name'] - : $this->data['opposing-account-iban']; - $account = Account::firstOrCreateEncrypted( // See issue #180 - [ - 'user_id' => Auth::user()->id, - 'account_type_id' => $accountType->id, - 'name' => $name, - 'iban' => $this->data['opposing-account-iban'], - 'active' => true, - ] - ); - - return $account; - } - - /** - * - * @return AccountType - */ - protected function getAccountType() - { - // opposing account type: - if ($this->data['amount'] < 0) { - // create expense account: - - return AccountType::where('type', 'Expense account')->first(); - } - - // create revenue account: - - return AccountType::where('type', 'Revenue account')->first(); - - - } - - /** - * @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['opposing-account-iban']) { - - return $entry; - } - } - $account = $this->createAccount(); - - - return $account; - } - - /** - * @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['opposing-account-name']) { - - return $entry; - } - } - // create if not exists: - $account = Account::firstOrCreateEncrypted( // See issue #180 - [ - 'user_id' => Auth::user()->id, - 'account_type_id' => $accountType->id, - 'name' => $this->data['opposing-account-name'], - 'iban' => '', - 'active' => true, - ] - ); - - return $account; - } -} diff --git a/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php b/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php deleted file mode 100644 index 58f6ff09d0..0000000000 --- a/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php +++ /dev/null @@ -1,31 +0,0 @@ -setProcessorType(self::POST_PROCESSOR); - } - - - /** - * @return array - */ - public function fix(): array - { - // Try to parse the description in known formats. - $parsed = $this->parseSepaDescription() || $this->parseTRTPDescription() || $this->parseGEABEADescription() || $this->parseABNAMRODescription(); - - // If the description could not be parsed, specify an unknown opposing - // account, as an opposing account is required - if (!$parsed) { - $this->data['opposing-account-name'] = trans('firefly.unknown'); - } - - return $this->data; - } - - /** - * @param array $data - */ - public function setData(array $data) - { - $this->data = $data; - } - - /** - * @param array $row - */ - public function setRow(array $row) - { - $this->row = $row; - } - - /** - * Parses the current description with costs from ABN AMRO itself - * - * @return bool true if the description is GEA/BEA-format, false otherwise - */ - protected function parseABNAMRODescription() - { - // See if the current description is formatted in ABN AMRO format - if (preg_match('/ABN AMRO.{24} (.*)/', $this->data['description'], $matches)) { - - $this->data['opposing-account-name'] = 'ABN AMRO'; - $this->data['description'] = $matches[1]; - - return true; - } - - return false; - } - - /** - * Parses the current description in GEA/BEA format - * - * @return bool true if the description is GEA/BEAformat, false otherwise - */ - protected function parseGEABEADescription() - { - // See if the current description is formatted in GEA/BEA format - if (preg_match('/([BG]EA) +(NR:[a-zA-Z:0-9]+) +([0-9.\/]+) +([^,]*)/', $this->data['description'], $matches)) { - - // description and opposing account will be the same. - $this->data['opposing-account-name'] = $matches[4]; - $this->data['description'] = $matches[4]; - - if ($matches[1] == 'GEA') { - $this->data['description'] = 'GEA ' . $matches[4]; - } - - return true; - } - - return false; - } - - /** - * Parses the current description in SEPA format - * - * @return bool true if the description is SEPA format, false otherwise - */ - protected function parseSepaDescription() - { - // See if the current description is formatted as a SEPA plain description - if (preg_match('/^SEPA(.{28})/', $this->data['description'], $matches)) { - - $type = $matches[1]; - $reference = ''; - $name = ''; - $newDescription = ''; - - // SEPA plain descriptions contain several key-value pairs, split by a colon - preg_match_all('/([A-Za-z]+(?=:\s)):\s([A-Za-z 0-9._#-]+(?=\s|$))/', $this->data['description'], $matches, PREG_SET_ORDER); - - if (is_array($matches)) { - foreach ($matches as $match) { - $key = $match[1]; - $value = trim($match[2]); - switch (strtoupper($key)) { - case 'OMSCHRIJVING': - $newDescription = $value; - break; - case 'NAAM': - $this->data['opposing-account-name'] = $value; - $name = $value; - break; - case 'KENMERK': - $reference = $value; - break; - case 'IBAN': - $this->data['opposing-account-iban'] = $value; - break; - default: - // Ignore the rest - } - } - } - - // Set a new description for the current transaction. If none was given - // set the description to type, name and reference - $this->data['description'] = $newDescription; - if (strlen($newDescription) === 0) { - $this->data['description'] = sprintf('%s - %s (%s)', $type, $name, $reference); - } - - return true; - } - - return false; - } - - /** - * Parses the current description in TRTP format - * - * @return bool true if the description is TRTP format, false otherwise - */ - protected function parseTRTPDescription() - { - // See if the current description is formatted in TRTP format - if (preg_match_all('!\/([A-Z]{3,4})\/([^/]*)!', $this->data['description'], $matches, PREG_SET_ORDER)) { - - $type = ''; - $name = ''; - $reference = ''; - $newDescription = ''; - - // Search for properties specified in the TRTP format. If no description - // is provided, use the type, name and reference as new description - if (is_array($matches)) { - foreach ($matches as $match) { - $key = $match[1]; - $value = trim($match[2]); - - switch (strtoupper($key)) { - case 'NAME': - $this->data['opposing-account-name'] = $name = $value; - break; - case 'REMI': - $newDescription = $value; - break; - case 'IBAN': - $this->data['opposing-account-iban'] = $value; - break; - case 'EREF': - $reference = $value; - break; - case 'TRTP': - $type = $value; - break; - default: - // Ignore the rest - } - } - - // Set a new description for the current transaction. If none was given - // set the description to type, name and reference - $this->data['description'] = $newDescription; - if (strlen($newDescription) === 0) { - $this->data['description'] = sprintf('%s - %s (%s)', $type, $name, $reference); - } - } - - return true; - } - - return false; - } - -} diff --git a/app/Helpers/Csv/Specifix/Dummy.php b/app/Helpers/Csv/Specifix/Dummy.php deleted file mode 100644 index 447bafadd9..0000000000 --- a/app/Helpers/Csv/Specifix/Dummy.php +++ /dev/null @@ -1,60 +0,0 @@ -setProcessorType(self::POST_PROCESSOR); - } - - /** - * @return array - */ - public function fix(): array - { - return $this->data; - - } - - /** - * @param array $data - */ - public function setData(array $data) - { - $this->data = $data; - } - - /** - * @param array $row - */ - public function setRow(array $row) - { - $this->row = $row; - } - - -} diff --git a/app/Helpers/Csv/Specifix/RabobankDescription.php b/app/Helpers/Csv/Specifix/RabobankDescription.php deleted file mode 100644 index 67dbdb6c49..0000000000 --- a/app/Helpers/Csv/Specifix/RabobankDescription.php +++ /dev/null @@ -1,76 +0,0 @@ -setProcessorType(self::POST_PROCESSOR); - } - - - /** - * @return array - */ - public function fix(): array - { - $this->rabobankFixEmptyOpposing(); - - return $this->data; - - } - - /** - * @param array $data - */ - public function setData(array $data) - { - $this->data = $data; - } - - /** - * @param array $row - */ - public function setRow(array $row) - { - $this->row = $row; - } - - /** - * Fixes Rabobank specific thing. - */ - protected function rabobankFixEmptyOpposing() - { - if (is_string($this->data['opposing-account-name']) && strlen($this->data['opposing-account-name']) == 0) { - $this->data['opposing-account-name'] = $this->row[10]; - - $this->data['description'] = trim(str_replace($this->row[10], '', $this->data['description'])); - } - - } - - -} diff --git a/app/Helpers/Csv/Specifix/Specifix.php b/app/Helpers/Csv/Specifix/Specifix.php deleted file mode 100644 index 8626d38dcb..0000000000 --- a/app/Helpers/Csv/Specifix/Specifix.php +++ /dev/null @@ -1,46 +0,0 @@ -processorType; - } - - /** - * @param int $processorType - * - * @return $this - */ - public function setProcessorType(int $processorType) - { - $this->processorType = $processorType; - - return $this; - } - - -} diff --git a/app/Helpers/Csv/Specifix/SpecifixInterface.php b/app/Helpers/Csv/Specifix/SpecifixInterface.php deleted file mode 100644 index e37d4d3ec4..0000000000 --- a/app/Helpers/Csv/Specifix/SpecifixInterface.php +++ /dev/null @@ -1,49 +0,0 @@ - $row) { - if ($this->useRow($hasHeaders, $index)) { - // collect all map values - - foreach ($keys as $column) { - $values[$column][] = $row[$column]; - } - } - } - /* - * Make each one unique. - */ - $values = $this->uniqueRecursive($values); - - return $values; - } - - /** - * @param array $roles - * @param array $map - * - * @return array - */ - public function processSelectedMapping(array $roles, array $map): array - { - $configRoles = config('csv.roles'); - $maps = []; - $keys = array_keys($map); - foreach ($keys as $index) { - if (isset($roles[$index])) { - $name = $roles[$index]; - if ($configRoles[$name]['mappable']) { - $maps[$index] = $name; - } - } - } - - return $maps; - - } - - /** - * @param array $input - * - * @return array - */ - public function processSelectedRoles(array $input): array - { - $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): bool - { - foreach ($fields as $field) { - if (!Session::has($field)) { - Log::error('Session is missing field: ' . $field); - - return false; - } - } - - return true; - } - - /** - * @param array $map - * - * @return array - * @throws FireflyException - */ - public function showOptions(array $map): array - { - $options = []; - foreach ($map as $index => $columnRole) { - - $mapper = config('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($class); - } catch (ReflectionException $e) { - throw new FireflyException('Column "' . $columnRole . '" cannot be mapped because mapper class ' . $mapper . ' does not exist.'); - } - $set = $mapObject->getMap(); - $options[$index] = $set; - } - - return $options; - } - - /** - * @param string $path - * - * @return string - */ - public function storeCsvFile(string $path): string - { - $time = str_replace(' ', '-', microtime()); - $fileName = 'csv-upload-' . Auth::user()->id . '-' . $time . '.csv.encrypted'; - $disk = Storage::disk('upload'); - $file = new SplFileObject($path, 'r'); - $content = $file->fread($file->getSize()); - $contentEncrypted = Crypt::encrypt($content); - $disk->put($fileName, $contentEncrypted); - - return $fileName; - - - } - - /** - * @param array $array - * - * @return array - */ - protected function uniqueRecursive(array $array) - { - foreach ($array as $column => $found) { - $array[$column] = array_unique($found); - } - - return $array; - } - - /** - * @param bool $hasHeaders - * @param int $index - * - * @return bool - */ - protected function useRow(bool $hasHeaders, int $index) - { - return ($hasHeaders && $index > 1) || !$hasHeaders; - } -} diff --git a/app/Helpers/Csv/WizardInterface.php b/app/Helpers/Csv/WizardInterface.php deleted file mode 100644 index cedcfbe5f6..0000000000 --- a/app/Helpers/Csv/WizardInterface.php +++ /dev/null @@ -1,68 +0,0 @@ -wizard = app(WizardInterface::class); - $this->data = app(Data::class); - - } - - /** - * Define column roles and mapping. - * - * STEP THREE - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View - */ - public function columnRoles() - { - - $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-import-account', 'csv-specifix', 'csv-delimiter']; - if (!$this->wizard->sessionHasValues($fields)) { - Log::error('Could not recover upload.'); - Session::flash('warning', strval(trans('firefly.could_not_recover'))); - - return redirect(route('csv.index')); - } - - $subTitle = trans('firefly.csv_define_column_roles'); - $firstRow = $this->data->getReader()->fetchOne(); - $count = count($firstRow); - $headers = []; - $example = $this->data->getReader()->fetchOne(1); - $availableRoles = []; - $roles = $this->data->getRoles(); - $map = $this->data->getMap(); - - for ($i = 1; $i <= $count; $i++) { - $headers[] = trans('firefly.csv_column') . ' #' . $i; - } - if ($this->data->hasHeaders()) { - $headers = $firstRow; - } - $keys = array_keys(config('csv.roles')); - foreach ($keys as $name) { - $availableRoles[$name] = trans('firefly.csv_column_' . $name); - } - asort($availableRoles); - - return view('csv.column-roles', compact('availableRoles', 'map', 'roles', 'headers', 'example', 'subTitle')); - } - - /** - * Optional download of mapping. - * - * STEP FOUR THREE-A - * - * @return \Illuminate\Http\RedirectResponse|string - */ - public function downloadConfig() - { - $fields = ['csv-date-format', 'csv-has-headers', 'csv-delimiter']; - if (!$this->wizard->sessionHasValues($fields)) { - Session::flash('warning', strval(trans('firefly.could_not_recover'))); - - return redirect(route('csv.index')); - } - $data = [ - 'date-format' => session('csv-date-format'), - 'has-headers' => session('csv-has-headers'), - ]; - if (Session::has('csv-map')) { - $data['map'] = session('csv-map'); - } - if (Session::has('csv-roles')) { - $data['roles'] = session('csv-roles'); - } - if (Session::has('csv-mapped')) { - $data['mapped'] = session('csv-mapped'); - } - - if (Session::has('csv-specifix')) { - $data['specifix'] = session('csv-specifix'); - } - - $result = json_encode($data, JSON_PRETTY_PRINT); - $name = sprintf('"%s"', addcslashes('csv-configuration-' . date('Y-m-d') . '.json', '"\\')); - - return response($result, 200) - ->header('Content-disposition', 'attachment; filename=' . $name) - ->header('Content-Type', 'application/json') - ->header('Content-Description', 'File Transfer') - ->header('Connection', 'Keep-Alive') - ->header('Expires', '0') - ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') - ->header('Pragma', 'public') - ->header('Content-Length', strlen($result)); - } - - /** - * @return \Illuminate\View\View - */ - public function downloadConfigPage() - { - $fields = ['csv-date-format', 'csv-has-headers', 'csv-delimiter']; - if (!$this->wizard->sessionHasValues($fields)) { - Session::flash('warning', strval(trans('firefly.could_not_recover'))); - - return redirect(route('csv.index')); - } - - $subTitle = trans('firefly.csv_download_config_title'); - - return view('csv.download-config', compact('subTitle')); - } - - /** - * This method shows the initial upload form. - * - * STEP ONE - * - * @param AccountCrudInterface $crud - * - * @return \Illuminate\View\View - */ - public function index(AccountCrudInterface $crud) - { - $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'); - Session::forget('csv-specifix'); - Session::forget('csv-delimiter'); - - // get list of supported specifix - $specifix = []; - foreach (config('csv.specifix') as $entry) { - $specifix[$entry] = trans('firefly.csv_specifix_' . $entry); - } - - // get a list of delimiters: - $delimiters = [ - ',' => trans('form.csv_comma'), - ';' => trans('form.csv_semicolon'), - 'tab' => trans('form.csv_tab'), - ]; - - // get a list of asset accounts: - $accounts = ExpandedForm::makeSelectList($crud->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT])); - - // can actually upload? - $uploadPossible = is_writable(storage_path('upload')); - $path = storage_path('upload'); - - return view('csv.index', compact('subTitle', 'uploadPossible', 'path', 'specifix', 'accounts', 'delimiters')); - } - - /** - * Parse the file. - * - * STEP FOUR - * - * @return \Illuminate\Http\RedirectResponse - */ - public function initialParse() - { - $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-delimiter']; - if (!$this->wizard->sessionHasValues($fields)) { - Session::flash('warning', strval(trans('firefly.could_not_recover'))); - - return redirect(route('csv.index')); - } - - // process given roles and mapping: - $inputMap = Input::get('map') ?? []; - $inputRoles = Input::get('role') ?? []; - $roles = $this->wizard->processSelectedRoles($inputRoles); - $maps = $this->wizard->processSelectedMapping($roles, $inputMap); - - Session::put('csv-map', $maps); - Session::put('csv-roles', $roles); - - // Go back when no roles defined: - if (count($roles) === 0) { - Session::flash('warning', strval(trans('firefly.must_select_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|\Illuminate\View\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', 'csv-delimiter']; - if (!$this->wizard->sessionHasValues($fields)) { - Session::flash('warning', strval(trans('firefly.could_not_recover'))); - - 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. - * - * options[column index] = [ - * field id => field identifier. - * ] - */ - $options = $this->wizard->showOptions($this->data->getMap()); - - // After these values are prepped, read the actual CSV file - $reader = $this->data->getReader(); - $map = $this->data->getMap(); - $hasHeaders = $this->data->hasHeaders(); - $values = $this->wizard->getMappableValues($reader, $map, $hasHeaders); - $map = $this->data->getMap(); - $mapped = $this->data->getMapped(); - $subTitle = trans('firefly.csv_map_values'); - - return view('csv.map', compact('map', 'options', 'values', 'mapped', 'subTitle')); - } - - /** - * - * Finally actually process the CSV file. - * - * STEP SEVEN - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View - */ - 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', 'csv-delimiter']; - if (!$this->wizard->sessionHasValues($fields)) { - Session::flash('warning', strval(trans('firefly.could_not_recover'))); - - return redirect(route('csv.index')); - } - - /** @var Importer $importer */ - $importer = app(Importer::class); - $importer->setData($this->data); - $importer->run(); - - $rows = $importer->getRows(); - $errors = $importer->getErrors(); - $imported = $importer->getImported(); - $journals = $importer->getJournals(); - - Preferences::mark(); - - $subTitle = trans('firefly.csv_process_title'); - - return view('csv.process', compact('rows', 'errors', 'imported', 'subTitle', 'journals')); - - } - - /** - * Store the mapping the user has made. This is - * - * STEP SIX - * - * - * @return \Illuminate\Http\RedirectResponse - */ - 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', 'csv-delimiter']; - if (!$this->wizard->sessionHasValues($fields)) { - Session::flash('warning', strval(trans('firefly.could_not_recover'))); - - return redirect(route('csv.index')); - } - - // save mapping to session. - $mapped = []; - if (!is_array(Input::get('mapping'))) { - Session::flash('warning', strval(trans('firefly.invalid_mapping'))); - - return redirect(route('csv.map')); - } - - foreach (Input::get('mapping') as $index => $data) { - $mapped[$index] = []; - foreach ($data as $value => $mapping) { - if (intval($mapping) !== 0) { - $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', strval(trans('firefly.no_file_uploaded'))); - - return redirect(route('csv.index')); - } - - $path = $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'] = is_array(Input::get('specifix')) ? Input::get('specifix') : []; - $settings['import-account'] = intval(Input::get('csv_import_account')); - $settings['delimiter'] = Input::get('csv_delimiter', ','); - - // A tab character cannot be used itself as option value in HTML - // See http://stackoverflow.com/questions/6064135/valid-characters-in-option-value - if ($settings['delimiter'] == 'tab') { - $settings['delimiter'] = "\t"; - } - - $settings['map'] = []; - $settings['mapped'] = []; - $settings['roles'] = []; - - if ($request->hasFile('csv_config')) { // Process config file if present. - - $size = $request->file('csv_config')->getSize(); - $data = $request->file('csv_config')->openFile()->fread($size); - $json = json_decode($data, true); - if (is_array($json)) { - $settings = array_merge($settings, $json); - } - } - - $this->data->setCsvFileLocation($path); - $this->data->setDateFormat($settings['date-format']); - $this->data->setHasHeaders($settings['has-headers']); - $this->data->setMap($settings['map']); - $this->data->setMapped($settings['mapped']); - $this->data->setRoles($settings['roles']); - $this->data->setSpecifix($settings['specifix']); - $this->data->setImportAccount($settings['import-account']); - $this->data->setDelimiter($settings['delimiter']); - - return redirect(route('csv.column-roles')); - - } -} diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index cc42ebbe64..f1925bc906 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -269,44 +269,6 @@ Breadcrumbs::register( } ); -/** - * CSV CONTROLLER - */ -Breadcrumbs::register( - 'csv.index', function (BreadCrumbGenerator $breadcrumbs) { - $breadcrumbs->parent('home'); - $breadcrumbs->push(trans('firefly.csv_index_title'), route('csv.index')); -} -); - -Breadcrumbs::register( - 'csv.column-roles', function (BreadCrumbGenerator $breadcrumbs) { - $breadcrumbs->parent('csv.index'); - $breadcrumbs->push(trans('firefly.csv_define_column_roles'), route('csv.column-roles')); -} -); - -Breadcrumbs::register( - 'csv.map', function (BreadCrumbGenerator $breadcrumbs) { - $breadcrumbs->parent('csv.index'); - $breadcrumbs->push(trans('firefly.csv_map_values'), route('csv.map')); -} -); - -Breadcrumbs::register( - 'csv.download-config-page', function (BreadCrumbGenerator $breadcrumbs) { - $breadcrumbs->parent('csv.index'); - $breadcrumbs->push(trans('firefly.csv_download_config'), route('csv.download-config-page')); -} -); - -Breadcrumbs::register( - 'csv.process', function (BreadCrumbGenerator $breadcrumbs) { - $breadcrumbs->parent('csv.index'); - $breadcrumbs->push(trans('firefly.csv_process_title'), route('csv.process')); -} -); - /** * CURRENCIES */ diff --git a/app/Http/routes.php b/app/Http/routes.php index 977c3fb18c..f51ea854a8 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -157,21 +157,6 @@ 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 */ diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index 47ad19c301..6ea3d33f3e 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -87,7 +87,6 @@ class FireflyServiceProvider extends ServiceProvider $this->app->bind('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface', 'FireflyIII\Repositories\Currency\CurrencyRepository'); $this->app->bind('FireflyIII\Support\Search\SearchInterface', 'FireflyIII\Support\Search\Search'); $this->app->bind('FireflyIII\Repositories\User\UserRepositoryInterface', 'FireflyIII\Repositories\User\UserRepository'); - $this->app->bind('FireflyIII\Helpers\Csv\WizardInterface', 'FireflyIII\Helpers\Csv\Wizard'); $this->app->bind('FireflyIII\Helpers\Attachments\AttachmentHelperInterface', 'FireflyIII\Helpers\Attachments\AttachmentHelper'); $this->app->bind( 'FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface', 'FireflyIII\Generator\Chart\Account\ChartJsAccountChartGenerator' diff --git a/config/firefly.php b/config/firefly.php index 4a045e1f6a..c3e5455c20 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -5,17 +5,23 @@ declare(strict_types = 1); return [ 'chart' => 'chartjs', 'version' => '3.9.0', - 'csv_import_enabled' => true, 'maxUploadSize' => 5242880, 'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'], 'resend_confirmation' => 3600, 'confirmation_age' => 14400, // four hours - 'export_formats' => [ + 'export_formats' => [ 'csv' => 'FireflyIII\Export\Exporter\CsvExporter', // mt940 FireflyIII Export Exporter MtExporter ], + 'import_formats' => [ + 'csv' => 'FireflyIII\Import\Importer\CsvImporter', + // mt940 FireflyIII Import Importer MtImporter + ], + + 'default_export_format' => 'csv', + 'default_import_format' => 'csv', 'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], 'accountRoles' => [ @@ -103,7 +109,7 @@ return [ ], - 'bindables' => [ + 'bindables' => [ // models 'account' => 'FireflyIII\Models\Account', 'attachment' => 'FireflyIII\Models\Attachment', diff --git a/resources/views/csv/column-roles.twig b/resources/views/csv/column-roles.twig deleted file mode 100644 index f25a0a2ea7..0000000000 --- a/resources/views/csv/column-roles.twig +++ /dev/null @@ -1,77 +0,0 @@ -{% extends "./layout/default.twig" %} - -{% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} -{% endblock %} - -{% block content %} - - -
-
-
-
-

{{ 'csv_column_roles_title'|_ }}

-
-
-

{{ 'csv_column_roles_text'|_ }}

-
-
- -
-
-
- - -
-
-
-
-

{{ 'csv_column_roles_table'|_ }}

-
-
- - - - - - - - - - - {% for index,header in headers %} - - - - - - - - {% endfor %} -
{{ 'csv_column_name'|_ }}{{ 'csv_column_example'|_ }}{{ 'csv_column_role'|_ }}{{ 'csv_do_map_value'|_ }}
{{ header }}{{ example[index] }} - {{ Form.select(('role['~index~']'), availableRoles,roles[index],{class: 'form-control'}) }} - - {{ Form.checkbox(('map['~index~']'),1,map[index]) }} -
- - -
-
-
-
- -
-
-
-
- {{ 'csv_go_back'|_ }} - -
-
-
-
-
-{% endblock %} diff --git a/resources/views/csv/download-config.twig b/resources/views/csv/download-config.twig deleted file mode 100644 index bd317bea7c..0000000000 --- a/resources/views/csv/download-config.twig +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "./layout/default.twig" %} - -{% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} -{% endblock %} - -{% block content %} - - -
-
-
-
-

{{ 'csv_download_config_title'|_ }}

-
-
-

- {{ 'csv_download_config_text'|_ }} -

- -

- {{ 'csv_do_download_config'|_ }} -

- -

- {{ 'csv_more_information_text'|_ }} -

-
-
-
-
- - - -{% endblock %} diff --git a/resources/views/csv/index.twig b/resources/views/csv/index.twig deleted file mode 100644 index cfa3f6450a..0000000000 --- a/resources/views/csv/index.twig +++ /dev/null @@ -1,96 +0,0 @@ -{% extends "./layout/default.twig" %} - -{% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} -{% endblock %} - -{% block content %} - - -
-
-
-
-

{{ 'csv_index_title'|_ }}

-
-
- {{ 'csv_index_text'|_ }} -

{{ 'csv_index_beta_warning'|_ }}

- {% if unsupported|length > 0 %} -

{{ 'csv_index_unsupported_warning'|_ }}

-
    - {% for message in unsupported %} -
  • {{ message }}
  • - {% endfor %} -
- {% endif %} -
-
- -
-
- -
- - -
-
-
-
-

{{ 'csv_upload_form'|_ }}

-
-
- - - {{ ExpandedForm.checkbox('has_headers',1,null,{helpText: 'csv_header_help'|_}) }} - {{ ExpandedForm.text('date_format','Ymd',{helpText: trans('firefly.csv_date_help', {dateExample: phpdate('Ymd')}) }) }} - - {{ ExpandedForm.file('csv',{helpText: 'csv_csv_file_help'|_}) }} - - {{ ExpandedForm.select('csv_delimiter', delimiters, 0, {helpText: 'csv_delimiter_help'|_} ) }} - - {{ 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 %} -
-
-   -
- -
-
{{ path }}
-

- {{ 'csv_upload_not_writeable'|_ }} -

-
-
- {% endif %} - -
-
-
-
- -
-
-
-
- -
-
-
-
-
- - - - -{% endblock %} diff --git a/resources/views/csv/map.twig b/resources/views/csv/map.twig deleted file mode 100644 index f8562205bd..0000000000 --- a/resources/views/csv/map.twig +++ /dev/null @@ -1,82 +0,0 @@ -{% extends "./layout/default.twig" %} - -{% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} -{% endblock %} - -{% block content %} - - -
-
-
-
-

{{ 'csv_map_title'|_ }}

-
-
-

- {{ 'csv_map_text'|_ }} -

-
-
- -
-
-
- - - {% for index,columnName in map %} - -
-
-
-
-

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

-
-
- - - - - - - - - {% for value in values[index] %} - - - - - {% endfor %} - - - -
{{ 'csv_field_value'|_ }}{{ 'csv_field_mapped_to'|_ }}
{{ value }} - {{ Form.select('mapping['~index~']['~value~']',options[index], mapped[index][value], {class: 'form-control'}) }} -
- - -
-
-
-
- {% endfor %} - - -
-
-
-
- {{ 'csv_go_back'|_ }} - -
-
-
-
- -
- - -{% endblock %} diff --git a/resources/views/csv/process.twig b/resources/views/csv/process.twig deleted file mode 100644 index 8ca39b64c9..0000000000 --- a/resources/views/csv/process.twig +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "./layout/default.twig" %} - -{% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} -{% endblock %} - -{% block content %} - - -
-
-
-
-

{{ 'csv_process_title'|_ }}

-
-
-

- - {{ trans('firefly.csv_process_text',{rows: rows}) }} -

- - {% if errors|length > 0 %} -

{{ Lang.choice('firefly.csv_import_with_errors',errors|length,{errors: errors|length}) }}

-
    - {% for index,err in errors %} -
  • {{ 'csv_row'|_ }} #{{ index }}: {{ err }}
  • - {% endfor %} -
-

- {{ trans('firefly.csv_error_see_logs') }} -

- {% endif %} - -

- - {{ trans('firefly.csv_process_new_entries',{imported: imported}) }} -

- - {% if journals|length > 0 %} - - {% endif %} - -

- {{ 'csv_start_over'|_ }} - {{ 'csv_to_index'|_ }} - {{ 'csv_do_download_config'|_ }} - -

-
-
- -
-
-{% endblock %} diff --git a/resources/views/partials/menu-sidebar.twig b/resources/views/partials/menu-sidebar.twig index 0f82830fc7..47dfbba00b 100644 --- a/resources/views/partials/menu-sidebar.twig +++ b/resources/views/partials/menu-sidebar.twig @@ -110,25 +110,15 @@ -
  • +
  • - {% if Config.get('firefly.csv_import_enabled') %} - {{ 'import_and_export'|_ }} - {% else %} - {{ 'export_data'|_ }} - {% endif %} + {{ 'import_and_export'|_ }}
      - {% if Config.get('firefly.csv_import_enabled') %} -
    • - {{ 'csv_import'|_ }} -
    • - {% endif %} -
    • {{ 'export_data'|_ }}
    • From a447c886c4472e532b9af5865c772e557a36adc1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 May 2016 21:11:30 +0200 Subject: [PATCH 02/12] Basic for for #262 Signed-off-by: James Cole --- app/Http/Controllers/ImportController.php | 41 ++++++++++++++++++ app/Http/routes.php | 5 +++ app/Support/Twig/General.php | 3 +- resources/views/import/index.twig | 52 +++++++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 app/Http/Controllers/ImportController.php create mode 100644 resources/views/import/index.twig diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php new file mode 100644 index 0000000000..0319bdb87f --- /dev/null +++ b/app/Http/Controllers/ImportController.php @@ -0,0 +1,41 @@ + 'Chart\ReportController@yearInOutSummarized']); Route::get('/chart/report/net-worth/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\ReportController@netWorth']); + /** + * IMPORT CONTROLLER + */ + Route::get('/import', ['uses' => 'ImportController@index','as' => 'import.index']); + Route::post('/import/upload', ['uses' => 'ImportController@upload','as' => 'import.upload']); /** * Help Controller diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 80315ff5d1..d937f51863 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -87,7 +87,8 @@ class General extends Twig_Extension 'activeRoutePartial', function () : string { $args = func_get_args(); $route = $args[0]; // name of the route. - if (!(strpos(Route::getCurrentRoute()->getName(), $route) === false)) { + $name = Route::getCurrentRoute()->getName() ?? ''; + if (!(strpos($name, $route) === false)) { return 'active'; } diff --git a/resources/views/import/index.twig b/resources/views/import/index.twig new file mode 100644 index 0000000000..16ea17886e --- /dev/null +++ b/resources/views/import/index.twig @@ -0,0 +1,52 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists }} +{% endblock %} +{% block content %} +
      +
      + +
      +
      +

      {{ 'import'|_ }}

      +
      +
      +

      + {{ 'import_intro_text'|_ }} +

      +

      +   +

      +
      +
      + + + +
      + {{ ExpandedForm.file('import_file',{helpText: 'import_file_help'|_}) }} + + {{ ExpandedForm.select('import_file_type', importFileTypes, defaultImportType, {'helpText' : 'import_file_type_help'|_}) }} + +
      + + +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      +{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} From ada43bc0ddef1a4eec74bb88b57d5b94c076a933 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 24 May 2016 11:28:24 +0200 Subject: [PATCH 03/12] Fix #265 --- app/Http/Controllers/PiggyBankController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php index 898adc8fb7..a3ec4d3d52 100644 --- a/app/Http/Controllers/PiggyBankController.php +++ b/app/Http/Controllers/PiggyBankController.php @@ -270,7 +270,7 @@ class PiggyBankController extends Controller */ public function postRemove(PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) { - $amount = round(Input::get('amount'), 2); + $amount = strval(round(Input::get('amount'), 2)); $savedSoFar = $piggyBank->currentRelevantRep()->currentamount; From 2e2619333397b156f98eb2373082190f91b20739 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 24 May 2016 16:08:43 +0200 Subject: [PATCH 04/12] This should fix #266 --- app/Http/Controllers/CategoryController.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 1169383eca..b826da6030 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -13,7 +13,9 @@ namespace FireflyIII\Http\Controllers; use Auth; use Carbon\Carbon; +use FireflyIII\Crud\Account\AccountCrudInterface; use FireflyIII\Http\Requests\CategoryFormRequest; +use FireflyIII\Models\AccountType; use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; use FireflyIII\Support\CacheProperties; @@ -156,12 +158,13 @@ class CategoryController extends Controller } /** - * @param CRI $repository - * @param Category $category + * @param CRI $repository + * @param AccountCrudInterface $crud + * @param Category $category * - * @return \Illuminate\View\View + * @return View */ - public function show(CRI $repository, Category $category) + public function show(CRI $repository, AccountCrudInterface $crud, Category $category) { /** @var Carbon $carbon */ $range = Preferences::get('viewRange', '1M')->data; @@ -205,12 +208,12 @@ class CategoryController extends Controller $categoryCollection = new Collection([$category]); - $empty = new Collection; + $accounts = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); while ($end >= $start) { $end = Navigation::startOfPeriod($end, $range); $currentEnd = Navigation::endOfPeriod($end, $range); - $spent = $repository->spentInPeriod($categoryCollection, $empty, $end, $currentEnd); - $earned = $repository->earnedInPeriod($categoryCollection, $empty, $end, $currentEnd); + $spent = $repository->spentInPeriod($categoryCollection, $accounts, $end, $currentEnd); + $earned = $repository->earnedInPeriod($categoryCollection, $accounts, $end, $currentEnd); $dateStr = $end->format('Y-m-d'); $dateName = Navigation::periodShow($end, $range); $entries->push([$dateStr, $dateName, $spent, $earned]); From 8da4abf655a8464ddafe58b0f67797ff894c4f82 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 6 Jun 2016 09:22:20 +0200 Subject: [PATCH 05/12] Restore helpers (#262) --- app/Helpers/Csv/Converter/AccountId.php | 35 ++ app/Helpers/Csv/Converter/Amount.php | 32 ++ app/Helpers/Csv/Converter/AmountComma.php | 36 ++ .../Csv/Converter/AssetAccountIban.php | 89 ++++ .../Csv/Converter/AssetAccountName.php | 65 +++ .../Csv/Converter/AssetAccountNumber.php | 77 ++++ app/Helpers/Csv/Converter/BasicConverter.php | 114 ++++++ app/Helpers/Csv/Converter/BillId.php | 36 ++ app/Helpers/Csv/Converter/BillName.php | 47 +++ app/Helpers/Csv/Converter/BudgetId.php | 36 ++ app/Helpers/Csv/Converter/BudgetName.php | 44 ++ app/Helpers/Csv/Converter/CategoryId.php | 36 ++ app/Helpers/Csv/Converter/CategoryName.php | 49 +++ .../Csv/Converter/ConverterInterface.php | 52 +++ app/Helpers/Csv/Converter/CurrencyCode.php | 43 ++ app/Helpers/Csv/Converter/CurrencyId.php | 36 ++ app/Helpers/Csv/Converter/CurrencyName.php | 42 ++ app/Helpers/Csv/Converter/CurrencySymbol.php | 43 ++ app/Helpers/Csv/Converter/Date.php | 46 +++ app/Helpers/Csv/Converter/Description.php | 31 ++ app/Helpers/Csv/Converter/INGDebetCredit.php | 34 ++ app/Helpers/Csv/Converter/Ignore.php | 28 ++ .../Csv/Converter/OpposingAccountIban.php | 64 +++ .../Csv/Converter/OpposingAccountId.php | 35 ++ .../Csv/Converter/OpposingAccountName.php | 41 ++ .../Csv/Converter/RabobankDebetCredit.php | 34 ++ app/Helpers/Csv/Converter/TagsComma.php | 53 +++ app/Helpers/Csv/Converter/TagsSpace.php | 54 +++ app/Helpers/Csv/Data.php | 336 +++++++++++++++ app/Helpers/Csv/Importer.php | 384 ++++++++++++++++++ app/Helpers/Csv/Mapper/AnyAccount.php | 42 ++ app/Helpers/Csv/Mapper/AssetAccount.php | 54 +++ app/Helpers/Csv/Mapper/Bill.php | 42 ++ app/Helpers/Csv/Mapper/Budget.php | 42 ++ app/Helpers/Csv/Mapper/Category.php | 42 ++ app/Helpers/Csv/Mapper/MapperInterface.php | 24 ++ app/Helpers/Csv/Mapper/Tag.php | 42 ++ .../Csv/Mapper/TransactionCurrency.php | 40 ++ app/Helpers/Csv/PostProcessing/Amount.php | 44 ++ .../Csv/PostProcessing/AssetAccount.php | 274 +++++++++++++ app/Helpers/Csv/PostProcessing/Bill.php | 45 ++ app/Helpers/Csv/PostProcessing/Currency.php | 49 +++ .../Csv/PostProcessing/Description.php | 47 +++ .../Csv/PostProcessing/OpposingAccount.php | 210 ++++++++++ .../PostProcessing/PostProcessorInterface.php | 31 ++ .../Csv/Specifix/AbnAmroDescription.php | 226 +++++++++++ app/Helpers/Csv/Specifix/Dummy.php | 60 +++ .../Csv/Specifix/RabobankDescription.php | 76 ++++ app/Helpers/Csv/Specifix/Specifix.php | 46 +++ .../Csv/Specifix/SpecifixInterface.php | 49 +++ app/Helpers/Csv/Wizard.php | 202 +++++++++ app/Helpers/Csv/WizardInterface.php | 68 ++++ 52 files changed, 3807 insertions(+) create mode 100644 app/Helpers/Csv/Converter/AccountId.php create mode 100644 app/Helpers/Csv/Converter/Amount.php create mode 100644 app/Helpers/Csv/Converter/AmountComma.php create mode 100644 app/Helpers/Csv/Converter/AssetAccountIban.php create mode 100644 app/Helpers/Csv/Converter/AssetAccountName.php create mode 100644 app/Helpers/Csv/Converter/AssetAccountNumber.php create mode 100644 app/Helpers/Csv/Converter/BasicConverter.php create mode 100644 app/Helpers/Csv/Converter/BillId.php create mode 100644 app/Helpers/Csv/Converter/BillName.php create mode 100644 app/Helpers/Csv/Converter/BudgetId.php create mode 100644 app/Helpers/Csv/Converter/BudgetName.php create mode 100644 app/Helpers/Csv/Converter/CategoryId.php create mode 100644 app/Helpers/Csv/Converter/CategoryName.php create mode 100644 app/Helpers/Csv/Converter/ConverterInterface.php create mode 100644 app/Helpers/Csv/Converter/CurrencyCode.php create mode 100644 app/Helpers/Csv/Converter/CurrencyId.php create mode 100644 app/Helpers/Csv/Converter/CurrencyName.php create mode 100644 app/Helpers/Csv/Converter/CurrencySymbol.php create mode 100644 app/Helpers/Csv/Converter/Date.php create mode 100644 app/Helpers/Csv/Converter/Description.php create mode 100644 app/Helpers/Csv/Converter/INGDebetCredit.php create mode 100644 app/Helpers/Csv/Converter/Ignore.php create mode 100644 app/Helpers/Csv/Converter/OpposingAccountIban.php create mode 100644 app/Helpers/Csv/Converter/OpposingAccountId.php create mode 100644 app/Helpers/Csv/Converter/OpposingAccountName.php create mode 100644 app/Helpers/Csv/Converter/RabobankDebetCredit.php create mode 100644 app/Helpers/Csv/Converter/TagsComma.php create mode 100644 app/Helpers/Csv/Converter/TagsSpace.php create mode 100644 app/Helpers/Csv/Data.php create mode 100644 app/Helpers/Csv/Importer.php create mode 100644 app/Helpers/Csv/Mapper/AnyAccount.php create mode 100644 app/Helpers/Csv/Mapper/AssetAccount.php create mode 100644 app/Helpers/Csv/Mapper/Bill.php create mode 100644 app/Helpers/Csv/Mapper/Budget.php create mode 100644 app/Helpers/Csv/Mapper/Category.php create mode 100644 app/Helpers/Csv/Mapper/MapperInterface.php create mode 100644 app/Helpers/Csv/Mapper/Tag.php create mode 100644 app/Helpers/Csv/Mapper/TransactionCurrency.php create mode 100644 app/Helpers/Csv/PostProcessing/Amount.php create mode 100644 app/Helpers/Csv/PostProcessing/AssetAccount.php create mode 100644 app/Helpers/Csv/PostProcessing/Bill.php create mode 100644 app/Helpers/Csv/PostProcessing/Currency.php create mode 100644 app/Helpers/Csv/PostProcessing/Description.php create mode 100644 app/Helpers/Csv/PostProcessing/OpposingAccount.php create mode 100644 app/Helpers/Csv/PostProcessing/PostProcessorInterface.php create mode 100644 app/Helpers/Csv/Specifix/AbnAmroDescription.php create mode 100644 app/Helpers/Csv/Specifix/Dummy.php create mode 100644 app/Helpers/Csv/Specifix/RabobankDescription.php create mode 100644 app/Helpers/Csv/Specifix/Specifix.php create mode 100644 app/Helpers/Csv/Specifix/SpecifixInterface.php create mode 100644 app/Helpers/Csv/Wizard.php create mode 100644 app/Helpers/Csv/WizardInterface.php diff --git a/app/Helpers/Csv/Converter/AccountId.php b/app/Helpers/Csv/Converter/AccountId.php new file mode 100644 index 0000000000..908cd132de --- /dev/null +++ b/app/Helpers/Csv/Converter/AccountId.php @@ -0,0 +1,35 @@ +mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; + $account = $crud->find($var); + + return $account; + } +} diff --git a/app/Helpers/Csv/Converter/Amount.php b/app/Helpers/Csv/Converter/Amount.php new file mode 100644 index 0000000000..8e2cfc4d0e --- /dev/null +++ b/app/Helpers/Csv/Converter/Amount.php @@ -0,0 +1,32 @@ +value)) { + return strval($this->value); + } + + return '0'; + } +} diff --git a/app/Helpers/Csv/Converter/AmountComma.php b/app/Helpers/Csv/Converter/AmountComma.php new file mode 100644 index 0000000000..8cddad6dc8 --- /dev/null +++ b/app/Helpers/Csv/Converter/AmountComma.php @@ -0,0 +1,36 @@ +value)); + + if (is_numeric($value)) { + return strval($value); + } + + return '0'; + } +} diff --git a/app/Helpers/Csv/Converter/AssetAccountIban.php b/app/Helpers/Csv/Converter/AssetAccountIban.php new file mode 100644 index 0000000000..4da3a73625 --- /dev/null +++ b/app/Helpers/Csv/Converter/AssetAccountIban.php @@ -0,0 +1,89 @@ +mapped[$this->index][$this->value])) { + $account = $crud->find(intval($this->mapped[$this->index][$this->value])); + + return $account; + } + + + if (strlen($this->value) > 0) { + $account = $this->searchOrCreate($crud); + + return $account; + } + + return new Account; + } + + /** + * @param AccountCrudInterface $crud + * + * @return Account + */ + private function searchOrCreate(AccountCrudInterface $crud) + { + // find or create new account: + $set = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + /** @var Account $entry */ + foreach ($set as $entry) { + if ($entry->iban == $this->value) { + + return $entry; + } + } + + + // create it if doesn't exist. + $accountData = [ + 'name' => $this->value, + 'accountType' => 'asset', + 'virtualBalance' => 0, + 'virtualBalanceCurrency' => 1, // hard coded. + 'active' => true, + 'user' => Auth::user()->id, + 'iban' => $this->value, + 'accountNumber' => $this->value, + 'accountRole' => null, + 'openingBalance' => 0, + 'openingBalanceDate' => new Carbon, + 'openingBalanceCurrency' => 1, // hard coded. + ]; + + $account = $crud->store($accountData); + + return $account; + } +} diff --git a/app/Helpers/Csv/Converter/AssetAccountName.php b/app/Helpers/Csv/Converter/AssetAccountName.php new file mode 100644 index 0000000000..e56620415e --- /dev/null +++ b/app/Helpers/Csv/Converter/AssetAccountName.php @@ -0,0 +1,65 @@ +mapped[$this->index][$this->value])) { + $account = $crud->find(intval($this->mapped[$this->index][$this->value])); + + return $account; + } + + $set = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + /** @var Account $entry */ + foreach ($set as $entry) { + if ($entry->name == $this->value) { + return $entry; + } + } + $accountData = [ + 'name' => $this->value, + 'accountType' => 'asset', + 'virtualBalance' => 0, + 'virtualBalanceCurrency' => 1, // hard coded. + 'active' => true, + 'user' => Auth::user()->id, + 'iban' => null, + 'accountNumber' => $this->value, + 'accountRole' => null, + 'openingBalance' => 0, + 'openingBalanceDate' => new Carbon, + 'openingBalanceCurrency' => 1, // hard coded. + ]; + + $account = $crud->store($accountData); + + return $account; + } +} diff --git a/app/Helpers/Csv/Converter/AssetAccountNumber.php b/app/Helpers/Csv/Converter/AssetAccountNumber.php new file mode 100644 index 0000000000..5b8efed50a --- /dev/null +++ b/app/Helpers/Csv/Converter/AssetAccountNumber.php @@ -0,0 +1,77 @@ +mapped[$this->index][$this->value])) { + $account = $crud->find(intval($this->mapped[$this->index][$this->value])); + + return $account; + } + // if not, search for it (or create it): + $value = $this->value ?? ''; + if (strlen($value) > 0) { + // find or create new account: + $set = $crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + /** @var Account $entry */ + foreach ($set as $entry) { + $accountNumber = $entry->getMeta('accountNumber'); + if ($accountNumber == $this->value) { + + return $entry; + } + } + + $accountData = [ + 'name' => $this->value, + 'accountType' => 'asset', + 'virtualBalance' => 0, + 'virtualBalanceCurrency' => 1, // hard coded. + 'active' => true, + 'user' => Auth::user()->id, + 'iban' => null, + 'accountNumber' => $this->value, + 'accountRole' => null, + 'openingBalance' => 0, + 'openingBalanceDate' => new Carbon, + 'openingBalanceCurrency' => 1, // hard coded. + + ]; + + $account = $crud->store($accountData); + + return $account; + } + + return null; // is this accepted? + } + +} diff --git a/app/Helpers/Csv/Converter/BasicConverter.php b/app/Helpers/Csv/Converter/BasicConverter.php new file mode 100644 index 0000000000..63e5f8bb96 --- /dev/null +++ b/app/Helpers/Csv/Converter/BasicConverter.php @@ -0,0 +1,114 @@ +data; + } + + /** + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } + + /** + * @return string + */ + public function getField(): string + { + return $this->field; + } + + /** + * @param string $field + */ + public function setField(string $field) + { + $this->field = $field; + } + + /** + * @return int + */ + public function getIndex(): int + { + return $this->index; + } + + /** + * @param int $index + */ + public function setIndex(int $index) + { + $this->index = $index; + } + + /** + * @return array + */ + public function getMapped(): array + { + return $this->mapped; + } + + /** + * @param array $mapped + */ + public function setMapped(array $mapped) + { + $this->mapped = $mapped; + } + + /** + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * @param string $value + */ + public function setValue(string $value) + { + $this->value = $value; + } + + +} diff --git a/app/Helpers/Csv/Converter/BillId.php b/app/Helpers/Csv/Converter/BillId.php new file mode 100644 index 0000000000..3e9102ccec --- /dev/null +++ b/app/Helpers/Csv/Converter/BillId.php @@ -0,0 +1,36 @@ +mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; + $bill = $repository->find($value); + + return $bill; + } +} diff --git a/app/Helpers/Csv/Converter/BillName.php b/app/Helpers/Csv/Converter/BillName.php new file mode 100644 index 0000000000..a1328ef6c1 --- /dev/null +++ b/app/Helpers/Csv/Converter/BillName.php @@ -0,0 +1,47 @@ +mapped[$this->index][$this->value])) { + return $repository->find($this->mapped[$this->index][$this->value]); + } + $bills = $repository->getBills(); + + /** @var Bill $bill */ + foreach ($bills as $bill) { + if ($bill->name == $this->value) { + return $bill; + } + } + + return new Bill; + } +} diff --git a/app/Helpers/Csv/Converter/BudgetId.php b/app/Helpers/Csv/Converter/BudgetId.php new file mode 100644 index 0000000000..d9302d7734 --- /dev/null +++ b/app/Helpers/Csv/Converter/BudgetId.php @@ -0,0 +1,36 @@ +mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; + $budget = $repository->find($value); + + return $budget; + } +} diff --git a/app/Helpers/Csv/Converter/BudgetName.php b/app/Helpers/Csv/Converter/BudgetName.php new file mode 100644 index 0000000000..84f777775b --- /dev/null +++ b/app/Helpers/Csv/Converter/BudgetName.php @@ -0,0 +1,44 @@ +mapped[$this->index][$this->value])) { + $budget = $repository->find($this->mapped[$this->index][$this->value]); + + return $budget; + } + $budget = $repository->store(['name' => $this->value, 'user' => Auth::user()->id]); + + + return $budget; + } +} diff --git a/app/Helpers/Csv/Converter/CategoryId.php b/app/Helpers/Csv/Converter/CategoryId.php new file mode 100644 index 0000000000..8ffedd9ae1 --- /dev/null +++ b/app/Helpers/Csv/Converter/CategoryId.php @@ -0,0 +1,36 @@ +mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; + $category = $repository->find($value); + + return $category; + } +} diff --git a/app/Helpers/Csv/Converter/CategoryName.php b/app/Helpers/Csv/Converter/CategoryName.php new file mode 100644 index 0000000000..d322f7b02f --- /dev/null +++ b/app/Helpers/Csv/Converter/CategoryName.php @@ -0,0 +1,49 @@ +mapped[$this->index][$this->value])) { + $category = $repository->find($this->mapped[$this->index][$this->value]); + + return $category; + } + + $data = [ + 'name' => $this->value, + 'user' => Auth::user()->id, + ]; + + $category = $repository->store($data); + + return $category; + } +} diff --git a/app/Helpers/Csv/Converter/ConverterInterface.php b/app/Helpers/Csv/Converter/ConverterInterface.php new file mode 100644 index 0000000000..c8fb526b83 --- /dev/null +++ b/app/Helpers/Csv/Converter/ConverterInterface.php @@ -0,0 +1,52 @@ +mapped[$this->index][$this->value])) { + $currency = $repository->find(intval($this->mapped[$this->index][$this->value])); + + return $currency; + } + + $currency = $repository->findByCode($this->value); + + + return $currency; + } +} diff --git a/app/Helpers/Csv/Converter/CurrencyId.php b/app/Helpers/Csv/Converter/CurrencyId.php new file mode 100644 index 0000000000..a18082872a --- /dev/null +++ b/app/Helpers/Csv/Converter/CurrencyId.php @@ -0,0 +1,36 @@ +mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; + $currency = $repository->find($value); + + return $currency; + } +} diff --git a/app/Helpers/Csv/Converter/CurrencyName.php b/app/Helpers/Csv/Converter/CurrencyName.php new file mode 100644 index 0000000000..62876fa9ba --- /dev/null +++ b/app/Helpers/Csv/Converter/CurrencyName.php @@ -0,0 +1,42 @@ +mapped[$this->index][$this->value])) { + $currency = $repository->find($this->mapped[$this->index][$this->value]); + + return $currency; + } + $currency = $repository->findByName($this->value); + + + return $currency; + } +} diff --git a/app/Helpers/Csv/Converter/CurrencySymbol.php b/app/Helpers/Csv/Converter/CurrencySymbol.php new file mode 100644 index 0000000000..51037ece2e --- /dev/null +++ b/app/Helpers/Csv/Converter/CurrencySymbol.php @@ -0,0 +1,43 @@ +mapped[$this->index][$this->value])) { + $currency = $repository->find($this->mapped[$this->index][$this->value]); + + return $currency; + } + + $currency = $repository->findBySymbol($this->value); + + + return $currency; + } +} diff --git a/app/Helpers/Csv/Converter/Date.php b/app/Helpers/Csv/Converter/Date.php new file mode 100644 index 0000000000..b164e9df5f --- /dev/null +++ b/app/Helpers/Csv/Converter/Date.php @@ -0,0 +1,46 @@ +value); + } catch (InvalidArgumentException $e) { + Log::error('Date conversion error: ' . $e->getMessage() . '. Value was "' . $this->value . '", format was "' . $format . '".'); + + $message = trans('firefly.csv_date_parse_error', ['format' => $format, 'value' => $this->value]); + + throw new FireflyException($message); + } + + + return $date; + } +} diff --git a/app/Helpers/Csv/Converter/Description.php b/app/Helpers/Csv/Converter/Description.php new file mode 100644 index 0000000000..ea864a3e3e --- /dev/null +++ b/app/Helpers/Csv/Converter/Description.php @@ -0,0 +1,31 @@ +data['description'] ?? ''; + + return trim($description . ' ' . $this->value); + } +} diff --git a/app/Helpers/Csv/Converter/INGDebetCredit.php b/app/Helpers/Csv/Converter/INGDebetCredit.php new file mode 100644 index 0000000000..59fd76248a --- /dev/null +++ b/app/Helpers/Csv/Converter/INGDebetCredit.php @@ -0,0 +1,34 @@ +value === 'Af') { + return -1; + } + + return 1; + } +} diff --git a/app/Helpers/Csv/Converter/Ignore.php b/app/Helpers/Csv/Converter/Ignore.php new file mode 100644 index 0000000000..c2da8c3033 --- /dev/null +++ b/app/Helpers/Csv/Converter/Ignore.php @@ -0,0 +1,28 @@ +mapped[$this->index][$this->value])) { + $account = $crud->find($this->mapped[$this->index][$this->value]); + + return $account; + } + + return $this->findAccount($crud); + } + + /** + * @param AccountCrudInterface $crud + * + * @return Account|string + */ + private function findAccount(AccountCrudInterface $crud) + { + if (strlen($this->value) > 0) { + + $set = $crud->getAccountsByType([]); + /** @var Account $account */ + foreach ($set as $account) { + if ($account->iban == $this->value) { + + return $account; + } + } + } + + return $this->value; + } + +} diff --git a/app/Helpers/Csv/Converter/OpposingAccountId.php b/app/Helpers/Csv/Converter/OpposingAccountId.php new file mode 100644 index 0000000000..17836907bf --- /dev/null +++ b/app/Helpers/Csv/Converter/OpposingAccountId.php @@ -0,0 +1,35 @@ +mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; + $account = $crud->find($value); + + return $account; + } +} diff --git a/app/Helpers/Csv/Converter/OpposingAccountName.php b/app/Helpers/Csv/Converter/OpposingAccountName.php new file mode 100644 index 0000000000..7c3d392985 --- /dev/null +++ b/app/Helpers/Csv/Converter/OpposingAccountName.php @@ -0,0 +1,41 @@ +mapped[$this->index][$this->value])) { + $account = $crud->find($this->mapped[$this->index][$this->value]); + + return $account; + } + + return $this->value; + + } +} diff --git a/app/Helpers/Csv/Converter/RabobankDebetCredit.php b/app/Helpers/Csv/Converter/RabobankDebetCredit.php new file mode 100644 index 0000000000..854f812d45 --- /dev/null +++ b/app/Helpers/Csv/Converter/RabobankDebetCredit.php @@ -0,0 +1,34 @@ +value == 'D') { + return -1; + } + + return 1; + } +} diff --git a/app/Helpers/Csv/Converter/TagsComma.php b/app/Helpers/Csv/Converter/TagsComma.php new file mode 100644 index 0000000000..c9477e7ada --- /dev/null +++ b/app/Helpers/Csv/Converter/TagsComma.php @@ -0,0 +1,53 @@ +value); + foreach ($strings as $string) { + $data = [ + 'tag' => $string, + 'date' => null, + 'description' => null, + 'latitude' => null, + 'longitude' => null, + 'zoomLevel' => null, + 'tagMode' => 'nothing', + ]; + if (strlen($string) > 0) { + $tag = $repository->store($data); // should validate first? + $tags->push($tag); + } + } + $tags = $tags->merge($this->data['tags']); + + return $tags; + } +} diff --git a/app/Helpers/Csv/Converter/TagsSpace.php b/app/Helpers/Csv/Converter/TagsSpace.php new file mode 100644 index 0000000000..aa2694a330 --- /dev/null +++ b/app/Helpers/Csv/Converter/TagsSpace.php @@ -0,0 +1,54 @@ +value); + foreach ($strings as $string) { + $data = [ + 'tag' => $string, + 'date' => null, + 'description' => null, + 'latitude' => null, + 'longitude' => null, + 'zoomLevel' => null, + 'tagMode' => 'nothing', + ]; + if (strlen($string) > 0) { + $tag = $repository->store($data); // should validate first? + $tags->push($tag); + } + } + $tags = $tags->merge($this->data['tags']); + + return $tags; + } +} diff --git a/app/Helpers/Csv/Data.php b/app/Helpers/Csv/Data.php new file mode 100644 index 0000000000..d56f790204 --- /dev/null +++ b/app/Helpers/Csv/Data.php @@ -0,0 +1,336 @@ +sessionHasHeaders(); + $this->sessionDateFormat(); + $this->sessionCsvFileLocation(); + $this->sessionMap(); + $this->sessionRoles(); + $this->sessionMapped(); + $this->sessionSpecifix(); + $this->sessionImportAccount(); + $this->sessionDelimiter(); + } + + /** + * + * @return string + */ + public function getCsvFileContent(): string + { + return $this->csvFileContent ?? ''; + } + + /** + * + * @param string $csvFileContent + */ + public function setCsvFileContent(string $csvFileContent) + { + $this->csvFileContent = $csvFileContent; + } + + /** + * FIXxME may return null + * + * @return string + */ + public function getCsvFileLocation(): string + { + return $this->csvFileLocation; + } + + /** + * + * @param string $csvFileLocation + */ + public function setCsvFileLocation(string $csvFileLocation) + { + Session::put('csv-file', $csvFileLocation); + $this->csvFileLocation = $csvFileLocation; + } + + /** + * FIXxME may return null + * + * @return string + */ + public function getDateFormat(): string + { + return $this->dateFormat; + } + + /** + * + * @param string $dateFormat + */ + public function setDateFormat(string $dateFormat) + { + Session::put('csv-date-format', $dateFormat); + $this->dateFormat = $dateFormat; + } + + /** + * FIXxME may return null + * + * @return string + */ + public function getDelimiter(): string + { + return $this->delimiter; + } + + /** + * + * @param string $delimiter + */ + public function setDelimiter(string $delimiter) + { + Session::put('csv-delimiter', $delimiter); + $this->delimiter = $delimiter; + } + + /** + * + * @return array + */ + public function getMap(): array + { + return $this->map; + } + + /** + * + * @param array $map + */ + public function setMap(array $map) + { + Session::put('csv-map', $map); + $this->map = $map; + } + + /** + * + * @return array + */ + public function getMapped(): array + { + return $this->mapped; + } + + /** + * + * @param array $mapped + */ + public function setMapped(array $mapped) + { + Session::put('csv-mapped', $mapped); + $this->mapped = $mapped; + } + + /** + * + * @return Reader + */ + public function getReader(): Reader + { + if (!is_null($this->csvFileContent) && strlen($this->csvFileContent) === 0) { + $this->loadCsvFile(); + } + + if (is_null($this->reader)) { + $this->reader = Reader::createFromString($this->getCsvFileContent()); + $this->reader->setDelimiter($this->delimiter); + } + + return $this->reader; + } + + /** + * + * @return array + */ + public function getRoles(): array + { + return $this->roles; + } + + /** + * + * @param array $roles + */ + public function setRoles(array $roles) + { + Session::put('csv-roles', $roles); + $this->roles = $roles; + } + + /** + * + * @return array + */ + public function getSpecifix(): array + { + return is_array($this->specifix) ? $this->specifix : []; + } + + /** + * + * @param array $specifix + */ + public function setSpecifix(array $specifix) + { + Session::put('csv-specifix', $specifix); + $this->specifix = $specifix; + } + + /** + * + * @return bool + */ + public function hasHeaders(): bool + { + return $this->hasHeaders; + } + + /** + * + * @param bool $hasHeaders + */ + public function setHasHeaders(bool $hasHeaders) + { + Session::put('csv-has-headers', $hasHeaders); + $this->hasHeaders = $hasHeaders; + } + + /** + * + * @param int $importAccount + */ + public function setImportAccount(int $importAccount) + { + Session::put('csv-import-account', $importAccount); + $this->importAccount = $importAccount; + } + + protected function loadCsvFile() + { + $file = $this->getCsvFileLocation(); + $disk = Storage::disk('upload'); + $content = $disk->get($file); + $contentDecrypted = Crypt::decrypt($content); + $this->setCsvFileContent($contentDecrypted); + } + + protected function sessionCsvFileLocation() + { + if (Session::has('csv-file')) { + $this->csvFileLocation = (string)session('csv-file'); + } + } + + protected function sessionDateFormat() + { + if (Session::has('csv-date-format')) { + $this->dateFormat = (string)session('csv-date-format'); + } + } + + protected function sessionDelimiter() + { + if (Session::has('csv-delimiter')) { + $this->delimiter = session('csv-delimiter'); + } + } + + protected function sessionHasHeaders() + { + if (Session::has('csv-has-headers')) { + $this->hasHeaders = (bool)session('csv-has-headers'); + } + } + + protected function sessionImportAccount() + { + if (Session::has('csv-import-account')) { + $this->importAccount = intval(session('csv-import-account')); + } + } + + protected function sessionMap() + { + if (Session::has('csv-map')) { + $this->map = (array)session('csv-map'); + } + } + + protected function sessionMapped() + { + if (Session::has('csv-mapped')) { + $this->mapped = (array)session('csv-mapped'); + } + } + + protected function sessionRoles() + { + if (Session::has('csv-roles')) { + $this->roles = (array)session('csv-roles'); + } + } + + protected function sessionSpecifix() + { + if (Session::has('csv-specifix')) { + $this->specifix = (array)session('csv-specifix'); + } + } +} diff --git a/app/Helpers/Csv/Importer.php b/app/Helpers/Csv/Importer.php new file mode 100644 index 0000000000..9e50f02a46 --- /dev/null +++ b/app/Helpers/Csv/Importer.php @@ -0,0 +1,384 @@ +errors; + } + + /** + * Used by CsvController + * + * @return int + */ + public function getImported(): int + { + return $this->imported; + } + + /** + * @return Collection + */ + public function getJournals(): Collection + { + return $this->journals; + } + + /** + * Used by CsvController + * + * @return int + */ + public function getRows(): int + { + return $this->rows; + } + + /** + * @return array + */ + public function getSpecifix(): array + { + return is_array($this->specifix) ? $this->specifix : []; + } + + /** + * @throws FireflyException + */ + public function run() + { + set_time_limit(0); + + $this->journals = new Collection; + $this->map = $this->data->getMap(); + $this->roles = $this->data->getRoles(); + $this->mapped = $this->data->getMapped(); + $this->specifix = $this->data->getSpecifix(); + + foreach ($this->data->getReader() as $index => $row) { + if ($this->parseRow($index)) { + $this->rows++; + $result = $this->importRow($row); + if (!($result instanceof TransactionJournal)) { + Log::error('Caught error at row #' . $index . ': ' . $result); + $this->errors[$index] = $result; + } else { + $this->imported++; + $this->journals->push($result); + event(new TransactionJournalStored($result, 0)); + } + } + } + } + + /** + * @param Data $data + */ + public function setData(Data $data) + { + $this->data = $data; + } + + /** + * @return TransactionJournal|string + */ + protected function createTransactionJournal() + { + $date = $this->importData['date']; + if (is_null($this->importData['date'])) { + $date = $this->importData['date-rent']; + } + + + $transactionType = $this->getTransactionType(); // defaults to deposit + $errors = new MessageBag; + $journal = TransactionJournal::create( + [ + 'user_id' => Auth::user()->id, + 'transaction_type_id' => $transactionType->id, + 'transaction_currency_id' => $this->importData['currency']->id, + 'description' => $this->importData['description'], + 'completed' => 0, + 'date' => $date, + 'bill_id' => $this->importData['bill-id'], + ] + ); + if ($journal->getErrors()->count() == 0) { + // 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]); + $errors = $transaction->getErrors()->merge($errors); + } + if ($errors->count() == 0) { + $journal->completed = 1; + $journal->save(); + } else { + $text = join(',', $errors->all()); + + return $text; + } + $this->saveBudget($journal); + $this->saveCategory($journal); + $this->saveTags($journal); + + // some debug info: + $journalId = $journal->id; + $type = $journal->transaction_type_type ?? $journal->transactionType->type; + /** @var Account $asset */ + $asset = $this->importData['asset-account-object']; + /** @var Account $opposing */ + $opposing = $this->importData['opposing-account-object']; + + Log::info('Created journal #' . $journalId . ' of type ' . $type . '!'); + Log::info('Asset account #' . $asset->id . ' lost/gained: ' . $this->importData['amount']); + Log::info($opposing->accountType->type . ' #' . $opposing->id . ' lost/gained: ' . bcmul($this->importData['amount'], '-1')); + + return $journal; + } + + /** + * @return TransactionType + */ + protected function getTransactionType() + { + $transactionType = TransactionType::where('type', TransactionType::DEPOSIT)->first(); + if ($this->importData['amount'] < 0) { + $transactionType = TransactionType::where('type', TransactionType::WITHDRAWAL)->first(); + } + + if (in_array($this->importData['opposing-account-object']->accountType->type, ['Asset account', 'Default account'])) { + $transactionType = TransactionType::where('type', TransactionType::TRANSFER)->first(); + } + + return $transactionType; + } + + /** + * @param array $row + * + * @throws FireflyException + * @return string|bool + */ + protected function importRow(array $row) + { + + $data = $this->getFiller(); // These fields are necessary to create a new transaction journal. Some are optional + foreach ($row as $index => $value) { + $role = $this->roles[$index] ?? '_ignore'; + $class = config('csv.roles.' . $role . '.converter'); + $field = config('csv.roles.' . $role . '.field'); + + + // here would be the place where preprocessors would fire. + + /** @var ConverterInterface $converter */ + $converter = app('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); + $data[$field] = $converter->convert(); + } + // move to class vars. + $this->importData = $data; + $this->importRow = $row; + unset($data, $row); + // post processing and validating. + $this->postProcess(); + $result = $this->validateData(); + + if (!($result === true)) { + return $result; // return error. + } + $journal = $this->createTransactionJournal(); + + return $journal; + } + + /** + * @param int $index + * + * @return bool + */ + protected function parseRow(int $index) + { + return (($this->data->hasHeaders() && $index >= 1) || !$this->data->hasHeaders()); + } + + /** + * Row denotes the original data. + * + * @return void + */ + protected function postProcess() + { + // do bank specific fixes (must be enabled but now all of them. + + foreach ($this->getSpecifix() as $className) { + /** @var SpecifixInterface $specifix */ + $specifix = app('FireflyIII\Helpers\Csv\Specifix\\' . $className); + if ($specifix->getProcessorType() == SpecifixInterface::POST_PROCESSOR) { + $specifix->setData($this->importData); + $specifix->setRow($this->importRow); + $this->importData = $specifix->fix(); + } + } + + + $set = config('csv.post_processors'); + foreach ($set as $className) { + /** @var PostProcessorInterface $postProcessor */ + $postProcessor = app('FireflyIII\Helpers\Csv\PostProcessing\\' . $className); + $array = $this->importData ?? []; + $postProcessor->setData($array); + $this->importData = $postProcessor->process(); + } + + } + + /** + * @param TransactionJournal $journal + */ + protected function saveBudget(TransactionJournal $journal) + { + // add budget: + if (!is_null($this->importData['budget'])) { + $journal->budgets()->save($this->importData['budget']); + } + } + + /** + * @param TransactionJournal $journal + */ + protected function saveCategory(TransactionJournal $journal) + { + // add category: + if (!is_null($this->importData['category'])) { + $journal->categories()->save($this->importData['category']); + } + } + + /** + * @param TransactionJournal $journal + */ + protected function saveTags(TransactionJournal $journal) + { + if (!is_null($this->importData['tags'])) { + foreach ($this->importData['tags'] as $tag) { + $journal->tags()->save($tag); + } + } + } + + /** + * + * @return bool|string + */ + protected function validateData() + { + $date = $this->importData['date'] ?? null; + $rentDate = $this->importData['date-rent'] ?? null; + if (is_null($date) && is_null($rentDate)) { + return 'No date value for this row.'; + } + if (is_null($this->importData['opposing-account-object'])) { + return 'Opposing account is null'; + } + + if (!($this->importData['asset-account-object'] instanceof Account)) { + return 'No asset account to import into.'; + } + + return true; + } + + /** + * @return array + */ + private function getFiller() + { + $filler = []; + foreach (config('csv.roles') as $role) { + if (isset($role['field'])) { + $fieldName = $role['field']; + $filler[$fieldName] = null; + } + } + // some extra's: + $filler['bill-id'] = null; + $filler['opposing-account-object'] = null; + $filler['asset-account-object'] = null; + $filler['amount-modifier'] = '1'; + + return $filler; + + } + +} diff --git a/app/Helpers/Csv/Mapper/AnyAccount.php b/app/Helpers/Csv/Mapper/AnyAccount.php new file mode 100644 index 0000000000..05c5bab9ea --- /dev/null +++ b/app/Helpers/Csv/Mapper/AnyAccount.php @@ -0,0 +1,42 @@ +accounts()->with('accountType')->orderBy('accounts.name', 'ASC')->get(['accounts.*']); + + $list = []; + /** @var Account $account */ + foreach ($result as $account) { + $list[$account->id] = $account->name . ' (' . $account->accountType->type . ')'; + } + asort($list); + + $list = [0 => trans('firefly.csv_do_not_map')] + $list; + + return $list; + } +} diff --git a/app/Helpers/Csv/Mapper/AssetAccount.php b/app/Helpers/Csv/Mapper/AssetAccount.php new file mode 100644 index 0000000000..8f32bbad57 --- /dev/null +++ b/app/Helpers/Csv/Mapper/AssetAccount.php @@ -0,0 +1,54 @@ +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) { + $name = $account->name; + $iban = $account->iban ?? ''; + if (strlen($iban) > 0) { + $name .= ' (' . $account->iban . ')'; + } + $list[$account->id] = $name; + } + + asort($list); + + $list = [0 => trans('firefly.csv_do_not_map')] + $list; + + return $list; + } +} diff --git a/app/Helpers/Csv/Mapper/Bill.php b/app/Helpers/Csv/Mapper/Bill.php new file mode 100644 index 0000000000..37946bf394 --- /dev/null +++ b/app/Helpers/Csv/Mapper/Bill.php @@ -0,0 +1,42 @@ +bills()->get(['bills.*']); + $list = []; + + /** @var BillModel $bill */ + foreach ($result as $bill) { + $list[$bill->id] = $bill->name . ' [' . $bill->match . ']'; + } + asort($list); + + $list = [0 => trans('firefly.csv_do_not_map')] + $list; + + return $list; + } +} diff --git a/app/Helpers/Csv/Mapper/Budget.php b/app/Helpers/Csv/Mapper/Budget.php new file mode 100644 index 0000000000..7664cc3378 --- /dev/null +++ b/app/Helpers/Csv/Mapper/Budget.php @@ -0,0 +1,42 @@ +budgets()->get(['budgets.*']); + $list = []; + + /** @var BudgetModel $budget */ + foreach ($result as $budget) { + $list[$budget->id] = $budget->name; + } + asort($list); + + $list = [0 => trans('firefly.csv_do_not_map')] + $list; + + return $list; + } +} diff --git a/app/Helpers/Csv/Mapper/Category.php b/app/Helpers/Csv/Mapper/Category.php new file mode 100644 index 0000000000..d0a104523a --- /dev/null +++ b/app/Helpers/Csv/Mapper/Category.php @@ -0,0 +1,42 @@ +categories()->get(['categories.*']); + $list = []; + + /** @var CategoryModel $category */ + foreach ($result as $category) { + $list[$category->id] = $category->name; + } + asort($list); + + $list = [0 => trans('firefly.csv_do_not_map')] + $list; + + return $list; + } +} diff --git a/app/Helpers/Csv/Mapper/MapperInterface.php b/app/Helpers/Csv/Mapper/MapperInterface.php new file mode 100644 index 0000000000..eb6286d4f2 --- /dev/null +++ b/app/Helpers/Csv/Mapper/MapperInterface.php @@ -0,0 +1,24 @@ +budgets()->get(['tags.*']); + $list = []; + + /** @var TagModel $tag */ + foreach ($result as $tag) { + $list[$tag->id] = $tag->tag; + } + asort($list); + + $list = [0 => trans('firefly.csv_do_not_map')] + $list; + + return $list; + } +} diff --git a/app/Helpers/Csv/Mapper/TransactionCurrency.php b/app/Helpers/Csv/Mapper/TransactionCurrency.php new file mode 100644 index 0000000000..f4257b1510 --- /dev/null +++ b/app/Helpers/Csv/Mapper/TransactionCurrency.php @@ -0,0 +1,40 @@ +id] = $currency->name . ' (' . $currency->code . ')'; + } + + asort($list); + + $list = [0 => trans('firefly.csv_do_not_map')] + $list; + + return $list; + } +} diff --git a/app/Helpers/Csv/PostProcessing/Amount.php b/app/Helpers/Csv/PostProcessing/Amount.php new file mode 100644 index 0000000000..504c887331 --- /dev/null +++ b/app/Helpers/Csv/PostProcessing/Amount.php @@ -0,0 +1,44 @@ +data['amount'] ?? '0'; + $modifier = strval($this->data['amount-modifier']); + $this->data['amount'] = bcmul($amount, $modifier); + + return $this->data; + } + + /** + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } +} diff --git a/app/Helpers/Csv/PostProcessing/AssetAccount.php b/app/Helpers/Csv/PostProcessing/AssetAccount.php new file mode 100644 index 0000000000..e57e0f73f1 --- /dev/null +++ b/app/Helpers/Csv/PostProcessing/AssetAccount.php @@ -0,0 +1,274 @@ +checkIdNameObject(); // has object in ID or Name? + if (!is_null($result)) { + return $result; + } + + // no object? maybe asset-account-iban is a string and we can find the matching account. + $result = $this->checkIbanString(); + if (!is_null($result)) { + return $result; + } + + // no object still? maybe we can find the account by name. + $result = $this->checkNameString(); + if (!is_null($result)) { + return $result; + } + // still nothing? Perhaps the account number can lead us to an account: + $result = $this->checkAccountNumberString(); + if (!is_null($result)) { + return $result; + } + + return null; + } + + /** + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } + + /** + * @return array|null + */ + protected function checkAccountNumberString() + { + $accountNumber = $this->data['asset-account-number'] ?? null; + if ($accountNumber instanceof Account) { // fourth: try to find account based on name, if any. + $this->data['asset-account-object'] = $accountNumber; + + return $this->data; + } + if (is_string($accountNumber)) { // it's an actual account number + $this->data['asset-account-object'] = $this->parseAccountNumberString(); + + return $this->data; + } + + return null; + } + + /** + * @return array|null + */ + protected function checkIbanString() + { + $iban = $this->data['asset-account-iban'] ?? ''; + $rules = ['iban' => 'iban']; + $check = ['iban' => $iban]; + $validator = Validator::make($check, $rules); + if (!$validator->fails()) { + $this->data['asset-account-object'] = $this->parseIbanString(); + + return $this->data; + } + + return null; + } + + /** + * @return array + */ + protected function checkIdNameObject() + { + $accountId = $this->data['asset-account-id'] ?? null; + $accountIban = $this->data['asset-account-iban'] ?? null; + $accountNumber = $this->data['asset-account-number'] ?? null; + if ($accountId instanceof Account) { // first priority. try to find the account based on ID, if any + $this->data['asset-account-object'] = $accountId; + + return $this->data; + } + if ($accountIban instanceof Account) { // second: try to find the account based on IBAN, if any. + $this->data['asset-account-object'] = $accountIban; + + return $this->data; + } + + if ($accountNumber instanceof Account) { // second: try to find the account based on account number, if any. + $this->data['asset-account-object'] = $accountNumber; + + return $this->data; + } + + + return null; + } + + /** + * @return array|null + */ + protected function checkNameString() + { + $accountName = $this->data['asset-account-name'] ?? null; + if ($accountName instanceof Account) { // third: try to find account based on name, if any. + $this->data['asset-account-object'] = $accountName; + + return $this->data; + } + if (is_string($accountName)) { + $this->data['asset-account-object'] = $this->parseNameString(); + + return $this->data; + } + + return null; + } + + /** + * @return Account|null + */ + protected function createAccount() + { + $accountType = $this->getAccountType(); + $name = $this->data['asset-account-name'] ?? ''; + $iban = $this->data['asset-account-iban'] ?? ''; + + // create if not exists: // See issue #180 + $name = strlen($name) > 0 ? $name : $iban; + $account = Account::firstOrCreateEncrypted( + [ + 'user_id' => Auth::user()->id, + 'account_type_id' => $accountType->id, + 'name' => $name, + 'iban' => $iban, + 'active' => true, + ] + ); + + return $account; + } + + /** + * + * @return AccountType + */ + protected function getAccountType() + { + return AccountType::where('type', 'Asset account')->first(); + } + + /** + * @return Account|null + */ + protected function parseIbanString() + { + // create by name and/or iban. + $iban = $this->data['asset-account-iban'] ?? ''; + $accounts = Auth::user()->accounts()->get(); + foreach ($accounts as $entry) { + if ($iban !== '' && $entry->iban === $iban) { + + return $entry; + } + } + $account = $this->createAccount(); + + return $account; + } + + /** + * @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']) { + + return $entry; + } + } + // create if not exists: + // See issue #180 + $account = Account::firstOrCreateEncrypted( + [ + 'user_id' => Auth::user()->id, + 'account_type_id' => $accountType->id, + 'name' => $this->data['asset-account-name'], + 'iban' => '', + 'active' => true, + ] + ); + + return $account; + } + + /** + * @return Account|null + */ + private function parseAccountNumberString() + { + /** @var AccountCrudInterface $crud */ + $crud = app(AccountCrudInterface::class); + + $accountNumber = $this->data['asset-account-number'] ?? ''; + $accountType = $this->getAccountType(); + $accounts = Auth::user()->accounts()->with(['accountmeta'])->where('account_type_id', $accountType->id)->get(); + /** @var Account $entry */ + foreach ($accounts as $entry) { + $metaFieldValue = $entry->getMeta('accountNumber'); + if ($metaFieldValue === $accountNumber && $metaFieldValue !== '') { + + return $entry; + } + } + // create new if not exists and return that one: + $accountData = [ + 'name' => $accountNumber, + 'accountType' => 'asset', + 'virtualBalance' => 0, + 'virtualBalanceCurrency' => 1, // hard coded. + 'active' => true, + 'user' => Auth::user()->id, + 'iban' => null, + 'accountNumber' => $accountNumber, + 'accountRole' => null, + 'openingBalance' => 0, + 'openingBalanceDate' => new Carbon, + 'openingBalanceCurrency' => 1, // hard coded. + ]; + $account = $crud->store($accountData); + + return $account; + } +} diff --git a/app/Helpers/Csv/PostProcessing/Bill.php b/app/Helpers/Csv/PostProcessing/Bill.php new file mode 100644 index 0000000000..60392b2446 --- /dev/null +++ b/app/Helpers/Csv/PostProcessing/Bill.php @@ -0,0 +1,45 @@ +data['bill']) && !is_null($this->data['bill']->id)) { + $this->data['bill-id'] = $this->data['bill']->id; + } + + return $this->data; + } + + /** + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } +} diff --git a/app/Helpers/Csv/PostProcessing/Currency.php b/app/Helpers/Csv/PostProcessing/Currency.php new file mode 100644 index 0000000000..2a6f46cfd5 --- /dev/null +++ b/app/Helpers/Csv/PostProcessing/Currency.php @@ -0,0 +1,49 @@ +data['currency'])) { + $currencyPreference = Preferences::get('currencyPreference', env('DEFAULT_CURRENCY', 'EUR')); + $this->data['currency'] = TransactionCurrency::whereCode($currencyPreference->data)->first(); + } + + return $this->data; + } + + /** + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } +} diff --git a/app/Helpers/Csv/PostProcessing/Description.php b/app/Helpers/Csv/PostProcessing/Description.php new file mode 100644 index 0000000000..8c4843a58b --- /dev/null +++ b/app/Helpers/Csv/PostProcessing/Description.php @@ -0,0 +1,47 @@ +data['description'] ?? ''; + $this->data['description'] = trim($description); + if (strlen($this->data['description']) == 0) { + $this->data['description'] = trans('firefly.csv_empty_description'); + } + + + return $this->data; + } + + /** + * @param array $data + */ + public function setData(array $data) + { + + $this->data = $data; + } +} diff --git a/app/Helpers/Csv/PostProcessing/OpposingAccount.php b/app/Helpers/Csv/PostProcessing/OpposingAccount.php new file mode 100644 index 0000000000..279328b24e --- /dev/null +++ b/app/Helpers/Csv/PostProcessing/OpposingAccount.php @@ -0,0 +1,210 @@ +checkIdNameObject(); + 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|null + */ + protected function checkIbanString() + { + $rules = ['iban' => 'iban']; + $iban = $this->data['opposing-account-iban']; + $check = ['iban' => $iban]; + $validator = Validator::make($check, $rules); + if (is_string($iban) && strlen($iban) > 0 && !$validator->fails()) { + + $this->data['opposing-account-object'] = $this->parseIbanString(); + + return $this->data; + } + + return null; + } + + /** + * @return array + */ + protected function checkIdNameObject() + { + if ($this->data['opposing-account-id'] instanceof Account) { // first priority. try to find the account based on ID, if any + $this->data['opposing-account-object'] = $this->data['opposing-account-id']; + + return $this->data; + } + if ($this->data['opposing-account-iban'] instanceof Account) { // second: try to find the account based on IBAN, if any. + $this->data['opposing-account-object'] = $this->data['opposing-account-iban']; + + return $this->data; + } + + return null; + } + + /** + * @return array|null + */ + protected function checkNameString() + { + if ($this->data['opposing-account-name'] instanceof Account) { // third: try to find account based on name, if any. + $this->data['opposing-account-object'] = $this->data['opposing-account-name']; + + return $this->data; + } + if (is_string($this->data['opposing-account-name'])) { + + $this->data['opposing-account-object'] = $this->parseNameString(); + + return $this->data; + } + + return null; + } + + /** + * @return Account|null + */ + protected function createAccount() + { + $accountType = $this->getAccountType(); + + // create if not exists: + $name = is_string($this->data['opposing-account-name']) && strlen($this->data['opposing-account-name']) > 0 ? $this->data['opposing-account-name'] + : $this->data['opposing-account-iban']; + $account = Account::firstOrCreateEncrypted( // See issue #180 + [ + 'user_id' => Auth::user()->id, + 'account_type_id' => $accountType->id, + 'name' => $name, + 'iban' => $this->data['opposing-account-iban'], + 'active' => true, + ] + ); + + return $account; + } + + /** + * + * @return AccountType + */ + protected function getAccountType() + { + // opposing account type: + if ($this->data['amount'] < 0) { + // create expense account: + + return AccountType::where('type', 'Expense account')->first(); + } + + // create revenue account: + + return AccountType::where('type', 'Revenue account')->first(); + + + } + + /** + * @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['opposing-account-iban']) { + + return $entry; + } + } + $account = $this->createAccount(); + + + return $account; + } + + /** + * @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['opposing-account-name']) { + + return $entry; + } + } + // create if not exists: + $account = Account::firstOrCreateEncrypted( // See issue #180 + [ + 'user_id' => Auth::user()->id, + 'account_type_id' => $accountType->id, + 'name' => $this->data['opposing-account-name'], + 'iban' => '', + 'active' => true, + ] + ); + + return $account; + } +} diff --git a/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php b/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php new file mode 100644 index 0000000000..58f6ff09d0 --- /dev/null +++ b/app/Helpers/Csv/PostProcessing/PostProcessorInterface.php @@ -0,0 +1,31 @@ +setProcessorType(self::POST_PROCESSOR); + } + + + /** + * @return array + */ + public function fix(): array + { + // Try to parse the description in known formats. + $parsed = $this->parseSepaDescription() || $this->parseTRTPDescription() || $this->parseGEABEADescription() || $this->parseABNAMRODescription(); + + // If the description could not be parsed, specify an unknown opposing + // account, as an opposing account is required + if (!$parsed) { + $this->data['opposing-account-name'] = trans('firefly.unknown'); + } + + return $this->data; + } + + /** + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } + + /** + * @param array $row + */ + public function setRow(array $row) + { + $this->row = $row; + } + + /** + * Parses the current description with costs from ABN AMRO itself + * + * @return bool true if the description is GEA/BEA-format, false otherwise + */ + protected function parseABNAMRODescription() + { + // See if the current description is formatted in ABN AMRO format + if (preg_match('/ABN AMRO.{24} (.*)/', $this->data['description'], $matches)) { + + $this->data['opposing-account-name'] = 'ABN AMRO'; + $this->data['description'] = $matches[1]; + + return true; + } + + return false; + } + + /** + * Parses the current description in GEA/BEA format + * + * @return bool true if the description is GEA/BEAformat, false otherwise + */ + protected function parseGEABEADescription() + { + // See if the current description is formatted in GEA/BEA format + if (preg_match('/([BG]EA) +(NR:[a-zA-Z:0-9]+) +([0-9.\/]+) +([^,]*)/', $this->data['description'], $matches)) { + + // description and opposing account will be the same. + $this->data['opposing-account-name'] = $matches[4]; + $this->data['description'] = $matches[4]; + + if ($matches[1] == 'GEA') { + $this->data['description'] = 'GEA ' . $matches[4]; + } + + return true; + } + + return false; + } + + /** + * Parses the current description in SEPA format + * + * @return bool true if the description is SEPA format, false otherwise + */ + protected function parseSepaDescription() + { + // See if the current description is formatted as a SEPA plain description + if (preg_match('/^SEPA(.{28})/', $this->data['description'], $matches)) { + + $type = $matches[1]; + $reference = ''; + $name = ''; + $newDescription = ''; + + // SEPA plain descriptions contain several key-value pairs, split by a colon + preg_match_all('/([A-Za-z]+(?=:\s)):\s([A-Za-z 0-9._#-]+(?=\s|$))/', $this->data['description'], $matches, PREG_SET_ORDER); + + if (is_array($matches)) { + foreach ($matches as $match) { + $key = $match[1]; + $value = trim($match[2]); + switch (strtoupper($key)) { + case 'OMSCHRIJVING': + $newDescription = $value; + break; + case 'NAAM': + $this->data['opposing-account-name'] = $value; + $name = $value; + break; + case 'KENMERK': + $reference = $value; + break; + case 'IBAN': + $this->data['opposing-account-iban'] = $value; + break; + default: + // Ignore the rest + } + } + } + + // Set a new description for the current transaction. If none was given + // set the description to type, name and reference + $this->data['description'] = $newDescription; + if (strlen($newDescription) === 0) { + $this->data['description'] = sprintf('%s - %s (%s)', $type, $name, $reference); + } + + return true; + } + + return false; + } + + /** + * Parses the current description in TRTP format + * + * @return bool true if the description is TRTP format, false otherwise + */ + protected function parseTRTPDescription() + { + // See if the current description is formatted in TRTP format + if (preg_match_all('!\/([A-Z]{3,4})\/([^/]*)!', $this->data['description'], $matches, PREG_SET_ORDER)) { + + $type = ''; + $name = ''; + $reference = ''; + $newDescription = ''; + + // Search for properties specified in the TRTP format. If no description + // is provided, use the type, name and reference as new description + if (is_array($matches)) { + foreach ($matches as $match) { + $key = $match[1]; + $value = trim($match[2]); + + switch (strtoupper($key)) { + case 'NAME': + $this->data['opposing-account-name'] = $name = $value; + break; + case 'REMI': + $newDescription = $value; + break; + case 'IBAN': + $this->data['opposing-account-iban'] = $value; + break; + case 'EREF': + $reference = $value; + break; + case 'TRTP': + $type = $value; + break; + default: + // Ignore the rest + } + } + + // Set a new description for the current transaction. If none was given + // set the description to type, name and reference + $this->data['description'] = $newDescription; + if (strlen($newDescription) === 0) { + $this->data['description'] = sprintf('%s - %s (%s)', $type, $name, $reference); + } + } + + return true; + } + + return false; + } + +} diff --git a/app/Helpers/Csv/Specifix/Dummy.php b/app/Helpers/Csv/Specifix/Dummy.php new file mode 100644 index 0000000000..447bafadd9 --- /dev/null +++ b/app/Helpers/Csv/Specifix/Dummy.php @@ -0,0 +1,60 @@ +setProcessorType(self::POST_PROCESSOR); + } + + /** + * @return array + */ + public function fix(): array + { + return $this->data; + + } + + /** + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } + + /** + * @param array $row + */ + public function setRow(array $row) + { + $this->row = $row; + } + + +} diff --git a/app/Helpers/Csv/Specifix/RabobankDescription.php b/app/Helpers/Csv/Specifix/RabobankDescription.php new file mode 100644 index 0000000000..67dbdb6c49 --- /dev/null +++ b/app/Helpers/Csv/Specifix/RabobankDescription.php @@ -0,0 +1,76 @@ +setProcessorType(self::POST_PROCESSOR); + } + + + /** + * @return array + */ + public function fix(): array + { + $this->rabobankFixEmptyOpposing(); + + return $this->data; + + } + + /** + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } + + /** + * @param array $row + */ + public function setRow(array $row) + { + $this->row = $row; + } + + /** + * Fixes Rabobank specific thing. + */ + protected function rabobankFixEmptyOpposing() + { + if (is_string($this->data['opposing-account-name']) && strlen($this->data['opposing-account-name']) == 0) { + $this->data['opposing-account-name'] = $this->row[10]; + + $this->data['description'] = trim(str_replace($this->row[10], '', $this->data['description'])); + } + + } + + +} diff --git a/app/Helpers/Csv/Specifix/Specifix.php b/app/Helpers/Csv/Specifix/Specifix.php new file mode 100644 index 0000000000..8626d38dcb --- /dev/null +++ b/app/Helpers/Csv/Specifix/Specifix.php @@ -0,0 +1,46 @@ +processorType; + } + + /** + * @param int $processorType + * + * @return $this + */ + public function setProcessorType(int $processorType) + { + $this->processorType = $processorType; + + return $this; + } + + +} diff --git a/app/Helpers/Csv/Specifix/SpecifixInterface.php b/app/Helpers/Csv/Specifix/SpecifixInterface.php new file mode 100644 index 0000000000..e37d4d3ec4 --- /dev/null +++ b/app/Helpers/Csv/Specifix/SpecifixInterface.php @@ -0,0 +1,49 @@ + $row) { + if ($this->useRow($hasHeaders, $index)) { + // collect all map values + + foreach ($keys as $column) { + $values[$column][] = $row[$column]; + } + } + } + /* + * Make each one unique. + */ + $values = $this->uniqueRecursive($values); + + return $values; + } + + /** + * @param array $roles + * @param array $map + * + * @return array + */ + public function processSelectedMapping(array $roles, array $map): array + { + $configRoles = config('csv.roles'); + $maps = []; + $keys = array_keys($map); + foreach ($keys as $index) { + if (isset($roles[$index])) { + $name = $roles[$index]; + if ($configRoles[$name]['mappable']) { + $maps[$index] = $name; + } + } + } + + return $maps; + + } + + /** + * @param array $input + * + * @return array + */ + public function processSelectedRoles(array $input): array + { + $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): bool + { + foreach ($fields as $field) { + if (!Session::has($field)) { + Log::error('Session is missing field: ' . $field); + + return false; + } + } + + return true; + } + + /** + * @param array $map + * + * @return array + * @throws FireflyException + */ + public function showOptions(array $map): array + { + $options = []; + foreach ($map as $index => $columnRole) { + + $mapper = config('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($class); + } catch (ReflectionException $e) { + throw new FireflyException('Column "' . $columnRole . '" cannot be mapped because mapper class ' . $mapper . ' does not exist.'); + } + $set = $mapObject->getMap(); + $options[$index] = $set; + } + + return $options; + } + + /** + * @param string $path + * + * @return string + */ + public function storeCsvFile(string $path): string + { + $time = str_replace(' ', '-', microtime()); + $fileName = 'csv-upload-' . Auth::user()->id . '-' . $time . '.csv.encrypted'; + $disk = Storage::disk('upload'); + $file = new SplFileObject($path, 'r'); + $content = $file->fread($file->getSize()); + $contentEncrypted = Crypt::encrypt($content); + $disk->put($fileName, $contentEncrypted); + + return $fileName; + + + } + + /** + * @param array $array + * + * @return array + */ + protected function uniqueRecursive(array $array) + { + foreach ($array as $column => $found) { + $array[$column] = array_unique($found); + } + + return $array; + } + + /** + * @param bool $hasHeaders + * @param int $index + * + * @return bool + */ + protected function useRow(bool $hasHeaders, int $index) + { + return ($hasHeaders && $index > 1) || !$hasHeaders; + } +} diff --git a/app/Helpers/Csv/WizardInterface.php b/app/Helpers/Csv/WizardInterface.php new file mode 100644 index 0000000000..cedcfbe5f6 --- /dev/null +++ b/app/Helpers/Csv/WizardInterface.php @@ -0,0 +1,68 @@ + Date: Mon, 6 Jun 2016 09:22:28 +0200 Subject: [PATCH 06/12] Restore controller (#262) --- app/Http/Controllers/CsvController.php | 444 +++++++++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 app/Http/Controllers/CsvController.php diff --git a/app/Http/Controllers/CsvController.php b/app/Http/Controllers/CsvController.php new file mode 100644 index 0000000000..1ce2c57b33 --- /dev/null +++ b/app/Http/Controllers/CsvController.php @@ -0,0 +1,444 @@ +wizard = app(WizardInterface::class); + $this->data = app(Data::class); + + } + + /** + * Define column roles and mapping. + * + * STEP THREE + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function columnRoles() + { + + $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-import-account', 'csv-specifix', 'csv-delimiter']; + if (!$this->wizard->sessionHasValues($fields)) { + Log::error('Could not recover upload.'); + Session::flash('warning', strval(trans('firefly.could_not_recover'))); + + return redirect(route('csv.index')); + } + + $subTitle = trans('firefly.csv_define_column_roles'); + $firstRow = $this->data->getReader()->fetchOne(); + $count = count($firstRow); + $headers = []; + $example = $this->data->getReader()->fetchOne(1); + $availableRoles = []; + $roles = $this->data->getRoles(); + $map = $this->data->getMap(); + + for ($i = 1; $i <= $count; $i++) { + $headers[] = trans('firefly.csv_column') . ' #' . $i; + } + if ($this->data->hasHeaders()) { + $headers = $firstRow; + } + $keys = array_keys(config('csv.roles')); + foreach ($keys as $name) { + $availableRoles[$name] = trans('firefly.csv_column_' . $name); + } + asort($availableRoles); + + return view('csv.column-roles', compact('availableRoles', 'map', 'roles', 'headers', 'example', 'subTitle')); + } + + /** + * Optional download of mapping. + * + * STEP FOUR THREE-A + * + * @return \Illuminate\Http\RedirectResponse|string + */ + public function downloadConfig() + { + $fields = ['csv-date-format', 'csv-has-headers', 'csv-delimiter']; + if (!$this->wizard->sessionHasValues($fields)) { + Session::flash('warning', strval(trans('firefly.could_not_recover'))); + + return redirect(route('csv.index')); + } + $data = [ + 'date-format' => session('csv-date-format'), + 'has-headers' => session('csv-has-headers'), + ]; + if (Session::has('csv-map')) { + $data['map'] = session('csv-map'); + } + if (Session::has('csv-roles')) { + $data['roles'] = session('csv-roles'); + } + if (Session::has('csv-mapped')) { + $data['mapped'] = session('csv-mapped'); + } + + if (Session::has('csv-specifix')) { + $data['specifix'] = session('csv-specifix'); + } + + $result = json_encode($data, JSON_PRETTY_PRINT); + $name = sprintf('"%s"', addcslashes('csv-configuration-' . date('Y-m-d') . '.json', '"\\')); + + return response($result, 200) + ->header('Content-disposition', 'attachment; filename=' . $name) + ->header('Content-Type', 'application/json') + ->header('Content-Description', 'File Transfer') + ->header('Connection', 'Keep-Alive') + ->header('Expires', '0') + ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->header('Pragma', 'public') + ->header('Content-Length', strlen($result)); + } + + /** + * @return \Illuminate\View\View + */ + public function downloadConfigPage() + { + $fields = ['csv-date-format', 'csv-has-headers', 'csv-delimiter']; + if (!$this->wizard->sessionHasValues($fields)) { + Session::flash('warning', strval(trans('firefly.could_not_recover'))); + + return redirect(route('csv.index')); + } + + $subTitle = trans('firefly.csv_download_config_title'); + + return view('csv.download-config', compact('subTitle')); + } + + /** + * This method shows the initial upload form. + * + * STEP ONE + * + * @param AccountCrudInterface $crud + * + * @return \Illuminate\View\View + */ + public function index(AccountCrudInterface $crud) + { + $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'); + Session::forget('csv-specifix'); + Session::forget('csv-delimiter'); + + // get list of supported specifix + $specifix = []; + foreach (config('csv.specifix') as $entry) { + $specifix[$entry] = trans('firefly.csv_specifix_' . $entry); + } + + // get a list of delimiters: + $delimiters = [ + ',' => trans('form.csv_comma'), + ';' => trans('form.csv_semicolon'), + 'tab' => trans('form.csv_tab'), + ]; + + // get a list of asset accounts: + $accounts = ExpandedForm::makeSelectList($crud->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT])); + + // can actually upload? + $uploadPossible = is_writable(storage_path('upload')); + $path = storage_path('upload'); + + return view('csv.index', compact('subTitle', 'uploadPossible', 'path', 'specifix', 'accounts', 'delimiters')); + } + + /** + * Parse the file. + * + * STEP FOUR + * + * @return \Illuminate\Http\RedirectResponse + */ + public function initialParse() + { + $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-delimiter']; + if (!$this->wizard->sessionHasValues($fields)) { + Session::flash('warning', strval(trans('firefly.could_not_recover'))); + + return redirect(route('csv.index')); + } + + // process given roles and mapping: + $inputMap = Input::get('map') ?? []; + $inputRoles = Input::get('role') ?? []; + $roles = $this->wizard->processSelectedRoles($inputRoles); + $maps = $this->wizard->processSelectedMapping($roles, $inputMap); + + Session::put('csv-map', $maps); + Session::put('csv-roles', $roles); + + // Go back when no roles defined: + if (count($roles) === 0) { + Session::flash('warning', strval(trans('firefly.must_select_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|\Illuminate\View\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', 'csv-delimiter']; + if (!$this->wizard->sessionHasValues($fields)) { + Session::flash('warning', strval(trans('firefly.could_not_recover'))); + + 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. + * + * options[column index] = [ + * field id => field identifier. + * ] + */ + $options = $this->wizard->showOptions($this->data->getMap()); + + // After these values are prepped, read the actual CSV file + $reader = $this->data->getReader(); + $map = $this->data->getMap(); + $hasHeaders = $this->data->hasHeaders(); + $values = $this->wizard->getMappableValues($reader, $map, $hasHeaders); + $map = $this->data->getMap(); + $mapped = $this->data->getMapped(); + $subTitle = trans('firefly.csv_map_values'); + + return view('csv.map', compact('map', 'options', 'values', 'mapped', 'subTitle')); + } + + /** + * + * Finally actually process the CSV file. + * + * STEP SEVEN + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + 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', 'csv-delimiter']; + if (!$this->wizard->sessionHasValues($fields)) { + Session::flash('warning', strval(trans('firefly.could_not_recover'))); + + return redirect(route('csv.index')); + } + + /** @var Importer $importer */ + $importer = app(Importer::class); + $importer->setData($this->data); + $importer->run(); + + $rows = $importer->getRows(); + $errors = $importer->getErrors(); + $imported = $importer->getImported(); + $journals = $importer->getJournals(); + + Preferences::mark(); + + $subTitle = trans('firefly.csv_process_title'); + + return view('csv.process', compact('rows', 'errors', 'imported', 'subTitle', 'journals')); + + } + + /** + * Store the mapping the user has made. This is + * + * STEP SIX + * + * + * @return \Illuminate\Http\RedirectResponse + */ + 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', 'csv-delimiter']; + if (!$this->wizard->sessionHasValues($fields)) { + Session::flash('warning', strval(trans('firefly.could_not_recover'))); + + return redirect(route('csv.index')); + } + + // save mapping to session. + $mapped = []; + if (!is_array(Input::get('mapping'))) { + Session::flash('warning', strval(trans('firefly.invalid_mapping'))); + + return redirect(route('csv.map')); + } + + foreach (Input::get('mapping') as $index => $data) { + $mapped[$index] = []; + foreach ($data as $value => $mapping) { + if (intval($mapping) !== 0) { + $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', strval(trans('firefly.no_file_uploaded'))); + + return redirect(route('csv.index')); + } + + $path = $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'] = is_array(Input::get('specifix')) ? Input::get('specifix') : []; + $settings['import-account'] = intval(Input::get('csv_import_account')); + $settings['delimiter'] = Input::get('csv_delimiter', ','); + + // A tab character cannot be used itself as option value in HTML + // See http://stackoverflow.com/questions/6064135/valid-characters-in-option-value + if ($settings['delimiter'] == 'tab') { + $settings['delimiter'] = "\t"; + } + + $settings['map'] = []; + $settings['mapped'] = []; + $settings['roles'] = []; + + if ($request->hasFile('csv_config')) { // Process config file if present. + + $size = $request->file('csv_config')->getSize(); + $data = $request->file('csv_config')->openFile()->fread($size); + $json = json_decode($data, true); + if (is_array($json)) { + $settings = array_merge($settings, $json); + } + } + + $this->data->setCsvFileLocation($path); + $this->data->setDateFormat($settings['date-format']); + $this->data->setHasHeaders($settings['has-headers']); + $this->data->setMap($settings['map']); + $this->data->setMapped($settings['mapped']); + $this->data->setRoles($settings['roles']); + $this->data->setSpecifix($settings['specifix']); + $this->data->setImportAccount($settings['import-account']); + $this->data->setDelimiter($settings['delimiter']); + + return redirect(route('csv.column-roles')); + + } +} From c9679f1d4f21aaedbe725b6a6ea1990a0f05c86e Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 6 Jun 2016 09:23:55 +0200 Subject: [PATCH 07/12] Restore bread crumbs (#262) --- app/Http/breadcrumbs.php | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index f1925bc906..cc42ebbe64 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -269,6 +269,44 @@ Breadcrumbs::register( } ); +/** + * CSV CONTROLLER + */ +Breadcrumbs::register( + 'csv.index', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.csv_index_title'), route('csv.index')); +} +); + +Breadcrumbs::register( + 'csv.column-roles', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('csv.index'); + $breadcrumbs->push(trans('firefly.csv_define_column_roles'), route('csv.column-roles')); +} +); + +Breadcrumbs::register( + 'csv.map', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('csv.index'); + $breadcrumbs->push(trans('firefly.csv_map_values'), route('csv.map')); +} +); + +Breadcrumbs::register( + 'csv.download-config-page', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('csv.index'); + $breadcrumbs->push(trans('firefly.csv_download_config'), route('csv.download-config-page')); +} +); + +Breadcrumbs::register( + 'csv.process', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('csv.index'); + $breadcrumbs->push(trans('firefly.csv_process_title'), route('csv.process')); +} +); + /** * CURRENCIES */ From 1502e08a7a89cfca86969b1d07f8f827426918a1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 6 Jun 2016 09:24:51 +0200 Subject: [PATCH 08/12] Restore routes (#262) --- app/Http/routes.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/Http/routes.php b/app/Http/routes.php index e5e0fe4085..159931d398 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -157,6 +157,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 */ @@ -223,8 +238,8 @@ Route::group( /** * IMPORT CONTROLLER */ - Route::get('/import', ['uses' => 'ImportController@index','as' => 'import.index']); - Route::post('/import/upload', ['uses' => 'ImportController@upload','as' => 'import.upload']); + Route::get('/import', ['uses' => 'ImportController@index', 'as' => 'import.index']); + Route::post('/import/upload', ['uses' => 'ImportController@upload', 'as' => 'import.upload']); /** * Help Controller From 7527433738b672f3cafaddd604d5c1ccb59b3ccd Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 6 Jun 2016 09:25:29 +0200 Subject: [PATCH 09/12] Restore provider (#262) --- app/Providers/FireflyServiceProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index 6ea3d33f3e..47ad19c301 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -87,6 +87,7 @@ class FireflyServiceProvider extends ServiceProvider $this->app->bind('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface', 'FireflyIII\Repositories\Currency\CurrencyRepository'); $this->app->bind('FireflyIII\Support\Search\SearchInterface', 'FireflyIII\Support\Search\Search'); $this->app->bind('FireflyIII\Repositories\User\UserRepositoryInterface', 'FireflyIII\Repositories\User\UserRepository'); + $this->app->bind('FireflyIII\Helpers\Csv\WizardInterface', 'FireflyIII\Helpers\Csv\Wizard'); $this->app->bind('FireflyIII\Helpers\Attachments\AttachmentHelperInterface', 'FireflyIII\Helpers\Attachments\AttachmentHelper'); $this->app->bind( 'FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface', 'FireflyIII\Generator\Chart\Account\ChartJsAccountChartGenerator' From 8dc106b79a3f6e667916ae272297be5bedff7e4b Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 6 Jun 2016 09:28:25 +0200 Subject: [PATCH 10/12] Restore config (#262) --- config/firefly.php | 1 + 1 file changed, 1 insertion(+) diff --git a/config/firefly.php b/config/firefly.php index c3e5455c20..1490926e34 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -5,6 +5,7 @@ declare(strict_types = 1); return [ 'chart' => 'chartjs', 'version' => '3.9.0', + 'csv_import_enabled' => true, 'maxUploadSize' => 5242880, 'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'], 'resend_confirmation' => 3600, From 8091dbfdfad9d776784f878b56804dd486ed802c Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 6 Jun 2016 09:28:35 +0200 Subject: [PATCH 11/12] Restore views (#262) --- resources/views/csv/column-roles.twig | 77 +++++++++++++++++ resources/views/csv/download-config.twig | 44 ++++++++++ resources/views/csv/index.twig | 96 ++++++++++++++++++++++ resources/views/csv/map.twig | 82 ++++++++++++++++++ resources/views/csv/process.twig | 58 +++++++++++++ resources/views/partials/menu-sidebar.twig | 9 +- 6 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 resources/views/csv/column-roles.twig create mode 100644 resources/views/csv/download-config.twig create mode 100644 resources/views/csv/index.twig create mode 100644 resources/views/csv/map.twig create mode 100644 resources/views/csv/process.twig diff --git a/resources/views/csv/column-roles.twig b/resources/views/csv/column-roles.twig new file mode 100644 index 0000000000..f25a0a2ea7 --- /dev/null +++ b/resources/views/csv/column-roles.twig @@ -0,0 +1,77 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} +{% endblock %} + +{% block content %} + + +
      +
      +
      +
      +

      {{ 'csv_column_roles_title'|_ }}

      +
      +
      +

      {{ 'csv_column_roles_text'|_ }}

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

      {{ 'csv_column_roles_table'|_ }}

      +
      +
      + + + + + + + + + + + {% for index,header in headers %} + + + + + + + + {% endfor %} +
      {{ 'csv_column_name'|_ }}{{ 'csv_column_example'|_ }}{{ 'csv_column_role'|_ }}{{ 'csv_do_map_value'|_ }}
      {{ header }}{{ example[index] }} + {{ Form.select(('role['~index~']'), availableRoles,roles[index],{class: 'form-control'}) }} + + {{ Form.checkbox(('map['~index~']'),1,map[index]) }} +
      + + +
      +
      +
      +
      + +
      +
      +
      +
      + {{ 'csv_go_back'|_ }} + +
      +
      +
      +
      +
      +{% endblock %} diff --git a/resources/views/csv/download-config.twig b/resources/views/csv/download-config.twig new file mode 100644 index 0000000000..bd317bea7c --- /dev/null +++ b/resources/views/csv/download-config.twig @@ -0,0 +1,44 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} +{% endblock %} + +{% block content %} + + +
      +
      +
      +
      +

      {{ 'csv_download_config_title'|_ }}

      +
      +
      +

      + {{ 'csv_download_config_text'|_ }} +

      + +

      + {{ 'csv_do_download_config'|_ }} +

      + +

      + {{ 'csv_more_information_text'|_ }} +

      +
      +
      +
      +
      + + + +{% endblock %} diff --git a/resources/views/csv/index.twig b/resources/views/csv/index.twig new file mode 100644 index 0000000000..cfa3f6450a --- /dev/null +++ b/resources/views/csv/index.twig @@ -0,0 +1,96 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} +{% endblock %} + +{% block content %} + + +
      +
      +
      +
      +

      {{ 'csv_index_title'|_ }}

      +
      +
      + {{ 'csv_index_text'|_ }} +

      {{ 'csv_index_beta_warning'|_ }}

      + {% if unsupported|length > 0 %} +

      {{ 'csv_index_unsupported_warning'|_ }}

      +
        + {% for message in unsupported %} +
      • {{ message }}
      • + {% endfor %} +
      + {% endif %} +
      +
      + +
      +
      + +
      + + +
      +
      +
      +
      +

      {{ 'csv_upload_form'|_ }}

      +
      +
      + + + {{ ExpandedForm.checkbox('has_headers',1,null,{helpText: 'csv_header_help'|_}) }} + {{ ExpandedForm.text('date_format','Ymd',{helpText: trans('firefly.csv_date_help', {dateExample: phpdate('Ymd')}) }) }} + + {{ ExpandedForm.file('csv',{helpText: 'csv_csv_file_help'|_}) }} + + {{ ExpandedForm.select('csv_delimiter', delimiters, 0, {helpText: 'csv_delimiter_help'|_} ) }} + + {{ 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 %} +
      +
      +   +
      + +
      +
      {{ path }}
      +

      + {{ 'csv_upload_not_writeable'|_ }} +

      +
      +
      + {% endif %} + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      +
      +
      + + + + +{% endblock %} diff --git a/resources/views/csv/map.twig b/resources/views/csv/map.twig new file mode 100644 index 0000000000..f8562205bd --- /dev/null +++ b/resources/views/csv/map.twig @@ -0,0 +1,82 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} +{% endblock %} + +{% block content %} + + +
      +
      +
      +
      +

      {{ 'csv_map_title'|_ }}

      +
      +
      +

      + {{ 'csv_map_text'|_ }} +

      +
      +
      + +
      +
      +
      + + + {% for index,columnName in map %} + +
      +
      +
      +
      +

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

      +
      +
      + + + + + + + + + {% for value in values[index] %} + + + + + {% endfor %} + + + +
      {{ 'csv_field_value'|_ }}{{ 'csv_field_mapped_to'|_ }}
      {{ value }} + {{ Form.select('mapping['~index~']['~value~']',options[index], mapped[index][value], {class: 'form-control'}) }} +
      + + +
      +
      +
      +
      + {% endfor %} + + +
      +
      +
      +
      + {{ 'csv_go_back'|_ }} + +
      +
      +
      +
      + +
      + + +{% endblock %} diff --git a/resources/views/csv/process.twig b/resources/views/csv/process.twig new file mode 100644 index 0000000000..8ca39b64c9 --- /dev/null +++ b/resources/views/csv/process.twig @@ -0,0 +1,58 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} +{% endblock %} + +{% block content %} + + +
      +
      +
      +
      +

      {{ 'csv_process_title'|_ }}

      +
      +
      +

      + + {{ trans('firefly.csv_process_text',{rows: rows}) }} +

      + + {% if errors|length > 0 %} +

      {{ Lang.choice('firefly.csv_import_with_errors',errors|length,{errors: errors|length}) }}

      +
        + {% for index,err in errors %} +
      • {{ 'csv_row'|_ }} #{{ index }}: {{ err }}
      • + {% endfor %} +
      +

      + {{ trans('firefly.csv_error_see_logs') }} +

      + {% endif %} + +

      + + {{ trans('firefly.csv_process_new_entries',{imported: imported}) }} +

      + + {% if journals|length > 0 %} + + {% endif %} + +

      + {{ 'csv_start_over'|_ }} + {{ 'csv_to_index'|_ }} + {{ 'csv_do_download_config'|_ }} + +

      +
      +
      + +
      +
      +{% endblock %} diff --git a/resources/views/partials/menu-sidebar.twig b/resources/views/partials/menu-sidebar.twig index 47dfbba00b..54de5a7a93 100644 --- a/resources/views/partials/menu-sidebar.twig +++ b/resources/views/partials/menu-sidebar.twig @@ -110,7 +110,8 @@ -
    • +
    • + @@ -119,6 +120,12 @@
        + {% if Config.get('firefly.csv_import_enabled') %} +
      • + {{ 'csv_import'|_ }} +
      • + {% endif %} +
      • {{ 'export_data'|_ }}
      • From 08ff08685c2c069f7a298031a10cfec869f26162 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 6 Jun 2016 20:25:01 +0200 Subject: [PATCH 12/12] Small updates for 3.9.1 --- app/Handlers/Events/ConnectTransactionToPiggyBank.php | 3 ++- app/Handlers/Events/UpdateJournalConnection.php | 4 ++++ config/firefly.php | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/Handlers/Events/ConnectTransactionToPiggyBank.php b/app/Handlers/Events/ConnectTransactionToPiggyBank.php index 3e09a6fbcf..17507027b1 100644 --- a/app/Handlers/Events/ConnectTransactionToPiggyBank.php +++ b/app/Handlers/Events/ConnectTransactionToPiggyBank.php @@ -32,10 +32,11 @@ class ConnectTransactionToPiggyBank */ public function handle(TransactionStored $event): bool { - echo '
        ';
        +
                 /** @var PiggyBankRepositoryInterface $repository */
                 $repository  = app(PiggyBankRepositoryInterface::class);
                 $transaction = $event->transaction;
        +
                 $piggyBank   = $repository->find($transaction['piggy_bank_id']);
         
                 // valid piggy:
        diff --git a/app/Handlers/Events/UpdateJournalConnection.php b/app/Handlers/Events/UpdateJournalConnection.php
        index ae9214c08d..15d711e7ec 100644
        --- a/app/Handlers/Events/UpdateJournalConnection.php
        +++ b/app/Handlers/Events/UpdateJournalConnection.php
        @@ -34,6 +34,10 @@ class UpdateJournalConnection
             {
                 $journal = $event->journal;
         
        +        if (!$journal->isTransfer()) {
        +            return true;
        +        }
        +
                 // get the event connected to this journal:
                 /** @var PiggyBankEvent $event */
                 $event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first();
        diff --git a/config/firefly.php b/config/firefly.php
        index 1490926e34..78a1b62634 100644
        --- a/config/firefly.php
        +++ b/config/firefly.php
        @@ -4,7 +4,7 @@ declare(strict_types = 1);
         
         return [
             'chart'               => 'chartjs',
        -    'version'             => '3.9.0',
        +    'version'             => '3.9.1',
             'csv_import_enabled'  => true,
             'maxUploadSize'       => 5242880,
             'allowedMimes'        => ['image/png', 'image/jpeg', 'application/pdf'],