diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6df27b4876..43ea1596b9 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,20 +4,64 @@ ## Feature requests -If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/). +I am always interested in expanding Firefly III's many features. If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/). ## Bugs -If you find a bug, please take the time and see if the [demo site](https://firefly-iii.nder.be/) is also suffering from this bug. Include as many log files and details as you think are necessary. +First of all: thank you for reporting a bug instead of ditching the tool altogether. If you find a bug, please take the time and see if the [demo site](https://firefly-iii.nder.be/) is also suffering from this bug. Include as many log files and details as you think are necessary. Bugs have a lot of priority! ## Installation problems -Take the time to read the [installation guide FAQ](https://firefly-iii.github.io/installation-guide-faq/) and make sure you search through closed issues for the problems other people have had. Your problem may be among them! +Please take the time to read the [installation guide FAQ](https://firefly-iii.github.io/installation-guide-faq/) and make sure you search through closed issues for the problems other people have had. Your problem may be among them! If not, open an issue and I will help where I can. ## Pull requests -I can only accept pull requests against the `develop` branch, never the `master` branch. +When contributing to Firefly III, please first discuss the change you wish to make via issue, email, or any other method. I can only accept pull requests against the `develop` branch, never the `master` branch. ## Translations :us: :fr: :de: -If you see a spelling error, grammatical error or a weird translation in your language, please join [our CrowdIn](https://crowdin.com/project/firefly-iii) project. There, you can submit your translations and fixes. The GitHub repository will download these automatically and they will be included in the next release. \ No newline at end of file +If you see a spelling error, grammatical error or a weird translation in your language, please join [our CrowdIn](https://crowdin.com/project/firefly-iii) project. There, you can submit your translations and fixes. The GitHub repository will download these automatically and they will be included in the next release. + +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at thegrumpydictator@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org/), version 1.4, available at [http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4). diff --git a/.travis.yml b/.travis.yml index 9e633c9bb1..930e42cb60 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: - php artisan optimize - php artisan env - cp .env.testing .env - - mv storage/database/databasecopy.sqlite storage/database/database.sqlite + - wget -q https://github.com/firefly-iii/test-data/raw/master/storage/database.sqlite -O storage/database/database.sqlite - mkdir -p build/logs script: diff --git a/CHANGELOG.md b/CHANGELOG.md index 33a9707ff9..6baf55b7b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,24 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [4.5.0] - 2017-07-07 +## [4.6.0] - 2017-06-28 + +### Changed +- Revamped import routine. Will be buggy. + +### Fixed +- Issue #667, postgresql reported by @skibbipl. +- Issue #680 by @Xeli +- Fixed #660 +- Fixes #672, reported by @dzaikos +- Translation error fixed by +- Fix a bug where the balance routine forgot to account for accounts without a currency preference. +- Various other bugfixes. + +### Security +- Initial release. + +## [4.5.0] - 2017-06-07 ### Added - Better support for multi-currency transactions and display of transactions, accounts and everything. This requires a database overhaul (moving the currency information to specific transactions) so be careful when upgrading. @@ -11,7 +28,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Expanded Docker to work with postgresql as well, thanks to @kressh ### Fixed -- PostgreSQL support in database upgrade routine (#644, reported by @) +- PostgreSQL support in database upgrade routine (#644, reported by @skibbipl) - Frontpage budget chart was off, fix by @nhaarman - Was not possible to remove opening balance. diff --git a/README.md b/README.md index 069c7cbee8..d1019609ff 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,9 @@ [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master) -Firefly III can be run on Heroku. Register for a free Heroku account and instantly run Firefly III on your very own cloud instance. +Firefly III can be run on Heroku. Register for a free Heroku account and instantly run Firefly III on your very own cloud instance. There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present. -There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present. - -## Installation +## Getting started To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://firefly-iii.github.io/using-installing.html). @@ -26,7 +24,7 @@ Personal financial management is pretty difficult, and everybody has their own a Firefly works on the principle that if you know where you're money is going, you can stop it from going there. -#### Some advantages of using Firefly +### Some advantages of using Firefly - Firefly can import any CSV file, so migrating from other systems is easy. - Firefly runs on your own server, so you are fully in control of your data. Remember, there is no such thing as "the cloud", it’s just somebody else’s computer! @@ -35,6 +33,25 @@ Firefly works on the principle that if you know where you're money is going, you Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/). +### Contributing + +Please read [CONTRIBUTING.md](https://github.com/firefly-iii/firefly-iii/blob/master/.github/CONTRIBUTING.md) for details on the code of conduct, and the process for submitting pull requests. + +### Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository. + +### Authors + +* James Cole +* Over time, [many people have contributed to Firefly III](https://github.com/firefly-iii/firefly-iii/graphs/contributors). + +### License + +This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/LICENSE) under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/). + +### Other stuff + If you like Firefly and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!) If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com). diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php index 3c0d14fb18..15ed5cd468 100644 --- a/app/Console/Commands/CreateImport.php +++ b/app/Console/Commands/CreateImport.php @@ -88,7 +88,7 @@ class CreateImport extends Command $this->line('Stored import data...'); $job->configuration = $configurationData; - $job->status = 'settings_complete'; + $job->status = 'configured'; $job->save(); $this->line('Stored configuration...'); diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php index d2ff9b8997..b8cecf4202 100644 --- a/app/Console/Commands/Import.php +++ b/app/Console/Commands/Import.php @@ -13,12 +13,13 @@ declare(strict_types=1); namespace FireflyIII\Console\Commands; -use FireflyIII\Import\ImportProcedure; use FireflyIII\Import\Logging\CommandHandler; +use FireflyIII\Import\Routine\ImportRoutine; use FireflyIII\Models\ImportJob; use FireflyIII\Models\TransactionJournal; use Illuminate\Console\Command; use Illuminate\Support\Collection; +use Illuminate\Support\MessageBag; use Log; /** @@ -75,15 +76,18 @@ class Import extends Command $monolog = Log::getMonolog(); $handler = new CommandHandler($this); $monolog->pushHandler($handler); - $importProcedure = new ImportProcedure; - $result = $importProcedure->runImport($job); - // display result to user: - $this->presentResults($result); - $this->line('The import has completed.'); + /** @var ImportRoutine $routine */ + $routine = app(ImportRoutine::class); + $routine->setJob($job); + $routine->run(); - // get any errors from the importer: - $this->presentErrors($job); + /** @var MessageBag $error */ + foreach ($routine->errors as $index => $error) { + $this->error(sprintf('Error importing line #%d: %s', $index, $error)); + } + + $this->line(sprintf('The import has finished. %d transactions have been imported out of %d records.', $routine->journals->count(), $routine->lines)); return; } @@ -96,12 +100,14 @@ class Import extends Command private function isValid(ImportJob $job): bool { if (is_null($job)) { + Log::error('This job does not seem to exist.'); $this->error('This job does not seem to exist.'); return false; } - if ($job->status != 'settings_complete') { + if ($job->status !== 'configured') { + Log::error(sprintf('This job is not ready to be imported (status is %s).', $job->status)); $this->error('This job is not ready to be imported.'); return false; @@ -109,36 +115,4 @@ class Import extends Command return true; } - - /** - * @param ImportJob $job - */ - private function presentErrors(ImportJob $job) - { - $extendedStatus = $job->extended_status; - if (isset($extendedStatus['errors']) && count($extendedStatus['errors']) > 0) { - $this->line(sprintf('The following %d error(s) occured during the import:', count($extendedStatus['errors']))); - foreach ($extendedStatus['errors'] as $error) { - $this->error($error); - } - } - } - - /** - * @param Collection $result - */ - private function presentResults(Collection $result) - { - /** - * @var int $index - * @var TransactionJournal $journal - */ - foreach ($result as $index => $journal) { - if (!is_null($journal->id)) { - $this->line(sprintf('Line #%d has been imported as transaction #%d.', $index, $journal->id)); - continue; - } - $this->error(sprintf('Could not store line #%d', $index)); - } - } } diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 107f61f604..42566a6cee 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -316,6 +316,7 @@ class UpgradeDatabase extends Command $notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.'; $transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.'; $driver = DB::connection()->getDriverName(); + $pgsql = ['pgsql', 'postgresql']; foreach ($types as $type => $operator) { $query = TransactionJournal @@ -328,10 +329,10 @@ class UpgradeDatabase extends Command ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id') ->where('transaction_types.type', $type) ->where('account_meta.name', 'currency_id'); - if ($driver === 'postgresql') { + if (in_array($driver, $pgsql)) { $query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('cast(account_meta.data as int)')); } - if ($driver !== 'postgresql') { + if (!in_array($driver, $pgsql)) { $query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data')); } diff --git a/app/Generator/Report/Tag/MonthReportGenerator.php b/app/Generator/Report/Tag/MonthReportGenerator.php index f8a253defd..3f247f4467 100644 --- a/app/Generator/Report/Tag/MonthReportGenerator.php +++ b/app/Generator/Report/Tag/MonthReportGenerator.php @@ -14,6 +14,7 @@ namespace FireflyIII\Generator\Report\Tag; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; +use FireflyIII\Generator\Report\Support; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Filter\NegativeAmountFilter; use FireflyIII\Helpers\Filter\OpposingAccountFilter; @@ -30,7 +31,7 @@ use Log; * * @package FireflyIII\Generator\Report\Tag */ -class MonthReportGenerator implements ReportGeneratorInterface +class MonthReportGenerator extends Support implements ReportGeneratorInterface { /** @var Collection */ diff --git a/app/Helpers/Attachments/AttachmentHelper.php b/app/Helpers/Attachments/AttachmentHelper.php index 58cf4da50e..af83f729a7 100644 --- a/app/Helpers/Attachments/AttachmentHelper.php +++ b/app/Helpers/Attachments/AttachmentHelper.php @@ -18,6 +18,7 @@ use FireflyIII\Models\Attachment; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; +use Log; use Storage; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -157,11 +158,14 @@ class AttachmentHelper implements AttachmentHelperInterface $attachment->size = $file->getSize(); $attachment->uploaded = 0; $attachment->save(); + Log::debug('Created attachment:', $attachment->toArray()); $fileObject = $file->openFile('r'); $fileObject->rewind(); $content = $fileObject->fread($file->getSize()); $encrypted = Crypt::encrypt($content); + Log::debug(sprintf('Full file length is %d and upload size is %d.', strlen($content), $file->getSize())); + Log::debug(sprintf('Encrypted content is %d', strlen($encrypted))); // store it: $this->uploadDisk->put($attachment->fileName(), $encrypted); diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php index cd8de38752..8e1bba3a03 100644 --- a/app/Http/Controllers/AttachmentController.php +++ b/app/Http/Controllers/AttachmentController.php @@ -98,10 +98,14 @@ class AttachmentController extends Controller */ public function download(AttachmentRepositoryInterface $repository, Attachment $attachment) { + + if ($repository->exists($attachment)) { $content = $repository->getContent($attachment); $quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\')); + + /** @var LaravelResponse $response */ $response = response($content, 200); $response @@ -149,6 +153,7 @@ class AttachmentController extends Controller { $image = 'images/page_green.png'; + if ($attachment->mime == 'application/pdf') { $image = 'images/page_white_acrobat.png'; } diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index c0af003e81..61194faff9 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -127,6 +127,7 @@ class AccountController extends Controller $chartData[$account->name] = $diff; } } + arsort($chartData); $data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData); $cache->store($data); @@ -424,7 +425,7 @@ class AccountController extends Controller } arsort($chartData); - $data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData); + $data = $this->generator->singleSet(strval(trans('firefly.earned')), $chartData); $cache->store($data); return Response::json($data); diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 325698f720..310b418789 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -12,32 +12,29 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; -use Crypt; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Requests\ImportUploadRequest; -use FireflyIII\Import\ImportProcedureInterface; -use FireflyIII\Import\Setup\SetupInterface; +use FireflyIII\Import\Configurator\ConfiguratorInterface; +use FireflyIII\Import\Routine\ImportRoutine; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; -use FireflyIII\Repositories\User\UserRepositoryInterface; use Illuminate\Http\Request; use Illuminate\Http\Response as LaravelResponse; use Log; use Response; -use Session; -use SplFileObject; -use Storage; -use Symfony\Component\HttpFoundation\File\UploadedFile; use View; /** - * Class ImportController + * Class ImportController. * * @package FireflyIII\Http\Controllers */ class ImportController extends Controller { + /** @var ImportJobRepositoryInterface */ + public $repository; + /** * */ @@ -48,7 +45,8 @@ class ImportController extends Controller $this->middleware( function ($request, $next) { View::share('mainTitleIcon', 'fa-archive'); - View::share('title', trans('firefly.import_data_full')); + View::share('title', trans('firefly.import_index_title')); + $this->repository = app(ImportJobRepositoryInterface::class); return $next($request); } @@ -56,28 +54,7 @@ class ImportController extends Controller } /** - * This is the last step before the import starts. - * - * @param ImportJob $job - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View - */ - public function complete(ImportJob $job) - { - Log::debug('Now in complete()', ['job' => $job->key]); - if (!$this->jobInCorrectStep($job, 'complete')) { - return $this->redirectToCorrectStep($job); - } - $subTitle = trans('firefly.import_complete'); - $subTitleIcon = 'fa-star'; - - return view('import.complete', compact('job', 'subTitle', 'subTitleIcon')); - } - - /** - * This is step 3. - * This is the first step in configuring the job. It can only be executed - * when the job is set to "import_status_never_started". + * This is step 3. This repeats until the job is configured. * * @param ImportJob $job * @@ -86,37 +63,43 @@ class ImportController extends Controller */ public function configure(ImportJob $job) { - Log::debug('Now at start of configure()'); - if (!$this->jobInCorrectStep($job, 'configure')) { - return $this->redirectToCorrectStep($job); - } + // create configuration class: + $configurator = $this->makeConfigurator($job); - // actual code - $importer = $this->makeImporter($job); - $importer->configure(); - $data = $importer->getConfigurationData(); - $subTitle = trans('firefly.configure_import'); + // is the job already configured? + if ($configurator->isJobConfigured()) { + $this->repository->updateStatus($job, 'configured'); + + return redirect(route('import.status', [$job->key])); + } + $view = $configurator->getNextView(); + $data = $configurator->getNextData(); + $subTitle = trans('firefly.import_config_bread_crumb'); $subTitleIcon = 'fa-wrench'; - return view('import.' . $job->file_type . '.configure', compact('data', 'job', 'subTitle', 'subTitleIcon')); + return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon')); } /** - * Generate a JSON file of the job's config and send it to the user. + * Generate a JSON file of the job's configuration and send it to the user. * * @param ImportJob $job * - * @return mixed + * @return LaravelResponse */ public function download(ImportJob $job) { Log::debug('Now in download()', ['job' => $job->key]); - $config = $job->configuration; + $config = $job->configuration; + + // TODO this is CSV import specific: $config['column-roles-complete'] = false; $config['column-mapping-complete'] = false; + $config['initial-config-complete'] = false; $config['delimiter'] = $config['delimiter'] === "\t" ? 'tab' : $config['delimiter']; - $result = json_encode($config, JSON_PRETTY_PRINT); - $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); + + $result = json_encode($config, JSON_PRETTY_PRINT); + $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); /** @var LaravelResponse $response */ $response = response($result, 200); @@ -134,26 +117,6 @@ class ImportController extends Controller } - /** - * @param ImportJob $job - * - * @return View - */ - public function finished(ImportJob $job) - { - if (!$this->jobInCorrectStep($job, 'finished')) { - return $this->redirectToCorrectStep($job); - } - - // if there is a tag (there might not be), we can link to it: - $tagId = $job->extended_status['importTag'] ?? 0; - - $subTitle = trans('firefly.import_finished'); - $subTitleIcon = 'fa-star'; - - return view('import.finished', compact('job', 'subTitle', 'subTitleIcon', 'tagId')); - } - /** * This is step 1. Upload a file. * @@ -161,8 +124,7 @@ class ImportController extends Controller */ public function index() { - Log::debug('Now at index'); - $subTitle = trans('firefly.import_data_index'); + $subTitle = trans('firefly.import_index_sub_title'); $subTitleIcon = 'fa-home'; $importFileTypes = []; $defaultImportType = config('firefly.default_import_format'); @@ -175,42 +137,75 @@ class ImportController extends Controller } /** + * This is step 2. It creates an Import Job. Stores the import. + * + * @param ImportUploadRequest $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function initialize(ImportUploadRequest $request) + { + Log::debug('Now in initialize()'); + + // create import job: + $type = $request->get('import_file_type'); + $job = $this->repository->create($type); + Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]); + + // process file: + $this->repository->processFile($job, $request->files->get('import_file')); + + // process config, if present: + if ($request->files->has('configuration_file')) { + $this->repository->processConfiguration($job, $request->files->get('configuration_file')); + } + + $this->repository->updateStatus($job, 'initialized'); + + return redirect(route('import.configure', [$job->key])); + } + + /** + * + * Show status of import job in JSON. + * * @param ImportJob $job * * @return \Illuminate\Http\JsonResponse */ public function json(ImportJob $job) { - $result = [ - 'showPercentage' => false, - 'started' => false, - 'finished' => false, - 'running' => false, - 'errors' => $job->extended_status['errors'], - 'percentage' => 0, - 'steps' => $job->extended_status['total_steps'], - 'stepsDone' => $job->extended_status['steps_done'], - 'statusText' => trans('firefly.import_status_' . $job->status), - 'finishedText' => '', + $result = [ + 'started' => false, + 'finished' => false, + 'running' => false, + 'errors' => array_values($job->extended_status['errors']), + 'percentage' => 0, + 'show_percentage' => false, + 'steps' => $job->extended_status['steps'], + 'done' => $job->extended_status['done'], + 'statusText' => trans('firefly.import_status_job_' . $job->status), + 'status' => $job->status, + 'finishedText' => '', ]; - $percentage = 0; - if ($job->extended_status['total_steps'] !== 0) { - $percentage = round(($job->extended_status['steps_done'] / $job->extended_status['total_steps']) * 100, 0); + + if ($job->extended_status['steps'] !== 0) { + $result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0); + $result['show_percentage'] = true; } - if ($job->status === 'import_complete') { - $tagId = $job->extended_status['importTag']; + + if ($job->status === 'finished') { + $tagId = $job->extended_status['tag']; /** @var TagRepositoryInterface $repository */ $repository = app(TagRepositoryInterface::class); $tag = $repository->find($tagId); $result['finished'] = true; - $result['finishedText'] = trans('firefly.import_finished_link', ['link' => route('tags.show', [$tag->id]), 'tag' => $tag->tag]); + $result['finishedText'] = trans('firefly.import_status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]); } - if ($job->status === 'import_running') { - $result['started'] = true; - $result['running'] = true; - $result['showPercentage'] = true; - $result['percentage'] = $percentage; + if ($job->status === 'running') { + $result['started'] = true; + $result['running'] = true; } return Response::json($result); @@ -219,286 +214,82 @@ class ImportController extends Controller /** * Step 4. Save the configuration. * - * @param Request $request - * @param ImportJobRepositoryInterface $repository - * @param ImportJob $job - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function postConfigure(Request $request, ImportJobRepositoryInterface $repository, ImportJob $job) - { - Log::debug('Now in postConfigure()', ['job' => $job->key]); - if (!$this->jobInCorrectStep($job, 'process')) { - return $this->redirectToCorrectStep($job); - } - Log::debug('Continue postConfigure()', ['job' => $job->key]); - - // actual code - $importer = $this->makeImporter($job); - $data = $request->all(); - $files = $request->files; - $importer->saveImportConfiguration($data, $files); - - // update job: - $repository->updateStatus($job, 'import_configuration_saved'); - - // return redirect to settings. - // this could loop until the user is done. - return redirect(route('import.settings', [$job->key])); - } - - /** - * This step 6. Depending on the importer, this will process the - * settings given and store them. - * * @param Request $request * @param ImportJob $job * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws FireflyException */ - public function postSettings(Request $request, ImportJob $job) + public function postConfigure(Request $request, ImportJob $job) { - Log::debug('Now in postSettings()', ['job' => $job->key]); - if (!$this->jobInCorrectStep($job, 'store-settings')) { - return $this->redirectToCorrectStep($job); - } - $importer = $this->makeImporter($job); - $importer->storeSettings($request); - - // return redirect to settings (for more settings perhaps) - return redirect(route('import.settings', [$job->key])); - } - - /** - * Step 5. Depending on the importer, this will show the user settings to - * fill in. - * - * @param ImportJobRepositoryInterface $repository - * @param ImportJob $job - * - * @return View - */ - public function settings(ImportJobRepositoryInterface $repository, ImportJob $job) - { - Log::debug('Now in settings()', ['job' => $job->key]); - if (!$this->jobInCorrectStep($job, 'settings')) { - return $this->redirectToCorrectStep($job); - } - Log::debug('Continue in settings()'); - $importer = $this->makeImporter($job); - $subTitle = trans('firefly.settings_for_import'); - $subTitleIcon = 'fa-wrench'; - - // now show settings screen to user. - if ($importer->requireUserSettings()) { - Log::debug('Job requires user config.'); - $data = $importer->getDataForSettings(); - $view = $importer->getViewForSettings(); - - return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon')); - } - Log::debug('Job does NOT require user config.'); - - $repository->updateStatus($job, 'settings_complete'); - - // if no more settings, save job and continue to process thing. - return redirect(route('import.complete', [$job->key])); - - // ask the importer for the requested action. - // for example pick columns or map data. - // depends of course on the data in the job. - } - - /** - * @param ImportProcedureInterface $importProcedure - * @param ImportJob $job - */ - public function start(ImportProcedureInterface $importProcedure, ImportJob $job) - { - set_time_limit(0); - if ($job->status == 'settings_complete') { - $importProcedure->runImport($job); - } - } - - /** - * This is the last step before the import starts. - * - * @param ImportJob $job - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View - */ - public function status(ImportJob $job) - { // - Log::debug('Now in status()', ['job' => $job->key]); - if (!$this->jobInCorrectStep($job, 'status')) { - return $this->redirectToCorrectStep($job); - } - $subTitle = trans('firefly.import_status'); - $subTitleIcon = 'fa-star'; - - return view('import.status', compact('job', 'subTitle', 'subTitleIcon')); - } - - - /** - * This is step 2. It creates an Import Job. Stores the import. - * - * @param ImportUploadRequest $request - * @param ImportJobRepositoryInterface $repository - * @param UserRepositoryInterface $userRepository - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function upload(ImportUploadRequest $request, ImportJobRepositoryInterface $repository, UserRepositoryInterface $userRepository) - { - Log::debug('Now in upload()'); - // create import job: - $type = $request->get('import_file_type'); - $job = $repository->create($type); - Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]); - - /** @var UploadedFile $upload */ - $upload = $request->files->get('import_file'); - $newName = $job->key . '.upload'; - $uploaded = new SplFileObject($upload->getRealPath()); - $content = $uploaded->fread($uploaded->getSize()); - $contentEncrypted = Crypt::encrypt($content); - $disk = Storage::disk('upload'); - - // user is demo user, replace upload with prepared file. - if ($userRepository->hasRole(auth()->user(), 'demo')) { - $stubsDisk = Storage::disk('stubs'); - $content = $stubsDisk->get('demo-import.csv'); - $contentEncrypted = Crypt::encrypt($content); - $disk->put($newName, $contentEncrypted); - Log::debug('Replaced upload with demo file.'); - - // also set up prepared configuration. - $configuration = json_decode($stubsDisk->get('demo-configuration.json'), true); - $repository->setConfiguration($job, $configuration); - Log::debug('Set configuration for demo user', $configuration); - - // also flash info - Session::flash('info', trans('demo.import-configure-security')); - } - if (!$userRepository->hasRole(auth()->user(), 'demo')) { - // user is not demo, process original upload: - $disk->put($newName, $contentEncrypted); - Log::debug('Uploaded file', ['name' => $upload->getClientOriginalName(), 'size' => $upload->getSize(), 'mime' => $upload->getClientMimeType()]); - } - - // store configuration file's content into the job's configuration thing. Otherwise, leave it empty. - // demo user's configuration upload is ignored completely. - if ($request->files->has('configuration_file') && !auth()->user()->hasRole('demo')) { - /** @var UploadedFile $configFile */ - $configFile = $request->files->get('configuration_file'); - Log::debug( - 'Uploaded configuration file', - ['name' => $configFile->getClientOriginalName(), 'size' => $configFile->getSize(), 'mime' => $configFile->getClientMimeType()] - ); - - $configFileObject = new SplFileObject($configFile->getRealPath()); - $configRaw = $configFileObject->fread($configFileObject->getSize()); - $configuration = json_decode($configRaw, true); - - // @codeCoverageIgnoreStart - if (!is_null($configuration) && is_array($configuration)) { - Log::debug('Found configuration', $configuration); - $repository->setConfiguration($job, $configuration); - } - // @codeCoverageIgnoreEnd + Log::debug('Now in postConfigure()', ['job' => $job->key]); + $configurator = $this->makeConfigurator($job); + + // is the job already configured? + if ($configurator->isJobConfigured()) { + return redirect(route('import.status', [$job->key])); } + $data = $request->all(); + $configurator->configureJob($data); + // return to configure return redirect(route('import.configure', [$job->key])); } /** * @param ImportJob $job - * @param string $method * - * @return bool + * @return \Illuminate\Http\JsonResponse + * @throws FireflyException */ - private function jobInCorrectStep(ImportJob $job, string $method): bool + public function start(ImportJob $job) { - Log::debug('Now in jobInCorrectStep()', ['job' => $job->key, 'method' => $method]); - switch ($method) { - case 'configure': - case 'process': - return $job->status === 'import_status_never_started'; - case 'settings': - case 'store-settings': - Log::debug(sprintf('Job %d with key %s has status %s', $job->id, $job->key, $job->status)); - - return $job->status === 'import_configuration_saved'; - case 'finished': - return $job->status === 'import_complete'; - case 'complete': - return $job->status === 'settings_complete'; - case 'status': - return ($job->status === 'settings_complete') || ($job->status === 'import_running'); + /** @var ImportRoutine $routine */ + $routine = app(ImportRoutine::class); + $routine->setJob($job); + $result = $routine->run(); + if ($result) { + return Response::json(['run' => 'ok']); } - return false; // @codeCoverageIgnore - + throw new FireflyException('Job did not complete succesfully.'); } /** * @param ImportJob $job * - * @return SetupInterface - * @throws FireflyException + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View */ - private function makeImporter(ImportJob $job): SetupInterface + public function status(ImportJob $job) { - // create proper importer (depends on job) - $type = strtolower($job->file_type); - - // validate type: - $validTypes = array_keys(config('firefly.import_formats')); - - - if (in_array($type, $validTypes)) { - /** @var SetupInterface $importer */ - $importer = app('FireflyIII\Import\Setup\\' . ucfirst($type) . 'Setup'); - $importer->setJob($job); - - return $importer; + $statuses = ['configured', 'running', 'finished']; + if (!in_array($job->status, $statuses)) { + return redirect(route('import.configure', [$job->key])); } - throw new FireflyException(sprintf('"%s" is not a valid file type', $type)); // @codeCoverageIgnore + $subTitle = trans('firefly.import_status_sub_title'); + $subTitleIcon = 'fa-star'; + return view('import.status', compact('job', 'subTitle', 'subTitleIcon')); } /** * @param ImportJob $job * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @return ConfiguratorInterface * @throws FireflyException */ - private function redirectToCorrectStep(ImportJob $job) + private function makeConfigurator(ImportJob $job): ConfiguratorInterface { - Log::debug('Now in redirectToCorrectStep()', ['job' => $job->key]); - switch ($job->status) { - case 'import_status_never_started': - Log::debug('Will redirect to configure()'); - - return redirect(route('import.configure', [$job->key])); - case 'import_configuration_saved': - Log::debug('Will redirect to settings()'); - - return redirect(route('import.settings', [$job->key])); - case 'settings_complete': - Log::debug('Will redirect to complete()'); - - return redirect(route('import.complete', [$job->key])); - case 'import_complete': - Log::debug('Will redirect to finished()'); - - return redirect(route('import.finished', [$job->key])); + $type = $job->file_type; + $key = sprintf('firefly.import_configurators.%s', $type); + $className = config($key); + if (is_null($className)) { + throw new FireflyException('Cannot find configurator class for this job.'); // @codeCoverageIgnore } + /** @var ConfiguratorInterface $configurator */ + $configurator = app($className); + $configurator->setJob($job); - throw new FireflyException('Cannot redirect for job state ' . $job->status); // @codeCoverageIgnore + return $configurator; } } diff --git a/app/Http/Controllers/Report/CategoryController.php b/app/Http/Controllers/Report/CategoryController.php index 1528f44460..969ea8afdc 100644 --- a/app/Http/Controllers/Report/CategoryController.php +++ b/app/Http/Controllers/Report/CategoryController.php @@ -120,7 +120,7 @@ class CategoryController extends Controller foreach ($categories as $category) { $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end); if (bccomp($spent, '0') !== 0) { - $report[$category->id] = ['name' => $category->name, 'spent' => $spent]; + $report[$category->id] = ['name' => $category->name, 'spent' => $spent,'id' => $category->id]; } } diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index fa94a1b612..91a0f6437f 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -15,6 +15,7 @@ namespace FireflyIII\Http\Controllers; use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Http\Requests\TagFormRequest; use FireflyIII\Models\Tag; use FireflyIII\Repositories\Tag\TagRepositoryInterface; @@ -245,6 +246,7 @@ class TagController extends Controller $periods = new Collection; $apiKey = env('GOOGLE_MAPS_API_KEY', ''); $sum = '0'; + $path = 'tags/show/' . $tag->id; // prep for "all" view. @@ -253,6 +255,7 @@ class TagController extends Controller $start = $repository->firstUseDate($tag); $end = new Carbon; $sum = $repository->sumOfTag($tag); + $path = 'tags/show/' . $tag->id . '/all'; } // prep for "specific date" view. @@ -282,13 +285,13 @@ class TagController extends Controller Log::info('Now at tag loop start.'); while ($count === 0 && $loop < 3) { $loop++; - Log::info('Count is zero, search for journals.'); + Log::info(sprintf('Count is zero, search for journals between %s and %s (pagesize %d, page %d).', $start, $end, $pageSize, $page)); /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() - ->setTag($tag)->withBudgetInformation()->withCategoryInformation(); + ->setTag($tag)->withBudgetInformation()->withCategoryInformation()->removeFilter(InternalTransferFilter::class); $journals = $collector->getPaginatedJournals(); - $journals->setPath('tags/show/' . $tag->id); + $journals->setPath($path); $count = $journals->getCollection()->count(); if ($count === 0) { $start->subDay(); diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index 2b1e8bf061..b01335dce7 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -229,8 +229,8 @@ class ConvertController extends Controller switch ($joined) { default: throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore - case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one - case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: // six + case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: + case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: if ($data['source_account_revenue'] === '') { // destination is a cash account. @@ -248,14 +248,14 @@ class ConvertController extends Controller ]; $source = $accountRepository->store($data); break; - case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: // two - case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: // five + case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: + case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: $source = $sourceAccount; break; - case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: // three + case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: $source = $destinationAccount; break; - case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: // four + case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: $source = $accountRepository->find(intval($data['source_account_asset'])); break; } diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 75bb6a3c8b..71b5bc9430 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -86,14 +86,16 @@ class SingleController extends Controller public function cloneTransaction(TransactionJournal $journal) { - $source = $journal->sourceAccountList()->first(); - $destination = $journal->destinationAccountList()->first(); - $budget = $journal->budgets()->first(); - $budgetId = is_null($budget) ? 0 : $budget->id; - $category = $journal->categories()->first(); - $categoryName = is_null($category) ? '' : $category->name; - $tags = join(',', $journal->tags()->get()->pluck('tag')->toArray()); - + $source = $journal->sourceAccountList()->first(); + $destination = $journal->destinationAccountList()->first(); + $budget = $journal->budgets()->first(); + $budgetId = is_null($budget) ? 0 : $budget->id; + $category = $journal->categories()->first(); + $categoryName = is_null($category) ? '' : $category->name; + $tags = join(',', $journal->tags()->get()->pluck('tag')->toArray()); + $transaction = $journal->transactions()->first(); + $amount = Steam::positive($transaction->amount); + $foreignAmount = is_null($transaction->foreign_amount) ? null : Steam::positive($transaction->foreign_amount); $preFilled = [ 'description' => $journal->description, @@ -101,7 +103,10 @@ class SingleController extends Controller 'source_account_name' => $source->name, 'destination_account_id' => $destination->id, 'destination_account_name' => $destination->name, - 'amount' => $journal->amountPositive(), + 'amount' => $amount, + 'source_amount' => $amount, + 'destination_amount' => $foreignAmount, + 'foreign_amount' => $foreignAmount, 'date' => $journal->date->format('Y-m-d'), 'budget_id' => $budgetId, 'category' => $categoryName, diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index e6668bc43e..32f0807c3b 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -218,7 +218,7 @@ class SplitController extends Controller 'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id), 'destinationAccounts' => $destinationAccounts, 'what' => strtolower($journal->transactionTypeStr()), - 'date' => $request->old('date', $journal->date), + 'date' => $request->old('date', $journal->date->format('Y-m-d')), 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), // all custom fields: diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 2ac6a55db8..764de4c669 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -78,6 +78,7 @@ class TransactionController extends Controller $start = null; $end = null; $periods = new Collection; + $path = '/transactions/' . $what; // prep for "all" view. if ($moment === 'all') { @@ -85,6 +86,7 @@ class TransactionController extends Controller $first = $repository->first(); $start = $first->date ?? new Carbon; $end = new Carbon; + $path = '/transactions/'.$what.'/all/'; } // prep for "specific date" view. @@ -118,7 +120,7 @@ class TransactionController extends Controller $collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount(); $collector->removeFilter(InternalTransferFilter::class); $journals = $collector->getPaginatedJournals(); - $journals->setPath('/transactions/' . $what); + $journals->setPath($path); $count = $journals->getCollection()->count(); if ($count === 0) { $start->subDay(); diff --git a/app/Http/Requests/ImportUploadRequest.php b/app/Http/Requests/ImportUploadRequest.php index 93065bcada..758ee6164a 100644 --- a/app/Http/Requests/ImportUploadRequest.php +++ b/app/Http/Requests/ImportUploadRequest.php @@ -38,8 +38,9 @@ class ImportUploadRequest extends Request $types = array_keys(config('firefly.import_formats')); return [ - 'import_file' => 'required|file', - 'import_file_type' => 'required|in:' . join(',', $types), + 'import_file' => 'required|file', + 'import_file_type' => 'required|in:' . join(',', $types), + 'configuration_file' => 'file', ]; } diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 5668c16162..7bd76f3d9e 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -469,26 +469,20 @@ Breadcrumbs::register( $breadcrumbs->push(trans('firefly.import'), route('import.index')); } ); -Breadcrumbs::register( - 'import.complete', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) { - $breadcrumbs->parent('import.index'); - $breadcrumbs->push(trans('firefly.bread_crumb_import_complete', ['key' => $job->key]), route('import.complete', [$job->key])); -} -); + Breadcrumbs::register( 'import.configure', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) { $breadcrumbs->parent('import.index'); - $breadcrumbs->push(trans('firefly.bread_crumb_configure_import', ['key' => $job->key]), route('import.configure', [$job->key])); + $breadcrumbs->push(trans('firefly.import_config_sub_title', ['key' => $job->key]), route('import.configure', [$job->key])); } ); Breadcrumbs::register( - 'import.finished', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) { + 'import.status', function (BreadCrumbGenerator $breadcrumbs, ImportJob $job) { $breadcrumbs->parent('import.index'); - $breadcrumbs->push(trans('firefly.bread_crumb_import_finished', ['key' => $job->key]), route('import.finished', [$job->key])); + $breadcrumbs->push(trans('firefly.import_status_bread_crumb', ['key' => $job->key]), route('import.status', [$job->key])); } ); - /** * PREFERENCES */ diff --git a/app/Import/Configurator/ConfiguratorInterface.php b/app/Import/Configurator/ConfiguratorInterface.php new file mode 100644 index 0000000000..4969109b01 --- /dev/null +++ b/app/Import/Configurator/ConfiguratorInterface.php @@ -0,0 +1,65 @@ +getConfigurationClass(); + $job = $this->job; + /** @var ConfigurationInterface $object */ + $object = new $class($this->job); + $object->setJob($job); + + return $object->storeConfiguration($data); + } + + /** + * Return the data required for the next step in the job configuration. + * + * @return array + * @throws FireflyException + */ + public function getNextData(): array + { + $class = $this->getConfigurationClass(); + $job = $this->job; + /** @var ConfigurationInterface $object */ + $object = app($class); + $object->setJob($job); + + return $object->getData(); + + } + + /** + * @return string + * @throws FireflyException + */ + public function getNextView(): string + { + if (!$this->job->configuration['initial-config-complete']) { + return 'import.csv.initial'; + } + if (!$this->job->configuration['column-roles-complete']) { + return 'import.csv.roles'; + } + if (!$this->job->configuration['column-mapping-complete']) { + return 'import.csv.map'; + } + + throw new FireflyException('No view for state'); + } + + /** + * @return bool + */ + public function isJobConfigured(): bool + { + $config = $this->job->configuration; + $config['initial-config-complete'] = $config['initial-config-complete'] ?? false; + $config['column-roles-complete'] = $config['column-roles-complete'] ?? false; + $config['column-mapping-complete'] = $config['column-mapping-complete'] ?? false; + $this->job->configuration = $config; + $this->job->save(); + + if ($this->job->configuration['initial-config-complete'] + && $this->job->configuration['column-roles-complete'] + && $this->job->configuration['column-mapping-complete'] + ) { + return true; + } + + return false; + } + + /** + * @param ImportJob $job + */ + public function setJob(ImportJob $job) + { + $this->job = $job; + if (is_null($this->job->configuration) || count($this->job->configuration) === 0) { + Log::debug(sprintf('Gave import job %s initial configuration.', $this->job->key)); + $this->job->configuration = config('csv.default_config'); + $this->job->save(); + } + } + + /** + * @return string + * @throws FireflyException + */ + private function getConfigurationClass(): string + { + $class = false; + switch (true) { + case (!$this->job->configuration['initial-config-complete']): + $class = Initial::class; + break; + case (!$this->job->configuration['column-roles-complete']): + $class = Roles::class; + break; + case (!$this->job->configuration['column-mapping-complete']): + $class = Map::class; + break; + default: + break; + } + + if ($class === false || strlen($class) === 0) { + throw new FireflyException('Cannot handle current job state in getConfigurationClass().'); + } + if (!class_exists($class)) { + throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class)); + } + + return $class; + } +} \ No newline at end of file diff --git a/app/Import/Converter/AccountId.php b/app/Import/Converter/AccountId.php deleted file mode 100644 index 88161515ab..0000000000 --- a/app/Import/Converter/AccountId.php +++ /dev/null @@ -1,69 +0,0 @@ - $value]); - if ($value === 0) { - $this->setCertainty(0); - - return new Account; - } - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $account = $repository->find(intval($this->mapping[$value])); - if (!is_null($account->id)) { - Log::debug('Found account by ID', ['id' => $account->id]); - - $this->setCertainty(100); - - return $account; - } - } - $account = $repository->find($value);// not mapped? Still try to find it first: - if (!is_null($account->id)) { - $this->setCertainty(90); - Log::debug('Found account by ID ', ['id' => $account->id]); - - return $account; - } - $this->setCertainty(0); // should not really happen. If the ID does not match FF, what is FF supposed to do? - - return new Account; - - } -} diff --git a/app/Import/Converter/Amount.php b/app/Import/Converter/Amount.php index c7840e397c..4cd861e25d 100644 --- a/app/Import/Converter/Amount.php +++ b/app/Import/Converter/Amount.php @@ -18,7 +18,7 @@ namespace FireflyIII\Import\Converter; * * @package FireflyIII\Import\Converter */ -class Amount extends BasicConverter implements ConverterInterface +class Amount implements ConverterInterface { /** @@ -28,9 +28,9 @@ class Amount extends BasicConverter implements ConverterInterface * * @param $value * - * @return float + * @return string */ - public function convert($value): float + public function convert($value): string { $len = strlen($value); $decimalPosition = $len - 3; @@ -59,10 +59,7 @@ class Amount extends BasicConverter implements ConverterInterface $value = str_replace($search, '', $value); } - $this->setCertainty(90); - - - return round(floatval($value), 12); + return strval(round(floatval($value), 12)); } } diff --git a/app/Import/Converter/AssetAccountIban.php b/app/Import/Converter/AssetAccountIban.php deleted file mode 100644 index f98bef6110..0000000000 --- a/app/Import/Converter/AssetAccountIban.php +++ /dev/null @@ -1,87 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new Account; - } - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - - if (isset($this->mapping[$value])) { - Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $account = $repository->find(intval($this->mapping[$value])); - if (!is_null($account->id)) { - $this->setCertainty(100); - Log::debug('Found account by ID', ['id' => $account->id]); - - return $account; - } - } - - // not mapped? Still try to find it first: - $account = $repository->findByIban($value, [AccountType::ASSET]); - if (!is_null($account->id)) { - Log::debug('Found account by IBAN', ['id' => $account->id]); - $this->setCertainty(50); - - return $account; - } - - - $account = $repository->store( - ['name' => 'Asset account with IBAN ' . $value, 'iban' => $value, 'user' => $this->user->id, 'accountType' => 'asset', 'virtualBalance' => 0, - 'active' => true, 'openingBalance' => 0] - ); - - if (is_null($account->id)) { - $this->setCertainty(0); - Log::info('Could not store new asset account by IBAN', $account->getErrors()->toArray()); - - return new Account; - } - - $this->setCertainty(100); - - return $account; - } -} diff --git a/app/Import/Converter/AssetAccountName.php b/app/Import/Converter/AssetAccountName.php deleted file mode 100644 index 8355c3b056..0000000000 --- a/app/Import/Converter/AssetAccountName.php +++ /dev/null @@ -1,90 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new Account; - } - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - - if (isset($this->mapping[$value])) { - Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $account = $repository->find(intval($this->mapping[$value])); - if (!is_null($account->id)) { - Log::debug('Found account by ID', ['id' => $account->id]); - $this->setCertainty(100); - - return $account; - } - } - - // not mapped? Still try to find it first: - $account = $repository->findByName($value, [AccountType::ASSET]); - if (!is_null($account->id)) { - Log::debug('Found asset account by name', ['value' => $value, 'id' => $account->id]); - - return $account; - } - - - $account = $repository->store( - ['name' => $value, 'iban' => null, 'openingBalance' => 0, 'user' => $this->user->id, 'accountType' => 'asset', 'virtualBalance' => 0, - 'active' => true] - ); - - if (is_null($account->id)) { - $this->setCertainty(0); - Log::info('Could not store new asset account by name', $account->getErrors()->toArray()); - - return new Account; - } - - $this->setCertainty(100); - - Log::debug('Created new asset account ', ['name' => $account->name, 'id' => $account->id]); - - return $account; - - - } -} diff --git a/app/Import/Converter/AssetAccountNumber.php b/app/Import/Converter/AssetAccountNumber.php deleted file mode 100644 index 880b4d3061..0000000000 --- a/app/Import/Converter/AssetAccountNumber.php +++ /dev/null @@ -1,96 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - return new Account; - } - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - - if (isset($this->mapping[$value])) { - Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $account = $repository->find(intval($this->mapping[$value])); - if (!is_null($account->id)) { - Log::debug('Found account by ID', ['id' => $account->id]); - - return $account; - } - } - - // not mapped? Still try to find it first: - $account = $repository->findByAccountNumber($value, [AccountType::ASSET]); - if (!is_null($account->id)) { - Log::debug('Found account by name', ['id' => $account->id]); - $this->setCertainty(50); - - return $account; - } - - // try to find by the name we would give it: - $accountName = 'Asset account with number ' . e($value); - $account = $repository->findByName($accountName, [AccountType::ASSET]); - if (!is_null($account->id)) { - Log::debug('Found account by name', ['id' => $account->id]); - $this->setCertainty(50); - - return $account; - } - - - $account = $repository->store( - ['name' => $accountName, 'openingBalance' => 0, 'iban' => null, 'user' => $this->user->id, - 'accountType' => 'asset', - 'virtualBalance' => 0, 'accountNumber' => $value, 'active' => true] - ); - - if (is_null($account->id)) { - $this->setCertainty(0); - Log::info('Could not store new asset account by account number', $account->getErrors()->toArray()); - - return new Account; - } - - $this->setCertainty(100); - - return $account; - - } -} diff --git a/app/Import/Converter/BasicConverter.php b/app/Import/Converter/BasicConverter.php deleted file mode 100644 index 49fba30f0d..0000000000 --- a/app/Import/Converter/BasicConverter.php +++ /dev/null @@ -1,85 +0,0 @@ -certainty; - } - - /** - * @param int $certainty - */ - protected function setCertainty(int $certainty) - { - $this->certainty = $certainty; - } - - /** - * @param array $config - */ - public function setConfig(array $config) - { - $this->config = $config; - } - - /** - * @param mixed $doMap - */ - public function setDoMap(bool $doMap) - { - $this->doMap = $doMap; - } - - /** - * @param array $mapping - * - */ - public function setMapping(array $mapping) - { - $this->mapping = $mapping; - } - - /** - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } -} diff --git a/app/Import/Converter/BillId.php b/app/Import/Converter/BillId.php deleted file mode 100644 index 11c91d6536..0000000000 --- a/app/Import/Converter/BillId.php +++ /dev/null @@ -1,76 +0,0 @@ - $value]); - - if ($value === 0) { - $this->setCertainty(0); - - return new Bill; - } - - /** @var BillRepositoryInterface $repository */ - $repository = app(BillRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found bill in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $bill = $repository->find(intval($this->mapping[$value])); - if (!is_null($bill->id)) { - Log::debug('Found bill by ID', ['id' => $bill->id]); - $this->setCertainty(100); - - return $bill; - } - } - - // not mapped? Still try to find it first: - $bill = $repository->find($value); - if (!is_null($bill->id)) { - Log::debug('Found bill by ID ', ['id' => $bill->id]); - $this->setCertainty(100); - - return $bill; - } - - // should not really happen. If the ID does not match FF, what is FF supposed to do? - Log::info(sprintf('Could not find bill with ID %d. Will return NULL', $value)); - - $this->setCertainty(0); - - return new Bill; - - } -} diff --git a/app/Import/Converter/BillName.php b/app/Import/Converter/BillName.php deleted file mode 100644 index 2ba4ad2761..0000000000 --- a/app/Import/Converter/BillName.php +++ /dev/null @@ -1,99 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new Bill; - } - - /** @var BillRepositoryInterface $repository */ - $repository = app(BillRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found bill in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $bill = $repository->find(intval($this->mapping[$value])); - if (!is_null($bill->id)) { - Log::debug('Found bill by ID', ['id' => $bill->id]); - $this->setCertainty(100); - - return $bill; - } - } - - // not mapped? Still try to find it first: - $bill = $repository->findByName($value); - if (!is_null($bill->id)) { - Log::debug('Found bill by name ', ['id' => $bill->id]); - $this->setCertainty(100); - - return $bill; - } - - // create new bill. Use a lot of made up values. - $bill = $repository->store( - [ - 'name' => $value, - 'match' => $value, - 'amount_min' => 1, - 'user' => $this->user->id, - 'amount_max' => 10, - 'date' => date('Ymd'), - 'repeat_freq' => 'monthly', - 'skip' => 0, - 'automatch' => 0, - 'active' => 1, - - ] - ); - if (is_null($bill->id)) { - $this->setCertainty(0); - Log::info('Could not store new bill by name', $bill->getErrors()->toArray()); - - return new Bill; - } - - $this->setCertainty(100); - - return $bill; - - - } -} diff --git a/app/Import/Converter/BudgetId.php b/app/Import/Converter/BudgetId.php deleted file mode 100644 index ea74b8302f..0000000000 --- a/app/Import/Converter/BudgetId.php +++ /dev/null @@ -1,76 +0,0 @@ - $value]); - - if ($value === 0) { - $this->setCertainty(0); - - return new Budget; - } - - /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found budget in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $budget = $repository->find(intval($this->mapping[$value])); - if (!is_null($budget->id)) { - Log::debug('Found budget by ID', ['id' => $budget->id]); - $this->setCertainty(100); - - return $budget; - } - } - - // not mapped? Still try to find it first: - $budget = $repository->find($value); - if (!is_null($budget->id)) { - Log::debug('Found budget by ID ', ['id' => $budget->id]); - $this->setCertainty(100); - - return $budget; - } - - // should not really happen. If the ID does not match FF, what is FF supposed to do? - $this->setCertainty(0); - - Log::info(sprintf('Could not find budget with ID %d. Will return NULL', $value)); - - return new Budget; - - } -} diff --git a/app/Import/Converter/BudgetName.php b/app/Import/Converter/BudgetName.php deleted file mode 100644 index 5d36a109ac..0000000000 --- a/app/Import/Converter/BudgetName.php +++ /dev/null @@ -1,80 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new Budget; - } - - /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found budget in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $budget = $repository->find(intval($this->mapping[$value])); - if (!is_null($budget->id)) { - Log::debug('Found budget by ID', ['id' => $budget->id]); - $this->setCertainty(100); - - return $budget; - } - } - - // not mapped? Still try to find it first: - $budget = $repository->findByName($value); - if (!is_null($budget->id)) { - Log::debug('Found budget by name ', ['id' => $budget->id]); - $this->setCertainty(100); - - return $budget; - } - - // create new budget. Use a lot of made up values. - $budget = $repository->store( - [ - 'name' => $value, - 'user' => $this->user->id, - ] - ); - $this->setCertainty(100); - - return $budget; - - } -} diff --git a/app/Import/Converter/CategoryId.php b/app/Import/Converter/CategoryId.php deleted file mode 100644 index 4b5cd4e6af..0000000000 --- a/app/Import/Converter/CategoryId.php +++ /dev/null @@ -1,76 +0,0 @@ - $value]); - - if ($value === 0) { - $this->setCertainty(0); - - return new Category; - } - - /** @var CategoryRepositoryInterface $repository */ - $repository = app(CategoryRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found category in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $category = $repository->find(intval($this->mapping[$value])); - if (!is_null($category->id)) { - Log::debug('Found category by ID', ['id' => $category->id]); - $this->setCertainty(100); - - return $category; - } - } - - // not mapped? Still try to find it first: - $category = $repository->find($value); - if (!is_null($category->id)) { - Log::debug('Found category by ID ', ['id' => $category->id]); - $this->setCertainty(100); - - return $category; - } - - // should not really happen. If the ID does not match FF, what is FF supposed to do? - $this->setCertainty(0); - - Log::info(sprintf('Could not find category with ID %d. Will return NULL', $value)); - - return new Category; - - } -} diff --git a/app/Import/Converter/CategoryName.php b/app/Import/Converter/CategoryName.php deleted file mode 100644 index fcd52413cc..0000000000 --- a/app/Import/Converter/CategoryName.php +++ /dev/null @@ -1,80 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new Category; - } - - /** @var CategoryRepositoryInterface $repository */ - $repository = app(CategoryRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found category in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $category = $repository->find(intval($this->mapping[$value])); - if (!is_null($category->id)) { - Log::debug('Found category by ID', ['id' => $category->id]); - $this->setCertainty(100); - - return $category; - } - } - - // not mapped? Still try to find it first: - $category = $repository->findByName($value); - if (!is_null($category->id)) { - Log::debug('Found category by name ', ['id' => $category->id]); - $this->setCertainty(100); - - return $category; - } - - // create new category. Use a lot of made up values. - $category = $repository->store( - [ - 'name' => $value, - 'user' => $this->user->id, - ] - ); - $this->setCertainty(100); - - return $category; - - } -} diff --git a/app/Import/Converter/ConverterInterface.php b/app/Import/Converter/ConverterInterface.php index f5c27a2746..010d06e4ca 100644 --- a/app/Import/Converter/ConverterInterface.php +++ b/app/Import/Converter/ConverterInterface.php @@ -13,8 +13,6 @@ declare(strict_types=1); namespace FireflyIII\Import\Converter; -use FireflyIII\User; - /** * Interface ConverterInterface * @@ -27,30 +25,4 @@ interface ConverterInterface * */ public function convert($value); - - /** - * @return int - */ - public function getCertainty(): int; - - /** - * @param array $config - */ - public function setConfig(array $config); - - /** - * @param bool $doMap - */ - public function setDoMap(bool $doMap); - - /** - * @param array $mapping - * - */ - public function setMapping(array $mapping); - - /** - * @param User $user - */ - public function setUser(User $user); } diff --git a/app/Import/Converter/CurrencyCode.php b/app/Import/Converter/CurrencyCode.php deleted file mode 100644 index 1afa778292..0000000000 --- a/app/Import/Converter/CurrencyCode.php +++ /dev/null @@ -1,71 +0,0 @@ - $value]); - - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found currency in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $currency = $repository->find(intval($this->mapping[$value])); - if (!is_null($currency->id)) { - Log::debug('Found currency by ID', ['id' => $currency->id]); - $this->setCertainty(100); - - return $currency; - } - } - - // not mapped? Still try to find it first: - $currency = $repository->findByCode($value); - if (!is_null($currency->id)) { - Log::debug('Found currency by code', ['id' => $currency->id]); - $this->setCertainty(100); - - return $currency; - } - $currency = $repository->store( - [ - 'name' => $value, - 'code' => $value, - 'symbol' => $value, - ] - ); - $this->setCertainty(100); - - return $currency; - } -} diff --git a/app/Import/Converter/CurrencyId.php b/app/Import/Converter/CurrencyId.php deleted file mode 100644 index d3b74da000..0000000000 --- a/app/Import/Converter/CurrencyId.php +++ /dev/null @@ -1,75 +0,0 @@ - $value]); - - if ($value === 0) { - $this->setCertainty(0); - - return new TransactionCurrency; - } - - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found currency in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $currency = $repository->find(intval($this->mapping[$value])); - if (!is_null($currency->id)) { - Log::debug('Found currency by ID', ['id' => $currency->id]); - $this->setCertainty(100); - - return $currency; - } - } - - // not mapped? Still try to find it first: - $currency = $repository->find($value); - if (!is_null($currency->id)) { - Log::debug('Found currency by ID ', ['id' => $currency->id]); - $this->setCertainty(100); - - return $currency; - } - $this->setCertainty(0); - // should not really happen. If the ID does not match FF, what is FF supposed to do? - - Log::info(sprintf('Could not find category with ID %d. Will return NULL', $value)); - - return new TransactionCurrency; - - } -} diff --git a/app/Import/Converter/CurrencyName.php b/app/Import/Converter/CurrencyName.php deleted file mode 100644 index f68ec043a1..0000000000 --- a/app/Import/Converter/CurrencyName.php +++ /dev/null @@ -1,81 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new TransactionCurrency; - } - - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found currency in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $currency = $repository->find(intval($this->mapping[$value])); - if (!is_null($currency->id)) { - Log::debug('Found currency by ID', ['id' => $currency->id]); - $this->setCertainty(100); - - return $currency; - } - } - - // not mapped? Still try to find it first: - $currency = $repository->findByName($value); - if (!is_null($currency->id)) { - Log::debug('Found currency by name ', ['id' => $currency->id]); - $this->setCertainty(100); - - return $currency; - } - - // create new currency - $currency = $repository->store( - [ - 'name' => $value, - 'code' => strtoupper(substr($value, 0, 3)), - 'symbol' => strtoupper(substr($value, 0, 1)), - ] - ); - $this->setCertainty(100); - - return $currency; - - } -} diff --git a/app/Import/Converter/CurrencySymbol.php b/app/Import/Converter/CurrencySymbol.php deleted file mode 100644 index a40b06af40..0000000000 --- a/app/Import/Converter/CurrencySymbol.php +++ /dev/null @@ -1,81 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new TransactionCurrency; - } - - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $repository->setUser($this->user); - - if (isset($this->mapping[$value])) { - Log::debug('Found currency in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $currency = $repository->find(intval($this->mapping[$value])); - if (!is_null($currency->id)) { - Log::debug('Found currency by ID', ['id' => $currency->id]); - $this->setCertainty(100); - - return $currency; - } - } - - // not mapped? Still try to find it first: - $currency = $repository->findBySymbol($value); - if (!is_null($currency->id)) { - Log::debug('Found currency by symbol ', ['id' => $currency->id]); - $this->setCertainty(100); - - return $currency; - } - - // create new currency - $currency = $repository->store( - [ - 'name' => 'Currency ' . $value, - 'code' => $value, - 'symbol' => $value, - ] - ); - $this->setCertainty(100); - - return $currency; - - } -} diff --git a/app/Import/Converter/Date.php b/app/Import/Converter/Date.php deleted file mode 100644 index b799aed9e7..0000000000 --- a/app/Import/Converter/Date.php +++ /dev/null @@ -1,53 +0,0 @@ - $value]); - Log::debug('Format: ', ['format' => $this->config['date-format']]); - try { - $date = Carbon::createFromFormat($this->config['date-format'], $value); - } catch (InvalidArgumentException $e) { - Log::info($e->getMessage()); - Log::info('Cannot convert this string using the given format.', ['value' => $value, 'format' => $this->config['date-format']]); - $this->setCertainty(0); - - return new Carbon; - } - Log::debug('Converted date', ['converted' => $date->toAtomString()]); - $this->setCertainty(100); - - return $date; - } -} diff --git a/app/Import/Converter/Description.php b/app/Import/Converter/Description.php deleted file mode 100644 index 9eb507acb8..0000000000 --- a/app/Import/Converter/Description.php +++ /dev/null @@ -1,39 +0,0 @@ -setCertainty(100); - - return strval($value); - - } -} diff --git a/app/Import/Converter/ExternalId.php b/app/Import/Converter/ExternalId.php deleted file mode 100644 index feb2e8c3d1..0000000000 --- a/app/Import/Converter/ExternalId.php +++ /dev/null @@ -1,39 +0,0 @@ -setCertainty(100); - - return strval(trim($value)); - - } -} diff --git a/app/Import/Converter/INGDebetCredit.php b/app/Import/Converter/INGDebetCredit.php index 80650257ae..7fa82f2cf6 100644 --- a/app/Import/Converter/INGDebetCredit.php +++ b/app/Import/Converter/INGDebetCredit.php @@ -20,7 +20,7 @@ use Log; * * @package FireflyIII\Import\Converter */ -class INGDebetCredit extends BasicConverter implements ConverterInterface +class INGDebetCredit implements ConverterInterface { /** @@ -34,12 +34,10 @@ class INGDebetCredit extends BasicConverter implements ConverterInterface if ($value === 'Af') { Log::debug('Return -1'); - $this->setCertainty(100); return -1; } - $this->setCertainty(100); Log::debug('Return 1'); return 1; diff --git a/app/Import/Converter/Ignore.php b/app/Import/Converter/Ignore.php deleted file mode 100644 index ac619bac3a..0000000000 --- a/app/Import/Converter/Ignore.php +++ /dev/null @@ -1,34 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new Account; - } - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - - if (isset($this->mapping[$value])) { - Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $account = $repository->find(intval($this->mapping[$value])); - if (!is_null($account->id)) { - Log::debug('Found account by ID', ['id' => $account->id]); - $this->setCertainty(100); - - return $account; - } - } - - // not mapped? Still try to find it first: - $account = $repository->findByIban($value, []); - if (!is_null($account->id)) { - Log::debug('Found account by IBAN', ['id' => $account->id]); - Log::info( - 'The match between IBAN and account is uncertain because the type of transactions may not have been determined.', - ['id' => $account->id, 'iban' => $value] - ); - $this->setCertainty(50); - - return $account; - } - - // the IBAN given may not be a valid IBAN. If not, we cannot store by - // iban and we have no opposing account. There should be some kind of fall back - // routine. - try { - $account = $repository->store( - ['name' => $value, 'iban' => $value, 'user' => $this->user->id, 'accountType' => 'import', 'virtualBalance' => 0, 'active' => true, - 'openingBalance' => 0] - ); - $this->setCertainty(100); - } catch (FireflyException $e) { - Log::error($e); - - $account = new Account; - } - - return $account; - } -} diff --git a/app/Import/Converter/OpposingAccountName.php b/app/Import/Converter/OpposingAccountName.php deleted file mode 100644 index 90a959408c..0000000000 --- a/app/Import/Converter/OpposingAccountName.php +++ /dev/null @@ -1,89 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new Account; - } - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - - if (isset($this->mapping[$value])) { - Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $account = $repository->find(intval($this->mapping[$value])); - if (!is_null($account->id)) { - Log::debug('Found account by ID', ['id' => $account->id]); - $this->setCertainty(100); - - return $account; - } - } - - // not mapped? Still try to find it first: - $account = $repository->findByName($value, []); - if (!is_null($account->id)) { - Log::debug('Found opposing account by name', ['id' => $account->id]); - Log::info( - 'The match between name and account is uncertain because the type of transactions may not have been determined.', - ['id' => $account->id, 'name' => $value] - ); - $this->setCertainty(50); - - return $account; - } - - $account = $repository->store( - ['name' => $value, 'iban' => null, 'user' => $this->user->id, 'accountType' => 'import', 'virtualBalance' => 0, 'active' => true, - 'openingBalance' => 0, - ] - ); - if (is_null($account->id)) { - $this->setCertainty(0); - - return new Account; - } - $this->setCertainty(100); - - Log::debug('Created new opposing account ', ['name' => $account->name, 'id' => $account->id]); - - return $account; - } -} diff --git a/app/Import/Converter/OpposingAccountNumber.php b/app/Import/Converter/OpposingAccountNumber.php deleted file mode 100644 index 8ede15ed85..0000000000 --- a/app/Import/Converter/OpposingAccountNumber.php +++ /dev/null @@ -1,91 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new Account; - } - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - - if (isset($this->mapping[$value])) { - Log::debug('Found account in mapping. Should exist.', ['value' => $value, 'map' => $this->mapping[$value]]); - $account = $repository->find(intval($this->mapping[$value])); - if (!is_null($account->id)) { - Log::debug('Found account by ID', ['id' => $account->id]); - $this->setCertainty(100); - - return $account; - } - } - - // not mapped? Still try to find it first: - $account = $repository->findByAccountNumber($value, []); - if (!is_null($account->id)) { - Log::debug('Found account by number', ['id' => $account->id]); - $this->setCertainty(50); - - return $account; - } - - // try to find by the name we would give it: - $accountName = 'Import account with number ' . e($value); - $account = $repository->findByName($accountName, [AccountType::IMPORT]); - if (!is_null($account->id)) { - Log::debug('Found account by name', ['id' => $account->id]); - $this->setCertainty(50); - - return $account; - } - - - $account = $repository->store( - ['name' => $accountName, 'openingBalance' => 0, 'iban' => null, 'user' => $this->user->id, - 'accountType' => 'import', - 'virtualBalance' => 0, 'accountNumber' => $value, 'active' => true] - ); - $this->setCertainty(100); - - return $account; - - } -} diff --git a/app/Import/Converter/RabobankDebetCredit.php b/app/Import/Converter/RabobankDebetCredit.php index 9b3e89314d..cef1a55607 100644 --- a/app/Import/Converter/RabobankDebetCredit.php +++ b/app/Import/Converter/RabobankDebetCredit.php @@ -20,7 +20,7 @@ use Log; * * @package FireflyIII\Import\Converter */ -class RabobankDebetCredit extends BasicConverter implements ConverterInterface +class RabobankDebetCredit implements ConverterInterface { /** @@ -34,13 +34,11 @@ class RabobankDebetCredit extends BasicConverter implements ConverterInterface if ($value === 'D') { Log::debug('Return -1'); - $this->setCertainty(100); return -1; } Log::debug('Return 1'); - $this->setCertainty(100); return 1; } diff --git a/app/Import/Converter/TagSplit.php b/app/Import/Converter/TagSplit.php deleted file mode 100644 index f5ebd034af..0000000000 --- a/app/Import/Converter/TagSplit.php +++ /dev/null @@ -1,86 +0,0 @@ -setUser($user); - - - /** @var string $part */ - foreach ($parts as $part) { - if (isset($mapping[$part])) { - Log::debug('Found tag in mapping. Should exist.', ['value' => $part, 'map' => $mapping[$part]]); - $tag = $repository->find(intval($mapping[$part])); - if (!is_null($tag->id)) { - Log::debug('Found tag by ID', ['id' => $tag->id]); - - $set->push($tag); - continue; - } - } - // not mapped? Still try to find it first: - $tag = $repository->findByTag($part); - if (!is_null($tag->id)) { - Log::debug('Found tag by name ', ['id' => $tag->id]); - - $set->push($tag); - } - if (is_null($tag->id)) { - // create new tag - $tag = $repository->store( - [ - 'tag' => $part, - 'date' => null, - 'description' => $part, - 'latitude' => null, - 'longitude' => null, - 'zoomLevel' => null, - 'tagMode' => 'nothing', - ] - ); - Log::debug('Created new tag', ['name' => $part, 'id' => $tag->id]); - $set->push($tag); - } - } - - return $set; - } - -} diff --git a/app/Import/Converter/TagsComma.php b/app/Import/Converter/TagsComma.php deleted file mode 100644 index 93e13698f0..0000000000 --- a/app/Import/Converter/TagsComma.php +++ /dev/null @@ -1,48 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new Collection; - } - $parts = array_unique(explode(',', $value)); - $set = TagSplit::createSetFromSplits($this->user, $this->mapping, $parts); - $this->setCertainty(100); - - return $set; - } -} diff --git a/app/Import/Converter/TagsSpace.php b/app/Import/Converter/TagsSpace.php deleted file mode 100644 index ae9635a8b7..0000000000 --- a/app/Import/Converter/TagsSpace.php +++ /dev/null @@ -1,49 +0,0 @@ - $value]); - - if (strlen($value) === 0) { - $this->setCertainty(0); - - return new Collection; - } - $parts = array_unique(explode(' ', $value)); - $set = TagSplit::createSetFromSplits($this->user, $this->mapping, $parts); - $this->setCertainty(100); - - return $set; - - } -} diff --git a/app/Import/FileProcessor/CsvProcessor.php b/app/Import/FileProcessor/CsvProcessor.php new file mode 100644 index 0000000000..5585cf4b04 --- /dev/null +++ b/app/Import/FileProcessor/CsvProcessor.php @@ -0,0 +1,235 @@ +objects = new Collection; + $this->validSpecifics = array_keys(config('csv.import_specifics')); + $this->validConverters = array_keys(config('csv.import_roles')); + } + + /** + * @return Collection + */ + public function getObjects(): Collection + { + return $this->objects; + } + + /** + * Does the actual job: + * + * @return bool + */ + public function run(): bool + { + Log::debug('Now in CsvProcessor run(). Job is now running...'); + + $entries = $this->getImportArray(); + $index = 0; + Log::notice('Building importable objects from CSV file.'); + foreach ($entries as $index => $row) { + // verify if not exists already: + if ($this->rowAlreadyImported($row)) { + $message = sprintf('Row #%d has already been imported.', $index); + $this->job->addError($index, $message); + $this->job->addStepsDone(5); // all steps. + Log::info($message); + continue; + } + $this->objects->push($this->importRow($index, $row)); + $this->job->addStepsDone(1); + } + // if job has no step count, set it now: + $extended = $this->job->extended_status; + if ($extended['steps'] === 0) { + $extended['steps'] = $index * 5; + $this->job->extended_status = $extended; + $this->job->save(); + } + + + return true; + } + + /** + * @param ImportJob $job + * + * @return FileProcessorInterface + */ + public function setJob(ImportJob $job): FileProcessorInterface + { + $this->job = $job; + + return $this; + } + + /** + * Add meta data to the individual value and verify that it can be handled in a later stage. + * + * @param int $index + * @param string $value + * + * @return array + * @throws FireflyException + */ + private function annotateValue(int $index, string $value) + { + $value = trim($value); + $config = $this->job->configuration; + $role = $config['column-roles'][$index] ?? '_ignore'; + $mapped = $config['column-mapping-config'][$index][$value] ?? null; + + // throw error when not a valid converter. + if (!in_array($role, $this->validConverters)) { + throw new FireflyException(sprintf('"%s" is not a valid role.', $role)); + } + + $entry = [ + 'role' => $role, + 'value' => $value, + 'mapped' => $mapped, + ]; + + return $entry; + } + + /** + * @return Iterator + */ + private function getImportArray(): Iterator + { + $content = $this->job->uploadFileContents(); + $config = $this->job->configuration; + $reader = Reader::createFromString($content); + $reader->setDelimiter($config['delimiter']); + $start = $config['has-headers'] ? 1 : 0; + $results = $reader->setOffset($start)->fetch(); + Log::debug(sprintf('Created a CSV reader starting at offset %d', $start)); + + return $results; + } + + /** + * Take a row, build import journal by annotating each value and storing it in the import journal. + * + * @param int $index + * @param array $row + * + * @return ImportJournal + */ + private function importRow(int $index, array $row): ImportJournal + { + Log::debug(sprintf('Now at row %d', $index)); + $row = $this->specifics($row); + $journal = new ImportJournal; + $journal->setUser($this->job->user); + $journal->setHash(hash('sha256', json_encode($row))); + + foreach ($row as $rowIndex => $value) { + $value = trim($value); + if (strlen($value) > 0) { + $annotated = $this->annotateValue($rowIndex, $value); + Log::debug('Annotated value', $annotated); + $journal->setValue($annotated); + } + } + Log::debug('ImportJournal complete, returning.'); + + return $journal; + } + + /** + * Checks if the row has not been imported before. + * + * @param array $array + * + * @return bool + */ + private function rowAlreadyImported(array $array): bool + { + $string = json_encode($array); + $hash = hash('sha256', json_encode($string)); + $json = json_encode($hash); + $entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') + ->where('data', $json) + ->where('name', 'importHash') + ->first(); + if (!is_null($entry)) { + return true; + } + + return false; + + } + + /** + * And this is the point where the specifix go to work. + * + * @param array $row + * + * @return array + * @throws FireflyException + */ + private function specifics(array $row): array + { + $config = $this->job->configuration; + // + foreach ($config['specifics'] as $name => $enabled) { + + if (!in_array($name, $this->validSpecifics)) { + throw new FireflyException(sprintf('"%s" is not a valid class name', $name)); + } + + /** @var SpecificInterface $specific */ + $specific = app('FireflyIII\Import\Specifics\\' . $name); + + // it returns the row, possibly modified: + $row = $specific->run($row); + } + + return $row; + + } +} \ No newline at end of file diff --git a/app/Import/FileProcessor/FileProcessorInterface.php b/app/Import/FileProcessor/FileProcessorInterface.php new file mode 100644 index 0000000000..5dfccd4dd7 --- /dev/null +++ b/app/Import/FileProcessor/FileProcessorInterface.php @@ -0,0 +1,42 @@ +validFields as $value) { - $this->fields[$value] = null; - $this->certain[$value] = 0; - } - $this->errors = new Collection; - - } - - /** - * @param string $role - * @param int $certainty - * @param $convertedValue - * - * @throws FireflyException - */ - public function importValue(string $role, int $certainty, $convertedValue) - { - switch ($role) { - default: - Log::error('Import entry cannot handle object.', ['role' => $role]); - throw new FireflyException('Import entry cannot handle object of type "' . $role . '".'); - case 'hash': - $this->hash = $convertedValue; - - return; - case 'amount': - /* - * Easy enough. - */ - $this->setFloat('amount', $convertedValue, $certainty); - $this->applyMultiplier('amount'); // if present. - - return; - case 'account-id': - case 'account-iban': - case 'account-name': - case 'account-number': - $this->setObject('asset-account', $convertedValue, $certainty); - break; - case 'opposing-number': - case 'opposing-iban': - case 'opposing-id': - case 'opposing-name': - $this->setObject('opposing-account', $convertedValue, $certainty); - break; - case 'bill-id': - case 'bill-name': - $this->setObject('bill', $convertedValue, $certainty); - break; - case 'budget-id': - case 'budget-name': - $this->setObject('budget', $convertedValue, $certainty); - break; - case 'category-id': - case 'category-name': - $this->setObject('category', $convertedValue, $certainty); - break; - case 'currency-code': - case 'currency-id': - case 'currency-name': - case 'currency-symbol': - $this->setObject('currency', $convertedValue, $certainty); - break; - case 'date-transaction': - $this->setDate('date-transaction', $convertedValue, $certainty); - break; - - case 'date-interest': - $this->setDate('date-interest', $convertedValue, $certainty); - break; - case 'date-book': - $this->setDate('date-book', $convertedValue, $certainty); - break; - case 'date-process': - $this->setDate('date-process', $convertedValue, $certainty); - break; - case 'sepa-ct-id': - case 'sepa-db': - case 'sepa-ct-op': - case 'description': - $this->setAppendableString('description', $convertedValue); - break; - case '_ignore': - break; - case 'ing-debet-credit': - case 'rabo-debet-credit': - $this->manipulateFloat('amount', 'multiply', $convertedValue); - $this->applyMultiplier('amount'); // if present. - break; - case 'tags-comma': - case 'tags-space': - $this->appendCollection('tags', $convertedValue); - break; - case 'external-id': - $this->externalID = $convertedValue; - break; - - } - } - - /** - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } - - /** - * @param string $field - * @param Collection $convertedValue - */ - private function appendCollection(string $field, Collection $convertedValue) - { - if (is_null($this->fields[$field])) { - $this->fields[$field] = new Collection; - } - $this->fields[$field] = $this->fields[$field]->merge($convertedValue); - } - - /** - * @param string $field - */ - private function applyMultiplier(string $field) - { - if ($this->fields[$field] != 0 && $this->amountMultiplier != 0) { - $this->fields[$field] = $this->fields[$field] * $this->amountMultiplier; - } - } - - /** - * @param string $field - * @param string $action - * @param $convertedValue - * - * @throws FireflyException - */ - private function manipulateFloat(string $field, string $action, $convertedValue) - { - switch ($action) { - default: - Log::error('Cannot handle manipulateFloat', ['field' => $field, 'action' => $action]); - throw new FireflyException('Cannot manipulateFloat with action ' . $action); - case 'multiply': - $this->amountMultiplier = $convertedValue; - break; - } - } - - /** - * @param string $field - * @param string $value - */ - private function setAppendableString(string $field, string $value) - { - $value = trim($value); - $this->fields[$field] .= ' ' . $value; - } - - /** - * @param string $field - * @param Carbon $date - * @param int $certainty - */ - private function setDate(string $field, Carbon $date, int $certainty) - { - if ($certainty > $this->certain[$field] && !is_null($date)) { - Log::debug(sprintf('ImportEntry: %s is now %s with certainty %d', $field, $date->format('Y-m-d'), $certainty)); - $this->fields[$field] = $date; - $this->certain[$field] = $certainty; - - return; - } - Log::info(sprintf('Will not set %s based on certainty %d (current certainty is %d) or NULL id.', $field, $certainty, $this->certain[$field])); - - } - - /** - * @param string $field - * @param float $value - * @param int $certainty - */ - private function setFloat(string $field, float $value, int $certainty) - { - if ($certainty > $this->certain[$field]) { - Log::debug(sprintf('ImportEntry: %s is now %f with certainty %d', $field, $value, $certainty)); - $this->fields[$field] = $value; - $this->certain[$field] = $certainty; - - return; - } - Log::info(sprintf('Will not set %s based on certainty %d (current certainty is %d).', $field, $certainty, $this->certain[$field])); - } - - /** - * @param string $field - * @param $object - * @param int $certainty - */ - private function setObject(string $field, $object, int $certainty) - { - if ($certainty > $this->certain[$field] && !is_null($object->id)) { - Log::debug(sprintf('ImportEntry: %s ID is now %d with certainty %d', $field, $object->id, $certainty)); - $this->fields[$field] = $object; - $this->certain[$field] = $certainty; - - return; - } - Log::info(sprintf('Will not set %s based on certainty %d (current certainty is %d) or NULL id.', $field, $certainty, $this->certain[$field])); - - } -} diff --git a/app/Import/ImportProcedure.php b/app/Import/ImportProcedure.php deleted file mode 100644 index 9150010dae..0000000000 --- a/app/Import/ImportProcedure.php +++ /dev/null @@ -1,85 +0,0 @@ -status = 'import_running'; - $job->save(); - - // create Importer - $valid = array_keys(config('firefly.import_formats')); - $class = 'INVALID'; - if (in_array($job->file_type, $valid)) { - $class = config('firefly.import_formats.' . $job->file_type); - } - - /** @var ImporterInterface $importer */ - $importer = app($class); - $importer->setJob($job); - - // create import entries - $collection = $importer->createImportEntries(); - - // validate / clean collection: - $validator = new ImportValidator($collection); - $validator->setUser($job->user); - $validator->setJob($job); - if ($job->configuration['import-account'] != 0) { - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($job->user); - $validator->setDefaultImportAccount($repository->find($job->configuration['import-account'])); - } - - $cleaned = $validator->clean(); - - // then import collection: - $storage = new ImportStorage($job->user, $cleaned); - $storage->setJob($job); - - // and run store routine: - $result = $storage->store(); - - // grab import tag: - $status = $job->extended_status; - $status['importTag'] = $storage->importTag->id; - $job->extended_status = $status; - $job->status = 'import_complete'; - $job->save(); - - return $result; - } - -} diff --git a/app/Import/ImportProcedureInterface.php b/app/Import/ImportProcedureInterface.php deleted file mode 100644 index ca2c6ff5da..0000000000 --- a/app/Import/ImportProcedureInterface.php +++ /dev/null @@ -1,33 +0,0 @@ -entries = $entries; - $this->user = $user; - $this->rules = $this->getUserRules(); - - } - - /** - * @param ImportJob $job - */ - public function setJob(ImportJob $job) - { - $this->job = $job; - } - - /** - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } - - /** - * @return Collection - */ - public function store(): Collection - { - // create a tag to join the transactions. - $this->importTag = $this->createImportTag(); - $collection = new Collection; - Log::notice(sprintf('Started storing %d entry(ies).', $this->entries->count())); - foreach ($this->entries as $index => $entry) { - Log::debug(sprintf('--- import store start for row %d ---', $index)); - - // store entry: - $journal = $this->storeSingle($index, $entry); - $this->job->addStepsDone(1); - - // apply rules: - $this->applyRules($journal); - $this->job->addStepsDone(1); - - $collection->put($index, $journal); - } - Log::notice(sprintf('Finished storing %d entry(ies).', $collection->count())); - - return $collection; - } - - /** - * @param string $hash - * - * @return TransactionJournal - */ - private function alreadyImported(string $hash): TransactionJournal - { - - $meta = TransactionJournalMeta - ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') - ->where('journal_meta.name', 'originalImportHash') - ->where('transaction_journals.user_id', $this->user->id) - ->where('journal_meta.data', json_encode($hash))->first(['journal_meta.*']); - if (!is_null($meta)) { - /** @var TransactionJournal $journal */ - $journal = $meta->transactionjournal; - if (intval($journal->completed) === 1) { - return $journal; - } - } - - return new TransactionJournal; - } - - /** - * @param TransactionJournal $journal - * - * @return bool - */ - private function applyRules(TransactionJournal $journal): bool - { - if ($this->rules->count() > 0) { - - /** @var Rule $rule */ - foreach ($this->rules as $rule) { - Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id)); - $processor = Processor::make($rule); - $processor->handleTransactionJournal($journal); - - if ($rule->stop_processing) { - return true; - } - } - } - - return true; - } - - /** - * @return Tag - */ - private function createImportTag(): Tag - { - /** @var TagRepositoryInterface $repository */ - $repository = app(TagRepositoryInterface::class); - $repository->setUser($this->user); - $data = [ - 'tag' => trans('firefly.import_with_key', ['key' => $this->job->key]), - 'date' => new Carbon, - 'description' => null, - 'latitude' => null, - 'longitude' => null, - 'zoomLevel' => null, - 'tagMode' => 'nothing', - ]; - $tag = $repository->store($data); - - return $tag; - } - - /** - * @return Collection - */ - private function getUserRules(): Collection - { - $set = Rule::distinct() - ->where('rules.user_id', $this->user->id) - ->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id') - ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') - ->where('rule_groups.active', 1) - ->where('rule_triggers.trigger_type', 'user_action') - ->where('rule_triggers.trigger_value', 'store-journal') - ->where('rules.active', 1) - ->orderBy('rule_groups.order', 'ASC') - ->orderBy('rules.order', 'ASC') - ->get(['rules.*', 'rule_groups.order']); - Log::debug(sprintf('Found %d user rules.', $set->count())); - - return $set; - - } - - /** - * @param $entry - * - * @return array - * @throws FireflyException - */ - private function storeAccounts($entry): array - { - // then create transactions. Single ones, unfortunately. - switch ($entry->fields['transaction-type']->type) { - default: - throw new FireflyException('ImportStorage cannot handle ' . $entry->fields['transaction-type']->type); - case TransactionType::WITHDRAWAL: - $source = $entry->fields['asset-account']; - $destination = $entry->fields['opposing-account']; - // make amount positive, if it is not. - break; - case TransactionType::DEPOSIT: - $source = $entry->fields['opposing-account']; - $destination = $entry->fields['asset-account']; - break; - case TransactionType::TRANSFER: - // depends on amount: - if ($entry->fields['amount'] < 0) { - $source = $entry->fields['asset-account']; - $destination = $entry->fields['opposing-account']; - break; - } - $destination = $entry->fields['asset-account']; - $source = $entry->fields['opposing-account']; - break; - } - - return [ - 'source' => $source, - 'destination' => $destination, - ]; - } - - /** - * @param TransactionJournal $journal - * @param ImportEntry $entry - */ - private function storeBill(TransactionJournal $journal, ImportEntry $entry) - { - - if (!is_null($entry->fields['bill']) && !is_null($entry->fields['bill']->id)) { - $journal->bill()->associate($entry->fields['bill']); - Log::debug('Attached bill', ['id' => $entry->fields['bill']->id, 'name' => $entry->fields['bill']->name]); - $journal->save(); - } - } - - /** - * @param TransactionJournal $journal - * @param ImportEntry $entry - */ - private function storeBudget(TransactionJournal $journal, ImportEntry $entry) - { - if (!is_null($entry->fields['budget']) && !is_null($entry->fields['budget']->id)) { - $journal->budgets()->save($entry->fields['budget']); - Log::debug('Attached budget', ['id' => $entry->fields['budget']->id, 'name' => $entry->fields['budget']->name]); - $journal->save(); - } - - } - - /** - * @param TransactionJournal $journal - * @param ImportEntry $entry - */ - private function storeCategory(TransactionJournal $journal, ImportEntry $entry) - { - if (!is_null($entry->fields['category']) && !is_null($entry->fields['category']->id)) { - $journal->categories()->save($entry->fields['category']); - Log::debug('Attached category', ['id' => $entry->fields['category']->id, 'name' => $entry->fields['category']->name]); - $journal->save(); - } - } - - /** - * @param $entry - * - * @return TransactionJournal - */ - private function storeJournal($entry): TransactionJournal - { - - $billId = is_null($entry->fields['bill']) || intval($entry->fields['bill']->id) === 0 ? null : intval($entry->fields['bill']->id); - $journalData = [ - 'user_id' => $entry->user->id, - 'transaction_type_id' => $entry->fields['transaction-type']->id, - 'bill_id' => $billId, - 'transaction_currency_id' => $entry->fields['currency']->id, - 'description' => $entry->fields['description'], - 'date' => $entry->fields['date-transaction'], - 'interest_date' => $entry->fields['date-interest'], - 'book_date' => $entry->fields['date-book'], - 'process_date' => $entry->fields['date-process'], - 'completed' => 0, - ]; - /** @var TransactionJournal $journal */ - $journal = TransactionJournal::create($journalData); - - foreach ($journal->getErrors()->all() as $err) { - Log::error('Error when storing journal: ' . $err); - } - Log::debug('Created journal', ['id' => $journal->id]); - - // save hash as meta value: - $meta = new TransactionJournalMeta; - $meta->name = 'originalImportHash'; - $meta->data = $entry->hash; - $meta->transactionJournal()->associate($journal); - $meta->save(); - - return $journal; - } - - /** - * @param int $index - * @param ImportEntry $entry - * - * @return TransactionJournal - * @throws FireflyException - */ - private function storeSingle(int $index, ImportEntry $entry): TransactionJournal - { - if ($entry->valid === false) { - Log::warning(sprintf('Cannot import row %d, because the entry is not valid.', $index)); - $errors = join(', ', $entry->errors->all()); - $errorText = sprintf('Row #%d: ' . $errors, $index); - $extendedStatus = $this->job->extended_status; - $extendedStatus['errors'][] = $errorText; - $this->job->extended_status = $extendedStatus; - $this->job->save(); - - return new TransactionJournal; - } - $alreadyImported = $this->alreadyImported($entry->hash); - if (!is_null($alreadyImported->id)) { - Log::warning(sprintf('Cannot import row %d, because it has already been imported (journal #%d).', $index, $alreadyImported->id)); - $errorText = trans( - 'firefly.import_double', - ['row' => $index, 'link' => route('transactions.show', [$alreadyImported->id]), 'description' => $alreadyImported->description] - ); - $extendedStatus = $this->job->extended_status; - $extendedStatus['errors'][] = $errorText; - $this->job->extended_status = $extendedStatus; - $this->job->save(); - - return new TransactionJournal; - } - - Log::debug(sprintf('Going to store row %d', $index)); - - - $journal = $this->storeJournal($entry); - $amount = Steam::positive($entry->fields['amount']); - $accounts = $this->storeAccounts($entry); - - // create new transactions. This is something that needs a rewrite for multiple/split transactions. - $sourceData = [ - 'account_id' => $accounts['source']->id, - 'transaction_journal_id' => $journal->id, - 'description' => null, - 'amount' => bcmul($amount, '-1'), - ]; - - $destinationData = [ - 'account_id' => $accounts['destination']->id, - 'transaction_journal_id' => $journal->id, - 'description' => null, - 'amount' => $amount, - ]; - - $one = Transaction::create($sourceData); - $two = Transaction::create($destinationData); - $error = false; - if (is_null($one->id)) { - Log::error('Could not create transaction 1.', $one->getErrors()->all()); - $error = true; - } - - if (is_null($two->id)) { - Log::error('Could not create transaction 1.', $two->getErrors()->all()); - $error = true; - } - - // respond to error - if ($error === true) { - $errorText = sprintf('Cannot import row %d, because an error occured when storing data.', $index); - Log::error($errorText); - $extendedStatus = $this->job->extended_status; - $extendedStatus['errors'][] = $errorText; - $this->job->extended_status = $extendedStatus; - $this->job->save(); - - return new TransactionJournal; - } - - Log::debug('Created transaction 1', ['id' => $one->id, 'account' => $one->account_id, 'account_name' => $accounts['source']->name]); - Log::debug('Created transaction 2', ['id' => $two->id, 'account' => $two->account_id, 'account_name' => $accounts['destination']->name]); - - $journal->completed = 1; - $journal->save(); - - // attach import tag. - $journal->tags()->save($this->importTag); - - // now attach budget and so on. - $this->storeBudget($journal, $entry); - $this->storeCategory($journal, $entry); - $this->storeBill($journal, $entry); - - return $journal; - } -} diff --git a/app/Import/ImportValidator.php b/app/Import/ImportValidator.php deleted file mode 100644 index 3a3a373b2b..0000000000 --- a/app/Import/ImportValidator.php +++ /dev/null @@ -1,438 +0,0 @@ -entries = $entries; - } - - /** - * Clean collection by filling in all the blanks. - */ - public function clean(): Collection - { - Log::notice(sprintf('Started validating %d entry(ies).', $this->entries->count())); - $newCollection = new Collection; - /** @var ImportEntry $entry */ - foreach ($this->entries as $index => $entry) { - Log::debug(sprintf('--- import validator start for row %d ---', $index)); - /* - * X Adds the date (today) if no date is present. - * X Determins the types of accounts involved (asset, expense, revenue). - * X Determins the type of transaction (withdrawal, deposit, transfer). - * - Determins the currency of the transaction. - * X Adds a default description if there isn't one present. - */ - $entry = $this->checkAmount($entry); - $entry = $this->setDate($entry); - $entry = $this->setAssetAccount($entry); - $entry = $this->setOpposingAccount($entry); - $entry = $this->cleanDescription($entry); - $entry = $this->setTransactionType($entry); - $entry = $this->setTransactionCurrency($entry); - - $newCollection->put($index, $entry); - $this->job->addStepsDone(1); - } - Log::notice(sprintf('Finished validating %d entry(ies).', $newCollection->count())); - - return $newCollection; - } - - /** - * @param Account $defaultImportAccount - */ - public function setDefaultImportAccount(Account $defaultImportAccount) - { - $this->defaultImportAccount = $defaultImportAccount; - } - - /** - * @param ImportJob $job - */ - public function setJob(ImportJob $job) - { - $this->job = $job; - } - - /** - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } - - /** - * @param ImportEntry $entry - * - * @return ImportEntry - */ - private function checkAmount(ImportEntry $entry): ImportEntry - { - if ($entry->fields['amount'] == 0) { - $entry->valid = false; - $entry->errors->push('Amount of transaction is zero, cannot handle.'); - Log::warning('Amount of transaction is zero, cannot handle.'); - - return $entry; - } - Log::debug('Amount is OK.'); - - return $entry; - } - - /** - * @param ImportEntry $entry - * - * @return ImportEntry - */ - private function cleanDescription(ImportEntry $entry): ImportEntry - { - - if (!isset($entry->fields['description'])) { - Log::debug('Set empty transaction description because field was not set.'); - $entry->fields['description'] = '(empty transaction description)'; - - return $entry; - } - if (is_null($entry->fields['description'])) { - Log::debug('Set empty transaction description because field was null.'); - $entry->fields['description'] = '(empty transaction description)'; - - return $entry; - } - $entry->fields['description'] = trim($entry->fields['description']); - - if (strlen($entry->fields['description']) == 0) { - Log::debug('Set empty transaction description because field was empty.'); - $entry->fields['description'] = '(empty transaction description)'; - - return $entry; - } - Log::debug('Transaction description is OK.', ['description' => $entry->fields['description']]); - - return $entry; - } - - /** - * @param Account $account - * @param string $type - * - * @return Account - */ - private function convertAccount(Account $account, string $type): Account - { - $accountType = $account->accountType->type; - if ($accountType === $type) { - Log::debug(sprintf('Account %s already of type %s', $account->name, $type)); - - return $account; - } - // find it first by new type: - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - $result = $repository->findByName($account->name, [$type]); - if (is_null($result->id)) { - // can convert account: - Log::debug(sprintf('No account named %s of type %s, create new account.', $account->name, $type)); - $result = $repository->store( - [ - 'user' => $this->user->id, - 'accountType' => config('firefly.shortNamesByFullName.' . $type), - 'name' => $account->name, - 'virtualBalance' => 0, - 'active' => true, - 'iban' => null, - 'openingBalance' => 0, - ] - ); - } - Log::debug( - sprintf( - 'Using another account named %s (#%d) of type %s, will use that one instead of %s (#%d)', $account->name, $result->id, $type, $account->name, - $account->id - ) - ); - - return $result; - - - } - - /** - * @return Account - */ - private function fallbackExpenseAccount(): Account - { - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - $name = 'Unknown expense account'; - $result = $repository->findByName($name, [AccountType::EXPENSE]); - if (is_null($result->id)) { - $result = $repository->store( - ['name' => $name, 'iban' => null, 'openingBalance' => 0, 'user' => $this->user->id, 'accountType' => 'expense', 'virtualBalance' => 0, - 'active' => true] - ); - } - - return $result; - } - - /** - * @return Account - */ - private function fallbackRevenueAccount(): Account - { - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - $name = 'Unknown revenue account'; - $result = $repository->findByName($name, [AccountType::REVENUE]); - - - if (is_null($result->id)) { - $result = $repository->store( - ['name' => $name, 'iban' => null, 'openingBalance' => 0, 'user' => $this->user->id, 'accountType' => 'revenue', 'virtualBalance' => 0, - 'active' => true] - ); - } - - return $result; - } - - /** - * @param ImportEntry $entry - * - * @return ImportEntry - */ - private function setAssetAccount(ImportEntry $entry): ImportEntry - { - if (is_null($entry->fields['asset-account'])) { - if (!is_null($this->defaultImportAccount)) { - Log::debug('Set asset account from default asset account'); - $entry->fields['asset-account'] = $this->defaultImportAccount; - - return $entry; - } - // default import is null? should not happen. Entry cannot be imported. - // set error message and block. - $entry->valid = false; - Log::warning('Cannot import entry. Asset account is NULL and import account is also NULL.'); - - return $entry; - } - Log::debug('Asset account is OK.', ['id' => $entry->fields['asset-account']->id, 'name' => $entry->fields['asset-account']->name]); - - return $entry; - } - - - /** - * @param ImportEntry $entry - * - * @return ImportEntry - */ - private function setDate(ImportEntry $entry): ImportEntry - { - if (is_null($entry->fields['date-transaction']) || $entry->certain['date-transaction'] == 0) { - // empty date field? find alternative. - $alternatives = ['date-book', 'date-interest', 'date-process']; - foreach ($alternatives as $alternative) { - if (!is_null($entry->fields[$alternative])) { - Log::debug(sprintf('Copied date-transaction from %s.', $alternative)); - $entry->fields['date-transaction'] = clone $entry->fields[$alternative]; - - return $entry; - } - } - // date is still null at this point - Log::debug('Set date-transaction to today.'); - $entry->fields['date-transaction'] = new Carbon; - - return $entry; - } - - // confidence is zero? - - Log::debug('Date-transaction is OK'); - - return $entry; - } - - /** - * @param ImportEntry $entry - * - * @return ImportEntry - */ - private function setOpposingAccount(ImportEntry $entry): ImportEntry - { - // empty opposing account. Create one based on amount. - if (is_null($entry->fields['opposing-account'])) { - - if ($entry->fields['amount'] < 0) { - // create or find general opposing expense account. - Log::debug('Created fallback expense account'); - $entry->fields['opposing-account'] = $this->fallbackExpenseAccount(); - - return $entry; - } - Log::debug('Created fallback revenue account'); - $entry->fields['opposing-account'] = $this->fallbackRevenueAccount(); - - return $entry; - } - - // opposing is of type "import". Convert to correct type (by amount): - $type = $entry->fields['opposing-account']->accountType->type; - if ($type == AccountType::IMPORT && $entry->fields['amount'] < 0) { - $account = $this->convertAccount($entry->fields['opposing-account'], AccountType::EXPENSE); - $entry->fields['opposing-account'] = $account; - Log::debug('Converted import account to expense account'); - - return $entry; - } - if ($type == AccountType::IMPORT && $entry->fields['amount'] > 0) { - $account = $this->convertAccount($entry->fields['opposing-account'], AccountType::REVENUE); - $entry->fields['opposing-account'] = $account; - Log::debug('Converted import account to revenue account'); - - return $entry; - } - // amount < 0, but opposing is revenue - if ($type == AccountType::REVENUE && $entry->fields['amount'] < 0) { - $account = $this->convertAccount($entry->fields['opposing-account'], AccountType::EXPENSE); - $entry->fields['opposing-account'] = $account; - Log::debug('Converted revenue account to expense account'); - - return $entry; - } - - // amount > 0, but opposing is expense - if ($type == AccountType::EXPENSE && $entry->fields['amount'] > 0) { - $account = $this->convertAccount($entry->fields['opposing-account'], AccountType::REVENUE); - $entry->fields['opposing-account'] = $account; - Log::debug('Converted expense account to revenue account'); - - return $entry; - } - // account type is OK - Log::debug('Opposing account is OK.'); - - return $entry; - - } - - /** - * @param ImportEntry $entry - * - * @return ImportEntry - */ - private function setTransactionCurrency(ImportEntry $entry): ImportEntry - { - if (is_null($entry->fields['currency'])) { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $repository->setUser($this->user); - // is the default currency for the user or the system - $defaultCode = Preferences::getForUser($this->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data; - - $entry->fields['currency'] = $repository->findByCode($defaultCode); - Log::debug(sprintf('Set currency to %s', $defaultCode)); - - return $entry; - } - Log::debug(sprintf('Currency is OK: %s', $entry->fields['currency']->code)); - - return $entry; - } - - /** - * @param ImportEntry $entry - * - * @return ImportEntry - */ - private function setTransactionType(ImportEntry $entry): ImportEntry - { - Log::debug(sprintf('Opposing account is of type %s', $entry->fields['opposing-account']->accountType->type)); - $type = $entry->fields['opposing-account']->accountType->type; - switch ($type) { - case AccountType::EXPENSE: - $entry->fields['transaction-type'] = TransactionType::whereType(TransactionType::WITHDRAWAL)->first(); - Log::debug('Transaction type is now withdrawal.'); - - return $entry; - case AccountType::REVENUE: - $entry->fields['transaction-type'] = TransactionType::whereType(TransactionType::DEPOSIT)->first(); - Log::debug('Transaction type is now deposit.'); - - return $entry; - case AccountType::DEFAULT: - case AccountType::ASSET: - $entry->fields['transaction-type'] = TransactionType::whereType(TransactionType::TRANSFER)->first(); - Log::debug('Transaction type is now transfer.'); - - return $entry; - } - Log::warning(sprintf('Opposing account is of type %s, cannot handle this.', $type)); - $entry->valid = false; - $entry->errors->push(sprintf('Opposing account is of type %s, cannot handle this.', $type)); - - return $entry; - } - - -} diff --git a/app/Import/Importer/CsvImporter.php b/app/Import/Importer/CsvImporter.php deleted file mode 100644 index 99f8c62a97..0000000000 --- a/app/Import/Importer/CsvImporter.php +++ /dev/null @@ -1,175 +0,0 @@ -collection = new Collection; - $this->validSpecifics = array_keys(config('csv.import_specifics')); - } - - /** - * Run the actual import - * - * @return Collection - */ - public function createImportEntries(): Collection - { - $config = $this->job->configuration; - $content = $this->job->uploadFileContents(); - - // create CSV reader. - $reader = Reader::createFromString($content); - $reader->setDelimiter($config['delimiter']); - $start = $config['has-headers'] ? 1 : 0; - $results = $reader->fetch(); - - Log::notice('Building importable objects from CSV file.'); - - foreach ($results as $index => $row) { - if ($index >= $start) { - $line = $index + 1; - Log::debug('----- import entry build start --'); - Log::debug(sprintf('Now going to import row %d.', $index)); - $importEntry = $this->importSingleRow($index, $row); - $this->collection->put($line, $importEntry); - /** - * 1. Build import entry. - * 2. Validate import entry. - * 3. Store journal. - * 4. Run rules. - */ - $this->job->addTotalSteps(4); - $this->job->addStepsDone(1); - } - } - Log::debug(sprintf('Import collection contains %d entries', $this->collection->count())); - Log::notice(sprintf('Built %d importable object(s) from your CSV file.', $this->collection->count())); - - return $this->collection; - } - - /** - * @param ImportJob $job - */ - public function setJob(ImportJob $job) - { - $this->job = $job; - } - - /** - * @param int $index - * @param array $row - * - * @return ImportEntry - * @throws FireflyException - */ - private function importSingleRow(int $index, array $row): ImportEntry - { - // create import object. This is where each entry ends up. - $object = new ImportEntry; - - Log::debug(sprintf('Now at row %d', $index)); - - // set some vars: - $object->setUser($this->job->user); - $config = $this->job->configuration; - $json = json_encode($row); - - if ($json === false) { - throw new FireflyException(sprintf('Could not process row #%d. Are you sure the uploaded file is encoded as "UTF-8"?', $index)); - } - - // hash the row: - $hash = hash('sha256', $json); - $object->importValue('hash', 100, $hash); - - // and this is the point where the specifix go to work. - foreach ($config['specifics'] as $name => $enabled) { - - if (!in_array($name, $this->validSpecifics)) { - throw new FireflyException(sprintf('"%s" is not a valid class name', $name)); - } - - /** @var SpecificInterface $specific */ - $specific = app('FireflyIII\Import\Specifics\\' . $name); - - // it returns the row, possibly modified: - $row = $specific->run($row); - } - - foreach ($row as $rowIndex => $value) { - // find the role for this column: - $role = $config['column-roles'][$rowIndex] ?? '_ignore'; - $doMap = $config['column-do-mapping'][$rowIndex] ?? false; - $validConverters = array_keys(config('csv.import_roles')); - - // throw error when not a valid converter. - if (!in_array($role, $validConverters)) { - throw new FireflyException(sprintf('"%s" is not a valid role.', $role)); - } - $converterClass = config('csv.import_roles.' . $role . '.converter'); - $mapping = $config['column-mapping-config'][$rowIndex] ?? []; - $className = sprintf('FireflyIII\\Import\\Converter\\%s', $converterClass); - /** @var ConverterInterface $converter */ - $converter = app($className); - // set some useful values for the converter: - $converter->setMapping($mapping); - $converter->setDoMap($doMap); - $converter->setUser($this->job->user); - $converter->setConfig($config); - - // run the converter for this value: - $convertedValue = $converter->convert($value); - $certainty = $converter->getCertainty(); - - // log it. - Log::debug('Value ', ['index' => $rowIndex, 'value' => $value, 'role' => $role]); - - // store in import entry: - Log::debug('Going to import', ['role' => $role, 'value' => $value, 'certainty' => $certainty]); - $object->importValue($role, $certainty, $convertedValue); - } - - - return $object; - - } -} diff --git a/app/Import/Importer/ImporterInterface.php b/app/Import/Importer/ImporterInterface.php deleted file mode 100644 index 06c18bd793..0000000000 --- a/app/Import/Importer/ImporterInterface.php +++ /dev/null @@ -1,38 +0,0 @@ - trans('csv.do_not_map')] + $list; + $list = [0 => trans('csv.map_do_not_map')] + $list; return $list; diff --git a/app/Import/Mapper/AssetAccounts.php b/app/Import/Mapper/AssetAccounts.php index 8335bea7e4..2679334fe3 100644 --- a/app/Import/Mapper/AssetAccounts.php +++ b/app/Import/Mapper/AssetAccounts.php @@ -47,7 +47,7 @@ class AssetAccounts implements MapperInterface asort($list); - $list = [0 => trans('csv.do_not_map')] + $list; + $list = [0 => trans('csv.map_do_not_map')] + $list; return $list; diff --git a/app/Import/Mapper/Bills.php b/app/Import/Mapper/Bills.php index 57d60b3013..a12ee950ea 100644 --- a/app/Import/Mapper/Bills.php +++ b/app/Import/Mapper/Bills.php @@ -40,7 +40,7 @@ class Bills implements MapperInterface } asort($list); - $list = [0 => trans('csv.do_not_map')] + $list; + $list = [0 => trans('csv.map_do_not_map')] + $list; return $list; diff --git a/app/Import/Mapper/Budgets.php b/app/Import/Mapper/Budgets.php index 37276ddcc0..88ca1e0d5e 100644 --- a/app/Import/Mapper/Budgets.php +++ b/app/Import/Mapper/Budgets.php @@ -41,7 +41,7 @@ class Budgets implements MapperInterface } asort($list); - $list = [0 => trans('csv.do_not_map')] + $list; + $list = [0 => trans('csv.map_do_not_map')] + $list; return $list; diff --git a/app/Import/Mapper/Categories.php b/app/Import/Mapper/Categories.php index 5144a06a41..30c81dfada 100644 --- a/app/Import/Mapper/Categories.php +++ b/app/Import/Mapper/Categories.php @@ -41,7 +41,7 @@ class Categories implements MapperInterface } asort($list); - $list = [0 => trans('csv.do_not_map')] + $list; + $list = [0 => trans('csv.map_do_not_map')] + $list; return $list; diff --git a/app/Import/Mapper/OpposingAccountIbans.php b/app/Import/Mapper/OpposingAccountIbans.php index bcf3372e10..3a4299c26a 100644 --- a/app/Import/Mapper/OpposingAccountIbans.php +++ b/app/Import/Mapper/OpposingAccountIbans.php @@ -56,7 +56,7 @@ class OpposingAccountIbans implements MapperInterface asort($list); $list = $topList + $list; - $list = [0 => trans('csv.do_not_map')] + $list; + $list = [0 => trans('csv.map_do_not_map')] + $list; return $list; diff --git a/app/Import/Mapper/OpposingAccounts.php b/app/Import/Mapper/OpposingAccounts.php index fd2d86a5bb..bd95610504 100644 --- a/app/Import/Mapper/OpposingAccounts.php +++ b/app/Import/Mapper/OpposingAccounts.php @@ -53,7 +53,7 @@ class OpposingAccounts implements MapperInterface asort($list); - $list = [0 => trans('csv.do_not_map')] + $list; + $list = [0 => trans('csv.map_do_not_map')] + $list; return $list; } diff --git a/app/Import/Mapper/Tags.php b/app/Import/Mapper/Tags.php index e549fd90ef..c1b7f324e0 100644 --- a/app/Import/Mapper/Tags.php +++ b/app/Import/Mapper/Tags.php @@ -40,7 +40,7 @@ class Tags implements MapperInterface } asort($list); - $list = [0 => trans('csv.do_not_map')] + $list; + $list = [0 => trans('csv.map_do_not_map')] + $list; return $list; diff --git a/app/Import/Mapper/TransactionCurrencies.php b/app/Import/Mapper/TransactionCurrencies.php index d24f083b0f..286f275214 100644 --- a/app/Import/Mapper/TransactionCurrencies.php +++ b/app/Import/Mapper/TransactionCurrencies.php @@ -36,7 +36,7 @@ class TransactionCurrencies implements MapperInterface asort($list); - $list = [0 => trans('csv.do_not_map')] + $list; + $list = [0 => trans('csv.map_do_not_map')] + $list; return $list; diff --git a/app/Import/Object/ImportAccount.php b/app/Import/Object/ImportAccount.php new file mode 100644 index 0000000000..19ff1c8f40 --- /dev/null +++ b/app/Import/Object/ImportAccount.php @@ -0,0 +1,299 @@ +expectedType = AccountType::ASSET; + $this->account = new Account; + $this->repository = app(AccountRepositoryInterface::class); + Log::debug('Created ImportAccount.'); + } + + /** + * @return Account + */ + public function getAccount(): Account + { + if (is_null($this->account->id)) { + $this->store(); + } + + return $this->account; + } + + /** + * @param array $accountIban + */ + public function setAccountIban(array $accountIban) + { + $this->accountIban = $accountIban; + } + + /** + * @param array $value + */ + public function setAccountId(array $value) + { + $this->accountId = $value; + } + + /** + * @param array $accountName + */ + public function setAccountName(array $accountName) + { + $this->accountName = $accountName; + } + + /** + * @param array $accountNumber + */ + public function setAccountNumber(array $accountNumber) + { + $this->accountNumber = $accountNumber; + } + + /** + * @param string $expectedType + */ + public function setExpectedType(string $expectedType) + { + $this->expectedType = $expectedType; + } + + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + $this->repository->setUser($user); + } + + /** + * @return Account + */ + private function findExistingObject(): Account + { + Log::debug('In findExistingObject() for Account'); + // 0: determin account type: + /** @var AccountType $accountType */ + $accountType = AccountType::whereType($this->expectedType)->first(); + + // 1: find by ID, iban or name (and type) + if (count($this->accountId) === 3) { + Log::debug(sprintf('Finding account of type %d and ID %d', $accountType->id, $this->accountId['value'])); + /** @var Account $account */ + $account = $this->user->accounts()->where('account_type_id', $accountType->id)->where('id', $this->accountId['value'])->first(); + if (!is_null($account)) { + Log::debug(sprintf('Found unmapped %s account by ID (#%d): %s', $this->expectedType, $account->id, $account->name)); + + return $account; + } + Log::debug('Found nothing.'); + } + /** @var Collection $accounts */ + $accounts = $this->repository->getAccountsByType([$accountType->type]); + // Two: find by IBAN (and type): + if (count($this->accountIban) === 3) { + $iban = $this->accountIban['value']; + Log::debug(sprintf('Finding account of type %d and IBAN %s', $accountType->id, $iban)); + $filtered = $accounts->filter( + function (Account $account) use ($iban) { + if ($account->iban === $iban) { + Log::debug( + sprintf('Found unmapped %s account by IBAN (#%d): %s (%s)', $this->expectedType, $account->id, $account->name, $account->iban) + ); + + return $account; + } + + return null; + } + ); + if ($filtered->count() === 1) { + return $filtered->first(); + } + Log::debug('Found nothing.'); + } + + // Three: find by name (and type): + if (count($this->accountName) === 3) { + $name = $this->accountName['value']; + Log::debug(sprintf('Finding account of type %d and name %s', $accountType->id, $name)); + $filtered = $accounts->filter( + function (Account $account) use ($name) { + if ($account->name === $name) { + Log::debug(sprintf('Found unmapped %s account by name (#%d): %s', $this->expectedType, $account->id, $account->name)); + + return $account; + } + + return null; + } + ); + + if ($filtered->count() === 1) { + return $filtered->first(); + } + Log::debug('Found nothing.'); + } + + // 4: do not search by account number. + Log::debug('Found NO existing accounts.'); + + return new Account; + + } + + /** + * @return Account + */ + private function findMappedObject(): Account + { + Log::debug('In findMappedObject() for Account'); + $fields = ['accountId', 'accountIban', 'accountNumber', 'accountName']; + foreach ($fields as $field) { + $array = $this->$field; + Log::debug(sprintf('Find mapped account based on field "%s" with value', $field), $array); + // check if a pre-mapped object exists. + $mapped = $this->getMappedObject($array); + if (!is_null($mapped->id)) { + Log::debug(sprintf('Found account #%d!', $mapped->id)); + + return $mapped; + } + + } + Log::debug('Found no account on mapped data or no map present.'); + + return new Account; + } + + /** + * @param array $array + * + * @return Account + */ + private function getMappedObject(array $array): Account + { + Log::debug('In getMappedObject() for Account'); + if (count($array) === 0) { + Log::debug('Array is empty, nothing will come of this.'); + + return new Account; + } + + if (array_key_exists('mapped', $array) && is_null($array['mapped'])) { + Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value'])); + + return new Account; + } + + Log::debug('Finding a mapped account based on', $array); + + $search = intval($array['mapped']); + $account = $this->repository->find($search); + + Log::debug(sprintf('Found account! #%d ("%s"). Return it', $account->id, $account->name)); + + return $account; + } + + /** + * @return bool + */ + private function store(): bool + { + // 1: find mapped object: + $mapped = $this->findMappedObject(); + if (!is_null($mapped->id)) { + $this->account = $mapped; + + return true; + } + // 2: find existing by given values: + $found = $this->findExistingObject(); + if (!is_null($found->id)) { + $this->account = $found; + + return true; + } + + // 3: if found nothing, retry the search with an asset account: + Log::debug('Will try to find an asset account just in case.'); + $oldExpectedType = $this->expectedType; + $this->expectedType = AccountType::ASSET; + $found = $this->findExistingObject(); + if (!is_null($found->id)) { + Log::debug('Found asset account!'); + $this->account = $found; + + return true; + } + $this->expectedType = $oldExpectedType; + + Log::debug(sprintf('Found no account of type %s so must create one ourselves.', $this->expectedType)); + + $data = [ + 'accountType' => config('firefly.shortNamesByFullName.' . $this->expectedType), + 'name' => $this->accountName['value'] ?? '(no name)', + 'iban' => $this->accountIban['value'] ?? null, + 'active' => true, + 'virtualBalance' => null, + ]; + + $this->account = $this->repository->store($data); + Log::debug(sprintf('Successfully stored new account #%d: %s', $this->account->id, $this->account->name)); + + return true; + } + + +} \ No newline at end of file diff --git a/app/Import/Object/ImportBill.php b/app/Import/Object/ImportBill.php new file mode 100644 index 0000000000..c3d5f2f93d --- /dev/null +++ b/app/Import/Object/ImportBill.php @@ -0,0 +1,228 @@ +bill = new Bill; + $this->repository = app(BillRepositoryInterface::class); + Log::debug('Created ImportBill.'); + } + + /** + * @return Bill + */ + public function getBill(): Bill + { + if (is_null($this->bill->id)) { + $this->store(); + } + + return $this->bill; + } + + /** + * @param array $id + */ + public function setId(array $id) + { + $this->id = $id; + } + + /** + * @param array $name + */ + public function setName(array $name) + { + $this->name = $name; + } + + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + $this->repository->setUser($user); + } + + /** + * @return Bill + */ + private function findExistingObject(): Bill + { + Log::debug('In findExistingObject() for Bill'); + // 1: find by ID, or name + + if (count($this->id) === 3) { + Log::debug(sprintf('Finding bill with ID #%d', $this->id['value'])); + /** @var Bill $bill */ + $bill = $this->repository->find(intval($this->id['value'])); + if (!is_null($bill->id)) { + Log::debug(sprintf('Found unmapped bill by ID (#%d): %s', $bill->id, $bill->name)); + + return $bill; + } + Log::debug('Found nothing.'); + } + // 2: find by name + if (count($this->name) === 3) { + /** @var Collection $bills */ + $bills = $this->repository->getBills(); + $name = $this->name['value']; + Log::debug(sprintf('Finding bill with name %s', $name)); + $filtered = $bills->filter( + function (Bill $bill) use ($name) { + if ($bill->name === $name) { + Log::debug(sprintf('Found unmapped bill by name (#%d): %s', $bill->id, $bill->name)); + + return $bill; + } + + return null; + } + ); + + if ($filtered->count() === 1) { + return $filtered->first(); + } + Log::debug('Found nothing.'); + } + + // 4: do not search by account number. + Log::debug('Found NO existing bills.'); + + return new Bill; + + } + + /** + * @return Bill + */ + private function findMappedObject(): Bill + { + Log::debug('In findMappedObject() for Bill'); + $fields = ['id', 'name']; + foreach ($fields as $field) { + $array = $this->$field; + Log::debug(sprintf('Find mapped bill based on field "%s" with value', $field), $array); + // check if a pre-mapped object exists. + $mapped = $this->getMappedObject($array); + if (!is_null($mapped->id)) { + Log::debug(sprintf('Found bill #%d!', $mapped->id)); + + return $mapped; + } + + } + Log::debug('Found no bill on mapped data or no map present.'); + + return new Bill; + } + + /** + * @param array $array + * + * @return Bill + */ + private function getMappedObject(array $array): Bill + { + Log::debug('In getMappedObject() for Bill'); + if (count($array) === 0) { + Log::debug('Array is empty, nothing will come of this.'); + + return new Bill; + } + + if (array_key_exists('mapped', $array) && is_null($array['mapped'])) { + Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value'])); + + return new Bill; + } + + Log::debug('Finding a mapped bill based on', $array); + + $search = intval($array['mapped']); + $account = $this->repository->find($search); + + Log::debug(sprintf('Found bill! #%d ("%s"). Return it', $account->id, $account->name)); + + return $account; + } + + /** + * @return bool + */ + private function store(): bool + { + // 1: find mapped object: + $mapped = $this->findMappedObject(); + if (!is_null($mapped->id)) { + $this->bill = $mapped; + + return true; + } + // 2: find existing by given values: + $found = $this->findExistingObject(); + if (!is_null($found->id)) { + $this->bill = $found; + + return true; + } + $name = $this->name['value'] ?? ''; + + if (strlen($name) === 0) { + return true; + } + + Log::debug('Found no bill so must create one ourselves.'); + + $data = [ + 'name' => $name, + ]; + + $this->bill = $this->repository->store($data); + Log::debug(sprintf('Successfully stored new bill #%d: %s', $this->bill->id, $this->bill->name)); + + return true; + } + +} \ No newline at end of file diff --git a/app/Import/Object/ImportBudget.php b/app/Import/Object/ImportBudget.php new file mode 100644 index 0000000000..b3732a4efa --- /dev/null +++ b/app/Import/Object/ImportBudget.php @@ -0,0 +1,229 @@ +budget = new Budget; + $this->repository = app(BudgetRepositoryInterface::class); + Log::debug('Created ImportBudget.'); + } + + /** + * @return Budget + */ + public function getBudget(): Budget + { + if (is_null($this->budget->id)) { + $this->store(); + } + + return $this->budget; + } + + /** + * @param array $id + */ + public function setId(array $id) + { + $this->id = $id; + } + + /** + * @param array $name + */ + public function setName(array $name) + { + $this->name = $name; + } + + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + $this->repository->setUser($user); + } + + /** + * @return Budget + */ + private function findExistingObject(): Budget + { + Log::debug('In findExistingObject() for Budget'); + // 1: find by ID, or name + + if (count($this->id) === 3) { + Log::debug(sprintf('Finding budget with ID #%d', $this->id['value'])); + /** @var Budget $budget */ + $budget = $this->repository->find(intval($this->id['value'])); + if (!is_null($budget->id)) { + Log::debug(sprintf('Found unmapped budget by ID (#%d): %s', $budget->id, $budget->name)); + + return $budget; + } + Log::debug('Found nothing.'); + } + // 2: find by name + if (count($this->name) === 3) { + /** @var Collection $budgets */ + $budgets = $this->repository->getBudgets(); + $name = $this->name['value']; + Log::debug(sprintf('Finding budget with name %s', $name)); + $filtered = $budgets->filter( + function (Budget $budget) use ($name) { + if ($budget->name === $name) { + Log::debug(sprintf('Found unmapped budget by name (#%d): %s', $budget->id, $budget->name)); + + return $budget; + } + + return null; + } + ); + + if ($filtered->count() === 1) { + return $filtered->first(); + } + Log::debug('Found nothing.'); + } + + // 4: do not search by account number. + Log::debug('Found NO existing budgets.'); + + return new Budget; + + } + + /** + * @return Budget + */ + private function findMappedObject(): Budget + { + Log::debug('In findMappedObject() for Budget'); + $fields = ['id', 'name']; + foreach ($fields as $field) { + $array = $this->$field; + Log::debug(sprintf('Find mapped budget based on field "%s" with value', $field), $array); + // check if a pre-mapped object exists. + $mapped = $this->getMappedObject($array); + if (!is_null($mapped->id)) { + Log::debug(sprintf('Found budget #%d!', $mapped->id)); + + return $mapped; + } + + } + Log::debug('Found no budget on mapped data or no map present.'); + + return new Budget; + } + + /** + * @param array $array + * + * @return Budget + */ + private function getMappedObject(array $array): Budget + { + Log::debug('In getMappedObject() for Budget'); + if (count($array) === 0) { + Log::debug('Array is empty, nothing will come of this.'); + + return new Budget; + } + + if (array_key_exists('mapped', $array) && is_null($array['mapped'])) { + Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value'])); + + return new Budget; + } + + Log::debug('Finding a mapped budget based on', $array); + + $search = intval($array['mapped']); + $account = $this->repository->find($search); + + Log::debug(sprintf('Found budget! #%d ("%s"). Return it', $account->id, $account->name)); + + return $account; + } + + /** + * @return bool + */ + private function store(): bool + { + // 1: find mapped object: + $mapped = $this->findMappedObject(); + if (!is_null($mapped->id)) { + $this->budget = $mapped; + + return true; + } + // 2: find existing by given values: + $found = $this->findExistingObject(); + if (!is_null($found->id)) { + $this->budget = $found; + + return true; + } + $name = $this->name['value'] ?? ''; + + if (strlen($name) === 0) { + return true; + } + + Log::debug('Found no budget so must create one ourselves.'); + + $data = [ + 'name' => $name, + ]; + + $this->budget = $this->repository->store($data); + Log::debug(sprintf('Successfully stored new budget #%d: %s', $this->budget->id, $this->budget->name)); + + return true; + } + + +} \ No newline at end of file diff --git a/app/Import/Object/ImportCategory.php b/app/Import/Object/ImportCategory.php new file mode 100644 index 0000000000..f8ee78442e --- /dev/null +++ b/app/Import/Object/ImportCategory.php @@ -0,0 +1,222 @@ +id = $id; + } + + /** + * ImportCategory constructor. + */ + public function __construct() + { + $this->category = new Category(); + $this->repository = app(CategoryRepositoryInterface::class); + Log::debug('Created ImportCategory.'); + } + + /** + * @param array $name + */ + public function setName(array $name) + { + $this->name = $name; + } + + + /** + * @return Category + */ + public function getCategory(): Category + { + if (is_null($this->category->id)) { + $this->store(); + } + + return $this->category; + } + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + $this->repository->setUser($user); + } + + /** + * @return Category + */ + private function findExistingObject(): Category + { + Log::debug('In findExistingObject() for Category'); + // 1: find by ID, or name + + if (count($this->id) === 3) { + Log::debug(sprintf('Finding category with ID #%d', $this->id['value'])); + /** @var Category $category */ + $category = $this->repository->find(intval($this->id['value'])); + if (!is_null($category->id)) { + Log::debug(sprintf('Found unmapped category by ID (#%d): %s', $category->id, $category->name)); + + return $category; + } + Log::debug('Found nothing.'); + } + // 2: find by name + if (count($this->name) === 3) { + /** @var Collection $categories */ + $categories = $this->repository->getCategories(); + $name = $this->name['value']; + Log::debug(sprintf('Finding category with name %s', $name)); + $filtered = $categories->filter( + function (Category $category) use ($name) { + if ($category->name === $name) { + Log::debug(sprintf('Found unmapped category by name (#%d): %s', $category->id, $category->name)); + + return $category; + } + + return null; + } + ); + + if ($filtered->count() === 1) { + return $filtered->first(); + } + Log::debug('Found nothing.'); + } + + // 4: do not search by account number. + Log::debug('Found NO existing categories.'); + + return new Category; + + } + + /** + * @return Category + */ + private function findMappedObject(): Category + { + Log::debug('In findMappedObject() for Category'); + $fields = ['id', 'name']; + foreach ($fields as $field) { + $array = $this->$field; + Log::debug(sprintf('Find mapped category based on field "%s" with value', $field), $array); + // check if a pre-mapped object exists. + $mapped = $this->getMappedObject($array); + if (!is_null($mapped->id)) { + Log::debug(sprintf('Found category #%d!', $mapped->id)); + + return $mapped; + } + + } + Log::debug('Found no category on mapped data or no map present.'); + + return new Category; + } + + /** + * @param array $array + * + * @return Category + */ + private function getMappedObject(array $array): Category + { + Log::debug('In getMappedObject() for Category'); + if (count($array) === 0) { + Log::debug('Array is empty, nothing will come of this.'); + + return new Category; + } + + if (array_key_exists('mapped', $array) && is_null($array['mapped'])) { + Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value'])); + + return new Category; + } + + Log::debug('Finding a mapped category based on', $array); + + $search = intval($array['mapped']); + $account = $this->repository->find($search); + + Log::debug(sprintf('Found category! #%d ("%s"). Return it', $account->id, $account->name)); + + return $account; + } + + /** + * @return bool + */ + private function store(): bool + { + // 1: find mapped object: + $mapped = $this->findMappedObject(); + if (!is_null($mapped->id)) { + $this->category = $mapped; + + return true; + } + // 2: find existing by given values: + $found = $this->findExistingObject(); + if (!is_null($found->id)) { + $this->category = $found; + + return true; + } + $name = $this->name['value'] ?? ''; + + if (strlen($name) === 0) { + return true; + } + + Log::debug('Found no category so must create one ourselves.'); + + $data = [ + 'name' => $name, + ]; + + $this->category = $this->repository->store($data); + Log::debug(sprintf('Successfully stored new category #%d: %s', $this->category->id, $this->category->name)); + + return true; + } + + +} \ No newline at end of file diff --git a/app/Import/Object/ImportCurrency.php b/app/Import/Object/ImportCurrency.php new file mode 100644 index 0000000000..8427f3ce5f --- /dev/null +++ b/app/Import/Object/ImportCurrency.php @@ -0,0 +1,218 @@ +currency = new TransactionCurrency; + $this->repository = app(CurrencyRepositoryInterface::class); + } + + /** + * @return TransactionCurrency + */ + public function getTransactionCurrency(): TransactionCurrency + { + if (!is_null($this->currency->id)) { + return $this->currency; + } + Log::debug('In createCurrency()'); + // check if any of them is mapped: + $mapped = $this->findMappedObject(); + + if (!is_null($mapped->id)) { + + Log::debug('Mapped existing currency.', ['new' => $mapped->toArray()]); + $this->currency = $mapped; + + return $mapped; + } + + $searched = $this->findExistingObject(); + if (!is_null($searched->id)) { + Log::debug('Found existing currency.', ['found' => $searched->toArray()]); + $this->currency = $searched; + + return $searched; + } + $data = [ + 'code' => $this->code['value'] ?? null, + 'symbol' => $this->symbol['value'] ?? null, + 'name' => $this->name['value'] ?? null, + 'decimal_places' => 2, + ]; + if (is_null($data['code'])) { + Log::info('Need at least a code to create currency, return nothing.'); + + return new TransactionCurrency(); + } + + Log::debug('Search for maps resulted in nothing, create new one based on', $data); + $currency = $this->repository->store($data); + $this->currency = $currency; + Log::info('Made new currency.', ['input' => $data, 'new' => $currency->toArray()]); + + + return $currency; + + } + + /** + * @param array $code + */ + public function setCode(array $code) + { + $this->code = $code; + } + + /** + * @param array $id + */ + public function setId(array $id) + { + $id['value'] = intval($id['value']); + $this->id = $id; + } + + /** + * @param array $name + */ + public function setName(array $name) + { + $this->name = $name; + } + + /** + * @param array $symbol + */ + public function setSymbol(array $symbol) + { + $this->symbol = $symbol; + } + + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + $this->repository->setUser($user); + } + + /** + * @return TransactionCurrency + */ + private function findExistingObject(): TransactionCurrency + { + $search = [ + 'id' => 'find', + 'code' => 'findByCode', + 'symbol' => 'findBySymbol', + 'name' => 'findByName', + ]; + foreach ($search as $field => $function) { + $value = $this->$field['value'] ?? null; + if (!is_null($value)) { + Log::debug(sprintf('Searching for %s using function %s and value %s', $field, $function, $value)); + $currency = $this->repository->$function($value); + + if (!is_null($currency->id)) { + return $currency; + } + } + } + + return new TransactionCurrency(); + } + + /** + * @return TransactionCurrency + */ + private function findMappedObject(): TransactionCurrency + { + Log::debug('In findMappedObject()'); + $fields = ['id', 'code', 'name', 'symbol']; + foreach ($fields as $field) { + $array = $this->$field; + Log::debug(sprintf('Find mapped currency based on field "%s" with value', $field), $array); + // check if a pre-mapped object exists. + $mapped = $this->getMappedObject($array); + if (!is_null($mapped->id)) { + Log::debug(sprintf('Found currency #%d!', $mapped->id)); + + return $mapped; + } + + } + Log::debug('Found no currency on mapped data or no map present.'); + + return new TransactionCurrency; + } + + /** + * @param array $array + * + * @return TransactionCurrency + */ + private function getMappedObject(array $array): TransactionCurrency + { + Log::debug('In getMappedObject()'); + if (count($array) === 0) { + Log::debug('Array is empty, nothing will come of this.'); + + return new TransactionCurrency; + } + + if (array_key_exists('mapped', $array) && is_null($array['mapped'])) { + Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value'])); + + return new TransactionCurrency; + } + + Log::debug('Finding a mapped object based on', $array); + + $search = intval($array['mapped']); + $currency = $this->repository->find($search); + + Log::debug(sprintf('Found currency! #%d ("%s"). Return it', $currency->id, $currency->name)); + + return $currency; + } + + +} \ No newline at end of file diff --git a/app/Import/Object/ImportJournal.php b/app/Import/Object/ImportJournal.php new file mode 100644 index 0000000000..a86cdffe5d --- /dev/null +++ b/app/Import/Object/ImportJournal.php @@ -0,0 +1,265 @@ +errors = new Collection; + $this->asset = new ImportAccount; + $this->opposing = new ImportAccount; + $this->bill = new ImportBill; + $this->category = new ImportCategory; + $this->budget = new ImportBudget; + $this->currency = new ImportCurrency; + } + + /** + * @param array $modifier + */ + public function addToModifier(array $modifier) + { + $this->modifiers[] = $modifier; + } + + /** + * @return TransactionJournal + * @throws FireflyException + */ + public function createTransactionJournal(): TransactionJournal + { + exit('does not work yet'); + } + + /** + * @return string + */ + public function getAmount(): string + { + + /** @var ConverterInterface $amountConverter */ + $amountConverter = app(Amount::class); + $this->amount = $amountConverter->convert($this->amount); + // modify + foreach ($this->modifiers as $modifier) { + $class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $modifier['role']))); + /** @var ConverterInterface $converter */ + $converter = app($class); + if ($converter->convert($modifier['value']) === -1) { + $this->amount = Steam::negative($this->amount); + } + } + + return $this->amount; + } + + /** + * @return ImportCurrency + */ + public function getCurrency(): ImportCurrency + { + return $this->currency; + } + + /** + * @param string $format + * + * @return Carbon + */ + public function getDate(string $format): Carbon + { + return Carbon::createFromFormat($format, $this->date); + } + + /** + * @param string $hash + */ + public function setHash(string $hash) + { + $this->hash = $hash; + } + + /** + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + + // set user for related objects: + $this->asset->setUser($user); + $this->opposing->setUser($user); + $this->budget->setUser($user); + $this->category->setUser($user); + $this->bill->setUser($user); + } + + /** + * @param array $array + * + * @throws FireflyException + */ + public function setValue(array $array) + { + switch ($array['role']) { + default: + throw new FireflyException(sprintf('ImportJournal cannot handle "%s" with value "%s".', $array['role'], $array['value'])); + case 'account-id': + $this->asset->setAccountId($array); + break; + case 'amount': + $this->amount = $array['value']; + break; + case 'account-iban': + $this->asset->setAccountIban($array); + break; + case 'account-name': + $this->asset->setAccountName($array); + break; + case 'account-number': + $this->asset->setAccountNumber($array); + break; + case 'bill-id': + $this->bill->setId($array); + break; + case 'bill-name': + $this->bill->setName($array); + break; + case 'budget-id': + $this->budget->setId($array); + break; + case 'budget-name': + $this->budget->setName($array); + break; + case 'category-id': + $this->category->setId($array); + break; + case 'category-name': + $this->category->setName($array); + break; + case 'currency-code': + $this->currency->setCode($array); + break; + case 'currency-id': + $this->currency->setId($array); + break; + case 'currency-name': + $this->currency->setName($array); + break; + case 'currency-symbol': + $this->currency->setSymbol($array); + break; + case 'date-transaction': + $this->date = $array['value']; + break; + case 'description': + $this->description = $array['value']; + break; + case 'sepa-ct-op': + case 'sepa-ct-id': + case 'sepa-db': + $this->notes .= ' '.$array['value']; + $this->notes = trim($this->notes); + break; + case 'external-id': + $this->externalId = $array['value']; + break; + case '_ignore': + break; + case 'ing-debet-credit': + case 'rabo-debet-credit': + $this->addToModifier($array); + break; + case 'opposing-iban': + $this->opposing->setAccountIban($array); + break; + case 'opposing-name': + $this->opposing->setAccountName($array); + break; + case 'opposing-number': + $this->opposing->setAccountNumber($array); + break; + case 'opposing-id': + $this->opposing->setAccountId($array); + break; + case 'tags-comma': + case 'tags-space': + $this->tags[] = $array; + break; + case 'date-interest': + $this->metaDates['interest_date'] = $array['value']; + break; + case 'date-book': + $this->metaDates['book_date'] = $array['value']; + break; + case 'date-process': + $this->metaDates['process_date'] = $array['value']; + break; + } + } +} \ No newline at end of file diff --git a/app/Import/Object/ImportTransaction.php b/app/Import/Object/ImportTransaction.php new file mode 100644 index 0000000000..cbb6a453cd --- /dev/null +++ b/app/Import/Object/ImportTransaction.php @@ -0,0 +1,157 @@ +currency = new ImportCurrency; + } + + public function addToModifier(array $modifier) + { + $this->modifiers[] = $modifier; + } + + /** + * @return string + */ + public function getAmount(): string + { + // use converter: + $this->amount = strval($this->parseAmount()); + + + // also apply modifiers: + $this->amount = Steam::positive($this->amount); + + // Can handle ING + foreach ($this->modifiers as $modifier) { + $class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $modifier['role']))); + /** @var ConverterInterface $converter */ + $converter = app($class); + if ($converter->convert($modifier['value']) === -1) { + $this->amount = Steam::negative($this->amount); + } + } + + return $this->amount; + } + + /** + * @param string $amount + */ + public function setAmount(string $amount) + { + $this->amount = $amount; + } + + /** + * @return ImportCurrency + */ + public function getCurrency(): ImportCurrency + { + return $this->currency; + } + + /** + * @param ImportCurrency $currency + */ + public function setCurrency(ImportCurrency $currency) + { + $this->currency = $currency; + } + + /** + * @param string $date + */ + public function setDate(string $date) + { + $this->date = $date; + } + + /** + * @param string $description + */ + public function setDescription(string $description) + { + $this->description = $description; + } + + /** + * @param bool $positive + */ + public function setPositive(bool $positive) + { + $this->positive = $positive; + } + + /** + * Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems. + * - Jamie Zawinski + * + * @return float + */ + private function parseAmount() + { + $value = $this->amount; + $len = strlen($value); + $decimalPosition = $len - 3; + $decimal = null; + + if (($len > 2 && $value{$decimalPosition} == '.') || ($len > 2 && strpos($value, '.') > $decimalPosition)) { + $decimal = '.'; + } + if ($len > 2 && $value{$decimalPosition} == ',') { + $decimal = ','; + } + + // if decimal is dot, replace all comma's and spaces with nothing. then parse as float (round to 4 pos) + if ($decimal === '.') { + $search = [',', ' ']; + $value = str_replace($search, '', $value); + } + if ($decimal === ',') { + $search = ['.', ' ']; + $value = str_replace($search, '', $value); + $value = str_replace(',', '.', $value); + } + if (is_null($decimal)) { + // replace all: + $search = ['.', ' ', ',']; + $value = str_replace($search, '', $value); + } + + return round(floatval($value), 12); + } + +} \ No newline at end of file diff --git a/app/Import/Routine/ImportRoutine.php b/app/Import/Routine/ImportRoutine.php new file mode 100644 index 0000000000..62da476f86 --- /dev/null +++ b/app/Import/Routine/ImportRoutine.php @@ -0,0 +1,166 @@ +journals = new Collection; + $this->errors = new Collection; + } + + /** + * @param ImportJob $job + */ + public function setJob(ImportJob $job) + { + $this->job = $job; + } + + + /** + * + */ + public function run(): bool + { + if ($this->job->status !== 'configured') { + Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->job->status)); + + return false; + } + set_time_limit(0); + Log::info(sprintf('Start with import job %s', $this->job->key)); + + $importObjects = $this->getImportObjects(); + $this->lines = $importObjects->count(); + + // once done, use storage thing to actually store them: + Log::info(sprintf('Returned %d valid objects from file processor', $this->lines)); + + $storage = $this->storeObjects($importObjects); + + // update job: + $this->job->status = 'finished'; + $this->job->save(); + + $this->journals = $storage->journals; + $this->errors = $storage->errors; + + // create tag, link tag to all journals: + $this->createImportTag(); + + Log::info(sprintf('Done with import job %s', $this->job->key)); + + return true; + } + + /** + * @return Collection + */ + protected function getImportObjects(): Collection + { + $objects = new Collection; + $type = $this->job->file_type; + $class = config(sprintf('firefly.import_processors.%s', $type)); + /** @var FileProcessorInterface $processor */ + $processor = app($class); + $processor->setJob($this->job); + + if ($this->job->status == 'configured') { + + // set job as "running"... + $this->job->status = 'running'; + $this->job->save(); + + Log::debug('Job is configured, start with run()'); + $processor->run(); + $objects = $processor->getObjects(); + } + + return $objects; + } + + /** + * + */ + private function createImportTag(): Tag + { + /** @var TagRepositoryInterface $repository */ + $repository = app(TagRepositoryInterface::class); + $repository->setUser($this->job->user); + $data = [ + 'tag' => trans('firefly.import_with_key', ['key' => $this->job->key]), + 'date' => new Carbon, + 'description' => null, + 'latitude' => null, + 'longitude' => null, + 'zoomLevel' => null, + 'tagMode' => 'nothing', + ]; + $tag = $repository->store($data); + $extended = $this->job->extended_status; + $extended['tag'] = $tag->id; + $this->job->extended_status = $extended; + $this->job->save(); + + $this->journals->each( + function (TransactionJournal $journal) use ($tag) { + $journal->tags()->save($tag); + } + ); + + return $tag; + + } + + /** + * @param Collection $objects + * + * @return ImportStorage + */ + private function storeObjects(Collection $objects): ImportStorage + { + $storage = new ImportStorage; + $storage->setJob($this->job); + $storage->setDateFormat($this->job->configuration['date-format']); + $storage->setObjects($objects); + $storage->store(); + + return $storage; + } +} \ No newline at end of file diff --git a/app/Import/Setup/CsvSetup.php b/app/Import/Setup/CsvSetup.php deleted file mode 100644 index e3888e0030..0000000000 --- a/app/Import/Setup/CsvSetup.php +++ /dev/null @@ -1,287 +0,0 @@ -defaultImportAccount = new Account; - } - - /** - * Create initial (empty) configuration array. - * - * @return bool - */ - public function configure(): bool - { - if (is_null($this->job->configuration) || (is_array($this->job->configuration) && count($this->job->configuration) === 0)) { - Log::debug('No config detected, will create empty one.'); - $this->job->configuration = config('csv.default_config'); - $this->job->save(); - - return true; - } - - // need to do nothing, for now. - Log::debug('Detected config in upload, will use that one. ', $this->job->configuration); - - return true; - } - - /** - * @return array - */ - public function getConfigurationData(): array - { - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - $delimiters = [ - ',' => trans('form.csv_comma'), - ';' => trans('form.csv_semicolon'), - 'tab' => trans('form.csv_tab'), - ]; - - $specifics = []; - - // collect specifics. - foreach (config('csv.import_specifics') as $name => $className) { - $specifics[$name] = [ - 'name' => $className::getName(), - 'description' => $className::getDescription(), - ]; - } - - $data = [ - 'accounts' => ExpandedForm::makeSelectList($accounts), - 'specifix' => [], - 'delimiters' => $delimiters, - 'upload_path' => storage_path('upload'), - 'is_upload_possible' => is_writable(storage_path('upload')), - 'specifics' => $specifics, - ]; - - return $data; - } - - /** - * This method returns the data required for the view that will let the user add settings to the import job. - * - * @return array - */ - public function getDataForSettings(): array - { - Log::debug('Now in getDataForSettings()'); - if ($this->doColumnRoles()) { - Log::debug('doColumnRoles() is true.'); - $data = $this->getDataForColumnRoles(); - - return $data; - } - - if ($this->doColumnMapping()) { - Log::debug('doColumnMapping() is true.'); - $data = $this->getDataForColumnMapping(); - - return $data; - } - - echo 'no settings to do.'; - exit; - - } - - /** - * This method returns the name of the view that will be shown to the user to further configure - * the import job. - * - * @return string - * @throws FireflyException - */ - public function getViewForSettings(): string - { - if ($this->doColumnRoles()) { - return 'import.csv.roles'; - } - - if ($this->doColumnMapping()) { - return 'import.csv.map'; - } - throw new FireflyException('There is no view for the current CSV import step.'); - } - - /** - * This method returns whether or not the user must configure this import - * job further. - * - * @return bool - */ - public function requireUserSettings(): bool - { - Log::debug(sprintf('doColumnMapping is %s', $this->doColumnMapping())); - Log::debug(sprintf('doColumnRoles is %s', $this->doColumnRoles())); - if ($this->doColumnMapping() || $this->doColumnRoles()) { - Log::debug('Return true'); - - return true; - } - Log::debug('Return false'); - - return false; - } - - /** - * @param array $data - * - * @param FileBag $files - * - * @return bool - */ - public function saveImportConfiguration(array $data, FileBag $files): bool - { - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $importId = $data['csv_import_account'] ?? 0; - $account = $repository->find(intval($importId)); - - $hasHeaders = isset($data['has_headers']) && intval($data['has_headers']) === 1 ? true : false; - $config = $this->job->configuration; - $config['has-headers'] = $hasHeaders; - $config['date-format'] = $data['date_format']; - $config['delimiter'] = $data['csv_delimiter']; - $config['delimiter'] = $config['delimiter'] === 'tab' ? "\t" : $config['delimiter']; - - Log::debug('Entered import account.', ['id' => $importId]); - - - if (!is_null($account->id)) { - Log::debug('Found account.', ['id' => $account->id, 'name' => $account->name]); - $config['import-account'] = $account->id; - } else { - Log::error('Could not find anything for csv_import_account.', ['id' => $importId]); - } - // loop specifics. - if (isset($data['specifics']) && is_array($data['specifics'])) { - foreach ($data['specifics'] as $name => $enabled) { - // verify their content. - $className = sprintf('FireflyIII\Import\Specifics\%s', $name); - if (class_exists($className)) { - $config['specifics'][$name] = 1; - } - } - } - $this->job->configuration = $config; - $this->job->save(); - - return true; - - - } - - /** - * @param ImportJob $job - */ - public function setJob(ImportJob $job) - { - $this->job = $job; - } - - /** - * Store the settings filled in by the user, if applicable. - * - * @param Request $request - * - */ - public function storeSettings(Request $request) - { - $config = $this->job->configuration; - $all = $request->all(); - if ($request->get('settings') == 'roles') { - $count = $config['column-count']; - - $roleSet = 0; // how many roles have been defined - $mapSet = 0; // how many columns must be mapped - for ($i = 0; $i < $count; $i++) { - $selectedRole = $all['role'][$i] ?? '_ignore'; - $doMapping = isset($all['map'][$i]) && $all['map'][$i] == '1' ? true : false; - if ($selectedRole == '_ignore' && $doMapping === true) { - $doMapping = false; // cannot map ignored columns. - } - if ($selectedRole != '_ignore') { - $roleSet++; - } - if ($doMapping === true) { - $mapSet++; - } - $config['column-roles'][$i] = $selectedRole; - $config['column-do-mapping'][$i] = $doMapping; - } - if ($roleSet > 0) { - $config['column-roles-complete'] = true; - $this->job->configuration = $config; - $this->job->save(); - } - if ($mapSet === 0) { - // skip setting of map: - $config['column-mapping-complete'] = true; - } - } - if ($request->get('settings') == 'map') { - if (isset($all['mapping'])) { - foreach ($all['mapping'] as $index => $data) { - $config['column-mapping-config'][$index] = []; - foreach ($data as $value => $mapId) { - $mapId = intval($mapId); - if ($mapId !== 0) { - $config['column-mapping-config'][$index][$value] = intval($mapId); - } - } - } - } - - // set thing to be completed. - $config['column-mapping-complete'] = true; - $this->job->configuration = $config; - $this->job->save(); - } - } -} diff --git a/app/Import/Setup/SetupInterface.php b/app/Import/Setup/SetupInterface.php deleted file mode 100644 index cffae80eba..0000000000 --- a/app/Import/Setup/SetupInterface.php +++ /dev/null @@ -1,88 +0,0 @@ -objects = new Collection; + $this->journals = new Collection; + $this->errors = new Collection; + } + + /** + * @param string $dateFormat + */ + public function setDateFormat(string $dateFormat) + { + $this->dateFormat = $dateFormat; + } + + /** + * @param ImportJob $job + */ + public function setJob(ImportJob $job) + { + $this->job = $job; + $this->rules = $this->getUserRules(); + } + + /** + * @param Collection $objects + */ + public function setObjects(Collection $objects) + { + $this->objects = $objects; + } + + /** + * Do storage of import objects + */ + public function store() + { + $this->defaultCurrency = Amount::getDefaultCurrencyByUser($this->job->user); + + // routine below consists of 3 steps. + /** + * @var int $index + * @var ImportJournal $object + */ + foreach ($this->objects as $index => $object) { + try { + $this->storeImportJournal($index, $object); + } catch (FireflyException $e) { + $this->errors->push($e->getMessage()); + } + } + + + } + + /** + * @param TransactionJournal $journal + * + * @return bool + */ + protected function applyRules(TransactionJournal $journal): bool + { + if ($this->rules->count() > 0) { + + /** @var Rule $rule */ + foreach ($this->rules as $rule) { + Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id)); + $processor = Processor::make($rule); + $processor->handleTransactionJournal($journal); + + if ($rule->stop_processing) { + return true; + } + } + } + + return true; + } + + /** + * @param int $journalId + * @param int $accountId + * @param int $currencyId + * @param string $amount + * + * @return bool + * @throws FireflyException + */ + private function createTransaction(int $journalId, int $accountId, int $currencyId, string $amount): bool + { + $transaction = new Transaction; + $transaction->account_id = $accountId; + $transaction->transaction_journal_id = $journalId; + $transaction->transaction_currency_id = $currencyId; + $transaction->amount = $amount; + $transaction->save(); + if (is_null($transaction->id)) { + $errorText = join(', ', $transaction->getErrors()->all()); + throw new FireflyException($errorText); + } + Log::debug(sprintf('Created transaction with ID #%d and account #%d', $transaction->id, $accountId)); + + return true; + } + + /** + * @param ImportJournal $importJournal + * + * @return TransactionCurrency + */ + private function getCurrency(ImportJournal $importJournal): TransactionCurrency + { + $currency = $importJournal->getCurrency()->getTransactionCurrency(); + if (is_null($currency->id)) { + $currency = $this->defaultCurrency; + } + + return $currency; + } + + /** + * @param ImportAccount $account + * @param $amount + * + * @return Account + */ + private function getOpposingAccount(ImportAccount $account, $amount): Account + { + if (bccomp($amount, '0') === -1) { + Log::debug(sprintf('%s is negative, create opposing expense account.', $amount)); + $account->setExpectedType(AccountType::EXPENSE); + + return $account->getAccount(); + } + Log::debug(sprintf('%s is positive, create opposing revenue account.', $amount)); + // amount is positive, it's a deposit, opposing is an revenue: + $account->setExpectedType(AccountType::REVENUE); + + return $account->getAccount(); + + } + + /** + * @param string $amount + * + * @return TransactionType + */ + private function getTransactionType(string $amount): TransactionType + { + $transactionType = new TransactionType(); + // amount is negative, it's a withdrawal, opposing is an expense: + if (bccomp($amount, '0') === -1) { + $transactionType = TransactionType::whereType(TransactionType::WITHDRAWAL)->first(); + } + if (bccomp($amount, '0') === 1) { + $transactionType = TransactionType::whereType(TransactionType::DEPOSIT)->first(); + } + + return $transactionType; + } + + /** + * @return Collection + */ + private function getUserRules(): Collection + { + $set = Rule::distinct() + ->where('rules.user_id', $this->job->user->id) + ->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id') + ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') + ->where('rule_groups.active', 1) + ->where('rule_triggers.trigger_type', 'user_action') + ->where('rule_triggers.trigger_value', 'store-journal') + ->where('rules.active', 1) + ->orderBy('rule_groups.order', 'ASC') + ->orderBy('rules.order', 'ASC') + ->get(['rules.*', 'rule_groups.order']); + Log::debug(sprintf('Found %d user rules.', $set->count())); + + return $set; + + } + + /** + * @param TransactionJournal $journal + * @param Bill $bill + */ + private function storeBill(TransactionJournal $journal, Bill $bill) + { + if (!is_null($bill->id)) { + Log::debug(sprintf('Linked bill #%d to journal #%d', $bill->id, $journal->id)); + $journal->bill()->associate($bill); + $journal->save(); + } + } + + /** + * @param TransactionJournal $journal + * @param Budget $budget + */ + private function storeBudget(TransactionJournal $journal, Budget $budget) + { + if (!is_null($budget->id)) { + Log::debug(sprintf('Linked budget #%d to journal #%d', $budget->id, $journal->id)); + $journal->budgets()->save($budget); + } + } + + /** + * @param TransactionJournal $journal + * @param Category $category + */ + private function storeCategory(TransactionJournal $journal, Category $category) + { + + if (!is_null($category->id)) { + Log::debug(sprintf('Linked category #%d to journal #%d', $category->id, $journal->id)); + $journal->categories()->save($category); + } + + } + + private function storeImportJournal(int $index, ImportJournal $importJournal): bool + { + Log::debug(sprintf('Going to store object #%d with description "%s"', $index, $importJournal->description)); + + $asset = $importJournal->asset->getAccount(); + $amount = $importJournal->getAmount(); + $currency = $this->getCurrency($importJournal); + $date = $importJournal->getDate($this->dateFormat); + $transactionType = $this->getTransactionType($amount); + $opposing = $this->getOpposingAccount($importJournal->opposing, $amount); + + // if opposing is an asset account, it's a transfer: + if ($opposing->accountType->type === AccountType::ASSET) { + Log::debug(sprintf('Opposing account #%d %s is an asset account, make transfer.', $opposing->id, $opposing->name)); + $transactionType = TransactionType::whereType(TransactionType::TRANSFER)->first(); + } + + /*** First step done! */ + $this->job->addStepsDone(1); + + // create a journal: + $journal = new TransactionJournal; + $journal->user_id = $this->job->user_id; + $journal->transaction_type_id = $transactionType->id; + $journal->transaction_currency_id = $currency->id; + $journal->description = $importJournal->description; + $journal->date = $date->format('Y-m-d'); + $journal->order = 0; + $journal->tag_count = 0; + $journal->encrypted = 0; + $journal->completed = false; + + if (!$journal->save()) { + $errorText = join(', ', $journal->getErrors()->all()); + $this->job->addStepsDone(3); + throw new FireflyException($errorText); + } + + // save meta data: + $journal->setMeta('importHash', $importJournal->hash); + Log::debug(sprintf('Created journal with ID #%d', $journal->id)); + + // create transactions: + $this->createTransaction($journal->id, $asset->id, $currency->id, $amount); + $this->createTransaction($journal->id, $opposing->id, $currency->id, Steam::opposite($amount)); + + /*** Another step done! */ + $this->job->addStepsDone(1); + + // store meta object things: + $this->storeCategory($journal, $importJournal->category->getCategory()); + $this->storeBudget($journal, $importJournal->budget->getBudget()); + $this->storeBill($journal, $importJournal->bill->getBill()); + $this->storeMeta($journal, $importJournal->metaDates); + + // sepa thing as note: + if (strlen($importJournal->notes) > 0) { + $journal->setMeta('notes', $importJournal->notes); + } + + // set journal completed: + $journal->completed = true; + $journal->save(); + + $this->job->addStepsDone(1); + + // run rules: + $this->applyRules($journal); + $this->job->addStepsDone(1); + + $this->journals->push($journal); + + return true; + } + + /** + * @param TransactionJournal $journal + * @param array $dates + */ + private function storeMeta(TransactionJournal $journal, array $dates) + { + // all other date fields as meta thing: + foreach ($dates as $name => $value) { + try { + $date = new Carbon($value); + $journal->setMeta($name, $date); + } catch (\Exception $e) { + // don't care, ignore: + Log::warning(sprintf('Could not parse "%s" into a valid Date object for field %s', $value, $name)); + } + } + } + +} \ No newline at end of file diff --git a/app/Import/notes.txt b/app/Import/notes.txt deleted file mode 100644 index 603e185f84..0000000000 --- a/app/Import/notes.txt +++ /dev/null @@ -1,47 +0,0 @@ -The import routine is as follows: - -1. Upload and setup: - -User uploads a file with entries. The Setup/SetupInterface gives the user -the opportunity (in any number of steps) to do the necessary configuration. -This could also be skipped of course. An ImportJob object is created with a -basic and empty configuration. - -Helper classes are as follows, greatly modelled to the CSV importer: - -- The Mapper classes give back lists of Firefly objects. You can show them to the -user in order to help you convert text values to their Firefly counterparts. -For example, the user maps "Gcrsr" to category Groceries. -- The Converter classes exist to help convert text values to their Firely counterparts. -Feed "AB12ABCD897829" to the AssetAccountIban Converter and you should end up with a new -or found asset account. The previously built mapping is used to narrow it down. Submit an empty -mapping if this one is not relevant. - -The mapping and possibly other configuration options are stored in a newly created -ImportJob object, stored in the database. This import job holds a reference to the uploaded file -(placed encrypted in /storage/uploads) and the status of the import. - -2. Actual import - -Using either the command line or the web interface the user can tell Firefly to start the import. - -The ImporterInterface runs and creates an ImportEntry for each line, blob or whatever distinction it -wants. - -For each line, the ImporterInterface should run each field through the selected Converter in order -to convert the text values to their Firefly counterparts. Again, this is modelled to the CSV importer -and may need an update for MT940. - -In any case, this newly minted set of ImportEntries (it cannot be saved or stored atm, -this has to be done in one go) is then fed to the ImportValidator which will reject entries -(almost never) and corrects fields if necessary. - - -- Adds the date (today) if no date is present. -- Determins the type of transaction (withdrawal, deposit, transfer). -- Determins the types of accounts involved (asset, expense, revenue). -- Determins the currency of the transaction. -- Adds a default description if there isn't one present. - -This set of corrected ImportEntries is then fed to the ImportStorage class which will generate -TransactionJournals, Transactions and other related objects. \ No newline at end of file diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php index 66dfc167eb..5b3febd6c5 100644 --- a/app/Models/ImportJob.php +++ b/app/Models/ImportJob.php @@ -42,11 +42,11 @@ class ImportJob extends Model protected $validStatus = [ - 'import_status_never_started', // initial state - 'import_configuration_saved', // import configuration saved. This step is going to be obsolete. - 'settings_complete', // aka: ready for import. - 'import_running', // import currently underway - 'import_complete', // done with everything + 'new', + 'initialized', + 'configured', + 'running', + 'finished', ]; /** @@ -66,13 +66,28 @@ class ImportJob extends Model throw new NotFoundHttpException; } + /** + * @param int $index + * @param string $message + * + * @return bool + */ + public function addError(int $index, string $message): bool + { + $extended = $this->extended_status; + $extended['errors'][$index][] = $message; + $this->extended_status = $extended; + + return true; + } + /** * @param int $count */ public function addStepsDone(int $count) { - $status = $this->extended_status; - $status['steps_done'] += $count; + $status = $this->extended_status; + $status['done'] += $count; $this->extended_status = $status; $this->save(); @@ -84,7 +99,7 @@ class ImportJob extends Model public function addTotalSteps(int $count) { $status = $this->extended_status; - $status['total_steps'] += $count; + $status['steps'] += $count; $this->extended_status = $status; $this->save(); @@ -165,7 +180,7 @@ class ImportJob extends Model $disk = Storage::disk('upload'); $encryptedContent = $disk->get($fileName); $content = Crypt::decrypt($encryptedContent); - Log::debug(sprintf('Content size is %d bytes.', $content)); + Log::debug(sprintf('Content size is %d bytes.', strlen($content))); return $content; } diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 30018b5e87..19573b2b29 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -133,6 +133,9 @@ class Tag extends Model */ public function getTagAttribute($value) { + if(is_null($value)) { + return null; + } return Crypt::decrypt($value); } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index c7d6422a50..86bd353c0a 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -243,9 +243,11 @@ class AccountRepository implements AccountRepositoryInterface } // account may exist already: - $existingAccount = $this->findByName($data['name'], [$data['accountType']]); + $existingAccount = $this->findByName($data['name'], [$type]); if (!is_null($existingAccount->id)) { - throw new FireflyException(sprintf('There already is an account named "%s" of type "%s".', $data['name'], $data['accountType'])); + Log::warning(sprintf('There already is an account named "%s" of type "%s".', $data['name'], $type)); + + return $existingAccount; } // create it: @@ -267,6 +269,7 @@ class AccountRepository implements AccountRepositoryInterface ); throw new FireflyException(sprintf('Tried to create account named "%s" but failed. The logs have more details.', $data['name'])); } + Log::debug(sprintf('Created new account #%d named "%s" of type %s.', $newAccount->id, $newAccount->name, $accountType->type)); return $newAccount; } diff --git a/app/Repositories/Account/FindAccountsTrait.php b/app/Repositories/Account/FindAccountsTrait.php index 062c7c1b8f..a3a4248ab2 100644 --- a/app/Repositories/Account/FindAccountsTrait.php +++ b/app/Repositories/Account/FindAccountsTrait.php @@ -13,11 +13,14 @@ namespace FireflyIII\Repositories\Account; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; +use FireflyIII\User; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; use Log; /** + * @property User $user + * * Trait FindAccountsTrait * * @package FireflyIII\Repositories\Account @@ -107,7 +110,7 @@ trait FindAccountsTrait $query->whereIn('account_types.type', $types); } - Log::debug(sprintf('Searching for account named %s of the following type(s)', $name), ['types' => $types]); + Log::debug(sprintf('Searching for account named "%s" (of user #%d) of the following type(s)', $name, $this->user->id), ['types' => $types]); $accounts = $query->get(['accounts.*']); /** @var Account $account */ @@ -118,7 +121,7 @@ trait FindAccountsTrait return $account; } } - Log::debug('Found nothing.'); + Log::debug(sprintf('There is no account with name "%s" or types', $name), $types); return new Account; } diff --git a/app/Repositories/Attachment/AttachmentRepository.php b/app/Repositories/Attachment/AttachmentRepository.php index b7c174b103..91a3dcbd73 100644 --- a/app/Repositories/Attachment/AttachmentRepository.php +++ b/app/Repositories/Attachment/AttachmentRepository.php @@ -19,6 +19,7 @@ use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Models\Attachment; use FireflyIII\User; use Illuminate\Support\Collection; +use Log; use Storage; /** @@ -95,16 +96,20 @@ class AttachmentRepository implements AttachmentRepositoryInterface public function getContent(Attachment $attachment): string { // create a disk. - $disk = Storage::disk('upload'); - $file = $attachment->fileName(); + $disk = Storage::disk('upload'); + $file = $attachment->fileName(); + $content = ''; if ($disk->exists($file)) { $content = Crypt::decrypt($disk->get($file)); + } + if (is_bool($content)) { + Log::error(sprintf('Attachment #%d may be corrupted: the content could not be decrypted.', $attachment->id)); - return $content; + return ''; } - return ''; + return $content; } /** diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 43599dc820..00ce78394f 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -262,6 +262,8 @@ class BillRepository implements BillRepositoryInterface } /** + * The "paid dates" list is a list of dates of transaction journals that are linked to this bill. + * * @param Bill $bill * @param Carbon $start * @param Carbon $end diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index 6af984a06c..36bd597406 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -13,10 +13,16 @@ declare(strict_types=1); namespace FireflyIII\Repositories\ImportJob; +use Crypt; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\User; use Illuminate\Support\Str; +use Log; +use SplFileObject; +use Storage; +use Symfony\Component\HttpFoundation\File\UploadedFile; /** * Class ImportJobRepository @@ -51,13 +57,13 @@ class ImportJobRepository implements ImportJobRepositoryInterface $importJob->user()->associate($this->user); $importJob->file_type = $fileType; $importJob->key = Str::random(12); - $importJob->status = 'import_status_never_started'; + $importJob->status = 'new'; + $importJob->configuration = []; $importJob->extended_status = [ - 'total_steps' => 0, - 'steps_done' => 0, - 'import_count' => 0, - 'importTag' => 0, - 'errors' => [], + 'steps' => 0, + 'done' => 0, + 'tag' => 0, + 'errors' => [], ]; $importJob->save(); @@ -86,6 +92,75 @@ class ImportJobRepository implements ImportJobRepositoryInterface return $result; } + /** + * @param ImportJob $job + * @param UploadedFile $file + * + * @return bool + */ + public function processConfiguration(ImportJob $job, UploadedFile $file): bool + { + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + // demo user's configuration upload is ignored completely. + if (!$repository->hasRole($this->user, 'demo')) { + Log::debug( + 'Uploaded configuration file', ['name' => $file->getClientOriginalName(), 'size' => $file->getSize(), 'mime' => $file->getClientMimeType()] + ); + + $configFileObject = new SplFileObject($file->getRealPath()); + $configRaw = $configFileObject->fread($configFileObject->getSize()); + $configuration = json_decode($configRaw, true); + + if (!is_null($configuration) && is_array($configuration)) { + Log::debug('Found configuration', $configuration); + $this->setConfiguration($job, $configuration); + } + } + + return true; + } + + /** + * @param ImportJob $job + * @param UploadedFile $file + * + * @return mixed + */ + public function processFile(ImportJob $job, UploadedFile $file): bool + { + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + $newName = sprintf('%s.upload', $job->key); + $uploaded = new SplFileObject($file->getRealPath()); + $content = $uploaded->fread($uploaded->getSize()); + $contentEncrypted = Crypt::encrypt($content); + $disk = Storage::disk('upload'); + + + // user is demo user, replace upload with prepared file. + if ($repository->hasRole($this->user, 'demo')) { + $stubsDisk = Storage::disk('stubs'); + $content = $stubsDisk->get('demo-import.csv'); + $contentEncrypted = Crypt::encrypt($content); + $disk->put($newName, $contentEncrypted); + Log::debug('Replaced upload with demo file.'); + + // also set up prepared configuration. + $configuration = json_decode($stubsDisk->get('demo-configuration.json'), true); + $this->setConfiguration($job, $configuration); + Log::debug('Set configuration for demo user', $configuration); + } + + if (!$repository->hasRole($this->user, 'demo')) { + // user is not demo, process original upload: + $disk->put($newName, $contentEncrypted); + Log::debug('Uploaded file', ['name' => $file->getClientOriginalName(), 'size' => $file->getSize(), 'mime' => $file->getClientMimeType()]); + } + + return true; + } + /** * @param ImportJob $job * @param array $configuration diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php index 5bdf636d42..421a7849b2 100644 --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php @@ -15,6 +15,7 @@ namespace FireflyIII\Repositories\ImportJob; use FireflyIII\Models\ImportJob; use FireflyIII\User; +use Symfony\Component\HttpFoundation\File\UploadedFile; /** * Interface ImportJobRepositoryInterface @@ -37,6 +38,22 @@ interface ImportJobRepositoryInterface */ public function findByKey(string $key): ImportJob; + /** + * @param ImportJob $job + * @param UploadedFile $file + * + * @return mixed + */ + public function processFile(ImportJob $job, UploadedFile $file): bool; + + /** + * @param ImportJob $job + * @param UploadedFile $file + * + * @return bool + */ + public function processConfiguration(ImportJob $job, UploadedFile $file): bool; + /** * @param ImportJob $job * @param array $configuration diff --git a/app/Repositories/Journal/CreateJournalsTrait.php b/app/Repositories/Journal/CreateJournalsTrait.php index dbfa818e84..77b97eadd2 100644 --- a/app/Repositories/Journal/CreateJournalsTrait.php +++ b/app/Repositories/Journal/CreateJournalsTrait.php @@ -19,16 +19,21 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use FireflyIII\User; use Illuminate\Support\Collection; use Log; /** + * @property User $user + * * Trait CreateJournalsTrait * * @package FireflyIII\Repositories\Journal */ trait CreateJournalsTrait { + + /** * * * Remember: a balancingAct takes at most one expense and one transfer. diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 89ef0dea05..6d68b4b455 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -18,6 +18,7 @@ use FireflyIII\Models\Transaction as TransactionModel; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; +use FireflyIII\User; use Illuminate\Support\Collection; use Preferences as Prefs; @@ -208,13 +209,27 @@ class Amount * @throws FireflyException */ public function getDefaultCurrency(): TransactionCurrency + { + $user = auth()->user(); + + return $this->getDefaultCurrencyByUser($user); + } + + /** + * @param User $user + * + * @return \FireflyIII\Models\TransactionCurrency + * @throws FireflyException + */ + public function getDefaultCurrencyByUser(User $user): TransactionCurrency { $cache = new CacheProperties; $cache->addProperty('getDefaultCurrency'); + $cache->addProperty($user->id); if ($cache->has()) { return $cache->get(); // @codeCoverageIgnore } - $currencyPreference = Prefs::get('currencyPreference', config('firefly.default_currency', 'EUR')); + $currencyPreference = Prefs::getForUser($user, 'currencyPreference', config('firefly.default_currency', 'EUR')); $currency = TransactionCurrency::where('code', $currencyPreference->data)->first(); if (is_null($currency)) { throw new FireflyException(sprintf('No currency found with code "%s"', $currencyPreference->data)); diff --git a/app/Support/Import/Configuration/ConfigurationInterface.php b/app/Support/Import/Configuration/ConfigurationInterface.php new file mode 100644 index 0000000000..f44203517e --- /dev/null +++ b/app/Support/Import/Configuration/ConfigurationInterface.php @@ -0,0 +1,46 @@ +getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + $delimiters = [ + ',' => trans('form.csv_comma'), + ';' => trans('form.csv_semicolon'), + 'tab' => trans('form.csv_tab'), + ]; + + $specifics = []; + + // collect specifics. + foreach (config('csv.import_specifics') as $name => $className) { + $specifics[$name] = [ + 'name' => $className::getName(), + 'description' => $className::getDescription(), + ]; + } + + $data = [ + 'accounts' => ExpandedForm::makeSelectList($accounts), + 'specifix' => [], + 'delimiters' => $delimiters, + 'specifics' => $specifics, + ]; + + return $data; + } + + /** + * @param ImportJob $job + * + * @return ConfigurationInterface + */ + public function setJob(ImportJob $job): ConfigurationInterface + { + $this->job = $job; + + return $this; + } + + /** + * Store the result. + * + * @param array $data + * + * @return bool + */ + public function storeConfiguration(array $data): bool + { + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $importId = $data['csv_import_account'] ?? 0; + $account = $repository->find(intval($importId)); + + $hasHeaders = isset($data['has_headers']) && intval($data['has_headers']) === 1 ? true : false; + $config = $this->job->configuration; + $config['initial-config-complete'] = true; + $config['has-headers'] = $hasHeaders; + $config['date-format'] = $data['date_format']; + $config['delimiter'] = $data['csv_delimiter']; + $config['delimiter'] = $config['delimiter'] === 'tab' ? "\t" : $config['delimiter']; + + Log::debug('Entered import account.', ['id' => $importId]); + + + if (!is_null($account->id)) { + Log::debug('Found account.', ['id' => $account->id, 'name' => $account->name]); + $config['import-account'] = $account->id; + } + + if (is_null($account->id)) { + Log::error('Could not find anything for csv_import_account.', ['id' => $importId]); + } + + // loop specifics. + if (isset($data['specifics']) && is_array($data['specifics'])) { + foreach ($data['specifics'] as $name => $enabled) { + // verify their content. + $className = sprintf('FireflyIII\Import\Specifics\%s', $name); + if (class_exists($className)) { + $config['specifics'][$name] = 1; + } + } + } + $this->job->configuration = $config; + $this->job->save(); + + return true; + } +} \ No newline at end of file diff --git a/app/Support/Import/Configuration/Csv/Map.php b/app/Support/Import/Configuration/Csv/Map.php new file mode 100644 index 0000000000..eb51c04e4d --- /dev/null +++ b/app/Support/Import/Configuration/Csv/Map.php @@ -0,0 +1,268 @@ +configuration = $this->job->configuration; + $this->getMappableColumns(); + + // in order to actually map we also need all possible values from the CSV file. + $content = $this->job->uploadFileContents(); + /** @var Reader $reader */ + $reader = Reader::createFromString($content); + $reader->setDelimiter($this->configuration['delimiter']); + $offset = $this->configuration['has-headers'] ? 1 : 0; + $results = $reader->setOffset($offset)->fetch(); + $this->validSpecifics = array_keys(config('csv.import_specifics')); + $indexes = array_keys($this->data); + $rowIndex = 0; + foreach ($results as $rowIndex => $row) { + + $row = $this->runSpecifics($row); + + //do something here + + foreach ($indexes as $index) { // this is simply 1, 2, 3, etc. + if (!isset($row[$index])) { + // don't really know how to handle this. Just skip, for now. + continue; + } + $value = $row[$index]; + if (strlen($value) > 0) { + + // we can do some preprocessing here, + // which is exclusively to fix the tags: + if (!is_null($this->data[$index]['preProcessMap']) && strlen($this->data[$index]['preProcessMap']) > 0) { + /** @var PreProcessorInterface $preProcessor */ + $preProcessor = app($this->data[$index]['preProcessMap']); + $result = $preProcessor->run($value); + $this->data[$index]['values'] = array_merge($this->data[$index]['values'], $result); + + Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]); + Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]); + Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $this->data[$index]['values']]); + + + continue; + } + + $this->data[$index]['values'][] = $value; + } + } + } + foreach ($this->data as $index => $entry) { + $this->data[$index]['values'] = array_unique($this->data[$index]['values']); + } + // save number of rows, thus number of steps, in job: + $steps = $rowIndex * 5; + $extended = $this->job->extended_status; + $extended['steps'] = $steps; + $this->job->extended_status = $extended; + $this->job->save(); + + return $this->data; + + } + + /** + * @param ImportJob $job + * + * @return ConfigurationInterface + */ + public function setJob(ImportJob $job): ConfigurationInterface + { + $this->job = $job; + + return $this; + } + + /** + * Store the result. + * + * @param array $data + * + * @return bool + */ + public function storeConfiguration(array $data): bool + { + $config = $this->job->configuration; + + foreach ($data['mapping'] as $index => $data) { + $config['column-mapping-config'][$index] = []; + foreach ($data as $value => $mapId) { + $mapId = intval($mapId); + if ($mapId !== 0) { + $config['column-mapping-config'][$index][$value] = intval($mapId); + } + } + } + + // set thing to be completed. + $config['column-mapping-complete'] = true; + $this->job->configuration = $config; + $this->job->save(); + + return true; + } + + /** + * @param string $column + * + * @return MapperInterface + */ + private function createMapper(string $column): MapperInterface + { + $mapperClass = config('csv.import_roles.' . $column . '.mapper'); + $mapperName = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass); + /** @var MapperInterface $mapper */ + $mapper = new $mapperName; + + return $mapper; + } + + /** + * @return bool + */ + private function getMappableColumns(): bool + { + $config = $this->job->configuration; + + /** + * @var int $index + * @var bool $mustBeMapped + */ + foreach ($config['column-do-mapping'] as $index => $mustBeMapped) { + $column = $this->validateColumnName($config['column-roles'][$index] ?? '_ignore'); + $shouldMap = $this->shouldMapColumn($column, $mustBeMapped); + if ($shouldMap) { + + + // create configuration entry for this specific column and add column to $this->data array for later processing. + $this->data[$index] = [ + 'name' => $column, + 'index' => $index, + 'options' => $this->createMapper($column)->getMap(), + 'preProcessMap' => $this->getPreProcessorName($column), + 'values' => [], + ]; + } + } + + return true; + + } + + /** + * @param string $column + * + * @return string + */ + private function getPreProcessorName(string $column): string + { + $name = ''; + $hasPreProcess = config('csv.import_roles.' . $column . '.pre-process-map'); + $preProcessClass = config('csv.import_roles.' . $column . '.pre-process-mapper'); + + if (!is_null($hasPreProcess) && $hasPreProcess === true && !is_null($preProcessClass)) { + $name = sprintf('\\FireflyIII\\Import\\MapperPreProcess\\%s', $preProcessClass); + } + + return $name; + } + + /** + * @param array $row + * + * @return array + * @throws FireflyException + */ + private function runSpecifics(array $row): array + { + // run specifics here: + // and this is the point where the specifix go to work. + foreach ($this->configuration['specifics'] as $name => $enabled) { + + if (!in_array($name, $this->validSpecifics)) { + throw new FireflyException(sprintf('"%s" is not a valid class name', $name)); + } + $class = config('csv.import_specifics.' . $name); + /** @var SpecificInterface $specific */ + $specific = app($class); + + // it returns the row, possibly modified: + $row = $specific->run($row); + } + + return $row; + } + + /** + * @param string $column + * @param bool $mustBeMapped + * + * @return bool + */ + private function shouldMapColumn(string $column, bool $mustBeMapped): bool + { + $canBeMapped = config('csv.import_roles.' . $column . '.mappable'); + + return ($canBeMapped && $mustBeMapped); + } + + /** + * @param string $column + * + * @return string + * @throws FireflyException + */ + private function validateColumnName(string $column): string + { + // is valid column? + $validColumns = array_keys(config('csv.import_roles')); + if (!in_array($column, $validColumns)) { + throw new FireflyException(sprintf('"%s" is not a valid column.', $column)); + } + + return $column; + } +} \ No newline at end of file diff --git a/app/Support/Import/Configuration/Csv/Roles.php b/app/Support/Import/Configuration/Csv/Roles.php new file mode 100644 index 0000000000..adb0707beb --- /dev/null +++ b/app/Support/Import/Configuration/Csv/Roles.php @@ -0,0 +1,263 @@ +job->configuration; + $content = $this->job->uploadFileContents(); + + // create CSV reader. + $reader = Reader::createFromString($content); + $reader->setDelimiter($config['delimiter']); + $start = $config['has-headers'] ? 1 : 0; + $end = $start + config('csv.example_rows'); + + // set data: + $this->data = [ + 'examples' => [], + 'roles' => $this->getRoles(), + 'total' => 0, + 'headers' => $config['has-headers'] ? $reader->fetchOne(0) : [], + ]; + + while ($start < $end) { + $row = $reader->fetchOne($start); + $row = $this->processSpecifics($row); + $count = count($row); + $this->data['total'] = $count > $this->data['total'] ? $count : $this->data['total']; + $this->processRow($row); + $start++; + } + + $this->updateColumCount(); + $this->makeExamplesUnique(); + + return $this->data; + } + + /** + * Store the result. + * + * @param array $data + * + * @return bool + */ + public function storeConfiguration(array $data): bool + { + Log::debug('Now in storeConfiguration of Roles.'); + $config = $this->job->configuration; + $count = $config['column-count']; + for ($i = 0; $i < $count; $i++) { + $role = $data['role'][$i] ?? '_ignore'; + $mapping = isset($data['map'][$i]) && $data['map'][$i] === '1' ? true : false; + $config['column-roles'][$i] = $role; + $config['column-do-mapping'][$i] = $mapping; + Log::debug(sprintf('Column %d has been given role %s', $i, $role)); + } + + + $this->job->configuration = $config; + $this->job->save(); + + $this->ignoreUnmappableColumns(); + $this->setRolesComplete(); + $this->isMappingNecessary(); + + + return true; + } + + /** + * @return array + */ + private function getRoles(): array + { + $roles = []; + foreach (array_keys(config('csv.import_roles')) as $role) { + $roles[$role] = trans('csv.column_' . $role); + } + + return $roles; + + } + + /** + * @return bool + */ + private function ignoreUnmappableColumns(): bool + { + $config = $this->job->configuration; + $count = $config['column-count']; + for ($i = 0; $i < $count; $i++) { + $role = $config['column-roles'][$i] ?? '_ignore'; + $mapping = $config['column-do-mapping'][$i] ?? false; + + if ($role === '_ignore' && $mapping === true) { + $mapping = false; + Log::debug(sprintf('Column %d has type %s so it cannot be mapped.', $i, $role)); + } + $config['column-do-mapping'][$i] = $mapping; + } + + $this->job->configuration = $config; + $this->job->save(); + + return true; + + } + + /** + * @return bool + */ + private function isMappingNecessary() + { + $config = $this->job->configuration; + $count = $config['column-count']; + $toBeMapped = 0; + for ($i = 0; $i < $count; $i++) { + $mapping = $config['column-do-mapping'][$i] ?? false; + if ($mapping === true) { + $toBeMapped++; + } + } + Log::debug(sprintf('Found %d columns that need mapping.', $toBeMapped)); + if ($toBeMapped === 0) { + // skip setting of map, because none need to be mapped: + $config['column-mapping-complete'] = true; + $this->job->configuration = $config; + $this->job->save(); + } + + return true; + } + + /** + * make unique example data + */ + private function makeExamplesUnique(): bool + { + foreach ($this->data['examples'] as $index => $values) { + $this->data['examples'][$index] = array_unique($values); + } + + return true; + } + + /** + * @param array $row + * + * @return bool + */ + private function processRow(array $row): bool + { + foreach ($row as $index => $value) { + $value = trim($value); + if (strlen($value) > 0) { + $this->data['examples'][$index][] = $value; + } + } + + return true; + } + + /** + * run specifics here: + * and this is the point where the specifix go to work. + * + * @param array $row + * + * @return array + */ + private function processSpecifics(array $row): array + { + foreach ($this->job->configuration['specifics'] as $name => $enabled) { + /** @var SpecificInterface $specific */ + $specific = app('FireflyIII\Import\Specifics\\' . $name); + $row = $specific->run($row); + } + + return $row; + } + + /** + * @return bool + */ + private function setRolesComplete(): bool + { + $config = $this->job->configuration; + $count = $config['column-count']; + $assigned = 0; + for ($i = 0; $i < $count; $i++) { + $role = $config['column-roles'][$i] ?? '_ignore'; + if ($role !== '_ignore') { + $assigned++; + } + } + if ($assigned > 0) { + $config['column-roles-complete'] = true; + $this->job->configuration = $config; + $this->job->save(); + } + + return true; + } + + /** + * @return bool + */ + private function updateColumCount(): bool + { + $config = $this->job->configuration; + $count = $this->data['total']; + $config['column-count'] = $count; + $this->job->configuration = $config; + $this->job->save(); + + return true; + } + + /** + * @param ImportJob $job + * + * @return ConfigurationInterface + */ + public function setJob(ImportJob $job): ConfigurationInterface + { + $this->job = $job; + + return $this; + } +} \ No newline at end of file diff --git a/app/Support/Import/CsvImportSupportTrait.php b/app/Support/Import/CsvImportSupportTrait.php deleted file mode 100644 index efee16704a..0000000000 --- a/app/Support/Import/CsvImportSupportTrait.php +++ /dev/null @@ -1,242 +0,0 @@ -job->configuration['column-do-mapping'] ?? []; - $doMap = false; - foreach ($mapArray as $value) { - if ($value === true) { - $doMap = true; - break; - } - } - - return $this->job->configuration['column-mapping-complete'] === false && $doMap; - } - - /** - * @return bool - */ - protected function doColumnRoles(): bool - { - return $this->job->configuration['column-roles-complete'] === false; - } - - /** - * @return array - * @throws FireflyException - */ - protected function getDataForColumnMapping(): array - { - $config = $this->job->configuration; - $data = []; - $indexes = []; - - foreach ($config['column-do-mapping'] as $index => $mustBeMapped) { - if ($mustBeMapped) { - - $column = $config['column-roles'][$index] ?? '_ignore'; - - // is valid column? - $validColumns = array_keys(config('csv.import_roles')); - if (!in_array($column, $validColumns)) { - throw new FireflyException(sprintf('"%s" is not a valid column.', $column)); - } - - $canBeMapped = config('csv.import_roles.' . $column . '.mappable'); - $preProcessMap = config('csv.import_roles.' . $column . '.pre-process-map'); - if ($canBeMapped) { - $mapperClass = config('csv.import_roles.' . $column . '.mapper'); - $mapperName = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass); - /** @var MapperInterface $mapper */ - $mapper = new $mapperName; - $indexes[] = $index; - $data[$index] = [ - 'name' => $column, - 'mapper' => $mapperName, - 'index' => $index, - 'options' => $mapper->getMap(), - 'preProcessMap' => null, - 'values' => [], - ]; - if ($preProcessMap) { - $preClass = sprintf( - '\\FireflyIII\\Import\\MapperPreProcess\\%s', - config('csv.import_roles.' . $column . '.pre-process-mapper') - ); - $data[$index]['preProcessMap'] = $preClass; - } - } - - } - } - - // in order to actually map we also need all possible values from the CSV file. - $content = $this->job->uploadFileContents(); - /** @var Reader $reader */ - $reader = Reader::createFromString($content); - $reader->setDelimiter($config['delimiter']); - $results = $reader->fetch(); - $validSpecifics = array_keys(config('csv.import_specifics')); - - foreach ($results as $rowIndex => $row) { - - // skip first row? - if ($rowIndex === 0 && $config['has-headers']) { - continue; - } - - // run specifics here: - // and this is the point where the specifix go to work. - foreach ($config['specifics'] as $name => $enabled) { - - if (!in_array($name, $validSpecifics)) { - throw new FireflyException(sprintf('"%s" is not a valid class name', $name)); - } - $class = config('csv.import_specifics.' . $name); - /** @var SpecificInterface $specific */ - $specific = app($class); - - // it returns the row, possibly modified: - $row = $specific->run($row); - } - - //do something here - foreach ($indexes as $index) { // this is simply 1, 2, 3, etc. - if (!isset($row[$index])) { - // don't really know how to handle this. Just skip, for now. - continue; - } - $value = $row[$index]; - if (strlen($value) > 0) { - - // we can do some preprocessing here, - // which is exclusively to fix the tags: - if (!is_null($data[$index]['preProcessMap'])) { - /** @var PreProcessorInterface $preProcessor */ - $preProcessor = app($data[$index]['preProcessMap']); - $result = $preProcessor->run($value); - $data[$index]['values'] = array_merge($data[$index]['values'], $result); - - Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]); - Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]); - Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $data[$index]['values']]); - - - continue; - } - - $data[$index]['values'][] = $value; - } - } - } - foreach ($data as $index => $entry) { - $data[$index]['values'] = array_unique($data[$index]['values']); - } - - return $data; - } - - /** - * This method collects the data that will enable a user to choose column content. - * - * @return array - */ - protected function getDataForColumnRoles(): array - { - Log::debug('Now in getDataForColumnRoles()'); - $config = $this->job->configuration; - $data = [ - 'columns' => [], - 'columnCount' => 0, - 'columnHeaders' => [], - ]; - - // show user column role configuration. - $content = $this->job->uploadFileContents(); - - // create CSV reader. - $reader = Reader::createFromString($content); - $reader->setDelimiter($config['delimiter']); - $start = $config['has-headers'] ? 1 : 0; - $end = $start + config('csv.example_rows'); - $header = []; - if ($config['has-headers']) { - $header = $reader->fetchOne(0); - } - - - // collect example data in $data['columns'] - Log::debug(sprintf('While %s is smaller than %d', $start, $end)); - while ($start < $end) { - $row = $reader->fetchOne($start); - Log::debug(sprintf('Row %d has %d columns', $start, count($row))); - // run specifics here: - // and this is the point where the specifix go to work. - foreach ($config['specifics'] as $name => $enabled) { - /** @var SpecificInterface $specific */ - $specific = app('FireflyIII\Import\Specifics\\' . $name); - Log::debug(sprintf('Will now apply specific "%s" to row %d.', $name, $start)); - // it returns the row, possibly modified: - $row = $specific->run($row); - } - - foreach ($row as $index => $value) { - $value = trim($value); - $data['columnHeaders'][$index] = $header[$index] ?? ''; - if (strlen($value) > 0) { - $data['columns'][$index][] = $value; - } - } - $start++; - $data['columnCount'] = count($row) > $data['columnCount'] ? count($row) : $data['columnCount']; - } - - // make unique example data - foreach ($data['columns'] as $index => $values) { - $data['columns'][$index] = array_unique($values); - } - - $data['set_roles'] = []; - // collect possible column roles: - $data['available_roles'] = []; - foreach (array_keys(config('csv.import_roles')) as $role) { - $data['available_roles'][$role] = trans('csv.column_' . $role); - } - - $config['column-count'] = $data['columnCount']; - $this->job->configuration = $config; - $this->job->save(); - - return $data; - } -} \ No newline at end of file diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 398400e05b..32e07f08dc 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace FireflyIII\Support; +use \Amount as GlobalAmount; use Carbon\Carbon; use Crypt; use DB; @@ -47,6 +48,11 @@ class Steam return $cache->get(); // @codeCoverageIgnore } $currencyId = intval($account->getMeta('currency_id')); + // use system default currency: + if ($currencyId === 0) { + $currency = GlobalAmount::getDefaultCurrency(); + $currencyId = $currency->id; + } // first part: get all balances in own currency: $nativeBalance = strval( $account->transactions() @@ -264,6 +270,32 @@ class Steam return $list; } + /** + * @param string $amount + * + * @return string + */ + public function negative(string $amount): string + { + if (bccomp($amount, '0') === 1) { + $amount = bcmul($amount, '-1'); + } + + return $amount; + } + + /** + * @param string $amount + * + * @return string + */ + public function opposite(string $amount): string + { + $amount = bcmul($amount, '-1'); + + return $amount; + } + /** * @param $string * @@ -299,8 +331,6 @@ class Steam } - // parse PHP size: - /** * @param string $amount * diff --git a/composer.lock b/composer.lock index cef2489c1d..2aef0058b7 100644 --- a/composer.lock +++ b/composer.lock @@ -104,16 +104,16 @@ }, { "name": "davejamesmiller/laravel-breadcrumbs", - "version": "3.0.2", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/davejamesmiller/laravel-breadcrumbs.git", - "reference": "6ca5a600003ecb52a5b5af14dad82033058604e1" + "reference": "0b0f4792dee645b0f084164aa17d4320e4bb734f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/davejamesmiller/laravel-breadcrumbs/zipball/6ca5a600003ecb52a5b5af14dad82033058604e1", - "reference": "6ca5a600003ecb52a5b5af14dad82033058604e1", + "url": "https://api.github.com/repos/davejamesmiller/laravel-breadcrumbs/zipball/0b0f4792dee645b0f084164aa17d4320e4bb734f", + "reference": "0b0f4792dee645b0f084164aa17d4320e4bb734f", "shasum": "" }, "require": { @@ -124,8 +124,7 @@ "require-dev": { "mockery/mockery": "0.9.*", "orchestra/testbench": "3.2.*|3.3.*", - "phpunit/phpunit": "4.*", - "satooshi/php-coveralls": "0.6.*" + "phpunit/phpunit": "4.*" }, "type": "library", "autoload": { @@ -144,12 +143,12 @@ "homepage": "https://davejamesmiller.com/" } ], - "description": "A simple Laravel-style way to create breadcrumbs in Laravel 4+.", + "description": "A simple Laravel-style way to create breadcrumbs in Laravel.", "homepage": "https://laravel-breadcrumbs.readthedocs.io/", "keywords": [ "laravel" ], - "time": "2017-01-30T21:16:53+00:00" + "time": "2017-06-24T11:10:49+00:00" }, { "name": "doctrine/annotations", @@ -665,16 +664,16 @@ }, { "name": "laravel/framework", - "version": "v5.4.24", + "version": "v5.4.27", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "ec8548db26c1b147570f661128649e98f3ac0f29" + "reference": "66f5e1b37cbd66e730ea18850ded6dc0ad570404" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/ec8548db26c1b147570f661128649e98f3ac0f29", - "reference": "ec8548db26c1b147570f661128649e98f3ac0f29", + "url": "https://api.github.com/repos/laravel/framework/zipball/66f5e1b37cbd66e730ea18850ded6dc0ad570404", + "reference": "66f5e1b37cbd66e730ea18850ded6dc0ad570404", "shasum": "" }, "require": { @@ -790,7 +789,7 @@ "framework", "laravel" ], - "time": "2017-05-30T12:44:32+00:00" + "time": "2017-06-15T19:08:25+00:00" }, { "name": "laravelcollective/html", @@ -1057,16 +1056,16 @@ }, { "name": "monolog/monolog", - "version": "1.22.1", + "version": "1.23.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", - "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", "shasum": "" }, "require": { @@ -1087,7 +1086,7 @@ "phpunit/phpunit-mock-objects": "2.3.0", "ruflin/elastica": ">=0.90 <3.0", "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "~5.3" + "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", @@ -1131,7 +1130,7 @@ "logging", "psr-3" ], - "time": "2017-03-13T07:08:03+00:00" + "time": "2017-06-19T01:22:40+00:00" }, { "name": "mtdowling/cron-expression", @@ -1815,16 +1814,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.21", + "version": "v2.8.22", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "7fc8e2b4118ff316550596357325dfd92a51f531" + "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7fc8e2b4118ff316550596357325dfd92a51f531", - "reference": "7fc8e2b4118ff316550596357325dfd92a51f531", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1377400fd641d7d1935981546aaef780ecd5bf6d", + "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d", "shasum": "" }, "require": { @@ -1871,7 +1870,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-04-26T16:56:54+00:00" + "time": "2017-06-02T07:47:27+00:00" }, { "name": "symfony/finder", @@ -2062,16 +2061,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + "reference": "f29dca382a6485c3cbe6379f0c61230167681937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937", "shasum": "" }, "require": { @@ -2083,7 +2082,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -2117,20 +2116,20 @@ "portable", "shim" ], - "time": "2016-11-14T01:06:16+00:00" + "time": "2017-06-09T14:24:12+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c" + "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/1dd42b9b89556f18092f3d1ada22cb05ac85383c", - "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/bc0b7d6cb36b10cfabb170a3e359944a95174929", + "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929", "shasum": "" }, "require": { @@ -2140,7 +2139,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -2173,20 +2172,20 @@ "portable", "shim" ], - "time": "2016-11-14T01:06:16+00:00" + "time": "2017-06-09T08:25:21+00:00" }, { "name": "symfony/polyfill-util", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", - "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb" + "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/746bce0fca664ac0a575e465f65c6643faddf7fb", - "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/ebccbde4aad410f6438d86d7d261c6b4d2b9a51d", + "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d", "shasum": "" }, "require": { @@ -2195,7 +2194,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -2225,7 +2224,7 @@ "polyfill", "shim" ], - "time": "2016-11-14T01:06:16+00:00" + "time": "2017-06-09T08:25:21+00:00" }, { "name": "symfony/process", @@ -2761,28 +2760,30 @@ }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.3.2", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5" + "reference": "87a02ff574f722c685e011f76692ab869ab64f6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e82de98cef0d6597b1b686be0b5813a3a4bb53c5", - "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/87a02ff574f722c685e011f76692ab869ab64f6c", + "reference": "87a02ff574f722c685e011f76692ab869ab64f6c", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.4", - "illuminate/console": "^5.0,<5.5", - "illuminate/filesystem": "^5.0,<5.5", - "illuminate/support": "^5.0,<5.5", + "illuminate/console": "^5.0,<5.6", + "illuminate/filesystem": "^5.0,<5.6", + "illuminate/support": "^5.0,<5.6", "php": ">=5.4.0", "symfony/class-loader": "^2.3|^3.0" }, "require-dev": { "doctrine/dbal": "~2.3", + "illuminate/config": "^5.0,<5.6", + "illuminate/view": "^5.0,<5.6", "phpunit/phpunit": "4.*", "scrutinizer/ocular": "~1.1", "squizlabs/php_codesniffer": "~2.3" @@ -2794,6 +2795,11 @@ "extra": { "branch-alias": { "dev-master": "2.3-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" + ] } }, "autoload": { @@ -2823,7 +2829,7 @@ "phpstorm", "sublime" ], - "time": "2017-02-22T12:27:33+00:00" + "time": "2017-06-16T14:08:59+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -3745,16 +3751,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.7.20", + "version": "5.7.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b" + "reference": "3b91adfb64264ddec5a2dee9851f354aa66327db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3cb94a5f8c07a03c8b7527ed7468a2926203f58b", - "reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3b91adfb64264ddec5a2dee9851f354aa66327db", + "reference": "3b91adfb64264ddec5a2dee9851f354aa66327db", "shasum": "" }, "require": { @@ -3823,7 +3829,7 @@ "testing", "xunit" ], - "time": "2017-05-22T07:42:55+00:00" + "time": "2017-06-21T08:11:54+00:00" }, { "name": "phpunit/phpunit-mock-objects", diff --git a/config/csv.php b/config/csv.php index 5da40e41e0..cc23de32f1 100644 --- a/config/csv.php +++ b/config/csv.php @@ -292,6 +292,7 @@ return [ // number of example rows: 'example_rows' => 5, 'default_config' => [ + 'initial-config-complete' => false, 'has-headers' => false, // assume 'date-format' => 'Ymd', // assume 'delimiter' => ',', // assume diff --git a/config/firefly.php b/config/firefly.php index c4f648e3a4..728a671fa0 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -23,15 +23,21 @@ return [ 'is_demo_site' => false, ], 'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true), - 'version' => '4.5.0', - 'maxUploadSize' => 5242880, + 'version' => '4.6.0', + 'maxUploadSize' => 15242880, 'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'], 'list_length' => 10, 'export_formats' => [ 'csv' => 'FireflyIII\Export\Exporter\CsvExporter', ], 'import_formats' => [ - 'csv' => 'FireflyIII\Import\Importer\CsvImporter', + 'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator', + ], + 'import_configurators' => [ + 'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator', + ], + 'import_processors' => [ + 'csv' => 'FireflyIII\Import\FileProcessor\CsvProcessor', ], 'default_export_format' => 'csv', 'default_import_format' => 'csv', diff --git a/database/migrations/2016_06_16_000002_create_main_tables.php b/database/migrations/2016_06_16_000002_create_main_tables.php index 0f5fe8d4e4..300024918a 100644 --- a/database/migrations/2016_06_16_000002_create_main_tables.php +++ b/database/migrations/2016_06_16_000002_create_main_tables.php @@ -474,7 +474,7 @@ class CreateMainTables extends Migration $table->text('description')->nullable(); $table->decimal('latitude', 24, 12)->nullable(); $table->decimal('longitude', 24, 12)->nullable(); - $table->boolean('zoomLevel')->nullable(); + $table->smallInteger('zoomLevel', false, true)->nullable(); // link user id to users table $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); diff --git a/database/seeds/TransactionCurrencySeeder.php b/database/seeds/TransactionCurrencySeeder.php index 8f51c69cd1..0d7732e80b 100644 --- a/database/seeds/TransactionCurrencySeeder.php +++ b/database/seeds/TransactionCurrencySeeder.php @@ -28,6 +28,7 @@ class TransactionCurrencySeeder extends Seeder TransactionCurrency::create(['code' => 'GBP', 'name' => 'British Pound', 'symbol' => '£', 'decimal_places' => 2]); TransactionCurrency::create(['code' => 'IDR', 'name' => 'Indonesian rupiah', 'symbol' => 'Rp', 'decimal_places' => 2]); TransactionCurrency::create(['code' => 'XBT', 'name' => 'Bitcoin', 'symbol' => 'B', 'decimal_places' => 8]); + TransactionCurrency::create(['code' => 'JPY', 'name' => 'Japanese yen', 'symbol' => '¥', 'decimal_places' => 2]); } } diff --git a/public/js/ff/budgets/index.js b/public/js/ff/budgets/index.js index 01a00c9b0f..06e696ebed 100644 --- a/public/js/ff/budgets/index.js +++ b/public/js/ff/budgets/index.js @@ -8,7 +8,7 @@ * See the LICENSE file for details. */ -/** global: spent, budgeted, available, currencySymbol, budgetIndexURI */ +/** global: spent, budgeted, available, currencySymbol, budgetIndexURI, accounting */ function drawSpentBar() { "use strict"; diff --git a/public/js/ff/import/status.js b/public/js/ff/import/status.js index cc1ec69a9e..f8a991074d 100644 --- a/public/js/ff/import/status.js +++ b/public/js/ff/import/status.js @@ -10,49 +10,164 @@ /** global: jobImportUrl, langImportSingleError, langImportMultiError, jobStartUrl, langImportTimeOutError, langImportFinished, langImportFatalError */ -var startedImport = false; -var startInterval = 2000; +var timeOutId; +var startInterval = 1000; var interval = 500; -var timeoutLimit = 5000; -var currentLimit = 0; -var stepCount = 0; + +// these vars are used to detect a stalled job: +var numberOfSteps = 0; +var numberOfReports = 0; +var jobFailed = false; + +// counts how many errors have been detected +var knownErrors = 0; + $(function () { "use strict"; - - $('#import-status-intro').hide(); - $('#import-status-more-info').hide(); - - // check status, every 500 ms. - setTimeout(checkImportStatus, startInterval); - + timeOutId = setTimeout(checkImportStatus, startInterval); + $('.start-job').click(startJob); }); - +/** + * Downloads some JSON and responds to its content to see what the status is of the current import. + */ function checkImportStatus() { - "use strict"; $.getJSON(jobImportUrl).done(reportOnJobImport).fail(failedJobImport); } -function importComplete() { - "use strict"; - var bar = $('#import-status-bar'); - bar.removeClass('active'); +/** + * This method is called when the JSON query returns an error. If possible, this error is relayed to the user. + */ +function failedJobImport(jqxhr, textStatus, error) { + // hide all possible boxes: + $('.statusbox').hide(); + + // fill in some details: + var errorMessage = textStatus + " " + error; + + $('.fatal_error_txt').text(errorMessage); + + // show the fatal error box: + $('.fatal_error').show(); } +/** + * This method is called when the job enquiry (JSON) returns some info. + * It also decides whether or not to check again. + * + * @param data + */ +function reportOnJobImport(data) { + + switch (data.status) { + case "configured": + // job is ready. Do not check again, just show the start-box. Hide the rest. + $('.statusbox').hide(); + $('.status_configured').show(); + break; + case "running": + // job is running! Show the running box: + $('.statusbox').hide(); + $('.status_running').show(); + + // update the bar + updateBar(data); + + // update the status text: + updateStatusText(data); + + // report on detected errors: + reportOnErrors(data); + + if (jobIsStalled(data)) { + // do something + showStalledBox(); + } else { + // check again in 500ms + timeOutId = setTimeout(checkImportStatus, interval); + } + break; + case "finished": + $('.statusbox').hide(); + $('.status_finished').show(); + // show text: + $('#import-status-more-info').html(data.finishedText); + + + break; + } +} + +/** + * Shows a fatal error when the job seems to be stalled. + */ +function showStalledBox() { + $('.statusbox').hide(); + $('.fatal_error').show(); + $('.fatal_error_txt').text(langImportTimeOutError); +} + +/** + * Detects if a job is frozen. + * + * @param data + */ +function jobIsStalled(data) { + if (data.done === numberOfSteps) { + numberOfReports++; + } + if (data.done !== numberOfSteps) { + numberOfReports = 0; + } + if (numberOfReports > 20) { + return true; + } + numberOfSteps = data.done; + + return false; +} + +/** + * This function tells Firefly start the job. It will also initialize a re-check in 500ms time. + */ +function startJob() { + // disable the button, add loading thing. + $('.start-job').prop('disabled', true).text('...'); + $.post(jobStartUrl).fail(reportOnSubmitError); + + // check status, every 500 ms. + timeOutId = setTimeout(checkImportStatus, startInterval); +} + +function reportOnSubmitError() { + // stop the refresh thing + clearTimeout(timeOutId); + + // hide all possible boxes: + $('.statusbox').hide(); + + // fill in some details: + var errorMessage = "Time out while waiting for job to finish."; + + $('.fatal_error_txt').text(errorMessage); + + // show the fatal error box: + $('.fatal_error').show(); + jobFailed = true; + +} + +/** + * This method updates the percentage bar thing if the job is running! + */ function updateBar(data) { - "use strict"; var bar = $('#import-status-bar'); - if (data.showPercentage) { + if (data.show_percentage) { bar.addClass('progress-bar-success').removeClass('progress-bar-info'); bar.attr('aria-valuenow', data.percentage); bar.css('width', data.percentage + '%'); - $('#import-status-bar').text(data.stepsDone + '/' + data.steps); - - if (data.percentage >= 100) { - importComplete(); - return; - } - return; + $('#import-status-bar').text(data.done + '/' + data.steps); + return true; } // dont show percentage: bar.removeClass('progress-bar-success').addClass('progress-bar-info'); @@ -60,8 +175,27 @@ function updateBar(data) { bar.css('width', '100%'); } -function reportErrors(data) { +/** + * Add text with current import status. + * @param data + */ +function updateStatusText(data) { "use strict"; + $('#import-status-txt').removeClass('text-danger').text(data.statusText); +} + +/** + * Report on errors found in import: + * @param data + */ +function reportOnErrors(data) { + if (knownErrors === data.errors.length) { + return; + } + if (data.errors.length === 0) { + return; + } + if (data.errors.length === 1) { $('#import-status-error-intro').text(langImportSingleError); //'An error has occured during the import. The import can continue, however.' @@ -70,108 +204,16 @@ function reportErrors(data) { // 'Errors have occured during the import. The import can continue, however.' $('#import-status-error-intro').text(langImportMultiError); } - + $('.info_errors').show(); // fill the list with error texts $('#import-status-error-list').empty(); for (var i = 0; i < data.errors.length; i++) { - var item = $('
  • ').html(data.errors[i]); - $('#import-status-error-list').append(item); + var errorSet = data.errors[i]; + for (var j = 0; j < errorSet.length; j++) { + var item = $('
  • ').html(errorSet[j]); + $('#import-status-error-list').append(item); + } } -} + return; -function reportStatus(data) { - "use strict"; - $('#import-status-txt').removeClass('text-danger').text(data.statusText); -} - -function kickStartJob() { - "use strict"; - $.post(jobStartUrl); - startedTheImport(); - startedImport = true; -} - -function updateTimeout(data) { - "use strict"; - if (data.stepsDone !== stepCount) { - stepCount = data.stepsDone; - currentLimit = 0; - return; - } - - currentLimit = currentLimit + interval; -} - -function timeoutError() { - "use strict"; - // set status - $('#import-status-txt').addClass('text-danger').text(langImportTimeOutError); - - // remove progress bar. - $('#import-status-holder').hide(); - -} - -function importJobFinished(data) { - "use strict"; - return data.finished; -} - -function finishedJob(data) { - "use strict"; - // "There was an error during the import routine. Please check the log files. The error seems to be: '" - $('#import-status-txt').removeClass('text-danger').addClass('text-success').text(langImportFinished); - - // remove progress bar. - $('#import-status-holder').hide(); - - // show info: - $('#import-status-intro').show(); - $('#import-status-more-info').html(data.finishedText).show(); - -} - -function reportOnJobImport(data) { - "use strict"; - updateBar(data); - reportErrors(data); - reportStatus(data); - updateTimeout(data); - - if (importJobFinished(data)) { - finishedJob(data); - return; - } - - - // same number of steps as last time? - if (currentLimit > timeoutLimit) { - timeoutError(); - return; - } - - // if the job has not actually started, do so now: - if (!data.started && !startedImport) { - kickStartJob(); - return; - } - - // trigger another check. - setTimeout(checkImportStatus, interval); - -} - -function startedTheImport() { - "use strict"; - setTimeout(checkImportStatus, interval); -} - -function failedJobImport(jqxhr, textStatus, error) { - "use strict"; - // set status - // "There was an error during the import routine. Please check the log files. The error seems to be: '" - $('#import-status-txt').addClass('text-danger').text(langImportFatalError + ' ' + textStatus + ' ' + error); - - // remove progress bar. - $('#import-status-holder').hide(); } \ No newline at end of file diff --git a/public/js/ff/transactions/list.js b/public/js/ff/transactions/list.js index 39cc72fc9a..d3fdd80214 100644 --- a/public/js/ff/transactions/list.js +++ b/public/js/ff/transactions/list.js @@ -41,7 +41,14 @@ function goToMassEdit() { var checkedArray = getCheckboxes(); // go to specially crafted URL: - window.location.href = 'transactions/mass/edit/' + checkedArray; + var bases = document.getElementsByTagName('base'); + var baseHref = null; + + if (bases.length > 0) { + baseHref = bases[0].href; + } + + window.location.href = baseHref + '/transactions/mass/edit/' + checkedArray; return false; } @@ -50,7 +57,13 @@ function goToMassDelete() { var checkedArray = getCheckboxes(); // go to specially crafted URL: - window.location.href = 'transactions/mass/delete/' + checkedArray; + var bases = document.getElementsByTagName('base'); + var baseHref = null; + + if (bases.length > 0) { + baseHref = bases[0].href; + } + window.location.href = baseHref + '/transactions/mass/delete/' + checkedArray; return false; } diff --git a/resources/lang/de_DE/csv.php b/resources/lang/de_DE/csv.php index 71a33e1fc8..632d14db44 100644 --- a/resources/lang/de_DE/csv.php +++ b/resources/lang/de_DE/csv.php @@ -13,36 +13,37 @@ declare(strict_types=1); return [ - 'import_configure_title' => 'Konfigurieren Sie Ihren Import', - 'import_configure_intro' => 'Es gibt einige Optionen für Ihren CSV-Import. Bitte geben Sie an, ob Ihre CSV-Datei Überschriften in der ersten Spalte enthält und was das Datumsformat in Ihrem Datumsfeld ist. Dieses kann einige Experimente erfordern. Das Trennzeichen ist in der Regel ein ",", könnte aber auch ein "." sein. Bitte überprüfen Sie dieses sorgfältig.', - 'import_configure_form' => 'Standard CSV Importoptionen', - 'header_help' => 'Hier auswählen, wenn die ersten Zeilen der CSV-Datei die Spaltenüberschriften sind', - 'date_help' => 'Datumsformat in ihrer CSV-Datei. Geben Sie das Format so an, wie es diese Seite zeigt. Die Standardeinstellung ergibt Daten die so aussehen: :dateExample.', - 'delimiter_help' => 'Wählen Sie das Trennzeichen, welches in ihrer Datei genutzt wird. Wenn Sie nicht sicher sind ist Komma die sicherste Option.', - 'import_account_help' => 'Wenn ihre CSV-Datei KEINE Informationen über ihre Girokonten enthält nutzen Sie bitte diese Dropdown-Liste um anzugeben, zu welchem Girokonto die Transaktionen in de CSV-Datei gehören.', - 'upload_not_writeable' => 'Das graue Feld enthält einen Dateipfad. Dieser sollte schreibbar sein. Bitte stellen Sie sicher, dass er es ist.', + // initial config + 'initial_title' => 'Import setup (1/3) - Basic CSV import setup', + 'initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'initial_box' => 'Basic CSV import setup', + 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'initial_submit' => 'Continue with step 2/3', - // roles - 'column_roles_title' => 'Definieren Sie Spaltenfunktionen', - 'column_roles_table' => 'Tabelle', - 'column_name' => 'Name der Spalte', - 'column_example' => 'Beispieldaten', - 'column_role' => 'Bedeutung der Spalte', - 'do_map_value' => 'Ordnen Sie diese Werte zu', - 'column' => 'Spalte', - 'no_example_data' => 'Keine Beispieldaten vorhanden', - 'store_column_roles' => 'Import fortsetzen', - 'do_not_map' => '(keine Zuordnung)', - 'map_title' => 'Verbinde Importdaten mit Firefly III Daten', - 'map_text' => 'In den folgenden Tabellen zeigt der linke Wert Informationen, die sich in Ihrer hochgeladenen CSV-Datei befinden. Es ist Ihre Aufgabe, diesen Wert, wenn möglich, einem bereits in der Datenbank vorhandem zuzuordnen. Firefly wird sich an diese Zuordnung halten. Wenn kein Wert für die Zuordnung vorhanden ist oder Sie den bestimmten Wert nicht abbilden möchten, wählen Sie nichts aus.', + // roles config + 'roles_title' => 'Import setup (2/3) - Define each column\'s role', + 'roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'roles_table' => 'Table', + 'roles_column_name' => 'Name of column', + 'roles_column_example' => 'Column example data', + 'roles_column_role' => 'Column data meaning', + 'roles_do_map_value' => 'Map these values', + 'roles_column' => 'Column', + 'roles_no_example_data' => 'No example data available', + 'roles_submit' => 'Continue with step 3/3', - 'field_value' => 'Feldwert', - 'field_mapped_to' => 'Zugeordnet zu', - 'store_column_mapping' => 'Speicherzuordnung', + // map data + 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', + 'map_text' => 'In den folgenden Tabellen zeigt der linke Wert Informationen, die sich in Ihrer hochgeladenen CSV-Datei befinden. Es ist Ihre Aufgabe, diesen Wert, wenn möglich, einem bereits in der Datenbank vorhandem zuzuordnen. Firefly wird sich an diese Zuordnung halten. Wenn kein Wert für die Zuordnung vorhanden ist oder Sie den bestimmten Wert nicht abbilden möchten, wählen Sie nichts aus.', + 'map_field_value' => 'Field value', + 'map_field_mapped_to' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'map_submit' => 'Start the import', // map things. - - 'column__ignore' => '(diese Spalte ignorieren)', 'column_account-iban' => 'Bestandskonto (IBAN)', 'column_account-id' => 'Bestandskonto (vgl. ID in Firefly)', diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index da3d194e10..e6f70006a4 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -98,7 +98,7 @@ return [ 'cannot_redirect_to_account' => 'Entschuldigung. Firefly III kann Sie nicht zur richtigen Seite weiterleiten.', 'sum_of_expenses' => 'Summe von Ausgaben', 'sum_of_income' => 'Summe von Einnahmen', - 'total_sum' => 'Total sum', + 'total_sum' => 'Gesamtsumme', 'spent_in_specific_budget' => 'Ausgegeben in Budget ":budget"', 'sum_of_expenses_in_budget' => 'Vollkommen ausgegeben in Budget ":budget"', 'left_in_budget_limit' => 'Übrig zum ausgeben aufgrund der Budgetierung', @@ -805,7 +805,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'account_role_defaultAsset' => 'Default asset account', 'account_role_sharedAsset' => 'Shared asset account', 'account_role_savingAsset' => 'Savings account', - 'account_role_ccAsset' => 'Credit card', + 'account_role_ccAsset' => 'Kreditkarte', // charts: 'chart' => 'Diagram', @@ -838,7 +838,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', // piggy banks: 'add_money_to_piggy' => 'Geld zum Sparschwein ":name" hinzufügen', 'piggy_bank' => 'Sparschwein', - 'new_piggy_bank' => 'New piggy bank', + 'new_piggy_bank' => 'Neues Sparschwein', 'store_piggy_bank' => 'Speichere neues Sparschwein', 'stored_piggy_bank' => 'Speichere neues Sparschwein ":name"', 'account_status' => 'Kontostatus', @@ -967,50 +967,51 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - // import - 'configuration_file_help' => 'Wenn Sie bereits zuvor Daten in Firefly III importiert haben, haben Sie eventuell eine Kofigurationsdatei, welche einige Einstellungen für Sie voreinstellt. Für einige Banken haben andere Nutzer freundlicherweise bereits ihre Konfigurationsdatei zur Verfügung gestellt.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (Kommagetrennte Werte)', - 'import_file_type_help' => 'Wählen Sie den Typ der hochzuladenen Datei', - 'import_start' => 'Starte den Import', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Beende die Konfiguration', - 'settings_for_import' => 'Einstellungen', - 'import_status' => 'Importstatus', - 'import_status_text' => 'Der Import läuft gerade oder startet in Kürze.', - 'import_complete' => 'Import der Konfiguration vollständig!', - 'import_complete_text' => 'Der Import ist bereit zu starten. Alle Einstellungen wurden von Ihnen erledigt. Bitte laden Sie die Konfigurationsdatei herunter. Diese wird Ihnen beim Import helfen, sollte dieser nicht wie gewünscht verlaufen. Um den Import tatsächlich zu starten führen Sie den folgenden Befehl in der Konsole aus oder nutzen Sie den Web-basierten Import. Abhängig von ihrer Konfiguration wird Ihnen der Konsolenimport mehr Rückmeldungen geben.', - 'import_download_config' => 'Download der Konfiguration', - 'import_start_import' => 'Import starten', - 'import_data' => 'Daten importieren', - 'import_data_full' => 'Importieren Sie Daten in Firefly III', + // import bread crumbs and titles: 'import' => 'Import', - 'import_file_help' => 'Datei auswählen', - 'import_status_settings_complete' => 'Der Import is startbereit.', - 'import_status_import_complete' => 'Import vollständig.', - 'import_status_import_running' => 'The import is currently running. Please be patient.', - 'import_status_header' => 'Importstatus und Fortschritt', - 'import_status_errors' => 'Importfehler', - 'import_status_report' => 'Import-Bericht', - 'import_finished' => 'Der Importierungsvorgang ist beendet', - 'import_error_single' => 'Beim Importieren ist ein Fehler aufgetreten.', - 'import_error_multi' => 'Beim Importieren sind Fehler aufgetreten.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'Der Import scheint ein Zeitlimit überschritten zu haben. Wenn dieser Fehler weiterhin auftritt importieren Sie die Daten bitte über die Kommandozeile.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', + 'import_data' => 'Daten importieren', + + // import index page: + 'import_index_title' => 'Import data into Firefly III', + 'import_index_sub_title' => 'Index', + 'import_index_intro' => 'Welcome to Firefly\'s import routine. These pages can help you import data from your bank into Firefly III. Please check out the help pages in the top right corner.', + 'import_index_file' => 'Select your file', + 'import_index_config' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', + 'import_index_type' => 'Select the type of file you will upload', + 'import_index_start' => 'Start importing', + + // supported file types: + 'import_file_type_csv' => 'CSV (Kommagetrennte Werte)', + + // import configuration routine: + 'import_config_sub_title' => 'Set up your import file', + 'import_config_bread_crumb' => 'Set up your import file', + + // import status page: + 'import_status_bread_crumb' => 'Import status', + 'import_status_sub_title' => 'Import status', + 'import_status_wait_title' => 'Please hold...', + 'import_status_wait_text' => 'This box will disappear in a moment.', + 'import_status_ready_title' => 'Import is ready to start', + 'import_status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'import_status_ready_config' => 'Download configuration', + 'import_status_ready_start' => 'Start the import', + 'import_status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'import_status_running_title' => 'The import is running', + 'import_status_running_placeholder' => 'Please hold for an update...', + 'import_status_errors_title' => 'Errors during the import', + 'import_status_errors_single' => 'An error has occured during the import. It does not appear to be fatal.', + 'import_status_errors_multi' => 'Some errors occured during the import. These do not appear to be fatal.', + 'import_status_fatal_title' => 'A fatal error occurred', + 'import_status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'import_status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'import_status_finished_title' => 'Import routine finished', + 'import_status_finished_text' => 'The import routine has imported your file.', + 'import_status_finished_job' => 'The transactions imported can be found in tag :tag.', 'import_with_key' => 'Import with key \':key\'', - 'import_share_configuration' => 'Bitte denken Sie darüber nach ihre Konfiguration herunterzuladen und in der Übersicht der Import-Einstellungen zu teilen. Dieses erlaubt es anderen Nutzern von Firefly III ihre Daten unkomplizierter zu importieren.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'Sie benötigen mindestens ein Bestandskonto, um ein Sparschwein zu erstellen', - 'see_help_top_right' => 'Mehr Information finden sie in den Hilfeseiten, indem sie auf das Fragezeichen in der rechten oberen Ecke klicken.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', + + // different states: + 'import_status_job_running' => 'The import is underway. Please be patient...', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', @@ -1027,7 +1028,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'no_accounts_create_expense' => 'Create an expense account', 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_imperative_revenue' => 'Revenue accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', 'no_accounts_create_revenue' => 'Create a revenue account', 'no_budgets_title_default' => 'Let\'s create a budget', 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', diff --git a/resources/lang/en_US/csv.php b/resources/lang/en_US/csv.php index d5306e2a88..f262f83b10 100644 --- a/resources/lang/en_US/csv.php +++ b/resources/lang/en_US/csv.php @@ -13,36 +13,37 @@ declare(strict_types=1); return [ - 'import_configure_title' => 'Configure your import', - 'import_configure_intro' => 'There are some options for your CSV import. Please indicate if your CSV file contains headers on the first column, and what the date format of your date-fields is. That might require some experimentation. The field delimiter is usually a ",", but could also be a ";". Check this carefully.', - 'import_configure_form' => 'Basic CSV import options', - 'header_help' => 'Check this if the first row of your CSV file are the column titles', - 'date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'upload_not_writeable' => 'The grey box contains a file path. It should be writeable. Please make sure it is.', + // initial config + 'initial_title' => 'Import setup (1/3) - Basic CSV import setup', + 'initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'initial_box' => 'Basic CSV import setup', + 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'initial_submit' => 'Continue with step 2/3', - // roles - 'column_roles_title' => 'Define column roles', - 'column_roles_table' => 'Table', - 'column_name' => 'Name of column', - 'column_example' => 'Column example data', - 'column_role' => 'Column data meaning', - 'do_map_value' => 'Map these values', - 'column' => 'Column', - 'no_example_data' => 'No example data available', - 'store_column_roles' => 'Continue import', - 'do_not_map' => '(do not map)', - 'map_title' => 'Connect import data to Firefly III data', - 'map_text' => 'In the following tables, the left value shows you information found in your uploaded CSV file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + // roles config + 'roles_title' => 'Import setup (2/3) - Define each column\'s role', + 'roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'roles_table' => 'Table', + 'roles_column_name' => 'Name of column', + 'roles_column_example' => 'Column example data', + 'roles_column_role' => 'Column data meaning', + 'roles_do_map_value' => 'Map these values', + 'roles_column' => 'Column', + 'roles_no_example_data' => 'No example data available', + 'roles_submit' => 'Continue with step 3/3', - 'field_value' => 'Field value', - 'field_mapped_to' => 'Mapped to', - 'store_column_mapping' => 'Store mapping', + // map data + 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', + 'map_text' => 'In the following tables, the left value shows you information found in your uploaded CSV file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'map_field_value' => 'Field value', + 'map_field_mapped_to' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'map_submit' => 'Start the import', // map things. - - 'column__ignore' => '(ignore this column)', 'column_account-iban' => 'Asset account (IBAN)', 'column_account-id' => 'Asset account ID (matching Firefly)', diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 501c03430a..e2110eae78 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -966,50 +966,51 @@ return [ 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'import_file_type_help' => 'Select the type of file you will upload', - 'import_start' => 'Start the import', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Finish configuration', - 'settings_for_import' => 'Settings', - 'import_status' => 'Import status', - 'import_status_text' => 'The import is currently running, or will start momentarily.', - 'import_complete' => 'Import configuration complete!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Download configuration', - 'import_start_import' => 'Start import', - 'import_data' => 'Import data', - 'import_data_full' => 'Import data into Firefly III', + // import bread crumbs and titles: 'import' => 'Import', - 'import_file_help' => 'Select your file', - 'import_status_settings_complete' => 'The import is ready to start.', - 'import_status_import_complete' => 'The import has completed.', - 'import_status_import_running' => 'The import is currently running. Please be patient.', - 'import_status_header' => 'Import status and progress', - 'import_status_errors' => 'Import errors', - 'import_status_report' => 'Import report', - 'import_finished' => 'Import has finished', - 'import_error_single' => 'An error has occured during the import.', - 'import_error_multi' => 'Some errors occured during the import.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', + 'import_data' => 'Import data', + + // import index page: + 'import_index_title' => 'Import data into Firefly III', + 'import_index_sub_title' => 'Index', + 'import_index_intro' => 'Welcome to Firefly\'s import routine. These pages can help you import data from your bank into Firefly III. Please check out the help pages in the top right corner.', + 'import_index_file' => 'Select your file', + 'import_index_config' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', + 'import_index_type' => 'Select the type of file you will upload', + 'import_index_start' => 'Start importing', + + // supported file types: + 'import_file_type_csv' => 'CSV (comma separated values)', + + // import configuration routine: + 'import_config_sub_title' => 'Set up your import file', + 'import_config_bread_crumb' => 'Set up your import file', + + // import status page: + 'import_status_bread_crumb' => 'Import status', + 'import_status_sub_title' => 'Import status', + 'import_status_wait_title' => 'Please hold...', + 'import_status_wait_text' => 'This box will disappear in a moment.', + 'import_status_ready_title' => 'Import is ready to start', + 'import_status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'import_status_ready_config' => 'Download configuration', + 'import_status_ready_start' => 'Start the import', + 'import_status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'import_status_running_title' => 'The import is running', + 'import_status_running_placeholder' => 'Please hold for an update...', + 'import_status_errors_title' => 'Errors during the import', + 'import_status_errors_single' => 'An error has occured during the import. It does not appear to be fatal.', + 'import_status_errors_multi' => 'Some errors occured during the import. These do not appear to be fatal.', + 'import_status_fatal_title' => 'A fatal error occurred', + 'import_status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'import_status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'import_status_finished_title' => 'Import routine finished', + 'import_status_finished_text' => 'The import routine has imported your file.', + 'import_status_finished_job' => 'The transactions imported can be found in tag :tag.', 'import_with_key' => 'Import with key \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', + + // different states: + 'import_status_job_running' => 'The import is underway. Please be patient...', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', @@ -1026,7 +1027,7 @@ return [ 'no_accounts_create_expense' => 'Create an expense account', 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_imperative_revenue' => 'Revenue accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', 'no_accounts_create_revenue' => 'Create a revenue account', 'no_budgets_title_default' => 'Let\'s create a budget', 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', diff --git a/resources/lang/es_ES/csv.php b/resources/lang/es_ES/csv.php index 27143be1bc..377460ee8e 100644 --- a/resources/lang/es_ES/csv.php +++ b/resources/lang/es_ES/csv.php @@ -13,36 +13,37 @@ declare(strict_types=1); return [ - 'import_configure_title' => 'Configurar su importación', - 'import_configure_intro' => 'Hay algunas opciones para su importación desde CSV. Por facor indique si su CSV contiene encabezados en la primera fila, y cuál es el formato de fecha utilizado. ¡Puede requerir un poco de experimentación! El delimitador de campos es usualmente ",", pero también puede ser ";". Verifíquelo cuidadosamente.', - 'import_configure_form' => 'Opciones básicas de importación desde CSV', - 'header_help' => 'Marque aquí si el CSV contiene títulos de columna en la primera fila', - 'date_help' => 'Formato de fecha y hora en el CSV. Siga el formato que esta página indica. El valor por defecto interpretará fechas que se vean así: :dateExample.', - 'delimiter_help' => 'Elija el delimitador de campos del archivo de entrada. Si no está seguro, la coma es la opción más segura.', - 'import_account_help' => 'Si el archivo NO contiene información sobre su(s) caja(s) de ahorros seleccion una opción para definir a qué cuenta pertenecen las transacciones del CSV.', - 'upload_not_writeable' => 'El texto en gris indica un directorio. Debe tener permiso de escritura. Por favor verifíquelo.', + // initial config + 'initial_title' => 'Import setup (1/3) - Basic CSV import setup', + 'initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'initial_box' => 'Basic CSV import setup', + 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'initial_submit' => 'Continue with step 2/3', - // roles - 'column_roles_title' => 'Definir roles de las columnas', - 'column_roles_table' => 'Tabla', - 'column_name' => 'Nombre de la columna', - 'column_example' => 'Ejemplo de datos de columna', - 'column_role' => 'Significado de los datos de la columna', - 'do_map_value' => 'Mapear estos valores', - 'column' => 'Columna', - 'no_example_data' => 'No hay datos de ejemplo disponibles', - 'store_column_roles' => 'Continuar importación', - 'do_not_map' => '(no mapear)', - 'map_title' => 'Conectar datos de importación con datos de Firefly-III', - 'map_text' => 'En las siguientes tablas el valor de la izquierda muestra información encontrada en el CSV cargado. Es su tarea mapear este valor, si es posible, a un valor ya presente en su base de datos. Firefly respeterá este mapeo. Si no hay un valor hacia el cual mapear o no desea mapear un valor específico, no seleccione ninguno.', + // roles config + 'roles_title' => 'Import setup (2/3) - Define each column\'s role', + 'roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'roles_table' => 'Table', + 'roles_column_name' => 'Name of column', + 'roles_column_example' => 'Column example data', + 'roles_column_role' => 'Column data meaning', + 'roles_do_map_value' => 'Map these values', + 'roles_column' => 'Column', + 'roles_no_example_data' => 'No example data available', + 'roles_submit' => 'Continue with step 3/3', - 'field_value' => 'Valor del campo', - 'field_mapped_to' => 'Mapeado a', - 'store_column_mapping' => 'Guardar mapeo', + // map data + 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', + 'map_text' => 'En las siguientes tablas el valor de la izquierda muestra información encontrada en el CSV cargado. Es su tarea mapear este valor, si es posible, a un valor ya presente en su base de datos. Firefly respeterá este mapeo. Si no hay un valor hacia el cual mapear o no desea mapear un valor específico, no seleccione ninguno.', + 'map_field_value' => 'Field value', + 'map_field_mapped_to' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'map_submit' => 'Start the import', // map things. - - 'column__ignore' => '(ignorar esta columna)', 'column_account-iban' => 'Caja de ahorro (CBU)', 'column_account-id' => 'ID de la caja de ahorro (coincide con Firefly)', diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 7c4e8c3fb3..6e95edeeec 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -966,50 +966,51 @@ return [ 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'import_file_type_help' => 'Select the type of file you will upload', - 'import_start' => 'Start the import', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Finish configuration', - 'settings_for_import' => 'Settings', - 'import_status' => 'Import status', - 'import_status_text' => 'The import is currently running, or will start momentarily.', - 'import_complete' => 'Import configuration complete!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Download configuration', - 'import_start_import' => 'Start import', - 'import_data' => 'Import data', - 'import_data_full' => 'Import data into Firefly III', + // import bread crumbs and titles: 'import' => 'Import', - 'import_file_help' => 'Select your file', - 'import_status_settings_complete' => 'The import is ready to start.', - 'import_status_import_complete' => 'The import has completed.', - 'import_status_import_running' => 'The import is currently running. Please be patient.', - 'import_status_header' => 'Import status and progress', - 'import_status_errors' => 'Import errors', - 'import_status_report' => 'Import report', - 'import_finished' => 'Import has finished', - 'import_error_single' => 'An error has occured during the import.', - 'import_error_multi' => 'Some errors occured during the import.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', + 'import_data' => 'Import data', + + // import index page: + 'import_index_title' => 'Import data into Firefly III', + 'import_index_sub_title' => 'Index', + 'import_index_intro' => 'Welcome to Firefly\'s import routine. These pages can help you import data from your bank into Firefly III. Please check out the help pages in the top right corner.', + 'import_index_file' => 'Select your file', + 'import_index_config' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', + 'import_index_type' => 'Select the type of file you will upload', + 'import_index_start' => 'Start importing', + + // supported file types: + 'import_file_type_csv' => 'CSV (comma separated values)', + + // import configuration routine: + 'import_config_sub_title' => 'Set up your import file', + 'import_config_bread_crumb' => 'Set up your import file', + + // import status page: + 'import_status_bread_crumb' => 'Import status', + 'import_status_sub_title' => 'Import status', + 'import_status_wait_title' => 'Please hold...', + 'import_status_wait_text' => 'This box will disappear in a moment.', + 'import_status_ready_title' => 'Import is ready to start', + 'import_status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'import_status_ready_config' => 'Download configuration', + 'import_status_ready_start' => 'Start the import', + 'import_status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'import_status_running_title' => 'The import is running', + 'import_status_running_placeholder' => 'Please hold for an update...', + 'import_status_errors_title' => 'Errors during the import', + 'import_status_errors_single' => 'An error has occured during the import. It does not appear to be fatal.', + 'import_status_errors_multi' => 'Some errors occured during the import. These do not appear to be fatal.', + 'import_status_fatal_title' => 'A fatal error occurred', + 'import_status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'import_status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'import_status_finished_title' => 'Import routine finished', + 'import_status_finished_text' => 'The import routine has imported your file.', + 'import_status_finished_job' => 'The transactions imported can be found in tag :tag.', 'import_with_key' => 'Import with key \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', + + // different states: + 'import_status_job_running' => 'The import is underway. Please be patient...', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', @@ -1026,7 +1027,7 @@ return [ 'no_accounts_create_expense' => 'Create an expense account', 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_imperative_revenue' => 'Revenue accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', 'no_accounts_create_revenue' => 'Create a revenue account', 'no_budgets_title_default' => 'Let\'s create a budget', 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', diff --git a/resources/lang/fr_FR/csv.php b/resources/lang/fr_FR/csv.php index 4c2cd1a072..4d51df7feb 100644 --- a/resources/lang/fr_FR/csv.php +++ b/resources/lang/fr_FR/csv.php @@ -13,36 +13,37 @@ declare(strict_types=1); return [ - 'import_configure_title' => 'Configurer l\'import', - 'import_configure_intro' => 'Il y a des options pour l\'import CSV. Veuillez indiquer si votre fichier CSV contient les en-têtes dans la première colonne, et quel est le format des dates de vos champs date. Cela peut nécessiter quelques essais. Le délimiteur de champ est généralement un « , », mais pourrait également être un « ; ». Cochez cette case avec soin.', - 'import_configure_form' => 'Options d’importation CSV basique', - 'header_help' => 'Cochez cette case si la première ligne de votre fichier CSV contient les entêtes des colonnes', - 'date_help' => 'Le format de la date et de l’heure dans votre fichier CSV. Utiliser les formats comme indiqué sur cette page. La valeur par défaut va analyser les dates ressemblant à ceci: :dateExample.', - 'delimiter_help' => 'Choisissez le délimiteur de champ qui est utilisé dans votre fichier d’entrée. Si vous n’êtes pas certain, la virgule est l’option la plus sûre.', - 'import_account_help' => 'Si votre fichier CSV ne contient AUCUNE information concernant vos compte(s) actif, utilisez cette liste déroulante pour choisir à quel compte les opérations contenues dans le CSV font référence.', - 'upload_not_writeable' => 'Le champ grisé contient un chemin d’accès. Il devrait être accessible en écriture. Veuillez vous en assurer.', + // initial config + 'initial_title' => 'Import setup (1/3) - Basic CSV import setup', + 'initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'initial_box' => 'Basic CSV import setup', + 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'initial_submit' => 'Continue with step 2/3', - // roles - 'column_roles_title' => 'Définir le rôle des colonnes', - 'column_roles_table' => 'Tableau', - 'column_name' => 'Nom de colonne', - 'column_example' => 'Données d’exemple de colonne', - 'column_role' => 'Sens de la donnée', - 'do_map_value' => 'Mapper ces valeurs', - 'column' => 'Colonne', - 'no_example_data' => 'Pas de données disponibles', - 'store_column_roles' => 'Continuer l\'import', - 'do_not_map' => '(ne pas mapper)', - 'map_title' => 'Lier les données importées aux données Firefly III', - 'map_text' => 'Dans les tableaux suivants, la valeur gauche vous montre des informations trouvées dans votre fichier CSV téléchargé. C’est votre rôle de mapper cette valeur, si possible, une valeur déjà présente dans votre base de données. Firefly s’en tiendra à ce mappage. Si il n’y a pas de valeur correspondante, ou vous ne souhaitez pas la valeur spécifique de la carte, ne sélectionnez rien.', + // roles config + 'roles_title' => 'Import setup (2/3) - Define each column\'s role', + 'roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'roles_table' => 'Table', + 'roles_column_name' => 'Name of column', + 'roles_column_example' => 'Column example data', + 'roles_column_role' => 'Column data meaning', + 'roles_do_map_value' => 'Map these values', + 'roles_column' => 'Column', + 'roles_no_example_data' => 'No example data available', + 'roles_submit' => 'Continue with step 3/3', - 'field_value' => 'Valeur du champ', - 'field_mapped_to' => 'Mappé à', - 'store_column_mapping' => 'Sauvegarder le mapping', + // map data + 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', + 'map_text' => 'Dans les tableaux suivants, la valeur gauche vous montre des informations trouvées dans votre fichier CSV téléchargé. C’est votre rôle de mapper cette valeur, si possible, une valeur déjà présente dans votre base de données. Firefly s’en tiendra à ce mappage. Si il n’y a pas de valeur correspondante, ou vous ne souhaitez pas la valeur spécifique de la carte, ne sélectionnez rien.', + 'map_field_value' => 'Field value', + 'map_field_mapped_to' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'map_submit' => 'Start the import', // map things. - - 'column__ignore' => '(ignorer cette colonne)', 'column_account-iban' => 'Compte d’actif (IBAN)', 'column_account-id' => 'Compte d\'actif (ID correspondant à Firefly)', diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 27cc32b44e..4e7cda4da0 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -966,50 +966,51 @@ return [ 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'import_file_type_help' => 'Select the type of file you will upload', - 'import_start' => 'Start the import', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Finish configuration', - 'settings_for_import' => 'Settings', - 'import_status' => 'Import status', - 'import_status_text' => 'The import is currently running, or will start momentarily.', - 'import_complete' => 'Import configuration complete!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Download configuration', - 'import_start_import' => 'Start import', - 'import_data' => 'Import data', - 'import_data_full' => 'Import data into Firefly III', + // import bread crumbs and titles: 'import' => 'Import', - 'import_file_help' => 'Select your file', - 'import_status_settings_complete' => 'The import is ready to start.', - 'import_status_import_complete' => 'The import has completed.', - 'import_status_import_running' => 'The import is currently running. Please be patient.', - 'import_status_header' => 'Import status and progress', - 'import_status_errors' => 'Import errors', - 'import_status_report' => 'Import report', - 'import_finished' => 'Import has finished', - 'import_error_single' => 'An error has occured during the import.', - 'import_error_multi' => 'Some errors occured during the import.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', + 'import_data' => 'Import data', + + // import index page: + 'import_index_title' => 'Import data into Firefly III', + 'import_index_sub_title' => 'Index', + 'import_index_intro' => 'Welcome to Firefly\'s import routine. These pages can help you import data from your bank into Firefly III. Please check out the help pages in the top right corner.', + 'import_index_file' => 'Select your file', + 'import_index_config' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', + 'import_index_type' => 'Select the type of file you will upload', + 'import_index_start' => 'Start importing', + + // supported file types: + 'import_file_type_csv' => 'CSV (comma separated values)', + + // import configuration routine: + 'import_config_sub_title' => 'Set up your import file', + 'import_config_bread_crumb' => 'Set up your import file', + + // import status page: + 'import_status_bread_crumb' => 'Import status', + 'import_status_sub_title' => 'Import status', + 'import_status_wait_title' => 'Please hold...', + 'import_status_wait_text' => 'This box will disappear in a moment.', + 'import_status_ready_title' => 'Import is ready to start', + 'import_status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'import_status_ready_config' => 'Download configuration', + 'import_status_ready_start' => 'Start the import', + 'import_status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'import_status_running_title' => 'The import is running', + 'import_status_running_placeholder' => 'Please hold for an update...', + 'import_status_errors_title' => 'Errors during the import', + 'import_status_errors_single' => 'An error has occured during the import. It does not appear to be fatal.', + 'import_status_errors_multi' => 'Some errors occured during the import. These do not appear to be fatal.', + 'import_status_fatal_title' => 'A fatal error occurred', + 'import_status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'import_status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'import_status_finished_title' => 'Import routine finished', + 'import_status_finished_text' => 'The import routine has imported your file.', + 'import_status_finished_job' => 'The transactions imported can be found in tag :tag.', 'import_with_key' => 'Import with key \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', + + // different states: + 'import_status_job_running' => 'The import is underway. Please be patient...', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', @@ -1026,7 +1027,7 @@ return [ 'no_accounts_create_expense' => 'Create an expense account', 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_imperative_revenue' => 'Revenue accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', 'no_accounts_create_revenue' => 'Create a revenue account', 'no_budgets_title_default' => 'Let\'s create a budget', 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', diff --git a/resources/lang/nl_NL/csv.php b/resources/lang/nl_NL/csv.php index 5de8d082fd..3cd5366a80 100644 --- a/resources/lang/nl_NL/csv.php +++ b/resources/lang/nl_NL/csv.php @@ -13,36 +13,37 @@ declare(strict_types=1); return [ - 'import_configure_title' => 'Import configureren', - 'import_configure_intro' => 'Hier zie je enkele opties voor jouw CSV bestand. Geef aan of je CSV bestand kolomtitels bevat, en hoe het datumveld is opgebouwd. Hier moet je wellicht wat experimenteren. Het scheidingsteken is meestal een ",", maar dat kan ook een ";" zijn. Controleer dit zorgvuldig.', - 'import_configure_form' => 'Standaard CSV import opties', - 'header_help' => 'Vink hier als de eerste rij kolomtitels bevat', - 'date_help' => 'Datum/tijd formaat in jouw CSV bestand. Volg het formaat zoals ze het op deze pagina uitleggen. Het standaardformaat ziet er zo uit: :dateExample.', - 'delimiter_help' => 'Kies het veldscheidingsteken dat in jouw bestand wordt gebruikt. Als je het niet zeker weet, is de komma de beste optie.', - 'import_account_help' => 'Als jouw CSV bestand geen referenties bevat naar jouw rekening(en), geef dan hier aan om welke rekening het gaat.', - 'upload_not_writeable' => 'Het grijze vlak bevat een bestandspad. Dit pad moet schrijfbaar zijn.', + // initial config + 'initial_title' => 'Importinstellingen (1/3) - Algemene CVS importinstellingen', + 'initial_text' => 'Om je bestand goed te kunnen importeren moet je deze opties verifiëren.', + 'initial_box' => 'Algemene CVS importinstellingen', + 'initial_header_help' => 'Vink hier als de eerste rij kolomtitels bevat.', + 'initial_date_help' => 'Datum/tijd formaat in jouw CSV bestand. Volg het formaat zoals ze het op deze pagina uitleggen. Het standaardformaat ziet er zo uit: :dateExample.', + 'initial_delimiter_help' => 'Kies het veldscheidingsteken dat in jouw bestand wordt gebruikt. Als je het niet zeker weet, is de komma de beste optie.', + 'initial_import_account_help' => 'Als jouw CSV bestand geen referenties bevat naar jouw rekening(en), geef dan hier aan om welke rekening het gaat.', + 'initial_submit' => 'Ga verder met stap 2/3', - // roles - 'column_roles_title' => 'Bepaal de inhoud van elke kolom', - 'column_roles_table' => 'Tabel', - 'column_name' => 'Kolomnaam', - 'column_example' => 'Voorbeeldgegevens', - 'column_role' => 'Kolomrol', - 'do_map_value' => 'Maak een mapping', - 'column' => 'Kolom', - 'no_example_data' => 'Geen voorbeeldgegevens', - 'store_column_roles' => 'Ga verder met import', - 'do_not_map' => '(niet mappen)', - 'map_title' => 'Verbind importdata met Firefly III data', - 'map_text' => 'In deze tabellen is de linkerwaarde een waarde uit je CSV bestand. Jij moet de link leggen, als mogelijk, met een waarde uit jouw database. Firefly houdt zich hier aan. Als er geen waarde is, selecteer dan ook niets.', + // roles config + 'roles_title' => 'Importinstellingen (2/3) - rol van elke kolom definiëren', + 'roles_text' => 'Elke kolom in je CSV-bestand bevat bepaalde gegevens. Gelieve aan te geven wat voor soort gegevens de import-routine kan verwachten. De optie "maak een link" betekent dat u elke vermelding in die kolom linkt aan een waarde uit je database. Een vaak gelinkte kolom is die met de IBAN-code van de tegenrekening. Die kan je dan linken aan de IBAN in jouw database.', + 'roles_table' => 'Tabel', + 'roles_column_name' => 'Kolomnaam', + 'roles_column_example' => 'Voorbeeldgegevens', + 'roles_column_role' => 'Kolomrol', + 'roles_do_map_value' => 'Maak een link', + 'roles_column' => 'Kolom', + 'roles_no_example_data' => 'Geen voorbeeldgegevens', + 'roles_submit' => 'Ga verder met stap 3/3', - 'field_value' => 'Veldwaarde', - 'field_mapped_to' => 'Gelinkt aan', - 'store_column_mapping' => 'Mapping opslaan', + // map data + 'map_title' => 'Importinstellingen (3/3) - Link importgegevens aan Firefly III-gegevens', + 'map_text' => 'In deze tabellen is de linkerwaarde een waarde uit je CSV bestand. Jij moet de link leggen, als mogelijk, met een waarde uit jouw database. Firefly houdt zich hier aan. Als er geen waarde is, selecteer dan ook niets.', + 'map_field_value' => 'Veldwaarde', + 'map_field_mapped_to' => 'Gelinkt aan', + 'map_do_not_map' => '(niet linken)', + 'map_submit' => 'Start importeren', // map things. - - 'column__ignore' => '(negeer deze kolom)', 'column_account-iban' => 'Betaalrekening (IBAN)', 'column_account-id' => 'Betaalrekening (ID gelijk aan Firefly)', diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 158bc9f235..6efbb844e0 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -963,53 +963,54 @@ return [ 'split_this_transfer' => 'Splits deze overschrijving', 'cannot_edit_multiple_source' => 'Je kan transactie #:id met omschrijving ":description" niet splitsen, want deze bevat meerdere bronrekeningen.', 'cannot_edit_multiple_dest' => 'Je kan transactie #:id met omschrijving ":description" niet wijzigen, want deze bevat meerdere doelrekeningen.', - 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', + 'cannot_edit_opening_balance' => 'Je kan het startsaldo van een rekening niet wijzigen via dit scherm.', 'no_edit_multiple_left' => 'Je hebt geen geldige transacties geselecteerd.', - // import - 'configuration_file_help' => 'Als je eerder gegevens hebt geïmporteerd in Firefly III, heb je wellicht een configuratiebestand, dat een aantal zaken alvast voor je kan instellen. Voor bepaalde banken hebben andere gebruikers uit de liefde van hun hart het benodigde configuratiebestand gedeeld.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (kommagescheiden waardes)', - 'import_file_type_help' => 'Selecteer het type bestand dat je zal uploaden', - 'import_start' => 'Start importeren', - 'configure_import' => 'Verder configureren van je import', - 'import_finish_configuration' => 'De configuratie voltooien', - 'settings_for_import' => 'Instellingen', - 'import_status' => 'Importstatus', - 'import_status_text' => 'De import is bezig of start over een momentje.', - 'import_complete' => 'Configureren van import is klaar!', - 'import_complete_text' => 'De import kan beginnen. Alle configuratie is opgeslagen. Download dit bestand. Het kan schelen als je de import opnieuw moet doen. Om daadwerkelijk te beginnen, gebruik je of het commando in je console, of de website. Afhankelijk van hoe je Firefly III hebt ingesteld, geeft de console-methode meer feedback.', - 'import_download_config' => 'Download importconfiguratie', - 'import_start_import' => 'Import starten', - 'import_data' => 'Importeer data', - 'import_data_full' => 'Gegevens importeren in Firefly III', + // import bread crumbs and titles: 'import' => 'Import', - 'import_file_help' => 'Selecteer je bestand', - 'import_status_settings_complete' => 'De import is klaar om te beginnen.', - 'import_status_import_complete' => 'Het importeren is voltooid.', - 'import_status_import_running' => 'Het importeren is nu bezig. Een momentje geduld.', - 'import_status_header' => 'Importstatus en voortgang', - 'import_status_errors' => 'Importfouten (Engels)', - 'import_status_report' => 'Importrapport', - 'import_finished' => 'Het importeren is voltooid', - 'import_error_single' => 'Er trad een fout op tijdens het importeren.', - 'import_error_multi' => 'Er traden fouten op tijdens het importeren.', - 'import_error_fatal' => 'Er was een fout tijdens het importeren. Bekijk ook de logbestanden. De error lijkt:', - 'import_error_timeout' => 'Het importeren lijkt een \'time-out\' te hebben en is wellicht gestopt. Als deze fout blijft voorkomen, gebruik dan het console commando.', - 'import_double' => 'Rij: #:row: Deze rij is al geimporteerd en is opgeslagen als :description.', - 'import_finished_all' => 'Het importeren is voltooid. Hieronder zie je de resultaten.', + 'import_data' => 'Importeer data', + + // import index page: + 'import_index_title' => 'Gegevens importeren in Firefly III', + 'import_index_sub_title' => 'Index', + 'import_index_intro' => 'Welkom bij de importroutine van Firefly. Deze pagina\'s helpen je met het importeren van gegevens in Firefly III. Bekijk ook de help-pagina\'s via het icoontje in de rechterbovenhoek.', + 'import_index_file' => 'Selecteer je bestand', + 'import_index_config' => 'Als je eerder gegevens hebt geïmporteerd in Firefly III, heb je wellicht een configuratiebestand, dat een aantal zaken alvast voor je kan instellen. Voor bepaalde banken hebben andere gebruikers uit de liefde van hun hart het benodigde configuratiebestand gedeeld.', + 'import_index_type' => 'Selecteer het type bestand dat je zal uploaden', + 'import_index_start' => 'Start met importeren', + + // supported file types: + 'import_file_type_csv' => 'CSV (kommagescheiden waardes)', + + // import configuration routine: + 'import_config_sub_title' => 'Instellen van je gegevensbestand', + 'import_config_bread_crumb' => 'Instellen van je gegevensbestand', + + // import status page: + 'import_status_bread_crumb' => 'Status van importeren', + 'import_status_sub_title' => 'Status van importeren', + 'import_status_wait_title' => 'Momentje...', + 'import_status_wait_text' => 'Dit vak verdwijnt zometeen.', + 'import_status_ready_title' => 'De import is klaar om te beginnen', + 'import_status_ready_text' => 'De import kan beginnen. Alle configuratie is opgeslagen. Download dit bestand. Het kan schelen als je de import opnieuw moet doen. Om daadwerkelijk te beginnen, gebruik je of het commando in je console, of de website. Afhankelijk van hoe je Firefly III hebt ingesteld, geeft de console-methode meer feedback.', + 'import_status_ready_config' => 'Download importconfiguratie', + 'import_status_ready_start' => 'Start importeren', + 'import_status_ready_share' => 'Overweeg om je configuratiebestand te downloaden en te delen op de configuratiebestand-wiki. Hiermee kan je het andere Firefly III gebruikers weer makkelijker maken.', + 'import_status_running_title' => 'De import is bezig', + 'import_status_running_placeholder' => 'Wacht even voor een update...', + 'import_status_errors_title' => 'Fouten tijdens het importeren', + 'import_status_errors_single' => 'Er is een niet-fatale fout opgetreden tijdens het importeren.', + 'import_status_errors_multi' => 'Er zijn een aantal niet-fatale fouten opgetreden tijdens het importeren.', + 'import_status_fatal_title' => 'Er is een fatale fout opgetreden', + 'import_status_fatal_text' => 'Een fatale fout opgetreden, waar de import-routine niet van terug heeft. Zie de uitleg in het rood hieronder.', + 'import_status_fatal_more' => 'Als de fout een time-out is, zal de import-routine halverwege gestopt zijn. Bij bepaalde serverconfiguraties is het alleen maar de server die gestopt terwijl de import-routine op de achtergrond doorloopt. Controleer de logboekbestanden om te zien wat er aan de hand is. Als het probleem zich blijft voordoen, gebruik dan de command-line opdracht.', + 'import_status_finished_title' => 'Importeren is klaar', + 'import_status_finished_text' => 'Je gegevensbestand is geïmporteerd.', + 'import_status_finished_job' => 'De geimporteerde transacties kan je vinden onder tag :tag.', 'import_with_key' => 'Import met code \':key\'', - 'import_share_configuration' => 'Overweeg om je configuratiebestand te downloaden en te delen op de configuratiebestand-wiki. Hiermee kan je het andere Firefly III gebruikers weer makkelijker maken.', - 'import_finished_report' => 'Het importeren is voltooid. Kijk naar eventuele fouten in het blok hierboven. Alle geimporteerde transacties hebben een tag, en die kan je hieronder bekijken. ', - 'import_finished_link' => 'De geimporteerde transacties kan je vinden onder tag :tag.', - 'need_at_least_one_account' => 'Je moet minstens één betaalrekening hebben voor je spaarpotjes kan maken', - 'see_help_top_right' => 'Meer informatie vind je in de help pagina\'s. Gebruik daarvoor het icoontje rechtsboven in de hoek.', - 'bread_crumb_import_complete' => 'Import ":key" compleet', - 'bread_crumb_configure_import' => 'Import ":key" instellen', - 'bread_crumb_import_finished' => 'Import ":key" klaar', - 'import_finished_intro' => 'De import is klaar! Je kan de transacties nu terugvinden in Firefly.', - 'import_finished_text_without_link' => 'Er is geen tag die al je transacties bevat. Kijk links in het menu onder "Transacties" en zoek daar je nieuwe transacties op.', - 'import_finished_text_with_link' => 'Je kan je geïmporteerde transacties op deze pagina terug vinden.', + + // different states: + 'import_status_job_running' => 'Het importeren is bezig. Een moment...', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Deze functie werkt niet als je Firefly III gebruikt in combinatie met Sandstorm.IO.', @@ -1026,7 +1027,7 @@ return [ 'no_accounts_create_expense' => 'Maak een crediteur', 'no_accounts_title_revenue' => 'Je hebt een debiteur nodig!', 'no_accounts_intro_revenue' => 'Je hebt nog geen debiteuren-rekeningen. Debiteuren zijn de plaatsen of mensen waar je geld van ontvangt, zoals je werkgever.', - 'no_accounts_imperative_revenue' => 'Debiteuren worden automatisch aangemaakt als je transacties aanmaakt, maar je kan ze ook met de hand maken. Zoals nu:', + 'no_accounts_imperative_revenue' => 'Crediteuren worden automatisch aangemaakt als je transacties aanmaakt, maar je kan ze ook met de hand maken. Zoals nu:', 'no_accounts_create_revenue' => 'Maak een debiteur', 'no_budgets_title_default' => 'Je hebt een budget nodig', 'no_budgets_intro_default' => 'Je hebt nog geen budgetten. Budgetten gebruik je om je uitgaven te groeperen. Daarmee kan je je uitgaven beperken.', diff --git a/resources/lang/pl_PL/csv.php b/resources/lang/pl_PL/csv.php index 72961b5ab5..ff59405d6f 100644 --- a/resources/lang/pl_PL/csv.php +++ b/resources/lang/pl_PL/csv.php @@ -13,36 +13,37 @@ declare(strict_types=1); return [ - 'import_configure_title' => 'Skonfiguruj import', - 'import_configure_intro' => 'There are some options for your CSV import. Please indicate if your CSV file contains headers on the first column, and what the date format of your date-fields is. That might require some experimentation. The field delimiter is usually a ",", but could also be a ";". Check this carefully.', - 'import_configure_form' => 'Podstawowe opcje importu CSV', - 'header_help' => 'Check this if the first row of your CSV file are the column titles', - 'date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'upload_not_writeable' => 'The grey box contains a file path. It should be writeable. Please make sure it is.', + // initial config + 'initial_title' => 'Import setup (1/3) - Basic CSV import setup', + 'initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'initial_box' => 'Basic CSV import setup', + 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'initial_submit' => 'Continue with step 2/3', - // roles - 'column_roles_title' => 'Zdefiniuj role dla kolumn', - 'column_roles_table' => 'Tabela', - 'column_name' => 'Nazwa kolumny', - 'column_example' => 'Przykładowe dane w kolumnie', - 'column_role' => 'Znaczenie danych w kolumnie', - 'do_map_value' => 'Zmapuj te wartości', - 'column' => 'Kolumna', - 'no_example_data' => 'Brak dostępnych danych przykładowych', - 'store_column_roles' => 'Kontynuuj import', - 'do_not_map' => '(nie mapuj)', - 'map_title' => 'Połącz dane z importu z danymi z Firefly III', - 'map_text' => 'In the following tables, the left value shows you information found in your uploaded CSV file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + // roles config + 'roles_title' => 'Import setup (2/3) - Define each column\'s role', + 'roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'roles_table' => 'Table', + 'roles_column_name' => 'Name of column', + 'roles_column_example' => 'Column example data', + 'roles_column_role' => 'Column data meaning', + 'roles_do_map_value' => 'Map these values', + 'roles_column' => 'Column', + 'roles_no_example_data' => 'No example data available', + 'roles_submit' => 'Continue with step 3/3', - 'field_value' => 'Wartość pola', - 'field_mapped_to' => 'Zmapowane do', - 'store_column_mapping' => 'Zapisz mapowanie', + // map data + 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', + 'map_text' => 'In the following tables, the left value shows you information found in your uploaded CSV file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'map_field_value' => 'Field value', + 'map_field_mapped_to' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'map_submit' => 'Start the import', // map things. - - 'column__ignore' => '(ignoruj tę kolumnę)', 'column_account-iban' => 'Konto aktywów (IBAN)', 'column_account-id' => 'ID konta aktywów (taki sam jak w Firefly)', diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index 2a6258b8cf..603a4b1ce3 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -966,50 +966,51 @@ return [ 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'Nie wybrałeś żadnych poprawnych transakcji do edycji.', - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Indeks', - 'import_file_type_csv' => 'CSV (wartości oddzielone przecinkami)', - 'import_file_type_help' => 'Wybierz typ pliku, który będziesz przesyłać', - 'import_start' => 'Rozpocznij Importowanie', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Zakończ konfigurację', - 'settings_for_import' => 'Ustawienia', - 'import_status' => 'Status importu', - 'import_status_text' => 'Import jest aktualnie uruchomiony, lub rozpocznie się chwilę.', - 'import_complete' => 'Zakończono import konfiguracji!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Pobierz konfigurację', - 'import_start_import' => 'Rozpocznij import', - 'import_data' => 'Importuj dane', - 'import_data_full' => 'Importuj dane do Firefly III', + // import bread crumbs and titles: 'import' => 'Importuj', - 'import_file_help' => 'Wybierz swój plik', - 'import_status_settings_complete' => 'Import jest gotowy do uruchomienia.', - 'import_status_import_complete' => 'Import został zakończony.', - 'import_status_import_running' => 'Import jest aktualnie uruchomiony. Proszę o cierpliwość.', - 'import_status_header' => 'Status oraz postęp importu', - 'import_status_errors' => 'Błędy importu', - 'import_status_report' => 'Raport z importu', - 'import_finished' => 'Import został zakończony', - 'import_error_single' => 'Wystąpił błąd podczas importu.', - 'import_error_multi' => 'Wystąpiły błędy podczas importu.', - 'import_error_fatal' => 'Wystąpił błąd podczas procedury importu. Proszę sprawdzić pliki dziennika błędów. Błąd wydaje się być:', - 'import_error_timeout' => 'Wygląda na to że został przekroczony limit czasu dla importu. Jeżeli bład będzie się powtarzał, należy zaimportować dane przy użyciu polecenia z konsoli.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', + 'import_data' => 'Importuj dane', + + // import index page: + 'import_index_title' => 'Import data into Firefly III', + 'import_index_sub_title' => 'Index', + 'import_index_intro' => 'Welcome to Firefly\'s import routine. These pages can help you import data from your bank into Firefly III. Please check out the help pages in the top right corner.', + 'import_index_file' => 'Select your file', + 'import_index_config' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', + 'import_index_type' => 'Select the type of file you will upload', + 'import_index_start' => 'Start importing', + + // supported file types: + 'import_file_type_csv' => 'CSV (wartości oddzielone przecinkami)', + + // import configuration routine: + 'import_config_sub_title' => 'Set up your import file', + 'import_config_bread_crumb' => 'Set up your import file', + + // import status page: + 'import_status_bread_crumb' => 'Import status', + 'import_status_sub_title' => 'Import status', + 'import_status_wait_title' => 'Please hold...', + 'import_status_wait_text' => 'This box will disappear in a moment.', + 'import_status_ready_title' => 'Import is ready to start', + 'import_status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'import_status_ready_config' => 'Download configuration', + 'import_status_ready_start' => 'Start the import', + 'import_status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'import_status_running_title' => 'The import is running', + 'import_status_running_placeholder' => 'Please hold for an update...', + 'import_status_errors_title' => 'Errors during the import', + 'import_status_errors_single' => 'An error has occured during the import. It does not appear to be fatal.', + 'import_status_errors_multi' => 'Some errors occured during the import. These do not appear to be fatal.', + 'import_status_fatal_title' => 'A fatal error occurred', + 'import_status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'import_status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'import_status_finished_title' => 'Import routine finished', + 'import_status_finished_text' => 'The import routine has imported your file.', + 'import_status_finished_job' => 'The transactions imported can be found in tag :tag.', 'import_with_key' => 'Import z kluczem \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'Potrzebujesz przynajmniej jednego konta aktywów, aby móc tworzyć skarbonki', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" został zakończony', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', + + // different states: + 'import_status_job_running' => 'The import is underway. Please be patient...', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', @@ -1026,7 +1027,7 @@ return [ 'no_accounts_create_expense' => 'Create an expense account', 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_imperative_revenue' => 'Revenue accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', 'no_accounts_create_revenue' => 'Create a revenue account', 'no_budgets_title_default' => 'Let\'s create a budget', 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', diff --git a/resources/lang/pt_BR/csv.php b/resources/lang/pt_BR/csv.php index 06ed873ac8..e6fdbdb3db 100644 --- a/resources/lang/pt_BR/csv.php +++ b/resources/lang/pt_BR/csv.php @@ -13,36 +13,37 @@ declare(strict_types=1); return [ - 'import_configure_title' => 'Configure sua importação', - 'import_configure_intro' => 'Existem algumas opções para sua importação CSV. Por favor, indique se o seu arquivo CSV contém cabeçalhos na primeira coluna, e qual o formato de data de seus campos de data. Isso pode exigir alguma experimentação. O delimitador de campo é geralmente um ",", mas também poderia ser um ";". Verifique isto cuidadosamente.', - 'import_configure_form' => 'Opções básicas de importação CSV', - 'header_help' => 'Verifique se a primeira linha do seu arquivo CSV está com os títulos de coluna', - 'date_help' => 'Formato de data e hora em seu CSV. Siga o formato como indica esta página. O valor padrão analisará datas que se parecem com isso: :dateExample.', - 'delimiter_help' => 'Escolha o delimitador de campo que é usado em seu arquivo de entrada. Se não tiver certeza, a vírgula é a opção mais segura.', - 'import_account_help' => 'Se o seu arquivo CSV NÃO contém informações sobre sua(s) conta(s) ativa(s), use este combobox para selecionar para qual conta pertencem as transações no CSV.', - 'upload_not_writeable' => 'A caixa cinza contém um caminho para um arquivo. Deve ser possível escrever nele. Por favor, certifique-se de que é.', + // initial config + 'initial_title' => 'Import setup (1/3) - Basic CSV import setup', + 'initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'initial_box' => 'Basic CSV import setup', + 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'initial_submit' => 'Continue with step 2/3', - // roles - 'column_roles_title' => 'Definir as funções da coluna', - 'column_roles_table' => 'Tabela', - 'column_name' => 'Nome da coluna', - 'column_example' => 'Dados de exemplo da coluna', - 'column_role' => 'Significado dos dados da coluna', - 'do_map_value' => 'Mapear estes valores', - 'column' => 'Coluna', - 'no_example_data' => 'Não há dados de exemplo disponíveis', - 'store_column_roles' => 'Continuar a importação', - 'do_not_map' => '(não mapear)', - 'map_title' => 'Conectar dados importados para dados do Firefly III', - 'map_text' => 'Nas tabelas a seguir, o valor à esquerda mostra informações encontradas no seu arquivo CSV carregado. É sua tarefa mapear esse valor, se possível, para um valor já presente em seu banco de dados. O Firefly vai se ater a esse mapeamento. Se não há nenhum valor para mapear, ou não quer mapear o valor específico, não selecione nada.', + // roles config + 'roles_title' => 'Import setup (2/3) - Define each column\'s role', + 'roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'roles_table' => 'Table', + 'roles_column_name' => 'Name of column', + 'roles_column_example' => 'Column example data', + 'roles_column_role' => 'Column data meaning', + 'roles_do_map_value' => 'Map these values', + 'roles_column' => 'Column', + 'roles_no_example_data' => 'No example data available', + 'roles_submit' => 'Continue with step 3/3', - 'field_value' => 'Valor do campo', - 'field_mapped_to' => 'Mapeado para', - 'store_column_mapping' => 'Armazenar mapeamento', + // map data + 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', + 'map_text' => 'Nas tabelas a seguir, o valor à esquerda mostra informações encontradas no seu arquivo CSV carregado. É sua tarefa mapear esse valor, se possível, para um valor já presente em seu banco de dados. O Firefly vai se ater a esse mapeamento. Se não há nenhum valor para mapear, ou não quer mapear o valor específico, não selecione nada.', + 'map_field_value' => 'Field value', + 'map_field_mapped_to' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'map_submit' => 'Start the import', // map things. - - 'column__ignore' => '(ignorar esta coluna)', 'column_account-iban' => 'Conta de Ativo (IBAN)', 'column_account-id' => 'ID da Conta de Ativo (correspondente Firefly)', diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 8e94668064..d82dbc6c5c 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -966,50 +966,51 @@ return [ 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'Você não selecionou nenhuma transação válida para editar.', - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Índice', - 'import_file_type_csv' => 'CSV (valores separados por vírgula)', - 'import_file_type_help' => 'Selecione o tipo de arquivo que você fará o upload', - 'import_start' => 'Iniciar a importação', - 'configure_import' => 'Além disso, configure sua importação', - 'import_finish_configuration' => 'Terminar configuração', - 'settings_for_import' => 'Preferências', - 'import_status' => 'Status de importação', - 'import_status_text' => 'A importação está atualmente em execução, ou vai começar dentro de momentos.', - 'import_complete' => 'Configuração de importação completa!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Download da configuração', - 'import_start_import' => 'Iniciar importação', - 'import_data' => 'Importar dados', - 'import_data_full' => 'Importar dados para o Firefly III', + // import bread crumbs and titles: 'import' => 'Importar', - 'import_file_help' => 'Selecione seu arquivo', - 'import_status_settings_complete' => 'A importação está pronta para começar.', - 'import_status_import_complete' => 'A importação foi concluída.', - 'import_status_import_running' => 'A importação está atualmente em execução. Por favor, seja paciente.', - 'import_status_header' => 'Importação status e progresso', - 'import_status_errors' => 'Erro de importação', - 'import_status_report' => 'Relatório de importação', - 'import_finished' => 'Importação concluida', - 'import_error_single' => 'Ocorreu um erro durante a importação.', - 'import_error_multi' => 'Ocorreram erros durante a importação do arquivo.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'A importação terminou. Por favor, verifique os resultados abaixo.', + 'import_data' => 'Importar dados', + + // import index page: + 'import_index_title' => 'Import data into Firefly III', + 'import_index_sub_title' => 'Index', + 'import_index_intro' => 'Welcome to Firefly\'s import routine. These pages can help you import data from your bank into Firefly III. Please check out the help pages in the top right corner.', + 'import_index_file' => 'Select your file', + 'import_index_config' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', + 'import_index_type' => 'Select the type of file you will upload', + 'import_index_start' => 'Start importing', + + // supported file types: + 'import_file_type_csv' => 'CSV (valores separados por vírgula)', + + // import configuration routine: + 'import_config_sub_title' => 'Set up your import file', + 'import_config_bread_crumb' => 'Set up your import file', + + // import status page: + 'import_status_bread_crumb' => 'Import status', + 'import_status_sub_title' => 'Import status', + 'import_status_wait_title' => 'Please hold...', + 'import_status_wait_text' => 'This box will disappear in a moment.', + 'import_status_ready_title' => 'Import is ready to start', + 'import_status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'import_status_ready_config' => 'Download configuration', + 'import_status_ready_start' => 'Start the import', + 'import_status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'import_status_running_title' => 'The import is running', + 'import_status_running_placeholder' => 'Please hold for an update...', + 'import_status_errors_title' => 'Errors during the import', + 'import_status_errors_single' => 'An error has occured during the import. It does not appear to be fatal.', + 'import_status_errors_multi' => 'Some errors occured during the import. These do not appear to be fatal.', + 'import_status_fatal_title' => 'A fatal error occurred', + 'import_status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'import_status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'import_status_finished_title' => 'Import routine finished', + 'import_status_finished_text' => 'The import routine has imported your file.', + 'import_status_finished_job' => 'The transactions imported can be found in tag :tag.', 'import_with_key' => 'Importação com chave \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'Você precisa de pelo menos uma conta de ativo para ser capaz de criar cofrinhos', - 'see_help_top_right' => 'Para obter mais informações, por favor, confira as páginas de ajuda usando o ícone no canto superior direito da página.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', + + // different states: + 'import_status_job_running' => 'The import is underway. Please be patient...', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', @@ -1026,7 +1027,7 @@ return [ 'no_accounts_create_expense' => 'Create an expense account', 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_imperative_revenue' => 'Revenue accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', 'no_accounts_create_revenue' => 'Create a revenue account', 'no_budgets_title_default' => 'Let\'s create a budget', 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', diff --git a/resources/lang/sl_SI/csv.php b/resources/lang/sl_SI/csv.php index 6f5ca5cac9..58591bd65a 100644 --- a/resources/lang/sl_SI/csv.php +++ b/resources/lang/sl_SI/csv.php @@ -13,36 +13,37 @@ declare(strict_types=1); return [ - 'import_configure_title' => 'Nastavitve uvoza', - 'import_configure_intro' => 'Tu je nekaj nastavitev za uvoz CSV datoteke. Prosim, označite, če vaša CSV datoteka vsebuje prvo vrstico z naslovi stolpcev in v kakšnem formatu so izpisani datumi. Morda bo to zahtevalo nekaj poizkušanja. Ločilo v CSV datoteki je ponavadi ",", lahko pa je tudi ";". Pozorno preverite.', - 'import_configure_form' => 'Osnovne možnosti za uvoz CSV datoteke.', - 'header_help' => 'Preverite ali prva vrstica v CSV datoteki vsebuje naslove stolpcev.', - 'date_help' => 'Formatiranje datuma in časa v vaši CSV datoteki. Uporabite obliko zapisa kot je navedena na tej strani. Privzeta vrednost bo prepoznala datume, ki so videti takole:: dateExample.', - 'delimiter_help' => 'Izberi ločilo, ki je uporabljeno za ločevanje med posameznimi stolpci v vaši datoteki. Če niste prepričani, je vejica najbolj pogosta izbira.', - 'import_account_help' => 'Če vaša CSV datoteka ne vsebuje informacij o vaših premoženjskih računih, uporabite ta seznam, da izberete kateremu računu pripadajo transakcije v CSV datoteki.', - 'upload_not_writeable' => 'Prosim zagotovite, da ima Firefly dovoljenje za pisanje v datoteko, ki je navedena v sivem okvirčku.', + // initial config + 'initial_title' => 'Import setup (1/3) - Basic CSV import setup', + 'initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'initial_box' => 'Basic CSV import setup', + 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'initial_submit' => 'Continue with step 2/3', - // roles - 'column_roles_title' => 'doličite pomen stolpcev', - 'column_roles_table' => 'tabela', - 'column_name' => 'Ime stolpca', - 'column_example' => 'primeri podatkov', - 'column_role' => 'pomen podatkov v stolpcu', - 'do_map_value' => 'poveži te vrednosti', - 'column' => 'stolpec', - 'no_example_data' => 'primeri podatkov niso na voljo', - 'store_column_roles' => 'nadaljuj z uvozom', - 'do_not_map' => '(ne poveži)', - 'map_title' => 'poveži podatke za uvoz s podatki iz Firefly III', - 'map_text' => 'Vrednosti na levi v spodnji tabeli prikazujejo podatke iz naložene CSV datoteke. Vaša naloga je, da jim, če je možno, določite obtoječio vrednost iz podatkovne baze. Firefly bo to upošteval pri uvozu. Če v podatkovni bazi ni ustrezne vrednosti, ali vrednosti ne želite določiti ničesar, potem pustite prazno.', + // roles config + 'roles_title' => 'Import setup (2/3) - Define each column\'s role', + 'roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'roles_table' => 'Table', + 'roles_column_name' => 'Name of column', + 'roles_column_example' => 'Column example data', + 'roles_column_role' => 'Column data meaning', + 'roles_do_map_value' => 'Map these values', + 'roles_column' => 'Column', + 'roles_no_example_data' => 'No example data available', + 'roles_submit' => 'Continue with step 3/3', - 'field_value' => 'podatek', - 'field_mapped_to' => 'povezan z', - 'store_column_mapping' => 'shrani nastavitve', + // map data + 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', + 'map_text' => 'Vrednosti na levi v spodnji tabeli prikazujejo podatke iz naložene CSV datoteke. Vaša naloga je, da jim, če je možno, določite obtoječio vrednost iz podatkovne baze. Firefly bo to upošteval pri uvozu. Če v podatkovni bazi ni ustrezne vrednosti, ali vrednosti ne želite določiti ničesar, potem pustite prazno.', + 'map_field_value' => 'Field value', + 'map_field_mapped_to' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'map_submit' => 'Start the import', // map things. - - 'column__ignore' => '(ignoriraj ta stolpec)', 'column_account-iban' => 'premoženjski račun (IBAN)', 'column_account-id' => 'ID premoženjskega računa (Firefly)', diff --git a/resources/lang/sl_SI/firefly.php b/resources/lang/sl_SI/firefly.php index 98280357e1..c913f89108 100644 --- a/resources/lang/sl_SI/firefly.php +++ b/resources/lang/sl_SI/firefly.php @@ -966,50 +966,51 @@ return [ 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'import_file_type_help' => 'Select the type of file you will upload', - 'import_start' => 'Start the import', - 'configure_import' => 'Further configure your import', - 'import_finish_configuration' => 'Finish configuration', - 'settings_for_import' => 'Settings', - 'import_status' => 'Import status', - 'import_status_text' => 'The import is currently running, or will start momentarily.', - 'import_complete' => 'Import configuration complete!', - 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', - 'import_download_config' => 'Download configuration', - 'import_start_import' => 'Start import', - 'import_data' => 'Import data', - 'import_data_full' => 'Import data into Firefly III', + // import bread crumbs and titles: 'import' => 'Import', - 'import_file_help' => 'Select your file', - 'import_status_settings_complete' => 'The import is ready to start.', - 'import_status_import_complete' => 'The import has completed.', - 'import_status_import_running' => 'The import is currently running. Please be patient.', - 'import_status_header' => 'Import status and progress', - 'import_status_errors' => 'Import errors', - 'import_status_report' => 'Import report', - 'import_finished' => 'Import has finished', - 'import_error_single' => 'An error has occured during the import.', - 'import_error_multi' => 'Some errors occured during the import.', - 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', - 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', - 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', - 'import_finished_all' => 'The import has finished. Please check out the results below.', + 'import_data' => 'Import data', + + // import index page: + 'import_index_title' => 'Import data into Firefly III', + 'import_index_sub_title' => 'Index', + 'import_index_intro' => 'Welcome to Firefly\'s import routine. These pages can help you import data from your bank into Firefly III. Please check out the help pages in the top right corner.', + 'import_index_file' => 'Select your file', + 'import_index_config' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', + 'import_index_type' => 'Select the type of file you will upload', + 'import_index_start' => 'Start importing', + + // supported file types: + 'import_file_type_csv' => 'CSV (comma separated values)', + + // import configuration routine: + 'import_config_sub_title' => 'Set up your import file', + 'import_config_bread_crumb' => 'Set up your import file', + + // import status page: + 'import_status_bread_crumb' => 'Import status', + 'import_status_sub_title' => 'Import status', + 'import_status_wait_title' => 'Please hold...', + 'import_status_wait_text' => 'This box will disappear in a moment.', + 'import_status_ready_title' => 'Import is ready to start', + 'import_status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'import_status_ready_config' => 'Download configuration', + 'import_status_ready_start' => 'Start the import', + 'import_status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'import_status_running_title' => 'The import is running', + 'import_status_running_placeholder' => 'Please hold for an update...', + 'import_status_errors_title' => 'Errors during the import', + 'import_status_errors_single' => 'An error has occured during the import. It does not appear to be fatal.', + 'import_status_errors_multi' => 'Some errors occured during the import. These do not appear to be fatal.', + 'import_status_fatal_title' => 'A fatal error occurred', + 'import_status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'import_status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'import_status_finished_title' => 'Import routine finished', + 'import_status_finished_text' => 'The import routine has imported your file.', + 'import_status_finished_job' => 'The transactions imported can be found in tag :tag.', 'import_with_key' => 'Import with key \':key\'', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', - 'import_finished_link' => 'The transactions imported can be found in tag :tag.', - 'need_at_least_one_account' => 'Imeti morate vsaj en premoženjski račun, da lahko ustvarite pujska.', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', + + // different states: + 'import_status_job_running' => 'The import is underway. Please be patient...', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', @@ -1026,7 +1027,7 @@ return [ 'no_accounts_create_expense' => 'ustvari konto za stroške', 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_imperative_revenue' => 'Revenue accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', 'no_accounts_create_revenue' => 'Create a revenue account', 'no_budgets_title_default' => 'Let\'s create a budget', 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', diff --git a/resources/lang/zh_TW/csv.php b/resources/lang/zh_TW/csv.php index c55b9cea43..29c6a61d2f 100644 --- a/resources/lang/zh_TW/csv.php +++ b/resources/lang/zh_TW/csv.php @@ -13,36 +13,37 @@ declare(strict_types=1); return [ - 'import_configure_title' => '匯入設定', - 'import_configure_intro' => '這裡有一些 CSV 匯入選項。請檢查你的 CSV 檔的第一列是否包含欄位名稱,和你的日期格式是什麼。你可能需要嘗試幾次來調整正確。欄位分隔符號是通常 ",",但也可能是";";仔細檢查這一點。', - 'import_configure_form' => 'Basic CSV import options', - 'header_help' => 'CSV 檔的第一行是標題', - 'date_help' => 'CSV 內的日期格式。請跟從這頁內的格式來填寫。 系統預設能夠解析像這樣的日期: :dateExample 。', - 'delimiter_help' => '請選擇你的檔案中所使用的欄位分隔符號。如果不肯定的話,逗號是最安全的選項。', - 'import_account_help' => '如果你的 CSV 檔中沒有包含資產帳戶的資料,請選擇相關聯的帳戶。', - 'upload_not_writeable' => '不能寫入檔案。灰色框內包含檔案的路徑,伺服器需要寫入該檔案的權限。請調整伺服器權限設定後再試。', + // initial config + 'initial_title' => 'Import setup (1/3) - Basic CSV import setup', + 'initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'initial_box' => 'Basic CSV import setup', + 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', + 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'initial_submit' => 'Continue with step 2/3', - // roles - 'column_roles_title' => '定義欄的內容', - 'column_roles_table' => '表格', - 'column_name' => '欄位名稱', - 'column_example' => '欄的示例資料', - 'column_role' => '欄內資料的含義', - 'do_map_value' => '配對這些資料', - 'column' => '欄', - 'no_example_data' => '沒有可用的示例資料', - 'store_column_roles' => '繼續匯入', - 'do_not_map' => '(不要配對)', - 'map_title' => '配對匯入了的資料到 Firefly III 的資料', - 'map_text' => '在下表中,左邊的是在你的CSV 檔中的資料。而你現在要把這些資料配對到資料庫中的資料(如有的話)。如果沒有資料能夠進行配對,或者你不想進行配對,請選擇不進行配對。', + // roles config + 'roles_title' => 'Import setup (2/3) - Define each column\'s role', + 'roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'roles_table' => 'Table', + 'roles_column_name' => 'Name of column', + 'roles_column_example' => 'Column example data', + 'roles_column_role' => 'Column data meaning', + 'roles_do_map_value' => 'Map these values', + 'roles_column' => 'Column', + 'roles_no_example_data' => 'No example data available', + 'roles_submit' => 'Continue with step 3/3', - 'field_value' => '欄位值', - 'field_mapped_to' => '配對到', - 'store_column_mapping' => '存儲配對', + // map data + 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', + 'map_text' => '在下表中,左邊的是在你的CSV 檔中的資料。而你現在要把這些資料配對到資料庫中的資料(如有的話)。如果沒有資料能夠進行配對,或者你不想進行配對,請選擇不進行配對。', + 'map_field_value' => 'Field value', + 'map_field_mapped_to' => 'Mapped to', + 'map_do_not_map' => '(do not map)', + 'map_submit' => 'Start the import', // map things. - - 'column__ignore' => '(忽略此欄)', 'column_account-iban' => '資產帳戶 (IBAN)', 'column_account-id' => '資產帳戶 ID (與 Firefly 匹配)', diff --git a/resources/lang/zh_TW/firefly.php b/resources/lang/zh_TW/firefly.php index fce1d22576..66bd9b21be 100644 --- a/resources/lang/zh_TW/firefly.php +++ b/resources/lang/zh_TW/firefly.php @@ -966,50 +966,51 @@ return [ 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - // import - 'configuration_file_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', - 'import_data_index' => 'Index', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'import_file_type_help' => 'Select the type of file you will upload', - 'import_start' => '開始匯入', - 'configure_import' => '進一步調整匯入設定', - 'import_finish_configuration' => 'Finish configuration', - 'settings_for_import' => 'Settings', - 'import_status' => '匯入狀態', - 'import_status_text' => '匯入正在進行中,或即將進行匯入。', - 'import_complete' => '匯入設定完成!', - 'import_complete_text' => '匯入程序已準備妥當。你已完成所有設定。請下載設定檔,當你的匯入出現問題時它將幫上忙。若要執行匯入程序,你可以在您的伺服器上執行以下命令,或運行網頁導入程序。根據您的配置,在伺服器上執行命令或會給你更多的資訊。', - 'import_download_config' => 'Download configuration', - 'import_start_import' => '開始匯入', - 'import_data' => '匯入資料', - 'import_data_full' => '匯入資料到 Firefly III', + // import bread crumbs and titles: 'import' => '匯入', - 'import_file_help' => 'Select your file', - 'import_status_settings_complete' => '匯入已準備妥當,可以開始。', - 'import_status_import_complete' => '匯入已完成。', - 'import_status_import_running' => '匯入正在進行中。請稍候。', - 'import_status_header' => '匯入狀態與進度', - 'import_status_errors' => '匯入錯誤', - 'import_status_report' => '匯入報告', - 'import_finished' => '匯入已完成', - 'import_error_single' => '匯入時發生了一個錯誤。', - 'import_error_multi' => '匯入時發生了一些錯誤。', - 'import_error_fatal' => '匯入時發生了一個錯誤。 請檢查紀錄檔。 錯誤好像是:', - 'import_error_timeout' => '匯入程序似乎已經越時。如果這個錯誤持續,請使用伺服器命令啟動匯入程序。', - 'import_double' => '行 #:row: 這行曾被匯入過,並已儲存在:description。', - 'import_finished_all' => '匯入已完成。請檢查下列的結果。', + 'import_data' => '匯入資料', + + // import index page: + 'import_index_title' => 'Import data into Firefly III', + 'import_index_sub_title' => 'Index', + 'import_index_intro' => 'Welcome to Firefly\'s import routine. These pages can help you import data from your bank into Firefly III. Please check out the help pages in the top right corner.', + 'import_index_file' => 'Select your file', + 'import_index_config' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file.', + 'import_index_type' => 'Select the type of file you will upload', + 'import_index_start' => 'Start importing', + + // supported file types: + 'import_file_type_csv' => 'CSV (comma separated values)', + + // import configuration routine: + 'import_config_sub_title' => 'Set up your import file', + 'import_config_bread_crumb' => 'Set up your import file', + + // import status page: + 'import_status_bread_crumb' => 'Import status', + 'import_status_sub_title' => 'Import status', + 'import_status_wait_title' => 'Please hold...', + 'import_status_wait_text' => 'This box will disappear in a moment.', + 'import_status_ready_title' => 'Import is ready to start', + 'import_status_ready_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'import_status_ready_config' => 'Download configuration', + 'import_status_ready_start' => 'Start the import', + 'import_status_ready_share' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'import_status_running_title' => 'The import is running', + 'import_status_running_placeholder' => 'Please hold for an update...', + 'import_status_errors_title' => 'Errors during the import', + 'import_status_errors_single' => 'An error has occured during the import. It does not appear to be fatal.', + 'import_status_errors_multi' => 'Some errors occured during the import. These do not appear to be fatal.', + 'import_status_fatal_title' => 'A fatal error occurred', + 'import_status_fatal_text' => 'A fatal error occurred, which the import-routine cannot recover from. Please see the explanation in red below.', + 'import_status_fatal_more' => 'If the error is a time-out, the import will have stopped half-way. For some server configurations, it is merely the server that stopped while the import keeps running in the background. To verify this, check out the log files. If the problem persists, consider importing over the command line instead.', + 'import_status_finished_title' => 'Import routine finished', + 'import_status_finished_text' => 'The import routine has imported your file.', + 'import_status_finished_job' => 'The transactions imported can be found in tag :tag.', 'import_with_key' => '以鍵 \':key\' 作匯入', - 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', - 'import_finished_report' => '匯入已完成。請留意在這行上面的錯誤記錄。這次所匯入的所有交易都已經進行標記,你可以在下面查看。 ', - 'import_finished_link' => '匯入成功的所有交易都可以在標籤 :tag 內找到。', - 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', - 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', - 'bread_crumb_import_complete' => 'Import ":key" complete', - 'bread_crumb_configure_import' => 'Configure import ":key"', - 'bread_crumb_import_finished' => 'Import ":key" finished', - 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', - 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', - 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', + + // different states: + 'import_status_job_running' => 'The import is underway. Please be patient...', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', @@ -1026,7 +1027,7 @@ return [ 'no_accounts_create_expense' => 'Create an expense account', 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', - 'no_accounts_imperative_revenue' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_imperative_revenue' => 'Revenue accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', 'no_accounts_create_revenue' => 'Create a revenue account', 'no_budgets_title_default' => 'Let\'s create a budget', 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', diff --git a/resources/views/import/complete.twig b/resources/views/import/complete.twig deleted file mode 100644 index 932dbe2caa..0000000000 --- a/resources/views/import/complete.twig +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "./layout/default" %} - -{% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists }} -{% endblock %} -{% block content %} -
    -
    -
    -
    -

    {{ 'import_complete'|_ }}

    -
    -
    -

    - {{ 'import_complete_text'|_ }} -

    -

    - php artisan firefly:start-import {{ job.key }} -

    - -

    -   -

    -

    - {{ 'import_share_configuration'|_ }} -

    -
    -
    -
    -
    -{% endblock %} -{% block scripts %} -{% endblock %} -{% block styles %} -{% endblock %} diff --git a/resources/views/import/csv/configure.twig b/resources/views/import/csv/initial.twig similarity index 61% rename from resources/views/import/csv/configure.twig rename to resources/views/import/csv/initial.twig index 9a9e53a0b0..bbfbc52d95 100644 --- a/resources/views/import/csv/configure.twig +++ b/resources/views/import/csv/initial.twig @@ -10,11 +10,11 @@
    -

    {{ trans('csv.import_configure_title') }}

    +

    {{ trans('csv.initial_title') }}

    - {{ trans('csv.import_configure_intro') }} + {{ trans('csv.initial_text') }}

    @@ -29,16 +29,16 @@
    -

    {{ trans('csv.import_configure_form') }}

    +

    {{ trans('csv.initial_box_title') }}

    - {{ ExpandedForm.checkbox('has_headers',1,job.configuration['has-headers'],{helpText: trans('csv.header_help')}) }} - {{ ExpandedForm.text('date_format',job.configuration['date-format'],{helpText: trans('csv.date_help', {dateExample: phpdate('Ymd')}) }) }} - {{ ExpandedForm.select('csv_delimiter', data.delimiters, job.configuration['delimiter'], {helpText: trans('csv.delimiter_help') } ) }} - {{ ExpandedForm.select('csv_import_account', data.accounts, job.configuration['import-account'], {helpText: trans('csv.import_account_help')} ) }} + {{ ExpandedForm.checkbox('has_headers',1,job.configuration['has-headers'],{helpText: trans('csv.initial_header_help')}) }} + {{ ExpandedForm.text('date_format',job.configuration['date-format'],{helpText: trans('csv.initial_date_help', {dateExample: phpdate('Ymd')}) }) }} + {{ ExpandedForm.select('csv_delimiter', data.delimiters, job.configuration['delimiter'], {helpText: trans('csv.initial_delimiter_help') } ) }} + {{ ExpandedForm.select('csv_import_account', data.accounts, job.configuration['import-account'], {helpText: trans('csv.initial_import_account_help')} ) }} {% for type, specific in data.specifics %}
    @@ -56,40 +56,23 @@
    {% endfor %} - - {% if not data.is_upload_possible %} -
    -
    -   -
    - -
    -
    {{ data.upload_path }}
    -

    - {{ trans('csv.upload_not_writeable') }} -

    -
    -
    - {% endif %}
    - {% if data.is_upload_possible %} -
    -
    -
    -
    - -
    +
    +
    +
    +
    +
    - {% endif %} +
    diff --git a/resources/views/import/csv/map.twig b/resources/views/import/csv/map.twig index ebc0cf669d..0d58cfcf51 100644 --- a/resources/views/import/csv/map.twig +++ b/resources/views/import/csv/map.twig @@ -1,7 +1,7 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, job) }} {% endblock %} {% block content %} @@ -22,9 +22,8 @@
    -
    + - {% for field in data %}
    @@ -37,8 +36,8 @@ - - + + @@ -63,53 +62,12 @@ {% endfor %} - {# - - {% for index,columnName in map %} - -
    -
    -
    -
    -

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

    -
    -
    -
    {{ trans('csv.field_value') }}{{ trans('csv.field_mapped_to') }}{{ trans('csv.map_field_value') }}{{ trans('csv.map_field_mapped_to') }}
    - - - - - - - - {% 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 %} - #} - -
    diff --git a/resources/views/import/csv/roles.twig b/resources/views/import/csv/roles.twig index 0df197187b..4513b1b7dd 100644 --- a/resources/views/import/csv/roles.twig +++ b/resources/views/import/csv/roles.twig @@ -1,7 +1,7 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, job) }} {% endblock %} {% block content %} @@ -10,18 +10,18 @@
    -

    {{ trans('csv.column_roles_title') }}

    +

    {{ trans('csv.roles_title') }}

    - {{ 'see_help_top_right'|_ }} + {{ trans('csv.roles_text') }}

    - + @@ -29,41 +29,41 @@
    -

    {{ trans('csv.column_roles_table') }}

    +

    {{ trans('csv.roles_table') }}

    - - - - + + + + - {% for i in 0..(data.columnCount-1) %} + {% for i in 0..(data.total -1) %} @@ -91,7 +91,7 @@
    diff --git a/resources/views/import/index.twig b/resources/views/import/index.twig index 922a3fa12e..c48d31ea6d 100644 --- a/resources/views/import/index.twig +++ b/resources/views/import/index.twig @@ -8,38 +8,34 @@
    -

    {{ 'import'|_ }}

    +

    {{ 'import_index_title'|_ }}

    -

    - {{ 'import_data_full'|_ }} -

    - {{ 'see_help_top_right'|_ }} + {{ 'import_index_intro'|_ }}

    -
    - {{ ExpandedForm.file('import_file', {helpText: 'import_file_help'|_}) }} - {{ ExpandedForm.file('configuration_file', {helpText: 'configuration_file_help'|_|raw}) }} - {{ ExpandedForm.select('import_file_type', importFileTypes, defaultImportType, {'helpText' : 'import_file_type_help'|_}) }} + {{ ExpandedForm.file('import_file', {helpText: 'import_index_file'|_}) }} + {{ ExpandedForm.file('configuration_file', {helpText: 'import_index_config'|_|raw}) }} + {{ ExpandedForm.select('import_file_type', importFileTypes, defaultImportType, {'helpText' : 'import_index_type'|_}) }}
    -
    diff --git a/resources/views/import/status.twig b/resources/views/import/status.twig index fbddfa8421..f1a8bcd5c6 100644 --- a/resources/views/import/status.twig +++ b/resources/views/import/status.twig @@ -4,50 +4,112 @@ {{ Breadcrumbs.renderIfExists }} {% endblock %} {% block content %} -
    + + {# Initial display. Will refresh (and disappear almost immediately. #} + +
    -

    {{ 'import_status_header'|_ }}

    +

    {{ 'import_status_wait_title'|_ }}

    -
    -
    -
    -
    -

    {{ 'import_status_settings_complete'|_ }}

    +

    + {{ 'import_status_wait_text'|_ }} +

    -
    + {# Fatal error display. Will be shown (duh) when something goes horribly wrong. #} + -
    + {# Box for when the job is ready to start #} + + + {# Box for when the job is running! #} + + + {# displays the finished status of the import. #} +
    {{ trans('csv.column_name') }}{{ trans('csv.column_example') }}{{ trans('csv.column_role') }}{{ trans('csv.do_map_value') }}{{ trans('csv.roles_column_name') }}{{ trans('csv.roles_column_example') }}{{ trans('csv.roles_column_role') }}{{ trans('csv.roles_do_map_value') }}
    - {% if data.columnHeaders[i] == '' %} - {{ trans('csv.column') }} #{{ loop.index }} + {% if data.headers[i] == '' %} + {{ trans('csv.roles_column') }} #{{ loop.index }} {% else %} - {{ data.columnHeaders[i] }} + {{ data.headers[i] }} {% endif %} - {% if data.columns[i]|length == 0 %} - {{ trans('csv.no_example_data') }} + {% if data.examples[i]|length == 0 %} + {{ trans('csv.roles_no_example_data') }} {% else %} - {% for example in data.columns[i] %} + {% for example in data.examples[i] %} {{ example }}
    {% endfor %} {% endif %}
    {{ Form.select(('role['~loop.index0~']'), - data.available_roles, + data.roles, job.configuration['column-roles'][loop.index0], {class: 'form-control'}) }}