diff --git a/app/Import/JobConfiguration/FinTSJobConfiguration.php b/app/Import/JobConfiguration/FinTSJobConfiguration.php new file mode 100644 index 0000000000..a3dc24b709 --- /dev/null +++ b/app/Import/JobConfiguration/FinTSJobConfiguration.php @@ -0,0 +1,137 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Import\JobConfiguration; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\ImportJob; +use FireflyIII\Support\Import\JobConfiguration\FinTS\ChooseAccountHandler; +use FireflyIII\Support\Import\JobConfiguration\FinTS\FinTSConfigurationInterface; +use FireflyIII\Support\Import\JobConfiguration\FinTS\NewFinTSJobHandler; +use Illuminate\Support\MessageBag; + +abstract class FinTSConfigurationSteps +{ + const NEW = 'new'; + const CHOOSE_ACCOUNT = 'choose_account'; + const GO_FOR_IMPORT = 'go-for-import'; +} + +class FinTSJobConfiguration implements JobConfigurationInterface +{ + /** @var ImportJob */ + private $importJob; + + /** + * Returns true when the initial configuration for this job is complete. + * + * @return bool + */ + public function configurationComplete(): bool + { + return $this->importJob->stage == FinTSConfigurationSteps::GO_FOR_IMPORT; + } + + /** + * Store any data from the $data array into the job. Anything in the message bag will be flashed + * as an error to the user, regardless of its content. + * + * @param array $data + * + * @return MessageBag + * @throws FireflyException + */ + public function configureJob(array $data): MessageBag + { + return $this->getConfigurationObject()->configureJob($data); + } + + /** + * Return the data required for the next step in the job configuration. + * + * @return array + * @throws FireflyException + */ + public function getNextData(): array + { + return $this->getConfigurationObject()->getNextData(); + } + + /** + * Returns the view of the next step in the job configuration. + * + * @return string + * @throws FireflyException + */ + public function getNextView(): string + { + switch ($this->importJob->stage) { + case FinTSConfigurationSteps::NEW: + case FinTSConfigurationSteps::CHOOSE_ACCOUNT: + return 'import.fints.' . $this->importJob->stage; + break; + default: + // @codeCoverageIgnoreStart + throw new FireflyException( + sprintf('FinTSJobConfiguration::getNextView() cannot handle stage "%s"', $this->importJob->stage) + ); + // @codeCoverageIgnoreEnd + } + } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + } + + /** + * Get the configuration handler for this specific stage. + * + * @return FinTSConfigurationInterface + * @throws FireflyException + */ + private function getConfigurationObject(): FinTSConfigurationInterface + { + $class = 'DoNotExist'; + switch ($this->importJob->stage) { + case FinTSConfigurationSteps::NEW: + $class = NewFinTSJobHandler::class; + break; + case FinTSConfigurationSteps::CHOOSE_ACCOUNT: + $class = ChooseAccountHandler::class; + break; + } + if (!class_exists($class)) { + throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class)); // @codeCoverageIgnore + } + + $configurator = app($class); + $configurator->setImportJob($this->importJob); + + return $configurator; + } + + +} \ No newline at end of file diff --git a/app/Import/Routine/FinTSRoutine.php b/app/Import/Routine/FinTSRoutine.php new file mode 100644 index 0000000000..7effeecfc5 --- /dev/null +++ b/app/Import/Routine/FinTSRoutine.php @@ -0,0 +1,84 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Import\Routine; + + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Import\JobConfiguration\FinTSConfigurationSteps; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Support\Import\Routine\FinTS\StageImportDataHandler; +use Illuminate\Support\Facades\Log; + +class FinTSRoutine implements RoutineInterface +{ + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * At the end of each run(), the import routine must set the job to the expected status. + * + * The final status of the routine must be "provider_finished". + * + * @throws FireflyException + */ + public function run(): void + { + Log::debug(sprintf('Now in FinTSRoutine::run() with status "%s" and stage "%s".', $this->importJob->status, $this->importJob->stage)); + $valid = ['ready_to_run']; // should be only ready_to_run + if (\in_array($this->importJob->status, $valid, true)) { + switch ($this->importJob->stage) { + default: + throw new FireflyException(sprintf('FinTSRoutine cannot handle stage "%s".', $this->importJob->stage)); // @codeCoverageIgnore + case FinTSConfigurationSteps::GO_FOR_IMPORT: + $this->repository->setStatus($this->importJob, 'running'); + /** @var StageImportDataHandler $handler */ + $handler = app(StageImportDataHandler::class); + $handler->setImportJob($this->importJob); + $handler->run(); + $transactions = $handler->getTransactions(); + + $this->repository->setTransactions($this->importJob, $transactions); + $this->repository->setStatus($this->importJob, 'provider_finished'); + $this->repository->setStage($this->importJob, 'final'); + + return; + } + } + } + + /** + * @param ImportJob $importJob + * + * @return void + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } + +} \ No newline at end of file diff --git a/app/Support/FinTS/FinTS.php b/app/Support/FinTS/FinTS.php new file mode 100644 index 0000000000..d5dba345ee --- /dev/null +++ b/app/Support/FinTS/FinTS.php @@ -0,0 +1,94 @@ +finTS = new \Fhp\FinTs( + $config['fints_url'], + $config['fints_port'], + $config['fints_bank_code'], + $config['fints_username'], + Crypt::decrypt($config['fints_password']) + ); + } + + public function checkConnection() + { + try { + $this->finTS->getSEPAAccounts(); + return true; + } catch (\Exception $exception) { + return $exception->getMessage(); + } + } + + /** + * @return SEPAAccount[] + * @throws FireflyException + */ + public function getAccounts() + { + try { + return $this->finTS->getSEPAAccounts(); + } catch (\Exception $exception) { + throw new FireflyException($exception->getMessage()); + } + } + + /** + * @param string $accountNumber + * @return SEPAAccount + * @throws FireflyException + */ + public function getAccount(string $accountNumber) + { + $accounts = $this->getAccounts(); + $filteredAccounts = array_filter($accounts, function (SEPAAccount $account) use ($accountNumber) { + return $account->getAccountNumber() == $accountNumber; + }); + if (count($filteredAccounts) != 1) { + throw new FireflyException("Cannot find account with number " . $accountNumber); + } + return reset($filteredAccounts); + } + + /** + * @param SEPAAccount $account + * @param \DateTime $from + * @param \DateTIme $to + * @return \Fhp\Model\StatementOfAccount\StatementOfAccount|null + * @throws FireflyException + */ + public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTIme $to) + { + try { + return $this->finTS->getStatementOfAccount($account, $from, $to); + } catch (\Exception $exception) { + throw new FireflyException($exception->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/Support/Import/JobConfiguration/FinTS/ChooseAccountHandler.php b/app/Support/Import/JobConfiguration/FinTS/ChooseAccountHandler.php new file mode 100644 index 0000000000..d08575288a --- /dev/null +++ b/app/Support/Import/JobConfiguration/FinTS/ChooseAccountHandler.php @@ -0,0 +1,120 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Support\Import\JobConfiguration\FinTS; + + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Import\JobConfiguration\FinTSConfigurationSteps; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Support\FinTS\FinTS; +use Illuminate\Support\MessageBag; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; + +class ChooseAccountHandler implements FinTSConfigurationInterface +{ + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + /** @var AccountRepositoryInterface */ + private $accountRepository; + + /** + * Store data associated with current stage. + * + * @param array $data + * + * @return MessageBag + */ + public function configureJob(array $data): MessageBag + { + $config = $this->importJob->configuration; + $config['fints_account'] = (string)($data['fints_account'] ?? ''); + $config['local_account'] = (string)($data['local_account'] ?? ''); + $config['from_date'] = (string)($data['from_date'] ?? ''); + $config['to_date'] = (string)($data['to_date'] ?? ''); + $this->repository->setConfiguration($this->importJob, $config); + + try { + $finTS = app(FinTS::class, ['config' => $this->importJob->configuration]); + $finTS->getAccount($config['fints_account']); + } catch (FireflyException $e) { + return new MessageBag([$e->getMessage()]); + } + + $this->repository->setStage($this->importJob, FinTSConfigurationSteps::GO_FOR_IMPORT); + + return new MessageBag(); + } + + /** + * Get the data necessary to show the configuration screen. + * + * @return array + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function getNextData(): array + { + $finTS = app(FinTS::class, ['config' => $this->importJob->configuration]); + $finTSAccounts = $finTS->getAccounts(); + $finTSAccountsData = []; + foreach ($finTSAccounts as $account) { + $finTSAccountsData[$account->getAccountNumber()] = $account->getIban(); + } + + $localAccounts = []; + foreach ($this->accountRepository->getAccountsByType([AccountType::ASSET]) as $localAccount) { + $display_name = $localAccount->name; + if ($localAccount->iban) { + $display_name .= " - $localAccount->iban"; + } + $localAccounts[$localAccount->id] = $display_name; + } + + $data = [ + 'fints_accounts' => $finTSAccountsData, + 'fints_account' => $this->importJob->configuration['fints_account'] ?? null, + 'local_accounts' => $localAccounts, + 'local_account' => $this->importJob->configuration['local_account'] ?? null, + 'from_date' => $this->importJob->configuration['from_date'] ?? (new Carbon('now - 1 month'))->format('Y-m-d'), + 'to_date' => $this->importJob->configuration['to_date'] ?? (new Carbon('now'))->format('Y-m-d') + ]; + return $data; + } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } + + +} \ No newline at end of file diff --git a/app/Support/Import/JobConfiguration/FinTS/FinTSConfigurationInterface.php b/app/Support/Import/JobConfiguration/FinTS/FinTSConfigurationInterface.php new file mode 100644 index 0000000000..47bcedad33 --- /dev/null +++ b/app/Support/Import/JobConfiguration/FinTS/FinTSConfigurationInterface.php @@ -0,0 +1,50 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Support\Import\JobConfiguration\FinTS; + +use FireflyIII\Models\ImportJob; +use Illuminate\Support\MessageBag; + +interface FinTSConfigurationInterface +{ + /** + * Store data associated with current stage. + * + * @param array $data + * + * @return MessageBag + */ + public function configureJob(array $data): MessageBag; + + /** + * Get the data necessary to show the configuration screen. + * + * @return array + */ + public function getNextData(): array; + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void; +} \ No newline at end of file diff --git a/app/Support/Import/JobConfiguration/FinTS/NewFinTSJobHandler.php b/app/Support/Import/JobConfiguration/FinTS/NewFinTSJobHandler.php new file mode 100644 index 0000000000..619177ff6d --- /dev/null +++ b/app/Support/Import/JobConfiguration/FinTS/NewFinTSJobHandler.php @@ -0,0 +1,105 @@ +. + */ +declare(strict_types=1); + + +namespace FireflyIII\Support\Import\JobConfiguration\FinTS; + + +use FireflyIII\Import\JobConfiguration\FinTSConfigurationSteps; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Support\FinTS\FinTS; +use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\MessageBag; + +class NewFinTSJobHandler implements FinTSConfigurationInterface +{ + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * Store data associated with current stage. + * + * @param array $data + * + * @return MessageBag + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function configureJob(array $data): MessageBag + { + $config = []; + + $config['fints_url'] = trim($data['fints_url'] ?? ''); + $config['fints_port'] = (int)($data['fints_port'] ?? ''); + $config['fints_bank_code'] = (string)($data['fints_bank_code'] ?? ''); + $config['fints_username'] = (string)($data['fints_username'] ?? ''); + $config['fints_password'] = (string)(Crypt::encrypt($data['fints_password']) ?? ''); + + $this->repository->setConfiguration($this->importJob, $config); + + $incomplete = false; + foreach ($config as $value) { + $incomplete = $value === '' or $incomplete; + } + if ($incomplete) { + return new MessageBag([trans('import.incomplete_fints_form')]); + } + + $finTS = app(FinTS::class, ['config' => $this->importJob->configuration]); + if (($checkConnection = $finTS->checkConnection()) !== true) { + return new MessageBag([trans('import.fints_connection_failed', ['originalError' => $checkConnection])]); + } + + $this->repository->setStage($this->importJob, FinTSConfigurationSteps::CHOOSE_ACCOUNT); + + return new MessageBag(); + } + + /** + * Get the data necessary to show the configuration screen. + * + * @return array + */ + public function getNextData(): array + { + $config = $this->importJob->configuration; + return [ + 'fints_url' => $config['fints_url'] ?? '', + 'fints_port' => $config['fints_port'] ?? '443', + 'fints_bank_code' => $config['fints_bank_code'] ?? '', + 'fints_username' => $config['fints_username'] ?? '' + ]; + } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } + +} \ No newline at end of file diff --git a/app/Support/Import/Routine/FinTS/StageImportDataHandler.php b/app/Support/Import/Routine/FinTS/StageImportDataHandler.php new file mode 100644 index 0000000000..2162b1d754 --- /dev/null +++ b/app/Support/Import/Routine/FinTS/StageImportDataHandler.php @@ -0,0 +1,152 @@ +transactions = []; + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->mapper = app(OpposingAccountMapper::class); + $this->mapper->setUser($importJob->user); + $this->repository->setUser($importJob->user); + $this->accountRepository->setUser($importJob->user); + } + + /** + * @throws FireflyException + */ + public function run() + { + Log::debug('Now in StageImportDataHandler::run()'); + + $localAccount = $this->accountRepository->findNull($this->importJob->configuration['local_account']); + if ($localAccount === null) { + throw new FireflyException('Cannot find Firefly account with id ' . $this->importJob->configuration['local_account']); + } + $finTS = app(FinTS::class, ['config' => $this->importJob->configuration]); + $fintTSAccount = $finTS->getAccount($this->importJob->configuration['fints_account']); + $statementOfAccount = $finTS->getStatementOfAccount($fintTSAccount, new \DateTime($this->importJob->configuration['from_date']), new \DateTime($this->importJob->configuration['to_date'])); + $collection = []; + foreach ($statementOfAccount->getStatements() as $statement) { + foreach ($statement->getTransactions() as $transaction) { + $collection[] = $this->convertTransaction($transaction, $localAccount); + } + } + + $this->transactions = $collection; + } + + private function convertTransaction(FinTSTransaction $transaction, LocalAccount $source): array + { + Log::debug(sprintf('Start converting transaction %s', $transaction->getDescription1())); + + $amount = (string) $transaction->getAmount(); + $debitOrCredit = $transaction->getCreditDebit(); + + Log::debug(sprintf('Amount is %s', $amount)); + if ($debitOrCredit == Transaction::CD_CREDIT) { + $type = TransactionType::DEPOSIT; + } else { + $type = TransactionType::WITHDRAWAL; + $amount = bcmul($amount, '-1'); + } + + $destination = $this->mapper->map( + null, + $amount, + ['iban' => $transaction->getAccountNumber(), 'name' => $transaction->getName()] + ); + if ($debitOrCredit == Transaction::CD_CREDIT) { + [$source, $destination] = [$destination, $source]; + } + + if ($source->accountType->type === AccountType::ASSET && $destination->accountType->type === AccountType::ASSET) { + $type = TransactionType::TRANSFER; + Log::debug('Both are assets, will make transfer.'); + } + + $storeData = [ + 'user' => $this->importJob->user_id, + 'type' => $type, + 'date' => $transaction->getValutaDate()->format('Y-m-d'), + 'description' => $transaction->getDescription1(), + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'bill_id' => null, + 'bill_name' => null, + 'tags' => [], + 'internal_reference' => null, + 'external_id' => null, + 'notes' => null, + 'bunq_payment_id' => null, + 'original-source' => sprintf('fints-v%s', config('firefly.version')), + 'transactions' => [ + // single transaction: + [ + 'description' => null, + 'amount' => $amount, + 'currency_id' => null, + 'currency_code' => 'EUR', + 'foreign_amount' => null, + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + 'source_id' => $source->id, + 'source_name' => null, + 'destination_id' => $destination->id, + 'destination_name' => null, + 'reconciled' => false, + 'identifier' => 0, + ], + ], + ]; + + return $storeData; + } + + /** + * @return array + */ + public function getTransactions(): array + { + return $this->transactions; + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 3a22067015..66d610c88b 100644 --- a/composer.json +++ b/composer.json @@ -64,6 +64,7 @@ "league/commonmark": "0.*", "league/csv": "9.*", "league/fractal": "^0.17.0", + "mschindler83/fints-hbci-php": "^1.0", "pragmarx/google2fa": "3.*", "pragmarx/google2fa-laravel": "0.*", "rcrowe/twigbridge": "0.9.*", diff --git a/config/import.php b/config/import.php index 5f6a724700..ff93e167ae 100644 --- a/config/import.php +++ b/config/import.php @@ -25,6 +25,7 @@ declare(strict_types=1); use FireflyIII\Import\JobConfiguration\BunqJobConfiguration; use FireflyIII\Import\JobConfiguration\FakeJobConfiguration; use FireflyIII\Import\JobConfiguration\FileJobConfiguration; +use FireflyIII\Import\JobConfiguration\FinTSJobConfiguration; use FireflyIII\Import\JobConfiguration\SpectreJobConfiguration; use FireflyIII\Import\JobConfiguration\YnabJobConfiguration; use FireflyIII\Import\Prerequisites\BunqPrerequisites; @@ -34,6 +35,7 @@ use FireflyIII\Import\Prerequisites\YnabPrerequisites; use FireflyIII\Import\Routine\BunqRoutine; use FireflyIII\Import\Routine\FakeRoutine; use FireflyIII\Import\Routine\FileRoutine; +use FireflyIII\Import\Routine\FinTSRoutine; use FireflyIII\Import\Routine\SpectreRoutine; use FireflyIII\Import\Routine\YnabRoutine; use FireflyIII\Support\Import\Routine\File\CSVProcessor; @@ -49,6 +51,7 @@ return [ 'plaid' => false, 'quovo' => false, 'yodlee' => false, + 'fints' => true, 'bad' => false, // always disabled ], // demo user can use these import providers (when enabled): @@ -61,6 +64,7 @@ return [ 'plaid' => false, 'quovo' => false, 'yodlee' => false, + 'fints' => false, ], // a normal user user can use these import providers (when enabled): 'allowed_for_user' => [ @@ -72,6 +76,7 @@ return [ 'plaid' => true, 'quovo' => true, 'yodlee' => true, + 'fints' => true, ], // some providers have pre-requisites. 'has_prereq' => [ @@ -83,6 +88,7 @@ return [ 'plaid' => true, 'quovo' => true, 'yodlee' => true, + 'fints' => false, ], // if so, there must be a class to handle them. 'prerequisites' => [ @@ -94,6 +100,7 @@ return [ 'plaid' => false, 'quovo' => false, 'yodlee' => false, + 'fints' => false, ], // some providers may need extra configuration per job 'has_job_config' => [ @@ -105,6 +112,7 @@ return [ 'plaid' => false, 'quovo' => false, 'yodlee' => false, + 'fints' => true, ], // if so, this is the class that handles it. 'configuration' => [ @@ -116,6 +124,7 @@ return [ 'plaid' => false, 'quovo' => false, 'yodlee' => false, + 'fints' => FinTSJobConfiguration::class, ], // this is the routine that runs the actual import. 'routine' => [ @@ -127,6 +136,7 @@ return [ 'plaid' => false, 'quovo' => false, 'yodlee' => false, + 'fints' => FinTSRoutine::class, ], 'options' => [ diff --git a/public/images/logos/fints.png b/public/images/logos/fints.png new file mode 100644 index 0000000000..0024f8af41 Binary files /dev/null and b/public/images/logos/fints.png differ diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 98f39fc57d..fd79a7f5aa 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -222,6 +222,16 @@ return [ 'public_key' => 'Public key', 'country_code' => 'Country code', 'provider_code' => 'Bank or data-provider', + 'fints_url' => 'FinTS API URL', + 'fints_port' => 'Port', + 'fints_bank_code' => 'Bank code', + 'fints_username' => 'Username', + 'fints_password' => 'PIN / Password', + 'fints_account' => 'FinTS account', + 'local_account' => 'Firefly III account', + 'from_date' => 'Date from', + 'to_date' => 'Date to', + 'due_date' => 'Due date', 'payment_date' => 'Payment date', diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 16f0e84c73..783b8f0a7f 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -199,7 +199,14 @@ return [ 'spectre_extra_key_units' => 'Units', 'spectre_extra_key_unit_price' => 'Unit price', 'spectre_extra_key_transactions_count' => 'Transaction count', - + //job configuration for finTS + 'fints_connection_failed' => 'An error occurred while trying to connecting to your bank. Please mak sure that all the data you entered is correct. Original error message: :originalError', + 'button_fints' => 'FinTS', + 'job_config_fints_url_help' => 'E.g. https://banking-dkb.s-fints-pt-dkb.de/fints30', + 'job_config_fints_username_help' => 'For many banks this is your account number.', + 'job_config_fints_port_help' => 'The default port is 443.', + 'job_config_fints_account_help' => 'Choose the bank account for which you want to import transactions.', + 'job_config_local_account_help' => 'Choose the Firefly III account corresponding to your bank account chosen above.', // specifics: 'specific_ing_name' => 'ING NL', 'specific_ing_descr' => 'Create better descriptions in ING exports', diff --git a/resources/views/import/fints/choose_account.twig b/resources/views/import/fints/choose_account.twig new file mode 100644 index 0000000000..8b0509dac7 --- /dev/null +++ b/resources/views/import/fints/choose_account.twig @@ -0,0 +1,44 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render }} +{% endblock %} +{% block content %} +
+{% endblock %} \ No newline at end of file diff --git a/resources/views/import/fints/new.twig b/resources/views/import/fints/new.twig new file mode 100644 index 0000000000..70e67b057a --- /dev/null +++ b/resources/views/import/fints/new.twig @@ -0,0 +1,39 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render }} +{% endblock %} +{% block content %} + +{% endblock %} \ No newline at end of file